Skip to content

v0.39.0: vendor Spec v0.3.0 and the retained-column leaf at its release identity; audit line-format fingerprints (#327); tool taxonomy closure (#329) - #341

Merged
kikashy merged 3 commits into
mainfrom
release-v0.39.0-final
Oct 29, 2026
Merged

kikashy merged 3 commits into
mainfrom
release-v0.39.0-final

Conversation

@kikashy

@kikashy kikashy commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Milestone v0.39.0. Release notes and the tag are left to the release step.

1. Core vendoring

internal/artifacts/jps/0.3.0 was already present (registered; lock pinned to commit 65c7cf5598a0f0fc532c80a9e2215090951104ce). It was removed and re-imported with tools/sync-spec-artifacts from a clean checkout at the published Spec tag v0.3.0 (commit 27cb29bf13e4a2d58d69fe19c7ebfeb762b014fc). All 165 files are byte-identical; only the lock's ref (v0.3.0) and baseCommit change. The bundle digest stays 018b8221c424fa530d7482b160f4d7faa59cd9d2e80d3f6e2f8748dfb0d7e743. The registry is unchanged. README, VERSIONING, CONFORMANCE, architecture and the Core pin test now name the tag.

2. Retained-column leaf at the release identity

  • The Spec v0.3.0 tar.gz asset was re-verified before import: sha256 6469a866b9c0e1671de3462c1b1823a1738561f6b84e52b470382fc620530ce9 matches the release's checksums.txt, gh attestation verify succeeds, and the release is immutable and is neither a draft nor a pre-release. Inventory releases/v0.3.0.retained-column.json has sha256 c8e489b2cabd0ee0b794a8b6a817f06f901e338348e32ec23c8c83d7c40dfe58, with the same bytes in git at the tag. Its schema and corpus digests equal rc.2's.
  • The rc.2 import was removed and the leaf re-imported with tools/sync-retained-column into the emptied official/. The bytes changed exactly where the Spec changed between rc.2 and the final release: the inventory (renamed), release-identity.json, the three spec pages and the two leaf readers.
  • The provenance pin is sha256:271bfd2b1858885a4748d8630e0cb98a3dcf3d93b015798ab4d7ad804dcb257b. The installed tuple is 0.3.0 at 27cb29bf…, and the selector is retained-column/1@27cb29bf13e4a2d58d69fe19c7ebfeb762b014fc, which is a public value change. The source URL follows the revision, and maturity stays draft.
  • internal/retainedcolumn now admits the final release: tag v0.3.0, version 0.3.0, and a release identity with no distribution member. The importer's archive prefix comes from the version. The installed reader refuses the rc.2 identity, which is tested three ways: a synthetic release-identity.json (candidate form and the exact rc.2 identity), the rc.2 tuple against the installed bytes, and the rc.2 selector.
  • The CLI help, the human output (Selected retained-column/1 v0.3.0 · <rev>) and the MCP descriptions and errors no longer say "RC2". The rc.2-named tests were renamed and updated. All 72 published cases and all 11 raw-value recipes run with the installed identity. Historical Draft resources are untouched.
  • The reader doc moved to docs/dialect-retained-column.md; the alpha.2 CHANGELOG link now points at the doc as tagged. In docs/releasing.md the leaf section takes the release identity, with one sentence on -rc.N and -alpha.N.
  • The corpus capabilities.json and manifest.json still say "distribution": "v0.3.0-rc.2". Their bytes are unchanged upstream, and the test keeps that label with a comment.
  • tools/check-release --tag v0.39.0 passes.

3. #329: nothing missing

#329 was closed on 2026-10-25 after #330. Checked against its acceptance:

  • All 38 served tools declare operation and effect in _meta (internal/mcp/taxonomy.go).
  • The standard annotations are derived from the effect.
  • check tools carry a checkKind, and experimental is a flag. No tool name changed.
  • docs/mcp-clients.md groups the tools by family. Cross-checked by script: 38/38 tools, no disagreement with the declarations.
  • TestTaxonomyMissingDeclarationRefusesActualPublication fails publication when a declaration is missing, and TestTaxonomyActualToolsPublication pins the metadata and hints.

Nothing was built for #329.

4. #327: audit line-format fingerprints

  • internal/audit/formats.go lists the five persisted line types: evaluations.jsonl record and discontinuity, signatures.jsonl record-signature and key-rotation, and stamps.jsonl stamp, each with its writer constant.
  • internal/auditformat/catalog.json is generated: the normalized JSON shape (grammar audit-line-type/1) and its SHA-256 for each line type.
  • changes.json is hand-kept and append-only. Each entry is a baseline, a compatible-addition at the same version, or a new-format-version at the next integer with a readingRule, and every non-baseline entry needs a reason.
  • status.json is kept apart. For each file it records writes (checked against RecordVersion, SidecarVersion and StampVersion), baseline and released, with baseline <= released <= writes. All three files are at 1/1/1.
  • The new check is audit-formats (go run ./tools/audit-formats). It is in dev.json and mapped in jpdev.json. Following the repository rule, it is also a step in the CI test matrix, so this PR edits .github/workflows/ci.yml. It fails on a stale catalog, on a digest that no change record names, and on a status the writer constants contradict. -write regenerates only the catalog.
  • Vectors: the normalized form of a handwritten shape and its digest were computed outside the code. Three real line digests were checked by hand with sha256sum.
  • Mutants, run through a build overlay and all caught: the digest-without-record check, the stale-catalog check, the status-vs-writer check, the last-record-vs-writer check (a test case was added to catch it), and the reading-rule requirement. The leaf's distribution refusal was mutated too and was caught by candidate release identity.

Closes #327. Refs #329.

Fix round (review of 15bc1de: F1, F2, F3, F5)

  • F1, disposition fingerprint. The canonical disposition is built from maps, so it now has a declared shape. result.CanonicalDispositionShape lists the members CanonicalWith writes. LineFormat.Embedded maps the record's disposition member to that type, and the fingerprint uses it instead of an opaque json. The record's normalized type now spells out every disposition member.
  • Exemplars. The check now also runs exemplar lines through the writers' own encoders. For the record, that is three dispositions built by CanonicalWith, which together carry every member. It holds them to the declared shape. It fails on a member the builder writes that the shape does not declare, on a declared member that no exemplar writes, and on a required member that is absent.
  • Stamp token. The token is tagged format:"base64", and the stamp exemplar must be standard padded base64.
  • Baselines. The two baselines this PR introduced (record and stamp) were re-pinned to the new digests. No baseline had been released yet.
  • F2, append-only records. status.json now carries releasedChanges, a count and digest pinning the released change-record prefix. Rewriting, reordering or removing a pinned record fails.
  • F3. The docs and the package comment now state that the fingerprint covers shape and the declared grammars, not other value spellings such as timestamps.
  • F5. The stale ci.yml comment is removed.
  • Mutants, all caught.
    • G1 (an extra disposition member) fails audit-formats with "member disposition.extra is not declared by the line's shape".
    • G2 (RawURLEncoding token) fails with "member token is not standard padded base64".
    • G5 (P1 plus -write, then the baseline rewritten in place) fails with "released change records … not unchanged and in order".
    • Each new safeguard was disabled in turn and its test failed: the pin, the undeclared-member check, the base64 grammar, the coverage check, the embedded substitution, and the exemplar wiring in CheckFiles.
  • jpdev check --changed origin/main: 10 passed, 0 failed.

Checks

== check summary (dev.json), jpdev check --changed origin/main (whole list: dev.json/jpdev.json changed) ==
PASS     local-paths              0s
PASS     agent-docs               0s
PASS     diff-check               0s
PASS     go-fmt                   16s
PASS     go-vet                   10s
PASS     go-test                  184s
PASS     conformance              802s
PASS     build                    71s
PASS     audit-formats            0s
    audit-formats: 5 persisted line types match their committed format decisions
PASS     registry-template        0s   (re-run with --only after the setup step; the first run lacked the Python dependency)
    registry template: valid
== 10 passed, 0 failed ==

Also: go run ./tools/check-release --tag v0.39.0: "Release inputs verified for v0.39.0."

🤖 Generated with Claude Code

kikashy and others added 2 commits October 29, 2026 04:05
Re-import the Core 0.3.0 bundle from the published v0.3.0 tag (bytes and
bundle digest unchanged; the lock's ref and commit move to the tag).
Remove the v0.3.0-rc.2 leaf import and import the leaf from the verified
v0.3.0 archive; install its provenance pin and exact tuple, and switch the
selected reader identity to 0.3.0. The installed reader refuses the
candidate's release identity and selector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
Add a generated catalog of every persisted audit-trail line type
(evaluations records and discontinuities, signature-sidecar record
signatures and key rotations, stamps) with its normalized JSON shape and
SHA-256 digest; hand-kept change records naming the format decision behind
each digest; and a separate format-status record (writes, baseline,
released) checked against the writer constants. The audit-formats check
(dev.json, and the CI matrix with the other Go steps) regenerates the
catalog and fails on a stale catalog, a digest without a change record or
a status the writer constants contradict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
@kikashy kikashy added this to the v0.39.0 milestone Oct 29, 2026
…review)

F1: the record's canonical disposition is built from maps, so it was an
opaque json value in the fingerprint. Declare its members in
result.CanonicalDispositionShape and fingerprint the record through it;
the check now holds exemplar lines from the writers' own encoders to the
declared shapes and grammars (an undeclared member, a declared member no
exemplar writes, or a stamp token that is not standard padded base64
fails). F2: status.json pins the released change records' count and
digest; rewriting, reordering or removing one fails. F3: state that the
fingerprint covers shape and declared grammars only. F5: remove a CI
comment left behind by #333.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
@kikashy

kikashy commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Verdict (re-read at 4760af6): RELEASABLE. 0 HIGH. F1, F2, F3 and F5 are resolved. One new MEDIUM (R1: the disposition shape is checked by exemplar sampling, so a member behind a branch no exemplar takes still passes). 2 LOW. F4 is unchanged and informational.

(Superseded first-round verdict at 15bc1de: NOT YET RELEASABLE. 1 HIGH (#327 does not fingerprint the persisted disposition member), 1 MEDIUM, 3 LOW. The vendoring (items 1 to 3) and #329 (item 5) verify clean.)

Clean-room review record: runtime PR #341 (v0.39.0 final prep) and the pre-release pass over v0.38.0..15bc1de

Probes

# What Command (abridged) Result
1 Archive digest sha256sum <archive>; compared with checksums.txt 6469a866…0ce9; matches
2 Archive attestation gh attestation verify <archive> --repo Judgment-Pack/judgment-pack-spec exit 0. The host printed no statement text.
3 Release state gh release view v0.3.0 on the spec repo not a draft, not a prerelease, published 2026-10-29T07:53:55Z
4 Tag to commit clone at tag v0.3.0; git rev-parse HEAD; git cat-file -p v0.3.0 annotated tag f4535372… → commit 27cb29bf13e4a2d58d69fe19c7ebfeb762b014fc, with an SSH signature block present
5 Archive equals git at the tag cmp of every archive file against the tagged checkout 249 compared, 0 differ. release-identity.json is archive-only, as expected.
6 Core re-import, independent go run ./tools/sync-spec-artifacts --source <spec@v0.3.0> --destination <room>/core-reimport --source-ref v0.3.0, then diff -r against internal/artifacts/jps/0.3.0 "165 files, 018b8221…"; directory IDENTICAL, lock.json included
7 Core lock against the archive each of the 165 locked files is mapped to its archive source and its sha256 checked 165/165 match
8 Core lock delta git show 40b8200^:…/lock.json diffed with the head Only baseCommit (65c7cf55… → 27cb29bf…) and ref (65c7cf55… → v0.3.0) changed. kind stays immutable-git-ref, and the bundle digest is unchanged.
9 Release gate go run ./tools/check-release --tag v0.39.0 "Release inputs verified for v0.39.0." immutableRef accepts an exact SemVer tag or the recorded commit, so v0.3.0 is the final tag.
10 Leaf bytes against the archive cmp of every file under internal/dialect/retained-column/official/ except provenance.json against the archive 29/29 byte-identical, none missing
11 Provenance inventory Python check of the files[] byteLength and sha256 against disk, plus set equality 29 entries correct, nothing unlisted, nothing missing
12 Provenance pin (the repository's rule: retainedcolumn.Verify takes the sha256 of the provenance.json bytes) sha256sum …/official/provenance.json 271bfd2b1858885a4748d8630e0cb98a3dcf3d93b015798ab4d7ad804dcb257b, which equals retainedColumnProvenancePin
13 Provenance fields read sourceArchiveSha256 = the archive digest. inventorySha256 = c8e489b2…. The identity is 0.3.0/27cb29bf… with the schema and corpus digests equal to rc.2's.
14 CLI with the final selector jpack dialect contract --draft-contract retained-column/1@27cb29bf… --draft-profile base exit 0: "Selected retained-column/1 v0.3.0 · 27cb29bf…"
15 CLI with rc.2 selectors the same with @bcac15b7537ce21879201b5e5dfe795496225121, @bcac15b and @v0.3.0-rc.2 all exit 2, capability-unsupported
16 MCP with the rc.2 selector stdio tools/call get_dialect_contract, draftContract=…@bcac15b… isError: true, status refused, capability-unsupported
17 rc.2 identity refusal in code read retainedcolumn.Inspect It requires tag v0.3.0, version 0.3.0 and distribution absent, and checks the inventory's distribution == v0.3.0. Tests cover the candidate-form identity, the rc.2 tuple and the rc.2 selector (artifacts_test.go:81-86, retained_column_artifacts_test.go:107, retained_column_release_test.go:202).
18 Leftover rc.2/RC2 strings git grep -i "bcac15b|rc\.2|rc2|release-candidate|a13a5c76" outside the Core bundle Remaining hits: historical CHANGELOG and alpha.2 notes, historical sentences in VERSIONING and docs, test comments, and upstream leaf bytes. No Go string that a host sees still says RC2.
19 Upstream rc.2 labels read official/spec/retained-column.md:266 The Spec final itself says "the schema, the corpus and their digests are the candidate's". capabilities.json and manifest.json keeping v0.3.0-rc.2 is therefore consistent with the final, and the bytes equal the archive.
20 JSON contract report jpack dialect contract … --format json spec.version is 0.3.0, specRevision is 27cb29bf…, and the specification URL follows the revision. No rc.2 label surfaces.
21 #327 shape probes P1–P5 below, each followed by go run ./tools/audit-formats all exit 1 (caught)
22 #327 gap mutants G1, G2, G3, G5 below audit-formats passes on all four (see the findings)
23 CI edit read ci.yml, dev.json, jpdev.json One step at the end of the test matrix (go run ./tools/audit-formats). It matches the header rule that the matrix runs dev.json's Go steps natively. The jpdev.json mapping adds **/*.go and internal/auditformat/*.json → audit-formats. The edit is minimal and correct. No API newer than the go 1.24.x floor is used (reflect.Pointer, strings.Cut).
24 #329 evidence tools/list over stdio; cross-checked against docs/mcp-clients.md; grep for the named tests; gh issue view 329 38 tools, all with _meta.operation and effect. Every name appears in the docs. All 14 check tools carry checkKind. Both named tests exist. The issue was CLOSED 2026-10-25.
25 Vendored devkit scripts the scripts compared with devkit tags changed_checks.py and handover_check.sh equal devkit v0.10.0 apart from the provenance header line. check.sh is v0.10.0 plus the patch of devkit commit 2f7f849 (unreleased; the header says so; it came in via #338).

Mutants

# Mutant Origin Run Caught?
M1 Stale-catalog comparison disabled (false &&) builder go test ./internal/auditformat/ yes (TestChangedMemberWithoutRecordFails)
M2 Status writes vs writer-constant check disabled builder same yes (TestCheckDecisionsAndStatus)
M3 readingRule no longer required for a new format version builder same yes (TestCheckDecisionsAndStatus)
P1 New member extra in recordSignatureLine own probe go run ./tools/audit-formats yes
P2 Stamp tag token → tok own probe same yes
P3 keyRotationLine fields at/keyId swapped own probe same yes
P4 discontinuity.line → json:"line,string" own probe same yes
P5 New member in result.AuditCheckpoint (another package, nested in the stamp line) own probe same yes
G1 result.Disposition.CanonicalWith adds an "extra" member to the persisted disposition own, gap audit-formats / go test ./internal/audit/ No for audit-formats (exit 0). Five byte tests in internal/audit fail.
G2 Stamp token base64.StdEncoding → RawURLEncoding own, gap same No for audit-formats. Stamp tests in internal/audit fail.
G3 Discontinuity at value changed from RFC3339Nano to another spelling own, gap same No for audit-formats, and no audit test fails
G5 P1 + -write, then the released baseline record's sha256 in changes.json rewritten in place instead of appending own, gap audit-formats and go test ./internal/auditformat/ No: both pass

Every mutant was reverted with git checkout. git status was clean afterwards.

Findings

# Sev Where Finding Suggested fix
F1 HIGH internal/auditformat / internal/audit/formats.go (#327) The persisted record's disposition is a json.RawMessage that result.Disposition.CanonicalWith builds from a Go map. Normalize therefore reduces it to the opaque kind json, so the runtime-produced disposition subtree has no fingerprint. Adding, renaming or dropping a disposition member changes every evaluations.jsonl record line, yet audit-formats passes and no change record is demanded (G1). This is a persisted-format change undetectable by #327's check (the brief's HIGH rule). The formats.go comment ("a change to a member of a persisted line cannot pass without a committed format decision") is false for this member. Mitigation: five byte-level tests in internal/audit fail on G1, so CI is red. But those tests would be updated without any format decision, which is the gap #327 exists to close. The inputs.facts/evidence members are caller documents and legitimately opaque. Fingerprint the disposition's shape as well: for example a descriptor struct of the canonical members (kind, reasons, handoff{state,routes?,triggeredBy?}, outcomeId?, value?) normalized into the record entry, plus a test that the Canonical output of a maximal disposition has exactly those members. If the coordinator rules the disposition Spec-owned (versioned by evaluatorSpecVersion), it should be downgraded and stated as an explicit exclusion in the catalog doc and the comment.
F2 MEDIUM internal/auditformat/changes.json rule "Only appended to, never rewritten" is unenforced. Rewriting a released baseline's digest in place passes both the check and the tests (G5), so a released format can be silently redefined. This is an unpinned safeguard. Pin released history: for example status.json (or a test) carries the digest of the change-record prefix through each file's released version, or CI checks against the merge base that the existing records are a byte prefix.
F3 LOW #327 scope and docs Only the shape is fingerprinted. Value grammars (timestamp spelling, token or signature encodings, digest spelling) and encoder options are not (G2: caught elsewhere; G3: caught by nothing). LineFormats() is hand-kept, so an unlisted new line type is invisible. The issue scopes #327 to shape, so this is a documentation gap. State these limits in docs/releasing.md "Audit line formats" and in the formats.go comment.
F4 LOW scripts/dev/check.sh (from #338) The vendored runner is devkit v0.10.0 plus an unreleased devkit commit's patch (2f7f849). The final would ship CI tooling equal to no devkit release. The header discloses it. Informational. Re-vendor only on the user's word (no automatic roll-out).
F5 LOW .github/workflows/ci.yml check job An orphaned comment ("First, so that a manifest that does not parse fails before the long steps start.") remains above "Set up Go", left behind when #333 removed the step it described. Delete the comment.

Depth 2: v0.38.0..15bc1de

PR Title Review record on the PR Material? Disposition
#330 MCP owner taxonomy and method skills Yes: independent clean-room review (same-vendor, Codex lane) Yes (MCP authority metadata, digest-addressed resources) Reviewed elsewhere. #329 evidence spot-checked here (probe 24).
#332 Changelog for v0.39.0-alpha.1 Yes: independent review, no findings No (CHANGELOG only) Reviewed elsewhere
#333 Re-vendor devkit v0.8.0 No Tooling plus a CI edit Read only. Superseded by #335/#336; final bytes compared with devkit (probe 25). F5 is its leftover comment.
#334 CI: changed-path check selection Yes: "Review record: runtime PR #334", 0/0/0 CI selection Reviewed elsewhere. Read the current check job: BASE_REF goes through env (no expression injection), and a push to main runs the whole list.
#335 Re-vendor devkit v0.9.0 No Tooling Read only (probe 25)
#336 Re-vendor devkit v0.10.0 No Tooling Read only (probe 25)
#337 Retained-column Draft document validation Yes: independent review (same-vendor, Codex lane) Yes (public surface) Reviewed elsewhere
#338 Adopt published retained-column RC2 Yes: independent review plus continuation Yes (vendored bytes, public surface) Reviewed elsewhere. Its leaf bytes are superseded by #341 and re-verified here (probes 10–20). F4 originates here.
(#341) This PR this record Yes Full depth above

None of the unreviewed PRs touches a security boundary, a record format or a public surface.

Public changes for the release notes (v0.39.0 compared with v0.39.0-alpha.2)

  1. Retained-column leaf: the installed identity moves from 0.3.0-rc.2 @ bcac15b7537ce21879201b5e5dfe795496225121 to 0.3.0 @ 27cb29bf13e4a2d58d69fe19c7ebfeb762b014fc (Spec v0.3.0). Maturity stays draft. The schema digest sha256:0cb49f23… and corpus digest sha256:17b16ff7… are unchanged.
  2. Selector value change (CLI --draft-contract, MCP draftContract): the selector is now retained-column/1@27cb29bf13e4a2d58d69fe19c7ebfeb762b014fc. The alpha.2 selector retained-column/1@bcac15b7… now refuses with capability-unsupported (CLI exit 2, MCP isError).
  3. JSON reports (dialect contract|canonicalize|digest|validate|check with the selector): spec.version is 0.3.0, spec.revision/specRevision is 27cb29bf…, and specification is …/blob/27cb29bf…/spec/retained-column.md.
  4. Human CLI text a host might match on: Selected RC2 retained-column/1 · <rev> becomes Selected retained-column/1 v0.3.0 · <rev>, and Selected RC2 checks passed: … becomes Selected leaf checks passed: ….
  5. MCP text: the tool descriptions and the retainedFrame and document property descriptions say "leaf" instead of "RC2". The error strings are now "Expected exact installed leaf selectors and JSON text arguments.", "Leaf selection members cannot be null." and "The selected leaf report could not be encoded."
  6. The reader doc moved from docs/dialect-retained-column-rc2.md to docs/dialect-retained-column.md.
  7. Core 0.3.0 bundle: now imported from the published Spec tag v0.3.0 (commit 27cb29bf…). Bytes and the bundle digest 018b8221… are unchanged, and there is no output change (artifactProvenance is still immutable-git-ref).
  8. New maintainer check audit-formats (Fingerprint the audit trail's persisted line formats, with a format-status record #327), with internal/auditformat/{catalog,changes,status}.json. No persisted audit format changes, and all three trail files stay at format 1.
  9. Declare each MCP tool's operation family and effect, with the standard MCP annotations #329 needed no change. The alpha.1 entries (taxonomy, method skills) and alpha.2 entries (retained-column reader, the 524288-byte generated-value limit) roll up into the final.

What was not established

Re-read at 4760af6

  • Candidate: head 4760af696b0f531604013b3f2852efedd0c31845, fetched into the room clone. The delta since 15bc1de is one commit, "audit-formats: fingerprint the disposition, pin released records (Fingerprint the audit trail's persisted line formats, with a format-status record #327 review)", touching 11 files.
  • Same reviewer, same toolchain (go1.26.5), and the same caps. Full suites were not re-run. Same-vendor statement: a Claude Opus review over a Claude Opus build; not a cross-vendor claim.
  • This is the one re-read the one-round rule allows.

Probes and mutants at 4760af6

# Mutant or probe Run Result
RR0 Unmodified head go run ./tools/audit-formats exit 0, "5 persisted line types match…"
G1 CanonicalWith adds an unconditional "extra" member same caught: "member disposition.extra is not declared by the line's shape"
G2 Stamp token StdEncoding → RawURLEncoding same caught: "member token is not standard padded base64"
G5 New recordSignatureLine member, -write, then the baseline digest rewritten in place in changes.json same caught: "the 5 released change records pinned by status.json are not unchanged and in order"
B5 Unconditional new member inside each route item ("priority": 0) same caught
B1 Top-level member written only when len(Reasons) > 1 (input-dependent) same, plus go test ./internal/audit/ ./internal/auditformat/ NOT caught. Both the check and the audit tests pass.
B2 Member inside a route item only when target.Kind == "person" (nested, path-dependent) audit-formats NOT caught
B6 Nested map handoff.urgency{level} only when the state is "required" audit-formats NOT caught
RR1 Are the baselines unreleased? git ls-tree -r --name-only <tag> | grep auditformat for v0.38.0, v0.39.0-alpha.1 and v0.39.0-alpha.2 0 files in all three tags. The catalog, change records and status are new in this PR.
RR2 Are the persisted bytes unchanged since v0.38.0? git diff v0.38.0..HEAD over internal/audit/{audit,stamp,repair,sign}.go and internal/result/{result,audit}.go The only change is a non-JSON struct tag format:"base64" on stampLine.Token plus a comment. CanonicalWith and every encoder are unchanged, so the record and stamp lines are the bytes v0.38.0 writes.
RR3 Members CanonicalWith can emit read every branch Unconditional: kind, reasons, handoff.state. Conditional: handoff.routes[]{kind,name} (non-nil), handoff.triggeredBy (non-empty), outcomeId (non-empty), value (non-nil). All are declared in result.CanonicalDispositionShape, and the three exemplars exercise every branch.
RR4 New exposure git grep for the callers of LineFormats() and the exemplar functions; read the delta Callers are only internal/auditformat and tools/audit-formats; no production path calls them. The exemplars use constant inputs with no I/O. The stamp tag does not affect encoding/json. Otherwise the delta is docs and a CI comment deletion. No new exposure.

All mutants were reverted, git status was clean afterwards, and the internal/auditformat tests pass on the restored head.

Answers to the re-read scope

  1. G1, G2 and G5 are all caught now.
  2. Today every member CanonicalWith can emit is declared and exercised (RR3). The shape is still hand-kept: it is checked against the writer only through three hand-chosen exemplars. That check catches any unconditional change (G1, B5) and any declared member that stops being written. A future member behind a branch no exemplar takes passes, whether it is top-level, nested in a route item or a nested map in handoff (B1, B2, B6). B1 also passes the existing audit tests, so nothing in CI sees it. value is map[string]any, so its contents are opaque by design (pack-defined outcome data).
  3. The re-pins are legitimate. No released runtime (v0.38.0, alpha.1, alpha.2) contains internal/auditformat (RR1). The persisted bytes of both line types are unchanged since v0.38.0 (RR2). Only the fingerprint grammar moved (disposition expanded, string(base64)), so no released format was redefined.
  4. The pin covers all 5 records (count 5). Each is the baseline of a format that has shipped (released: 1 for all three files), and none of the records has shipped yet, so pinning them for v0.39.0 is correct. The pin is not tied to released: when a later release ships a new format version, nothing forces releasedChanges.count to move over its record. docs/releasing.md makes this a manual release step. The check also prints the digest a full-prefix pin would need, so a deliberate re-pin is easy but visible in status.json. See R2.
  5. No new exposure (RR4).

Status of the first-round findings

# Status at 4760af6
F1 HIGH Resolved for every member the current writer emits (G1 caught; RR3). The residual future-branch gap is R1.
F2 MEDIUM Resolved (G5 caught). Residual: R2.
F3 LOW Resolved: the shape-only and declared-grammar limit is stated in docs/releasing.md, formats.go and auditformat.go.
F4 LOW Unchanged, informational: the vendored check.sh is devkit v0.10.0 plus the patch from unreleased commit 2f7f849.
F5 LOW Resolved: the stale ci.yml comment is removed.

New findings at 4760af6

# Sev Where Finding Suggested fix
R1 MEDIUM result.CanonicalDispositionShape and recordExemplars The declared shape is hand-kept and linked to CanonicalWith only by three sample dispositions. A disposition member added behind a branch the exemplars do not take passes audit-formats and the audit tests (B1, B2, B6). I grade it MEDIUM (an unpinned safeguard), not HIGH: no current writer path escapes, the unconditional case F1 named is now caught, and a disposition member change normally arrives with a Spec change. Read the brief's HIGH rule literally for future code paths and this would be HIGH; that is the coordinator's call. Make the shape the writer. Have CanonicalWith fill a CanonicalDispositionShape value and JCS-encode that, mirroring the non-nil routes rule with a pointer or custom handling. A new member then has to be a struct field, and the fingerprint sees it. Failing that, add a test that enumerates the branch conditions (Routes nil/empty/non-empty, TriggeredBy, OutcomeID, Value) as exemplars.
R2 LOW status.json releasedChanges The pinned prefix is a manual release step and is not tied to released. A record admitting a version later marked released can stay outside the pin. When a file's released equals a record's formatVersion, have the check require that record to fall inside the pinned prefix.
R3 LOW CanonicalDispositionShape coverage rule The coverage rule ("every declared member appears in at least one exemplar") is per path, not per branch combination. A declared member that some kinds stop writing (for example outcomeId on Core 0.3) is not noticed. This is acceptable under omitempty semantics and is recorded for completeness. None required.

Public changes for the release notes

The list above stands. The delta adds no public change: maintainer tooling, docs and a CI comment only. Persisted audit lines are byte-identical to v0.38.0.

Not established at 4760af6

The full suites and the macOS/Windows legs (CI owns them). Branch coverage of CanonicalWith beyond reading the code. The rest of the first-round list stands.

@kikashy
kikashy merged commit 4d77446 into main Oct 29, 2026
7 checks passed
@kikashy
kikashy deleted the release-v0.39.0-final branch October 29, 2026 08:55
@github-actions

Copy link
Copy Markdown
Contributor

🎉 Thank you for your contribution to Judgment Pack!

If you’d like another task, choose a path that interests you:

@kikashy kikashy mentioned this pull request Oct 29, 2026
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.

Fingerprint the audit trail's persisted line formats, with a format-status record

1 participant