From d61c0d67bb17631670ec52ed9b1b99309c179113 Mon Sep 17 00:00:00 2001 From: garrytan Date: Thu, 22 Oct 2026 13:42:26 +0000 Subject: [PATCH] fix(qa): materialize names the next write; fixture states the write boundary (GSTA-63) qa-functional-webhook-fix (gate 39646086155) and qa-functional-cli-fix (gate 39166935531) each wrote a stray placeholder beside the fixture right after a fail verdict that read "final for this report root". Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 11 +++++++++++ lib/qa-evidence.ts | 2 +- test/helpers/qa-functional-eval.ts | 2 +- test/qa-evidence.test.ts | 3 ++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 885ac513e..49ff74205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [Unreleased] + +**A /qa run no longer gets a hint that another report root could change its verdict.** Train gate 39646086155 failed qa-functional-webhook-fix once, and gate 39166935531 failed qa-functional-cli-fix the same way on 2026-10-18. Both times the agent ran `gstack-qa-evidence materialize`, read a fail verdict that ended "this verdict is final for this report root", and its very next call wrote a stray placeholder file in the fixture's parent directory (`qa-reports-placeholder-unused`, `qa-Q3vLqZ-placeholder`) before writing `report.md`. Each agent then reported the stray file itself. The out-of-fixture write detector was right both times and is unchanged. + +### Fixed +- **`lib/qa-evidence.ts`**: materialize's `next` instruction now ends "this verdict is final. Next, write the Markdown report beside evidence.json." It names the next write and where it goes, instead of qualifying the verdict by report root. +- **`test/helpers/qa-functional-eval.ts`**: the functional fixture's declared interface says that every Write/Edit targets an owned path inside the working directory, and that a write anywhere else, including the parent directory, fails acceptance. That was already the acceptance rule; the prompt now states it. + +### Gate integrity +- No detector, threshold, trial count or case kind changed. `test/qa-evidence.test.ts` pins the new `next` wording and checks that it no longer mentions a report root. + ## [1.91.176.0] - 2026-10-21 **A qualified /cso runtime set can be released again after the S1 shebang change.** The release contract fingerprints every source byte compiled into a runtime image (`preparationSha256`). S1 (#3312) changed only line 1 of the two compiled helpers, `lib/cso/verifier.ts` and `lib/cso/preparation-container.ts`, from `#!/usr/bin/env bun` to the isolated shebang. That line is embedded in the compiled binary as an unused string and never runs in the image, but it moved the fingerprint. Qualify run 39535948607 therefore refused the run-5 statements, staged at 133cb36, with `STALE_RUNTIME_QUALIFICATION: preparationSha256`. Garry chose this narrow rule change over a re-stage and a fresh paid qualification (2026-10-21, option 2). diff --git a/lib/qa-evidence.ts b/lib/qa-evidence.ts index e32212410..4fc9eead6 100644 --- a/lib/qa-evidence.ts +++ b/lib/qa-evidence.ts @@ -385,7 +385,7 @@ function materialize(root: string, source: string) { const sha256 = publish(root, 'evidence.json', { ...annotations, evidence, learning, verdict }); return { action: 'materialize', status: 'complete', sha256, annotationsSha256: hash(bytes), exitCode: 0, verdict, reportLinks: notes.map(note => `[checkpoint ${note.name.slice(12, 15)}](${note.name})`), - next: `Include every reportLinks entry in the Markdown report, and report the overall status as ${verdict.status}${verdict.open.length ? ` (open: ${verdict.open.join('; ')})` : ''}; this verdict is final for this report root.` }; + next: `Include every reportLinks entry in the Markdown report, and report the overall status as ${verdict.status}${verdict.open.length ? ` (open: ${verdict.open.join('; ')})` : ''}; this verdict is final. Next, write the Markdown report beside evidence.json.` }; } const QA_EVIDENCE_USAGE = 'capture ROOT ID [--public] --deadline FILE|--timeout-ms MS [--after PREVIOUS_CAPTURE --hypothesis TEXT] -- COMMAND ARGS (--after publishes checkpoint ID linking PREVIOUS_CAPTURE to this probe; required after the first complete capture unless a checkpoint was published) | checkpoint ROOT ID CAPTURE OBSERVATION_COMMAND HYPOTHESIS NEXT_COMMAND | checkpoint ROOT ID INTENT_FILE | materialize ROOT ANNOTATIONS (annotations: {evidence: [{capture, command, contract, expected, classification: pass|superseded|product-defect|fail|setup-blocked|blocked|inconclusive}], limits: [..]}; revision, runtime, cwd and learning are filled in)'; diff --git a/test/helpers/qa-functional-eval.ts b/test/helpers/qa-functional-eval.ts index b21aab9e2..b20483980 100644 --- a/test/helpers/qa-functional-eval.ts +++ b/test/helpers/qa-functional-eval.ts @@ -51,7 +51,7 @@ Use one causal sentence per checkpoint hypothesis (English, more than 20 charact Fixture execution boundary: - No prior plans, global learnings, remote or cross-session artifact store. Branch/base are main, with a clean successful seed commit. Do not run global setup, telemetry, plan discovery, base-detection scripts or learning writes. -- Reports belong only in existing qa-reports. Retain fixture state; the owner cleans it after preserving evidence. Authorized source/test edits use Write/Edit. Read/Glob/Grep support arbitrary read-only discovery, including directory/path inventory. +- Reports belong only in existing qa-reports. Retain fixture state; the owner cleans it after preserving evidence. Authorized source/test edits use Write/Edit. Every Write/Edit targets an owned path inside this working directory; a write anywhere else, including its parent directory, fails acceptance. Read/Glob/Grep support arbitrary read-only discovery, including directory/path inventory. - Bash accepts separate literal commands only: no shell composition, scripts or added path operands. Read-only forms are pwd, ls, ls -la, git status --short, git status --porcelain, git branch --show-current, git diff, git diff --stat, git rev-parse HEAD, bun --version, and exactly date -u +%Y-%m-%dT%H:%M:%SZ. Native tests use bun test with optional named test/*.test.ts selectors. - These are complete command forms, not general shell examples. For inventory inside a named directory, use Read/Glob/Grep; the listed ls forms inspect only the working directory. Do not add operands or flags beyond the declared forms, even for read-only discovery. - The installed production helper is bin/gstack-qa-evidence (absolute owned path also accepted); \`bun bin/gstack-qa-evidence --help\` prints its usage, and the helper source is not part of the task. Probe outputs are declared public/synthetic, so capture with: bun bin/gstack-qa-evidence capture qa-reports NNN --public --timeout-ms 10000 -- NATIVE_PROBE. The child must be one of the observation forms below. Each execution/replay gets a fresh three-digit ID. The helper does not authorize another command, interpreter, path, pipeline or redirect. diff --git a/test/qa-evidence.test.ts b/test/qa-evidence.test.ts index 04097731d..5cf292db7 100644 --- a/test/qa-evidence.test.ts +++ b/test/qa-evidence.test.ts @@ -495,7 +495,8 @@ test('every capture after an input change names the commands still to rerun, unt const materialized = f.run('materialize', f.root, 'annotations.json'); expect(materialized.status, materialized.stderr).toBe(0); expect(receipt(materialized.stdout).verdict).toEqual({ status: 'pass', open: [] }); - expect(receipt(materialized.stdout).next).toContain('this verdict is final for this report root'); + expect(receipt(materialized.stdout).next).toContain('this verdict is final. Next, write the Markdown report beside evidence.json.'); + expect(receipt(materialized.stdout).next).not.toContain('report root'); }); test('a descriptive classification is rejected before publication, so the corrected label can still materialize', () => {