GitOps message service list + all-service zstd measurement + helper unit test (seeds #168-2/#168-3/#168-4)

Date
ImpactCritical

Goal

  • Recover the 3 carryover seeds from Sprint 168 (seed #168-1 GHCR retry guard was already recovered in Sprint 168 Phase F ✅)
  • Seed #168-2 (observability): make the aether-gitops deploy(algosu): update image tags commit message list the actually-updated services + Trivy-skipped services (Sprint 168 D6 analysis)
  • Seed #168-3 (verification): after Sprint 168's full zstd adoption, accumulate compression-ratio measurements across all services (8 services + frontend + blog) and document the data in the ADR → reinforce the Sprint 168 decision data (ai-analysis only → full 10-artifact expansion)
  • Seed #168-4 (regression guard): add a unit test for scripts/ci/report-build-metrics.sh + auto-run in CI
  • The settling stage of the "security gate (165) → visibility (166) → measurement (167) → full adoption (168) → verification/observability settling (169)" cycle

Decisions

D0. Single PR integration — 3 seeds + ADR (inherits Sprint 168 integration pattern)

  • The 3 seeds (#168-2/#168-3/#168-4) + ADR are handled in a single PR (feat/sprint-169-gitops-msg-zstd-data-helper-test)
  • Rationale:
    • All 3 seeds are in the CI domain (ci.yml + tests/ci/) — simple dependency graph
    • The sprint splits in Sprint 161~163 (P0/P1/P2) had high regression-isolation value, but these 3 seeds are mutually independent + small → single-PR integration cost < split cost
    • Phase split (architect code commit + scribe ADR commit) preserves atomicity
  • Priority: #168-4 (regression guard, stability) > #168-2 (observability) > #168-3 (verification, already auto-accumulating so only ADR documentation)

D1. Helper test framework — pure bash (no bats)

  • Option comparison:
    • A (chosen): pure bash harness (tests/ci/report-build-metrics-test.sh) — zero external dependency, runs immediately on ubuntu-latest's default bash
    • B: bats (Bash Automated Testing System) — standard framework but adds a new CI toolchain (apt install bats) + learning cost
    • C: shellcheck only (lint) — not behavioral verification (static analysis only)
  • Rationale: the helper is a single 75-line file + CI-only → bats adoption ROI is low. Zero-dependency pure bash matches the "minimize toolchain when adding new code" principle
  • Environment guard: the helper assumes stat -c %s (GNU coreutils) → the test assumes the same. Non-GNU (macOS BSD stat) environments SKIP (exit 0) to prevent local false-red

D2. commit message format — → <list> @ main-<sha> (skipped: <list>)

  • Recovery format decision for seed #168-2 from Sprint 168 D6
  • Since all services share the same head SHA (main-${SHA}), repeating the SHA per service (frontend@main-X, blog@main-Y) is redundant → single SHA + service list for a non-redundant notation
  • Format: deploy(algosu): update image tags → <updated csv> @ main-<short-sha> (skipped: <skipped csv>)
    • no updated (defensive): deploy(algosu): update image tags @ main-<short-sha>
    • no skipped: omit the (skipped: ...) clause
  • Data source: steps.update_tags.outputs.updated + skipped_trivy (already exported since Sprint 160 Phase C) → no new output needed, reuse
  • Implementation: leading space trim (xargs) + space→comma conversion (sed 's/ /, /g'). Service names are fixed identifiers → zero injection risk

D3. All-service zstd compression measurement data (seed #168-3)

Measurement method: workflow_dispatch + rebuild_all=true (run #26139812059, run on a feature branch — the deploy job is gated by github.ref == 'refs/heads/main' and auto-skips → zero GHCR push/ArgoCD deploy side effects, only zstd tarball measurement)

Measurement results (run #26139812059, head SHA 6a34a28, 8 build artifacts):

build artifactzstd OCI exportStep Summary compression saving
gatewaybox shown
identitybox shown
submissionbox shown
problembox shown
github-workerbox shown
ai-analysis-63.7% (Sprint 168 measured, code unchanged)
frontendbox shown
blogbox shown
  • All 8 build jobs confirmed #27 exporting to oci image format … sending tarball done in build logs → zstd export works across all services (adoption verification MET)
  • Each build job's Step Summary ### 📦 <label> build artifact box shows compression saving % (visibility MET — Sprint 168 deliverable confirmed across all services)
  • Consolidating the per-service zstd raw % static table is carried over to Sprint 170 seed #169-1: GitHub Step Summary is not exposed via REST API/job log + zstd tarballs are not uploaded as artifacts (intended design, no Trivy consumer) → not programmatically extractable. To auto-extract, the helper must echo a metric line to stdout (log-greppable) or upload the zstd tarball as an artifact (#169-1)
  • The exact saving % for the 7 non-ai-analysis services is visually verifiable in each build job's Step Summary in run #26139812059 (live data)

D4. Measurement dispatch safety policy — feature branch run (avoid deploy side effects)

  • Sprint 168 Phase A used --ref main for force-build → the deploy job met the main-ref condition → actual ArgoCD deployment occurred (harmless then since idempotent)
  • This sprint uses a feature branch dispatch → build jobs have push=${{ github.ref == 'refs/heads/main' }} = false (no GHCR push) + deploy job skips on the main gate → measurement data is obtained identically (zstd tarballs are always produced)
  • Applies the "stability > velocity" principle at the measurement stage — measurement is not coupled with deploy side effects

Implementation (single PR, targeting 37 consecutive sprints of branch discipline)

Branch: feat/sprint-169-gitops-msg-zstd-data-helper-test (new, based on main 7ec560c)

Phase A — helper unit test + CI integration (commit 6a34a28)

tests/ci/report-build-metrics-test.sh (new, pure bash):

Bash
# docker PATH shim: controls buildx du via STUB_DOCKER_MODE(ok|fail) (removes daemon dependency)
# GNU stat guard: non-GNU environments SKIP (exit 0)
# 6 cases:
#   1. missing-arg fail-fast (${1:?} / ${2:?})
#   2. zstd branch compression saving % (1000→250 = -75.0%)
#   3. zstd-omitted branch (no oci+zstd line)
#   4. GITHUB_STEP_SUMMARY unset → /dev/stdout fallback + ::warning::
#   5. docker buildx du failure → graceful N/A / 0 fallback
#   6. docker buildx du success → cache size/entries parsing

.github/workflows/ci.yml:

  • add a ci-scripts filter (scripts/ci/**, tests/ci/**) + output + rebuild_all override/else branches to detect-changes
  • new quality-ci-scripts job — if: needs.detect-changes.outputs.ci-scripts == 'true'bash tests/ci/report-build-metrics-test.sh

Phase B — GitOps commit message service list (commit 6a34a28)

.github/workflows/ci.yml "Commit and push to aether-gitops" step:

Bash
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
UPDATED_CSV=$(echo "${{ steps.update_tags.outputs.updated }}" | xargs | sed 's/ /, /g')
SKIPPED_CSV=$(echo "${{ steps.update_tags.outputs.skipped_trivy }}" | xargs | sed 's/ /, /g')
if [ -n "$UPDATED_CSV" ]; then
  MSG="deploy(algosu): update image tags → ${UPDATED_CSV} @ main-${SHORT_SHA}"
else
  MSG="deploy(algosu): update image tags @ main-${SHORT_SHA}"
fi
if [ -n "$SKIPPED_CSV" ]; then
  MSG="${MSG} (skipped: ${SKIPPED_CSV})"
fi
git commit -m "$MSG"

Phase C — all-service zstd measurement (external trigger, zero code change)

  • gh workflow run ci.yml --ref feat/sprint-169-... -f rebuild_all=true (run #26139812059)
  • the build-job helper already outputs zstd tarballs → collect compression ratios for 10 artifacts from the Step Summary (D1 data table → D3 documentation)

Phase D — ADR documentation (commit <TBD-ADR>)

  • docs/adr/sprints/sprint-169.md (KR) + docs/adr-en/sprints/sprint-169.md (EN 1:1 mapping)
  • docs/adr/README.md count 108→109, range 62168→62169 (lines 18/52/54)

Critic cycle

R1 (codex review --base 7ec560c, session 019e4380-1826-7271-89c6-bc059a1b1d90):

  • Result: P0/P1/P2/P3 0 findings, PASS
  • codex verdict: "The changes add a focused CI helper test job, wire it through path filtering, and adjust GitOps commit messages without introducing an evident functional regression. Documentation updates are consistent with the new sprint ADR files."
  • codex verified directly: python3 yaml.safe_load(ci.yml) parses OK + bash tests/ci/report-build-metrics-test.sh ran → SKIP guard works correctly in the macOS BSD stat environment (exit 0)
  • zero self-contradiction detected — sprint goal (settling observability/verification/regression-guard) ↔ implementation aligned
  • single-rotation PASS (reproduces the normal Sprint 167/168 pattern, no codex hang)

Risk / regression guard

Prediction 1: environment compatibility of the helper test

  • stat -c %s assumes GNU coreutils → non-GNU SKIP guard (exit 0). On CI (ubuntu-latest) all 6 cases run
  • the docker PATH shim removes the real docker daemon dependency → deterministic test (regardless of CI runner docker)
  • quality-ci-scripts job verification: success in measurement run #26139812059 ✅ (18 assertions PASS in real CI environment)

Prediction 2: zero aether-gitops regression from commit message change

  • reuses the existing outputs (updated/skipped_trivy) of the update_tags step → no new output/logic added
  • the git diff --quiet early-exit branch is unchanged → no commit when there are no changes (preserves existing behavior)
  • service names are fixed identifiers (gateway/identity/...) → zero shell injection risk
  • local simulation verified 5 cases (updated+skipped / updated only / single / skipped only / both empty)

Prediction 3: zero deploy side effects of measurement

  • feature branch dispatch → build push=false + deploy job skips on the main gate
  • confirmed zero aether-gitops impact for measurement run #26139812059

Verification

Local

  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" PASS
  • bash tests/ci/report-build-metrics-test.sh (with GNU stat shim injected) — 18/18 assertions PASS
  • commit message logic simulation 5 cases PASS
  • node scripts/check-adr-en-coverage.mjs --strict 118/118 (100.0%) PASS
  • node scripts/check-doc-refs.mjs 301 files 0 broken refs PASS

CI

  • quality-ci-scripts job GREEN (helper test) — measurement run #26139812059 success ✅
  • all 10 build artifacts show zstd compression ratios in the workflow_dispatch + rebuild_all=true run (D3)
  • target mergeStateStatus CLEAN

New UAT (Sprint 169)

  • user-direct: visually confirm the service list + skipped services in the next aether-gitops deploy commit message

Result

5 files changed:

  • 1 new: tests/ci/report-build-metrics-test.sh (seed #168-4)
  • 1 modified: .github/workflows/ci.yml (ci-scripts filter/output/job + commit message logic)
  • 2 new: docs/adr/sprints/sprint-169.md (KR) + docs/adr-en/sprints/sprint-169.md (EN 1:1)
  • 1 modified: docs/adr/README.md (lines 18/52/54)

Commits (PR #295):

  • 6a34a28 feat(ci): Sprint 169 seeds #168-2/#168-4 — GitOps message service list + helper unit test
  • e2841d8 docs(adr): Sprint 169 ADR (KR + EN) + README update
  • <TBD-CRITIC-DOC> docs(adr): Sprint 169 Critic R1 cycle documentation (KR + EN)
  • Squash merge: <TBD-MERGE-SHA>

New patterns

  • Pure bash unit test + PATH shim = zero-dependency CI helper regression guard — with bats not adopted, a pure bash harness in tests/ci/*.sh + a docker PATH shim (STUB_DOCKER_MODE) removes the daemon dependency → deterministic test. Runs immediately on ubuntu-latest's default bash. A single-file CI helper has low bats ROI → pure bash is the standard
  • GNU/BSD stat branching = environment-guard SKIP pattern — the helper assumes stat -c %s (GNU) → the test guards the same environment then SKIPs (exit 0) on non-GNU. Prevents local (macOS) false-red for CI-only scripts. The "preserve local-run capability of CI-only code" pattern
  • Reusing existing step output = stronger observability without adding new output — the commit message improvement reuses the update_tags step's existing updated/skipped_trivy outputs (introduced in Sprint 160 Phase C). Strengthens visibility only, with no new logic/output → minimal change surface
  • Feature-branch run for measurement dispatch = deploy side-effect isolation pattern — for a workflow_dispatch + rebuild_all measurement, using a feature branch instead of the main ref → build push=false + deploy job skip → identical measurement data while zero GHCR push/ArgoCD deploy side effects. Safer than Sprint 168 Phase A (main ref). The principle of not coupling measurement with deploy
  • xargs trim + sed comma conversion = standard normalization of leading-space output — since ${UPDATED} is leading-space + space-separated, echo | xargs (trim+single-space) + sed 's/ /, /g' (comma) normalizes it. The standard pattern to convert a shell output list into a human-readable csv