Files
Dragan Spiridonov 4f340bc183 feat(verification): chaos workflow + VERIFICATION.md + matrix embed (#409, #410)
Closes the two remaining init-corpus follow-ups from #401 in one PR:
makes the verification layer visible to humans (#409) and adds
adversarial-rare coverage to complement the everyday-real release
gate (#410).

# #410 — Chaos workflow

Weekly cron-driven workflow that runs aqe init against 6 pathological
project shapes the everyday-real corpus deliberately doesn't cover.
Per-shape assertion is "exit anything except 124" — the watchdog is
the load-bearing thing under test, not init's ability to make sense
of garbage input. Fail-loudly in the Actions tab; no auto-issue
creation, matching the #411 mirror-test pattern.

Six shapes: utf16le-bom, mixed-line-endings, symlink-loop,
binary-as-text, minified-bundle, control-chars. Generated at runtime
into a tmpdir by tests/fixtures/init-chaos/generate.sh — synthetic
generation is appropriate here per the chaos README's "Why synthetic
generation here" section: chaos tests need controlled adversarial
inputs that don't exist in any real public repo.

Verified locally: ran the generator + ran aqe init against all 6
shapes. All 6 pass with rc=0 against the current build, proving the
generator produces valid shapes and the existing watchdog handles
them today. The workflow exists to catch the day a future change
breaks one of these paths.

# #409 — VERIFICATION.md + matrix embed

New docs/VERIFICATION.md is the maintainer-facing entry point: gate
architecture diagram, how to interpret a failed gate, how to add a
new fixture (links to corpus README), how to embed the verification
matrix into a release notes file.

scripts/embed-verification-matrix.sh downloads the init-corpus-logs
artifact from a given workflow run, parses summary.txt, and emits a
markdown matrix. Status-only (option C from the proposal) — Time and
KG-entries columns are tracked as future work because the gate's
current summary.txt doesn't carry that data and extending it would
touch load-bearing infrastructure that's out of scope.

docs/policies/release-verification.md is fully rewritten around the
post-#401 reality: the manual aqe init checklist that pre-dated the
corpus is removed (the gate now does what it tried to do, with
22 assertions per fixture × 4 fixtures × the load-bearing doubleInit
flag). The Version Update Policy + Workflow sections are preserved
intact — they cover the one class of pre-release error the gate
cannot catch.

CONTRIBUTING.md links to the new VERIFICATION.md from the
Documentation section.

# Housekeeping

Removes 3 stale rows from tests/fixtures/init-corpus/README.md's
deferred-items table: #408 (already merged), #409 and #410 (this
PR). Only #407 remains — its 4 trigger conditions still haven't
fired, so it stays deferred per its own definition.

# Verification of this PR (per #401 rules)

- actionlint passed on .github/workflows/init-chaos.yml (#408 lesson:
  js-yaml is not enough for workflow files)
- bash -n on both new shell scripts
- generator ran cleanly and produced all 6 shapes with the expected
  byte content (verified BOM, line endings, symlink targets, PNG
  signature, single-line bundle size, embedded NULs)
- embed script ran against synthetic summary files (PASS-only and
  mixed PASS/FAIL) — both render correctly
- live smoke test: ran aqe init against all 6 chaos shapes locally
  using the globally-installed aqe binary, all 6 exited rc=0 within
  the watchdog budget
- every link in docs/VERIFICATION.md and docs/policies/release-
  verification.md verified to point at a file that exists

Closes #409
Closes #410

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:52:29 +00:00

8.9 KiB
Raw Permalink Blame History

Release verification

The verification layer that was structurally missing during the v3.9.1–v3.9.4 init regression series is now in place. This document is the maintainer-facing entry point for understanding how it works, how to interpret a failure, and how to add new coverage.

TL;DR

Every npm publish runs aqe init --auto end-to-end against a corpus of 4 pinned real public repositories with 22 assertions per fixture. If any assertion fails, the publish does not happen — the release tag stays unpublished until the bug is fixed. After publish, a canary re-runs the corpus against the freshly-published package on the actual npm CDN.

This is not aspirational. It is the load-bearing verification layer that prevents another #401-class regression from shipping.

                       (PR merged to main)
                              │
                              ▼
                      release tag created
                              │
                              ▼
              ┌───────────────────────────────┐
              │  npm-publish.yml              │
              │  ├─ build                     │
              │  ├─ tests-on-tag-sha          │
              │  └─ pre-publish-gate          │ ◄──── this is the gate
              │       ├─ setup.sh             │
              │       ├─ run-gate.sh          │
              │       └─ summary.txt → ARTIFACT
              └───────────────────────────────┘
                              │
                  (only if gate passes)
                              │
                              ▼
                          npm publish
                              │
                              ▼
              ┌───────────────────────────────┐
              │  post-publish-canary.yml      │
              │  ├─ install from npm registry │
              │  ├─ run-gate.sh against it    │
              │  └─ open P0 issue on failure  │
              └───────────────────────────────┘

How a release is verified

1. The release-gate corpus (tests/fixtures/init-corpus/)

Four pinned slices of real public repositories. Each runs aqe init --auto end-to-end against a cleanroom checkout and asserts 22 things (A1–A22). See the corpus README for the per-fixture purpose, and the gate script (tests/fixtures/init-corpus/run-gate.sh) for the assertion contract.

The corpus is not a unit-test fixture. It only ever runs aqe init --auto end-to-end against an installed agentic-qe package, exactly the way a real user runs it.

2. The release-gate workflow

.github/workflows/npm-publish.yml's pre-publish-gate job runs the corpus against a freshly-built npm pack tarball after build and tests-on-tag-sha complete. If any fixture fails any assertion, the publish job does not run and the tag stays unpublished. The job uploads tests/fixtures/init-corpus/run-logs/ as the init-corpus-logs artifact and appends summary.txt to the workflow run summary.

3. The post-publish canary

.github/workflows/post-publish-canary.yml re-runs the same corpus against the just-published version on the npm registry. This catches the rare case where the published tarball differs from what was tested (a packaging mismatch). On failure it opens a P0 issue automatically.

4. The mirror-test workflow

.github/workflows/init-corpus-mirror-test.yml exercises the tarball.mirror fallback path in setup.sh. Runs on any PR touching the corpus and weekly on Mondays. See #411 for the rationale.

5. The chaos workflow

.github/workflows/init-chaos.yml runs adversarial inputs (UTF-16LE BOM, symlink loops, binary-as-text, minified bundles, control characters, mixed line endings) against aqe init weekly. Catches a different class of failure than the everyday-real gate — see tests/fixtures/init-chaos/README.md and #410.

How to interpret a failed gate

When the pre-publish-gate job goes red, do this:

  1. Read the per-fixture verdict. Open the workflow run, look at the Append gate summary to job summary step's output. It prints one line per fixture in the form <id> PASS or <id> FAIL <code> where <code> identifies which assertion failed (e.g. A7-timeout, A11-kg-entries-low, A22-second-init-failed).
  2. Download the init-corpus-logs artifact. Each fixture has ${id}.log (stderr from init), ${id}.json (the --json output), and ${id}.run-meta.txt (timing + exit code). Read the log for the failing fixture first.
  3. Reproduce locally with the same install spec the gate used. From a clean checkout: npm run build && npm pack && AQE_LOCAL_TARBALL=./agentic-qe-$(node -p "require('./package.json').version").tgz ./tests/fixtures/init-corpus/run-gate.sh.
  4. Fix the bug, push the fix, and re-tag. Do not delete and re-create the tag without the fix landing first — the gate runs on the tag SHA, not on main.

The cardinal rule from #401 applies: "I believe it's unlikely" is not verification. If you find yourself wanting to mark a fixture as "flaky" rather than "broken", stop and reproduce it locally first.

How to add a new fixture

See the corpus README's Updating the corpus section. Quick version:

  1. Pick a new immutable commit SHA from a real public repo.
  2. Compute its sha256, update MANIFEST.json with the new entry.
  3. Run ./scripts/upload-init-corpus-mirror.sh to populate the self-hosted mirror with the new tarball, then set tarball.mirror in MANIFEST.json accordingly.
  4. Run ./tests/fixtures/init-corpus/setup.sh && ./tests/fixtures/init-corpus/run-gate.sh locally to confirm the new fixture passes the gate.
  5. Land the change in a PR that explains why the new fixture is needed (404 on existing? new bug class? upstream repo migration?).

Verification matrix in release notes

Every release-notes file under docs/releases/vX.Y.Z.md should include a verification matrix showing which fixtures were exercised by the gate that produced the release. The matrix is generated by the embed-verification-matrix.sh script.

How to embed the matrix

After the npm-publish.yml workflow completes for a tag:

# 1. Find the workflow run ID for the release tag
gh run list --workflow=npm-publish.yml --branch=main --limit=10

# 2. Generate the matrix from that run's gate artifact
./scripts/embed-verification-matrix.sh <run-id> >> docs/releases/vX.Y.Z.md

# 3. Commit + open PR for the release notes update

The script downloads the init-corpus-logs artifact from the run, parses summary.txt, and emits a markdown table. Output format:

## Verification matrix (agentic-qe@X.Y.Z)

Generated from npm-publish.yml run <run-id> on <date>.

| Fixture | Status |
|---|---|
| tiny-ts          | PASS |
| mid-ts           | PASS |
| multi-lang-real  | PASS |
| self-dogfood     | PASS |

The current matrix only carries Status. Time and KG-entries columns are tracked as a future enhancement — the gate's summary.txt doesn't carry per-fixture timing or KG counts today, so populating those columns would require either parsing the per-fixture JSON or extending the gate. Either is a larger change than #409 was scoped for; the status-only matrix ships value immediately and can be expanded later.

The post-mortem this all comes from

The verification layer described above was built in response to #401, the post-mortem for the v3.9.1–v3.9.4 init regression series. Every shipped CI gate, fixture, and policy in this document maps to a specific lesson from that post-mortem. Read it before proposing structural changes here — particularly the part about why we test against real public repos rather than synthetic fixtures.