R.6-R.10: sealing, claim/lease, masking, and the nine defects the critic found - #7
Merged
Merged
Conversation
…ritic found R.6 per-half sealing, R.7 the claim/lease protocol, R.8 secrets masking, R.9 the retention document, R.10 the adversarial critic. Plus the fix round for everything R.10 raised, and an amendment to my own enum ruling. The critic returned FAIL with 3 blockers and 6 majors, and reproduced every one with a named probe rather than arguing it. All are fixed, and every fix leaves a permanent regression test behind -- critique02_regression_test.go in both packages. A fixed defect with no test is a defect waiting to return. Blockers B1 TWO LIVE LEASES ON ONE FINDING. Durable uniqueness was (finding_id, audit_record_id), not (fingerprint, record_version): a re-scan mints a new audit_record per scan_run, each defaulting to audit_version 1, so one fingerprint got independent concurrent leases. Now guarded in both the selecting and the granting statement -- the latter reads fingerprint and version via scalar subqueries on handoff_id so there is no read-then-write window. Verified by reverting the guard and watching two probes fail. Honest limit: this is a statement-level guarantee, not a durable constraint. It does not stop a row being set to 'leased' by a different binary. The DDL that would make it structural is recorded rather than silently omitted. B2 A CONSUMED AUDIT FELL OUT OF THE ELIGIBILITY GATE and was then expired. S1 requires a RE-ENTRANT consumer, and R.6's own sealer already kept consumed audits readable -- R.7's gate simply disagreed with it. 'expired' stays excluded, matching the sealer's other refusal arm. B3 MASKING MISSED FIELDS CARRYING LIVE CREDENTIALS. Discovery covered only webRequest/webResponse headers and parameters, so anvil/repro.curl -- a reproduction command line that routinely carries a bearer token -- went to the store unmasked. S7 names this the highest-risk field in the system. Masking now walks a single enumerated surface of ten pointers, and curl arguments are masked structurally (-H, -b, -u, -d/-F, bare URLs), failing CLOSED on an unparseable header argument. The new tests build a MINIMAL record and assert the planted secret appears exactly once before masking, so they cannot pass by propagation from a header -- that false-confidence pattern was itself one of the findings. Majors: AssertMasked was weaker than Mask (the gate sinks are told to trust) and now drives off the same enumeration, with a divergence guard that walks the surface rather than a hand-written list; ReadPacket checked nothing and is now gated on lease ownership, audit eligibility and record version, re-asserted against the database rather than a stale Handle; Sealer.Inspect bypassed the expiry arm; IdempotencyKey hashed an autoincrement rowid instead of the audit identity; and a requires_dynamic_confirmation finding could reach 'validated' with no dynamic evidence, which contradicts S7 outright -- only a DAST reproduction that now fails earns "verified fixed". An amendment to my own ruling, and the mistake I made making it R.6 flagged that the frozen nine-value anvil/dastStatus enum had no image for "the DAST half itself broke", and was folding it onto completed_partial. That fold is wrong for the reason S6 gives about targets: a half that CRASHED differs from one that COVERED PART of the surface, and collapsing them makes dast_coverage uninterpretable. So the enum gains completed_failed and DeriveDastStatus is now total -- every (provenance, half-status) pair has exactly one image, asserted by enumerating all of them. I landed that amendment in two of the five places the vocabulary lives, and the tree went red. R.4's TestEnumCheckConstraintsMatchContractLiteralForLiteral caught it by comparing the SQL CHECK against the Go enum literal-for-literal -- the guard doing exactly its job. Had it shipped, the consequence would have been worse than the fold it replaced: an audit whose DAST half crashed could not be persisted AT ALL, because the derivation produced a literal the store rejected. All five copies now move together, and CONTRACT.md records the list, because one vocabulary with five definitions is the same defect section 6 exists to close -- and an amendment is exactly when it recurs. Left open, deliberately, and named rather than buried: - Queue.PacketPath stays exported and ungated; the gated ReadPacket is the polite path, not the only one. Cache-file confidentiality currently rests on the 0700 directory mode. - checkDynamicEvidence trusts the caller's Handle.DastStatus rather than re-reading audit_record, unlike the checks beside it. - CRITIQUE-02 F10 (a voluntary hand-back burns an attempt) and F13 (any caller can set the 'expired' terminal state from outside the reaper that owns that clock) still reproduce. Neither was in the eight I asked to be fixed. Evidence: gofmt, vet, build clean; go test -count=1 ./... green across cmd/anvil, internal/handoff, internal/record, internal/store; 163 tests pass. go test -race cannot run on this Windows host (cgo.exe exit 2, no C toolchain); CI runs it on Linux, and the B1 concurrency probe needs that confirmation.
CI caught this and the dev host never would have. TestRunSweepsUntilCancelled failed on ubuntu-latest under -race with "sweep error: handoff: scanning leases: context canceled" while passing every run on Windows, because the race detector slows execution enough to turn the cancel-during-sweep window from rare into reliable. That is the argument for -race being a required check rather than an optional one, and it is why the PR flagged this specific test as needing Linux confirmation. The defect is in the reaper, not the test. When ctx is cancelled while a sweep is mid-query, the query returns context.Canceled, Run wrapped it and handed it to observe as a sweep error, and only then returned ctx.Err(). So every shutdown that happened to interrupt a sweep put "context canceled" in the operator's error channel. That is not cosmetic. The entire value of reporting sweep errors is that a real one gets noticed -- the reaper is the only thing that unwedges crashed consumers, so its failures matter. An error channel that cries wolf on every restart is one nobody reads, and the real error is then invisible in the noise. Run now returns ctx.Err() silently when a sweep is interrupted by its own cancellation. Both conditions are required and each is load-bearing: ctx.Err() alone would swallow a genuine database failure landing in the same instant as a shutdown, and errors.Is alone would swallow a context.Canceled arriving from some caller-supplied context nested inside the sweep, which is a real fault. TestRunReportsNoSweepErrorOnCancellation pins it by hammering the window 40 times at varying offsets rather than hoping to hit it once. Its assertion is deliberately not "no error is returned" -- Run correctly returns ctx.Err() -- but that the OBSERVER is never told a clean shutdown was a failure. Evidence: gofmt, vet clean; the two tests pass locally; full suite green. -race still cannot run on this Windows host, so this PR's CI run is the check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
R.6sealing,R.7claim/lease,R.8masking,R.9retention doc,R.10critic — plus the fix round for everythingR.10raised, and an amendment to my own enum ruling.The critic returned FAIL with 3 blockers and 6 majors, and reproduced every one with a named probe rather than arguing it. All are fixed, and every fix leaves a permanent regression test behind (
critique02_regression_test.goin both packages). A fixed defect with no test is a defect waiting to return.Blockers
B1 — two live leases on one finding. Durable uniqueness was
(finding_id, audit_record_id), not(fingerprint, record_version): a re-scan mints a newaudit_recordperscan_run, each defaulting toaudit_version1, so one fingerprint got independent concurrent leases. Now guarded in both the selecting and the granting statement — the latter reads fingerprint and version via scalar subqueries onhandoff_id, so there's no read-then-write window. Verified by reverting the guard and watching two probes fail.B2 — a
consumedaudit fell out of the eligibility gate and was then expired. S1 requires a re-entrant consumer, andR.6's own sealer already kept consumed audits readable —R.7's gate simply disagreed with it.B3 — masking missed fields carrying live credentials. Discovery covered only
webRequest/webResponseheaders and parameters, soanvil/repro.curl— a reproduction command line that routinely carries a bearer token — reached the store unmasked. S7 names this the highest-risk field in the system. Masking now walks a single enumerated surface of ten pointers, and curl arguments are masked structurally (-H,-b,-u,-d/-F, bare URLs), failing closed on an unparseable header argument.The new tests build a minimal record and assert the planted secret appears exactly once before masking, so they cannot pass by propagation from a header — that false-confidence pattern was itself one of the findings.
Majors
AssertMaskedwas weaker thanMask— the gate sinks are told to trust — and now drives off the same enumeration with a divergence guard that walks the surface rather than a hand-written list.ReadPacketchecked nothing and is now gated on lease ownership, audit eligibility and record version, re-asserted against the database rather than a staleHandle.Sealer.Inspectbypassed the expiry arm.IdempotencyKeyhashed an autoincrement rowid instead of the audit identity. And arequires_dynamic_confirmationfinding could reachvalidatedwith no dynamic evidence, contradicting S7 outright.An amendment to my own ruling, and the mistake I made making it
R.6flagged — rather than absorbed — that the frozen nine-valuedastStatusenum had no image for "the DAST half itself broke", and was folding it ontocompleted_partial. Wrong for the reason S6 gives about targets: a half that crashed differs from one that covered part of the surface, and collapsing them makesdast_coverageuninterpretable. So the enum gainscompleted_failed, andDeriveDastStatusis now total.I landed that amendment in two of the five places the vocabulary lives, and the tree went red.
R.4'sTestEnumCheckConstraintsMatchContractLiteralForLiteralcaught it by comparing the SQL CHECK against the Go enum literal-for-literal — the guard doing exactly its job. Had it shipped, the consequence would have been worse than the fold it replaced: an audit whose DAST half crashed could not be persisted at all.All five copies now move together, and
CONTRACT.mdrecords the list — because one vocabulary with five definitions is the same defect §6 exists to close, and an amendment is exactly when it recurs.Left open, deliberately
Queue.PacketPathstays exported and ungated; gatedReadPacketis the polite path, not the only one. Cache-file confidentiality rests on the 0700 directory mode.checkDynamicEvidencetrusts the caller'sHandle.DastStatusrather than re-readingaudit_record, unlike the checks beside it.CRITIQUE-02F10 (a voluntary hand-back burns an attempt) and F13 (any caller can setexpiredfrom outside the reaper that owns that clock) still reproduce. Neither was in the eight I asked to be fixed.Testing
go test -racecannot run on this Windows host (cgo.exeexit 2). CI runs it on Linux — and the B1 concurrency probe needs that confirmation, so this PR's own CI run is the real check on it.