Repository navigation
fix(gate): five defects found while running Feature 081 in a sync target - #19
Conversation
`--phase diff` was structurally unusable. `diff_evidence()` hashed every untracked file's full content, and the classifier writes its own receipt into the untracked `.specify/audit-tiers/`; the aggregation writes the station receipt into the tracked `.specify/specter-run.jsonl`. Producing either receipt therefore changed the very evidence that receipt records, so the receipt was stale the instant it existed and a T3 station could never reach a valid one. Exclude gate machinery — receipts, the run ledger, continuity artifacts, review state, and the installed runtime copies under `.specify/` — from the classified bundle, on both the tracked pathspec and the untracked scan. The tracked sources those copies come from (`scripts/specter/`, `docs/templates/`) stay in the diff, so a real policy, classifier, or gate change still forces its floor; a regression test pins that, because excluding a receipt must never become a hole that also excludes a code change. Also make the receipt write idempotent. Re-classification that observes the same inputs and reaches the same verdict no longer rewrites the file, because human WARN acknowledgements bind to the receipt sha256 — the mandatory re-classify at `/ms.implement` was voiding an acknowledgement the user had given minutes earlier against byte-identical evidence, forcing them to approve twice. Only `timestamp` is exempt from the comparison, so a genuine change still rewrites and still voids the ack. Found while running Feature 081 in the suseonglm sync target. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two parser defects made valid reviewer output look invalid or empty, and both failed in the dangerous direction: silent under-reporting. `extract_field` stripped leading whitespace only, so a report whose header line ended in the Markdown hard-break (two trailing spaces) compared unequal on Mode and Result and was rejected as coming from the wrong station. Surrounding whitespace is never semantic in these fields; trim both sides. The continuity packet builder matched only CRITICAL and HIGH severity cells, while §6 of the protocol calls these "prior blocking findings" and the station prompts fix no severity vocabulary. A round whose report carried BLOCKING findings produced "(no blocking findings in this lane at this round)", so the re-round was never told to carry that lineage forward. Accept BLOCKING. Found while running Feature 081 in the suseonglm sync target. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…that runs it `ruff format` rewrote `except (OSError, json.JSONDecodeError):` into PEP 758's unparenthesized form, which `pyproject.toml`'s `requires-python = ">=3.14"` permits. But this script is executed by whatever `python3` a consumer repo has on PATH, and the test harness's own restricted PATH resolves to 3.11, where that form is a SyntaxError — the classifier died at import and two tiered-gate tests went red. Split the handler into two clauses. They are valid on every interpreter and cannot be merged back into a form the formatter will rewrite. The underlying mismatch is left alone deliberately: the package declares 3.14 while its scripts must run under whatever the consumer has. That is a repo-wide decision, not this PR's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI status: red on an inherited baseline, merging by operator decision
The failure is pre-existing on master. Both Formatting them is left out per the surgical-change rule. Operator decision: merge with CI red rather than expand this PR's scope. One real defect found while diagnosing this, and fixed here (
|
Five gate defects surfaced while running a real T3 Feature cycle (
/ms.specter 081) in the suseonglm sync target. Two of them made a T3 station structurally unable to reach a valid receipt; the other three silently discarded valid reviewer output or a human's acknowledgement.None was visible to the existing suite, because none changes a tier value — they change whether a receipt can ever be valid, whether a report is accepted, and whether prior findings survive into a re-round.
The defects
--phase diffreceipt is stale the instant it is writtendiff_evidence()hashes every untracked file's content, and the classifier writes its receipt into the untracked.specify/audit-tiers/valid: true.specify/specter-run.jsonlis tracked and is the station receipt, so emitting it changed the diffacknowledge-warncould never bind;/ms.reviewcould not be completedtimestampdiffers but the sha256 the ack binds to movesextract_fieldstrips leading whitespace only; a Markdown hard-break (two trailing spaces) broke the exact-match compareCRITICAL/HIGH, but §6 calls these "blocking findings" and the prompts fix no vocabularyThe fix for 1–2, and why it is not a hole
Gate machinery is excluded from the classified bundle: receipts, the run ledger, continuity artifacts, review state, and the installed runtime copies under
.specify/.Only installed copies and outputs are excluded. The tracked sources they come from —
scripts/specter/,docs/templates/— stay in the diff, so a real policy, classifier, or gate change still forces its floor.test_gate_machinery_is_excluded_but_its_tracked_source_is_notpins exactly that, because "excluding a receipt" must never quietly become "excluding a code change".A side effect worth noting: because the untracked bundle previously swept in the policy file and workflow prose, classification was firing
audit-policy-change,secrets-crypto, andmoney-financialfloors that no line of the Feature's code justified. That noise is gone too.The fix for 3
The receipt write is now idempotent — identical inputs and identical verdict do not rewrite the file.
timestampis the only field exempt from the comparison, so a genuine change still rewrites and still voids the ack (test_reclassifying_changed_evidence_does_rewrite_the_receipt).Tests
Every new test was verified to fail without its fix — the fixes were stashed and the suite re-run. Two of the new tests needed correcting before they discriminated at all:
utc_timestamp()has second granularity and both runs landed in the same second — it now crosses a second boundary deliberately.Not in this PR
The Antigravity plugin's
scripts/commands/rescue.mjsexits 0 with no output for every input (bin/antigravity.mjs rescueworks). That is a separate repository.Full evidence, with file:line, is in the sync target's review report:
docs/review/review_Claude_260727-221341.md(beomeodev/suseonglm#276).🤖 Generated with Claude Code