Skip to content

R.6-R.10: sealing, claim/lease, masking, and the nine defects the critic found - #7

Merged
tom-snyder merged 2 commits into
mainfrom
feat/phase1-sealing-handoff
Aug 8, 2026
Merged

R.6-R.10: sealing, claim/lease, masking, and the nine defects the critic found#7
tom-snyder merged 2 commits into
mainfrom
feat/phase1-sealing-handoff

Conversation

@tom-snyder

Copy link
Copy Markdown
Member

Summary

R.6 sealing, R.7 claim/lease, R.8 masking, R.9 retention doc, R.10 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's 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.

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 — 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

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, contradicting S7 outright.

An amendment to my own ruling, and the mistake I made making it

R.6 flagged — rather than absorbed — that the frozen nine-value dastStatus enum had no image for "the DAST half itself broke", and was folding it onto completed_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 makes dast_coverage uninterpretable. So the enum gains completed_failed, and DeriveDastStatus is now total.

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.

All five copies now move together, and CONTRACT.md records 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.PacketPath stays exported and ungated; gated ReadPacket is the polite path, not the only one. Cache-file confidentiality 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 expired from outside the reaper that owns that clock) still reproduce. Neither was in the eight I asked to be fixed.

Testing

gofmt / vet / build   clean
go test -count=1      green: cmd/anvil, internal/handoff, internal/record, internal/store
                      163 tests pass

go test -race cannot run on this Windows host (cgo.exe exit 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.

…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.
@tom-snyder
tom-snyder merged commit d6661d1 into main Aug 8, 2026
4 checks passed
@tom-snyder
tom-snyder deleted the feat/phase1-sealing-handoff branch August 8, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant