Skip to content

R.1: the record contract, and the six enums the review gates froze - #5

Merged
tom-snyder merged 1 commit into
mainfrom
feat/phase1-record
Aug 8, 2026
Merged

R.1: the record contract, and the six enums the review gates froze#5
tom-snyder merged 1 commit into
mainfrom
feat/phase1-record

Conversation

@tom-snyder

Copy link
Copy Markdown
Member

Summary

R.1 — the packet everything in areas A, B, D, O and X is written against. Its scope grew after it was written: the review gates made it the single point where all shared cross-area vocabulary is fixed.

Ten confirmed defects came from four areas each defining that vocabulary from their own side. Every one was a produce/consume break — one area writing literals another area's NOT NULL column could not accept. dast_status shared zero values between two areas that each claimed authority over it.

What landed

  • internal/record/contract.go — the Go contract and its validators
  • internal/record/CONTRACT.md — the human-readable version other areas get pointed at, naming a producer and a consumer for every field
  • schemas/anvil-record-v1.schema.json — SARIF 2.1.0 + the anvil/* extension, Draft 2020-12

Four distinctions carry comments explaining why, because they look like redundancy and someone will otherwise simplify them away:

Distinction Why it exists
dast_sealed / both_sealed S1 requires two independently sealed halves. A machine that can only seal SAST-then-DAST cannot express a DAST-first seal — which is what area O's four-state version got wrong.
skipped_no_manifest vs not_run No manifest declared, versus the DAST tier not installed. S6 requires a failed target to be distinguishable from one scanned clean.
target.provenance vs target.provisioning One field with two meanings in two areas. dastStatus derives from the former; merging loses information S6 requires.
insufficient_context A verdict, not a low confidence score.

Tests added by me, because R.1 shipped without any

The packet validated itself through a scratchpad harness — 17 negative Go fixtures and 20 negative JSON-schema fixtures, all correctly rejected — but none of it landed in the repo, so nothing would ever run it again. That's the same defect fixed for the experiment register two commits ago: a schema nobody runs is a comment.

contract_test.go now pins, in-tree:

  • All six frozen enums as literal strings, compared against the accessors other packages consume. Not circular — the table is what the ruling says, the accessors are what the code does, and a "tidy" edit has to defeat both.
  • Every pre-ruling literal the four areas were actually usingopen, complete, clean, partial, failed_to_boot, EXHIBITS and the rest — asserted to be rejected. If one starts validating, the ruling has been quietly undone.
  • The four handoff dispositions that existed only in area 60's deleted anvil_ledger, without which area X's exit criterion 14 is unsatisfiable.
  • That a repo source snippet is untrusted, not anvil_generated. Area B was found stamping the latter on a struct whose field is verbatim target-repo source — which would disable area X's containment check on exactly the string that most needs it.
  • That a CWE match alone never qualifies a correlation as verified (S7).
  • That the empty string is rejected everywhere, so a zero-valued Go string can't pass as "unset but fine".

What is deliberately NOT here

R.2 (the fingerprint). It wrote fingerprint.go and fingerprint_test.go before the session cap killed it, and the plan's own §0.3 rule says to check the filesystem before concluding a step failed — so I did. But §0.3 requires the artifact be structurally complete, and this one is not:

  • a compile error (string concatenation with + leading the continuation line, which semicolon insertion breaks)
  • testdata/fingerprint_corpus/ was never written — 5 of its 8 test failures are just the missing fixtures
  • two of its own tests disagree with its implementation

Fingerprint drift fails silently and permanently — two producers emitting different digests means regression matching breaks forever with nothing surfacing it. That earns a clean re-run, not a repair by guesswork. The WIP is preserved outside the tree.

Testing

gofmt -l .          clean
go vet ./...        clean
go build ./...      clean
go test -count=1    ok (cmd/anvil, internal/record)
schema self-check   valid Draft 2020-12

R.1 is the packet everything in areas A, B, D, O and X is written against. Its
scope grew after it was written: IMPLEMENTATION-PLAN.md section 6 makes it the
single point where all shared cross-area vocabulary is fixed, because ten
confirmed defects came from four areas each defining that vocabulary from their
own side. Every one was a produce/consume break -- one area writing literals
another area's NOT NULL column could not accept, with `dast_status` sharing
ZERO values between two areas that each claimed authority over it.

What landed: internal/record/contract.go (the Go contract and its validators),
CONTRACT.md (the human-readable version other areas get pointed at, naming a
producer and a consumer for every field), and schemas/anvil-record-v1.schema.json
(SARIF 2.1.0 plus the anvil/* extension, Draft 2020-12).

Four distinctions carry comments explaining why, because they look like
redundancy and someone will otherwise simplify them away:
  - dast_sealed and both_sealed exist because S1 requires two INDEPENDENTLY
    sealed halves. A machine that can only seal SAST-then-DAST cannot express a
    DAST-first seal, which is what area O's four-state version got wrong.
  - skipped_no_manifest is deliberately distinct from not_run: no manifest
    declared, versus the DAST tier not installed. S6 requires a target that
    failed to be distinguishable from one scanned clean.
  - target.provenance (boot outcome) and target.provisioning (which path) were
    one field with two meanings in two areas. dastStatus derives from the
    former; merging them loses information S6 requires.
  - insufficient_context is a verdict, not a low confidence score.

Tests added by the orchestrator, because R.1 shipped without any

The packet validated itself through a scratchpad harness -- 17 negative Go
fixtures and 20 negative JSON-schema fixtures, all correctly rejected -- but
none of it landed in the repository, so nothing would ever run it again. That
is the same defect fixed for the experiment register two commits ago: a schema
nobody runs is a comment.

contract_test.go now pins, in-tree:
  - all six frozen enums as literal strings, compared against the accessors
    other packages consume. Not circular: the table is what the ruling says,
    the accessors are what the code does, and a "tidy" edit has to defeat both.
  - every pre-ruling literal the four areas were actually using -- `open`,
    `complete`, `clean`, `partial`, `failed_to_boot`, `EXHIBITS`, and the rest
    -- asserted to be REJECTED. If one starts validating, the ruling has been
    quietly undone.
  - the four handoff dispositions that existed only in area 60's deleted
    anvil_ledger table, without which area X's exit criterion 14 is
    unsatisfiable.
  - that a repo source snippet is `untrusted` and not `anvil_generated`. Area B
    was found stamping the latter on a struct whose field is verbatim
    target-repo source, which would disable area X's containment check on
    exactly the string that most needs it.
  - that a CWE match alone never qualifies a correlation as verified (S7).
  - that the empty string is rejected everywhere, so a zero-valued Go string
    cannot pass as "unset but fine".

Evidence: gofmt clean; go vet clean; go build clean; go test -count=1 ./...
passes; schema self-checks as valid Draft 2020-12.

R.2 is NOT in this commit. It wrote fingerprint.go and fingerprint_test.go
before the session cap killed it, but the artifact is not structurally complete:
it had a compile error (string concatenation with the + leading the
continuation line, which semicolon insertion breaks), it never wrote its
testdata/fingerprint_corpus/ fixtures, and two of its own tests disagree with
its implementation. Fingerprint drift fails silently and permanently, so it gets
a clean re-run rather than a repair by guesswork. The work-in-progress is
preserved outside the tree.
@tom-snyder
tom-snyder merged commit 9c367d1 into main Aug 8, 2026
4 checks passed
@tom-snyder
tom-snyder deleted the feat/phase1-record branch August 8, 2026 20:12
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