Skip to content

Prepare the v0.3.0 final: immutable notes, the retained-column leaf in the final bundle, a release draft that is not a pre-release (#231) - #278

Merged
kikashy merged 5 commits into
mainfrom
release-v0.3.0-final
Oct 28, 2026
Merged

kikashy merged 5 commits into
mainfrom
release-v0.3.0-final

Conversation

@kikashy

@kikashy kikashy commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Prepares the v0.3.0 final for milestone v0.3.0 (#231). This PR creates no tag; the tag is cut later. The published candidates' files under releases/ (rc.1, rc.2) are untouched. Nothing under conformance/, schema/ or rfcs/ changes. conformance/ and schema/ are unchanged, and under spec/ and rfcs/ only the leaf's adoption sentences change, per the maintainer's decisions below.

Maintainer decision: the final is the leaf's normative support distribution

The maintainer chose to make v0.3.0 the normative home of the selected retained-column/1 leaf, so a final installation supports the leaf. Without this, the readers named only the rc.2 distribution, and the final bundle answered a leaf selection with capability-unsupported. The change:

  • spec/retained-column.md, lines 6, 23, 28 and 266 only:
    • Line 6 (the A2 decision): accepted at draft maturity in the v0.3.0 release, first distributed as the v0.3.0-rc.2 candidate.
    • The support tuple's distribution version is 0.3.0.
    • The inventory is releases/v0.3.0.retained-column.json.
    • The normative support version is exactly 0.3.0, the v0.3.0 distribution.
    • v0.3.0-rc.2 was its candidate distribution and remains published. The schema, corpus and digests are the candidate's, while the companion page and the two readers name the release.
    • A leaf support claim is made against a v0.3.0 installation.
  • The readers (tools/retained_column.py, tools/dialect-verify-ts/src/retained.ts):
    • They check the generated release-identity.json (tag v0.3.0, releaseVersion 0.3.0, specVersion, maturity, a 40-hex sourceCommit).
    • They check releases/v0.3.0.retained-column.json (distribution v0.3.0, every pinned byte).
    • They report support version 0.3.0. They name 0.3.0 only, because rc.2 installations run rc.2's published readers.
    • They still refuse unknown exact pins and roles before artifact admission.
  • The builder:
    • The v0.3.0 bundles carry a generated release-identity.json. It is generated, never a tracked mapping; no releases/v0.3.0.json exists or is admitted.
    • The final's inventory must equal RC2's in the schema, the corpus and every other digest. Only these may differ: the companion page, spec/judgment-pack-core.md, spec/dialect.md, tools/retained_column.py and retained.ts.
    • RC2's inventory is used for that comparison only when it has the SHA-256 that tag v0.3.0-rc.2 published (RC2_RETAINED_INVENTORY_SHA256).
    • Both readers must name the tag, its version and its inventory.
  • The frozen corpus keeps its v0.3.0-rc.2 labels: the cases.json status, the manifest/capabilities distribution and the synthetic test pins. The notes say in one sentence that these labels name the candidate distribution whose bytes the final publishes unchanged.
  • Probe: a final bundle built from this branch with the real builder CLI answers as follows. A 0.3.0 selection naming its own commit is supported by both the Python and the TypeScript reader. A 0.3.0-rc.2 selection is capability-unsupported from both. The CLI test in tests/test_retained_release.py repeats this probe on every run.

What changed

  1. Final notes at releases/v0.3.0.md (the immutable path the builder prefers). They keep the prepared notes' Included work, Versioning and maturity, Migration note and preserved-schema sections. They add:

    docs/releases/v0.3.0.md becomes a pointer; it is kept because the published rc.1 notes link to it. The site's project/releases/0.3.0/ page renders the immutable notes.

  2. The leaf in the final bundle (tools/build_release.py). For v0.3.0, bundle_paths() adds the reader paths, releases/v0.3.0.retained-column.json, releases/v0.3.0.milestone.json and the notes, and no mapping. validate_artifacts() holds the final to RC2's leaf rules (a valid inventory with draft maturity, RFC 0029 Accepted, milestone Hosting: multi-site config + keyless CI/CD deploy (WIF) #2) plus the rules above. It refuses when:

    • either inventory is missing;
    • a mapping sits beside a tag the RC allowlist does not name.
  3. The draft is not a pre-release for a plain tag (release.yml). The evidence job hands the release job its route output, which is the tag grammar alone. --prerelease is passed unless the route is exactly evidence, so v0.3.0 is drafted as a release the maintainer publishes, while RCs, policy-v tags and any other route value stay pre-releases. web/DEPLOYMENT.md and .github/ci-evidence.json's fresh_at_release text are updated to match.

  4. VERSIONING.md names vX.Y.Z-rc.N and vX.Y.Z-alpha.N. A candidate graduates with no change to its normative artifacts (schemas, corpus, Core and companion rules). Text and tooling that named the candidate distribution are updated to name the release, and the release's notes list those changes.

  5. CHANGELOG.md:

    • The 0.3.0 entries move under ## \0.3.0``; RFC 0027 (targets 0.4.0) stays under Unreleased; a leaf entry is added.
    • 0.2.0-draft's entries, which were still under Unreleased, move verbatim under ## \0.2.0-draft` — 2026-07-28`, so the move is exact.
    • The 0.3.0 heading has no date: the maintainer adds it at the tag.

What the final bundle carries beyond rc.2's

  • Adds: releases/v0.3.0.md, releases/v0.3.0.retained-column.json and releases/v0.3.0.milestone.json. The generated release-identity.json now names v0.3.0.
  • Drops: rc.2's mapping, inventory and milestone link, and docs/releases/v0.3.0.md.
  • Leaf bytes: the schema and corpus are byte-identical to rc.2's, and so is every other leaf file except the companion page and the two readers.

Safeguards and their mutants

I broke each safeguard in a scratch copy and confirmed that the named test failed: 26 mutants, all caught, including the review round's published-digest rule and the narrowed revised-set mutant. When a mutant targeted a reader, the scratch copy re-pinned that reader's digest, so only the mutated logic could fail the test.

Safeguard Mutants Caught by
final inventory equals RC2's except distribution and the three revised files any file allowed to differ; top-level comparison removed; file-set comparison removed; revised set widened to _retained_column.py test_retained_release.test_final_route_refuses_each_leaf_rule (and the pin tests for the widened set)
RC2's inventory has the bytes tag v0.3.0-rc.2 published (a leaf change re-pinned in both inventories is refused) check removed test_retained_release.test_final_route_refuses_each_leaf_rule
both readers name the tag, its version and its inventory check skipped; Python-only; Python tag-only same
published RC2 inventory / the exact tag's inventory must exist each check removed same
inventory distribution equals the exact tag any allowlisted distribution accepted same
final keeps milestone #2; requires RFC 0029 Accepted each skipped for v0.3.0 same
no mapping outside the RC allowlist check removed same
final bundle carries the leaf leaf paths limited to rc.2 test_final_bundle_paths_carry_the_leaf_without_a_mapping, test_final_cli_archives_the_leaf_and_its_readers_support_it
final bundle carries the generated identity identity generated for RC tags only test_final_cli_archives_the_leaf_and_its_readers_support_it
Python reader: inventory distribution, releaseVersion, reported version each check dropped or rc.2 reported test_retained_column.test_installed_identity_is_the_v0_3_0_distribution
TypeScript reader: the same three each check dropped or rc.2 reported retained.test.ts "installed identity is the v0.3.0 distribution"
--prerelease only outside vMAJOR.MINOR.PATCH always; never; prefix match on evidence test_release_evidence.test_only_a_plain_release_tag_is_drafted_without_the_prerelease_flag

The RC2 route tests no longer validate RC2 against this tree, because the tree's support identity is now the final's. The rc.2 assertions on its own immutable files (mapping, milestone, inventory pins outside the revised three) stay. The RC2 route now refuses this tree, which is pinned by a test, so rc.2 is built only from its tag.

Tests

  • python -m unittest tests.test_release tests.test_retained_release tests.test_release_milestone: 41 tests, OK.
  • python -m unittest tests.test_retained_column tests.test_site: 70 tests, OK.
  • PYTHONPATH=tests python -m unittest test_release_evidence test_release_recovery test_historical_release test_dialect_ts: 62 tests, OK.
  • The TypeScript tool (node --disable-wasm-trap-handler --test test/*.test.ts): 385 tests, all pass.
  • All of these ran under ulimit -v 4000000. --disable-wasm-trap-handler, the flag the suite's own Node calls use, lets Node's TypeScript stripping run under the cap.

Checks

I ran jpdev check --changed origin/main on the head under ulimit -v 4000000, with NODE_OPTIONS=--disable-wasm-trap-handler. That flag is the one the suite's own Node calls use, and it lets test_profiles run under the cap. The workflow change is an unmapped path, so the whole list ran:

== jpdev check --changed: 23 changed paths since origin/main (merge base bcac15b) ==
unmapped path .github/workflows/release.yml: whole list
== check summary (dev.json) ==
handover: boundary 2 paths, shared 3 paths; see AGENTS.md "Before you hand over"
PASS     local-paths              0s
PASS     agent-docs               0s
    sync-agent-docs: CLAUDE.md matches AGENTS.md
PASS     diff-check               0s
PASS     repository-tests         119s, python .venv/bin/python3 (Python 3.12.11)
    Ran 472 tests in 118.377s
    OK
PASS     policy-prototype         1s, python .venv/bin/python3 (Python 3.12.11)
    Ran 38 tests in 1.123s
    OK
PASS     policy-contract          3s, python .venv/bin/python3 (Python 3.12.11)
    Ran 13 tests in 2.632s
    OK
PASS     policy-official-draft    2s, python .venv/bin/python3 (Python 3.12.11)
    Ran 16 tests in 2.402s
    OK
PASS     retained-column          45s, python .venv/bin/python3 (Python 3.12.11)
    21 lines matched; last: Draft reference/source schema: 16 changed member/alternative controls PASS
== 8 passed, 0 failed, 0 skipped, 0 not run ==

Self-checks (AGENTS.md):

  • (a) Under spec/, only the three support-identity lines named above changed; schema/ has no diff.
  • (b) No schema member or enum value is added.
  • (c) Both readers pass the new installation tests and the shared corpus.
  • (d) Every refusal added has a caught mutant (table above).
  • (g) The spec, the notes and the code describe the same final.

Review fixes (clean-room record of cdb9f6d)

  • M1: validate_final_retained_inventory uses RC2's inventory only when that file has the SHA-256 tag v0.3.0-rc.2 published. That digest is a constant, so the tests read no tag. I checked it against git show refs/tags/v0.3.0-rc.2:releases/v0.3.0-rc.2.retained-column.json. The tests cover three cases: a leaf change re-pinned in both inventories (refused), the honest tree (accepted), and the comparison behind the digest.
  • M2: the notes no longer claim a release-body edit. The run id and attempt are in the decision record kept as release-evidence-<attempt>, which is what web/DEPLOYMENT.md "Release evidence" already documents.
  • M3: the VERSIONING.md sentence now says what holds (item 4).
  • L1 and L2: issues Restore historical schema identities and correct Core 0.3 migration claims before RC #259 and Pin release Node and recover the existing RC without moving its signed tag #261 are added to the rc.1 review list, and the notes gain the Candidate period paragraph.
  • H1, the maintainer's decision (A2): the normative text names v0.3.0 as the leaf's adoption release, and v0.3.0-rc.2 as its candidate.
    • spec/retained-column.md lines 6 and 266.
    • spec/judgment-pack-core.md lines 54–55 and 371.
    • spec/dialect.md lines 142 and 144: the closing "Immutable RC2 publication remains separate." is dropped.
    • Each sentence keeps its existing non-claims.
    • rfcs/0029-retained-column-conversions.md gains one dated note after the first adoption paragraph (new line 19). It names v0.3.0 as the accepted route's final distribution; its Status and history are unchanged. RFC 0000 does not forbid this. It is a non-material note in the repository's dated-note convention, not an amendment of the design.
    • The builder's allowed-to-differ set adds the Core and Dialect pages. The RFC is not inventory-pinned, so the set does not name it.
    • The notes list these text changes in one sentence. The inventory-rule mutants were re-run, plus a narrowed-set mutant: 8 of 8 caught.
  • Not in these rounds: L3–L6.

🤖 Generated with Claude Code

…n the final bundle, a release draft that is not a pre-release (#231)

- releases/v0.3.0.md: the final notes, from the prepared notes, naming both
  candidates (rc.1 2026-10-25, rc.2 2026-10-27) and what each carried, the
  selected retained-column/1 leaf as part of the release, RFC 0024 as not in
  it, the Policy companion unchanged, what the release pins, and a checks
  sentence whose run id and commit are completed at the tag.
  docs/releases/v0.3.0.md becomes a pointer (the published RC1 notes link to
  it); the site's 0.3.0 release page renders the immutable notes.
- tools/build_release.py: the v0.3.0 bundle carries the leaf (the RC2 reader
  paths, releases/v0.3.0.retained-column.json and the milestone link) and no
  mapping. The final is held to RC2's leaf rules (inventory, RFC 0029
  acceptance, draft maturity, milestone #2) and refused when its inventory
  differs from the published RC2 inventory in anything but its distribution,
  when either inventory is missing, or when a mapping sits beside a tag the RC
  allowlist does not name.
- releases/v0.3.0.retained-column.json: RC2's inventory with distribution
  v0.3.0; every digest is unchanged and recomputed by the builder.
- release.yml: the evidence job hands its route (the tag grammar) to the
  release job; a plain vMAJOR.MINOR.PATCH tag is drafted as a release,
  every other tag and any other route value as a pre-release.
- VERSIONING.md names the rc.N and alpha.N forms; CHANGELOG.md records 0.3.0
  and, for the move to be exact, 0.2.0-draft under their own headings, with
  RFC 0027 (0.4.0) left under Unreleased.

Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kikashy kikashy added this to the v0.3.0 milestone Oct 28, 2026
kikashy and others added 4 commits October 28, 2026 18:16
…ceholders (#231)

The "Checks run for this release" section now states the mechanism: the
release reuses main's repository-conformance evidence for the exact commit
through the release evidence gate, the reused run's id and attempt are in
the release's decision artifact and are appended to the published release
body at publication, and the candidates were reviewed on their pull
requests (#246, #248 and #258 for rc.1; #274 for rc.2, with its same-vendor
statement). The notes test checks that form and that no placeholder remains.

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

The maintainer chose to make the final the leaf's normative home.

- spec/retained-column.md, three lines: the support tuple's distribution
  version is 0.3.0; the inventory is releases/v0.3.0.retained-column.json;
  the normative support version is exactly 0.3.0 (the v0.3.0
  distribution), v0.3.0-rc.2 was the candidate distribution of the same
  leaf bytes and remains published, and a support claim is made against a
  v0.3.0 installation.
- tools/retained_column.py and dialect-verify-ts/src/retained.ts check the
  generated release-identity.json for tag v0.3.0 and releaseVersion 0.3.0,
  and releases/v0.3.0.retained-column.json with distribution v0.3.0, and
  report support version 0.3.0. They name 0.3.0 only: rc.2 installations
  run rc.2's published readers.
- tools/build_release.py: the v0.3.0 bundles carry a generated
  release-identity.json (tag, releaseVersion, specVersion, maturity,
  sourceCommit), never a tracked mapping. The final's inventory must equal
  RC2's in schema, corpus and every other digest; only the companion page
  and the two readers may differ. Both readers must name the tag, its
  version and its inventory.
- releases/v0.3.0.retained-column.json re-pins those three files; the
  schema and corpus digests are unchanged and the frozen corpus keeps its
  rc.2 labels, which the notes say in one sentence.
- Tests: both readers' real installation check on a v0.3.0 copy; the
  builder's new rules; the CLI test builds a final bundle and both of its
  readers answer a 0.3.0 selection with support and a 0.3.0-rc.2 one with
  capability-unsupported. The RC2 route tests no longer validate RC2 on
  this tree, whose support identity is the final's.

Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and candidate period (#231)

Review fixes on the v0.3.0 final preparation:

- tools/build_release.py: the final is compared with RC2's inventory only
  when that file has the SHA-256 tag v0.3.0-rc.2 published, so a leaf
  change re-pinned in both inventories is refused. Tests cover that case,
  the honest tree, and the comparison behind the digest.
- releases/v0.3.0.md: the reused run's id and attempt are in the gate's
  decision record kept as the release run's release-evidence-<attempt>
  artifact (web/DEPLOYMENT.md, "Release evidence"); nothing claims a body
  edit. The rc.1 review list adds issues #259 and #261. A "Candidate
  period" paragraph gives both publication dates, the feedback record on
  #231 before the tag, and the one-week floor after rc.2.
- VERSIONING.md: a candidate graduates with no change to its normative
  artifacts; text and tooling that named the candidate distribution are
  updated to name the release, and the release's notes list them.

Signed-off-by: kikashy <35789537+kikashy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alect and RFC 0029 (#231)

The maintainer's decision on the held finding (A2): the normative text
stops naming RC2 as the leaf's home.

- spec/retained-column.md line 6: accepted at draft maturity in the
  v0.3.0 release, first distributed as the v0.3.0-rc.2 candidate; line 266:
  the schema, corpus and digests are the candidate's, while the companion
  page and the two readers name the release.
- spec/judgment-pack-core.md lines 54-55 and 371, spec/dialect.md lines 142
  and 144: adopted at draft maturity in v0.3.0 (candidate distribution
  v0.3.0-rc.2), keeping each sentence's existing non-claims.
- rfcs/0029: one dated note in the adoption section naming v0.3.0 as the
  accepted route's final distribution; its Status and history unchanged.
- tools/build_release.py: the files allowed to differ from RC2's inventory
  are now the companion, Core and Dialect pages and the two readers (the
  RFC is not inventory-pinned); the final inventory re-pins the three pages.
- releases/v0.3.0.md lists these text changes in one sentence.

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

kikashy commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Clean-room review record: spec PR #278 (v0.3.0 final preparation, #231)

  • PR: Prepare the v0.3.0 final: immutable notes, the retained-column leaf in the final bundle, a release draft that is not a pre-release (#231) #278, head cdb9f6d450beba05e81b1e848729c6763994e3d1 (three commits on bcac15b7 = tag v0.3.0-rc.2).
  • Reviewer: Claude Opus clean room. The reviewer did not build the change and had no contact with its builder.
  • Same-vendor statement: this is a Claude Opus review of a Claude Opus build. It is same-vendor review, not a cross-vendor claim.
  • Room: <scratch>/review-spec-278/. The clone is repo/. Probes, scratch clones, the built bundle, the site build and logs are under .review/. The local annotated tag v0.3.0 exists only in the scratch clone .review/probe; nothing was pushed, posted or commented.
  • Caps: ulimit -v 4000000 and timeout on every run. Node 22.23.1 ran with --disable-wasm-trap-handler, and Python 3.12.11 ran from a review venv with requirements-dev.txt. The scratch disk had more than 11 GB free throughout.

Verdict

Needs fixes: 1 HIGH, 3 MEDIUM, 6 LOW.

The HIGH is at the level of the decision. The fix changes a fourth line under spec/, so it needs the maintainer's word on scope before the in-PR fix.

Everything mechanical holds:

  • Bytes: exact as claimed.
  • Builder rules: each one refuses as claimed.
  • Final bundle: builds with the real CLI.
  • Readers: both answer as claimed.
  • Workflow: drafts v0.3.0 without --prerelease.
  • CHANGELOG: the split is exact.
  • Site: renders the notes.
  • Mutants: all 15 re-run mutants were caught.

Probes and outcomes

  1. Bytes. Commands: git diff v0.3.0-rc.2 HEAD --stat (20 files); git diff --quiet v0.3.0-rc.2 HEAD -- conformance schema rfcs examples; and git show <tag>:<f> | sha256sum against the tree for every releases/v0.3.0-rc.1* and releases/v0.3.0-rc.2* file.
  2. Inventories. A Python comparison of releases/v0.3.0.retained-column.json with releases/v0.3.0-rc.2.retained-column.json:
    • At the top level, only distribution differs (v0.3.0 vs v0.3.0-rc.2).
    • Both have the same 27-file set.
    • Digests differ only for spec/retained-column.md, tools/retained_column.py and tools/dialect-verify-ts/src/retained.ts.
    • Every final digest matches the tree, and every rc.2 inventory digest matches the bytes at tag v0.3.0-rc.2.
    • The schema digest sha256:0cb49f23…0557 and corpus digest sha256:17b16ff7…5863 match the notes.
  3. Builder CLI probe. Run in a scratch clone at the head with a local annotated v0.3.0, the way release.yml invokes it: python tools/build_release.py --tag v0.3.0 --commit cdb9f6d4… --output .review/dist.
    • Exit 0.
    • release-identity.json = {"maturity":"draft","releaseVersion":"0.3.0","sourceCommit":"cdb9f6d450beba05e81b1e848729c6763994e3d1","specVersion":"0.3.0","tag":"v0.3.0"}.
    • checksums.txt equals sha256sum of both archives, and the zip and tar contents are identical.
    • Against the downloaded published rc.2 tarball (checksum verified):
      • Adds: releases/v0.3.0.md, releases/v0.3.0.milestone.json and releases/v0.3.0.retained-column.json.
      • Drops: rc.2's mapping, notes, milestone, inventory and docs/releases/v0.3.0.md.
      • Differs only in: CHANGELOG.md, VERSIONING.md, release-identity.json, the three revised leaf files and tools/dialect-verify-ts/test/retained.test.ts.
      • The five preserved historical schemas are present, and there are no Policy paths.
    • The builder did not refuse the tree: with the tag present, HEAD equal to the commit and a clean tree, it passed.
  4. Readers. Both readers were run on the extracted final bundle and on the published rc.2 bundle, for selections {0.3.0, 0.3.0-rc.2} × revisions {head, rc.2 source bcac15b7}.
    • The final answers supported only for 0.3.0 at its own commit, from both Python and TypeScript.
    • Every other cell is capability-unsupported, including the rc.2 selection.
    • The published rc.2 installation still supports only 0.3.0-rc.2 at bcac15b7.
    • The two readers agree on all 16 cells.
  5. Builder rules. Run directly on a scratch clone with validate_artifacts('0.3.0','v0.3.0', root). Each case below was changed, its digest re-pinned in the final inventory, and then restored.
    • Changed and re-pinned non-allowed files: spec/dialect.md, spec/judgment-pack-core.md, tools/_retained_column.py, tools/dialect-verify-ts/src/verify.ts, schema/retained-column/1/contract.schema.json and conformance/retained-column/1/manifest.json. All refuse.
    • The allowed spec/retained-column.md, changed and re-pinned, is accepted.
    • A reader naming rc.2 (Python TAG, Python INVENTORY, TS VERSION) refuses with "readers do not name the exact release distribution".
    • releases/v0.3.0.json refuses, and so does releases/v0.3.1.json for v0.3.1.
    • The rc.2 route on this tree refuses with "immutable artifact bytes differ".
    • The leaf bundle paths, the final inventory, the milestone and the notes are all in bundle_paths('v0.3.0').
    • Gap (M1): a non-allowed leaf change is accepted when the working copy of rc.2's inventory is re-pinned along with the final's.
  6. Workflow. The route and draft run: blocks were extracted from release.yml and run with a stub gh.
    • v0.3.0 and v1.2.3 get route evidence and no --prerelease, with no empty argument.
    • v0.3.0-rc.9, v0.3.0-rc.2, policy-v1.0.0-draft.2, v01.2.3, v0.3.0+meta, v0.3 and demo/x/v0.2.0 get route fresh and --prerelease.
    • Route values '', Evidence, evidence , fresh and evidencex all get --prerelease.
    • The evidence job is unchanged in meaning: it gains a route output equal to the route step's output; only the suite step reads reuse; the draft step reads only route.
    • The release job still runs milestone validation, the suite when not reused, the Policy corpus for policy-v tags and the builder, all before the draft.
    • The .github/ci-evidence.json change is only the description text in fresh_at_release, which the gate validates as text.
  7. CHANGELOG. A programmatic comparison of the rc.2 Unreleased section with the head's sections:
    • All 38 Added and 38 Changed entries are kept, and one leaf entry is added.
    • The new 0.2.0-draft section's 12 Added and 7 Changed entries are byte-equal to the 19 entries Unreleased held at tag v0.2.0-draft, all of them, beyond the two the brief asked to spot-check.
    • No 0.3.0 entry comes from that tag.
    • The only edit inside a moved entry is "the entries above" → "its entries below", a pointer fix.
    • The 0.3.0 heading is undated, and RFC 0027 stays under Unreleased.
  8. Site. python -m unittest tests.test_site ran 58 tests, OK. A production build to .review/site renders project/releases/0.3.0/ from releases/v0.3.0.md. Its links to rfcs/0029…/, spec/retained-column/, ../0.2.0-draft/#what-preserved-means-for-the-010-draft-schemas (the anchor is present) and spec/0.3.0/ all resolve.
  9. Suites. python -m unittest discover -s tests ran 472 tests: 467 pass, and the 5 failures are all in test_profiles. They are an artifact of the cap: Node without the flag under the 4 GB limit. test_profiles with NODE_OPTIONS=--disable-wasm-trap-handler passes 13/13. The other suites:
    • TypeScript node --test test/*.test.ts: 385/385.
    • tools/check_retained_column.py: PASS.
    • Policy prototype, Policy contract and the official-draft corpus: OK.
    • PR CI: repository-conformance and DCO succeed.
  10. Notes facts. Checked against the tree and GitHub:

Mutants re-run

15 mutants were run in a scratch clone. Each was restored after its run, and the clone ended clean. For the reader mutants, the scratch inventory was re-pinned so that only the mutated logic could fail.

# Mutant Test that failed
A final inventory: any file may differ (digest clause removed) test_retained_release.test_final_route_refuses_each_leaf_rule (2 failures)
B top-level inventory comparison removed same (2)
C validate_reader_support_identity not called same (4)
D TypeScript half of the reader check removed same (2)
E milestone check skipped for v0.3.0 same (2)
F no-mapping-outside-allowlist check removed same (1)
G leaf bundle paths for rc.2 only test_final_bundle_paths_carry_the_leaf_without_a_mapping (1) and test_final_cli_archives_the_leaf_and_its_readers_support_it (error)
H release-identity.json generated for RC tags only test_final_cli_archives_the_leaf_and_its_readers_support_it (error)
I Python reader: inventory distribution check dropped test_retained_column.test_installed_identity_is_the_v0_3_0_distribution
J Python reader reports 0.3.0-rc.2 same
O Python reader: releaseVersion check dropped same
K TS reader: inventory distribution check dropped retained.test.ts "installed identity is the v0.3.0 distribution"
L TS reader: releaseVersion check dropped same
M workflow: never --prerelease test_release_evidence.test_only_a_plain_release_tag_is_drafted_without_the_prerelease_flag (11)
N workflow: prefix match evidence* same (1)

All named tests pass on the unmutated head.

Findings

ID Severity Where What Evidence
H1 HIGH (decision-level: needs the maintainer's word) spec/retained-column.md:5-6; also spec/judgment-pack-core.md:54-55, :371, spec/dialect.md:142-144 and RFC 0029's adoption section (lines 13-15) The final would publish immutable normative text that contradicts itself about which distribution the leaf belongs to. The companion's own Status section still says it is "accepted … for the exact v0.3.0-rc.2 distribution". Core says the companion is normative "for the exact RC2 selected leaf support scope only … publication requires the immutable RC2 release", and calls the leaf's ceilings "an RC2 adoption candidate, not a support claim for an existing release". Against this, line 266 and the notes say "The normative support version is exactly 0.3.0" / "This release is the leaf's normative support distribution" / "A leaf support claim is made against a v0.3.0 installation". The builder's self-check (g), "the spec, the notes and the code describe the same final", does not hold. Line 5-6 sits in an allowed file, but it is a fourth line. The Core and Dialect sentences are pinned to rc.2 bytes by the builder. grep -n "RC2|rc\.2" spec/*.md at the head. Fix options: (a) amend line 5-6 and either widen FINAL_REVISED_LEAF_FILES for the Core and Dialect pointer sentences, or (b) add one notes sentence, like the corpus-labels sentence, saying that the RC2-worded status sentences in the frozen Core, Dialect and RFC bytes name the candidate whose bytes the final publishes, and that the companion's §Installed identity governs.
M1 MEDIUM tools/build_release.py:111-128 (validate_final_retained_inventory) The final is held to the working-tree copy of rc.2's inventory, not to its published bytes. Editing a non-allowed leaf file and re-pinning both inventories passes the builder. No test catches this: the pin-set test only compares the set of differing files. Today the copy is byte-identical to tag v0.3.0-rc.2 (verified), so the gap is for edits made between merge and tag. Probe 5, case 1c. Fix: compare against git show refs/tags/v0.3.0-rc.2:releases/v0.3.0-rc.2.retained-column.json inside validate(), as validate_policy_history does (the tags are fetched at release), or pin that file's SHA-256 (b4d62b32…40160) as a constant so tests stay tag-free.
M2 MEDIUM releases/v0.3.0.md:140-142 "…and are appended to the published release body at publication" states as fact a manual step that neither release.yml nor web/DEPLOYMENT.md performs or documents. The decision-artifact half of the sentence is supported. grep of web/DEPLOYMENT.md and the workflow. Fix: add the step to DEPLOYMENT.md's publication text, or word it as the maintainer's step.
M3 MEDIUM VERSIONING.md:57-59 The new sentence says vX.Y.Z-rc.N "is a candidate of the complete milestone that graduates to vX.Y.Z with no code change". This very final changes both readers and three normative lines from rc.2, and rc.1 was not the complete milestone. VERSIONING's only leaf text (the RC2 section) still says the tuple names 0.3.0-rc.2 and does not record the final's re-homing to 0.3.0. Bundle diff (probe 3). Fix: scope the convention to later milestones, or record 0.3.0's decided exception (option A) in one sentence.
L1 LOW releases/v0.3.0.md:144-146 The rc.1 review list (#246, #248, #258) leaves out issue #259, where the whole-release review (1 HIGH + 2 MEDIUM, with dispositions) is recorded, and #261 (the recovery reviews). #231's comment of 2026-10-25T11:37Z; gh api …/issues/259.
L2 LOW (gate note) release timing #231 sets the review of adopter feedback for around 2026-11-01 14:25 UTC, before the final is decided. VERSIONING.md says "Final publication follows about one week of public RC availability and review of adopter feedback". The rc.2 notes and RFC 0029 also require the feedback disposition before the final. The immutable notes do not say where that disposition is recorded. The tag should wait for it, and the record should be named. #231; VERSIONING.md §Core pre-releases.
L3 LOW releases/v0.3.0.md links The relative links (../rfcs/…, ../spec/…, v0.2.0-draft.md#…) break in the GitHub release body, because the file is --notes-file. The published rc.2 body has the same pattern. The site rewrites them correctly. Probe 8; gh release view v0.3.0-rc.2.
L4 LOW spec/retained-column.md:266 "the candidate distribution of the same leaf bytes" is imprecise: the companion and the two readers differ from rc.2's. Notes lines 48-50 state this precisely. Fold it into the H1 rewording if line 266 is touched again. Probe 2.
L5 LOW (scope) web/build.py, web/DEPLOYMENT.md, tests/test_historical_release.py, .github/ci-evidence.json These four changed files are outside the brief's list. Each follows directly from a claimed change (the site source of the 0.3.0 page, the draft-release wording, the notes path, the fresh_at_release text) and is named in the PR body. None adds behavior outside the claimed scope. git diff v0.3.0-rc.2 HEAD.
L6 LOW (pre-existing, not this PR) web/DEPLOYMENT.md "Release evidence"; CHANGELOG DEPLOYMENT.md says the kit's prerelease grammar is opt-in "until this repository opts in", yet .github/ci-evidence.json already sets "tag": {"prerelease": true}. Behavior is correct, because the route step admits plain tags only. Separately, RFC 0028 (#253) has no CHANGELOG entry. The rc.2 tag's bytes.

No finding concerns conformance or maturity overstatement in the notes. The notes keep draft, scope the leaf claim and restate the non-claims.

Re-read (2026-10-28): head 1131a23547e299c2c883efcfd15c81cff42b2d88

This re-read is by the same reviewer. It covers two new commits on top of cdb9f6d4: 8963d15 and 1131a23. For H1 the maintainer chose A2. Same caps as before (ulimit -v 4000000, timeout, NODE_OPTIONS=--disable-wasm-trap-handler). Nothing was posted or pushed. PR CI on the new head: repository-conformance and DCO succeed.

Verdict: mergeable. 0 HIGH; H1, M1, M2, M3, L1, L2 and L4 are resolved; two new LOWs and the earlier L3, L5 and L6 ride.

What was re-run

  • Every changed line under spec/ and rfcs/ since tag v0.3.0-rc.2 (git diff -U0 v0.3.0-rc.2 HEAD -- spec rfcs):
    • spec/retained-column.md lines 6, 23, 28 and 266.
    • spec/judgment-pack-core.md lines 54–55 and 371.
    • spec/dialect.md lines 142 and 144.
    • rfcs/0029-retained-column-conversions.md: one dated note at new line 19 (and a blank line 20). Its Status line ("Accepted") and the adoption history are untouched.
    • No other line under spec/ or rfcs/ changed.
    • The changed sentences now say the leaf is adopted in v0.3.0, with v0.3.0-rc.2 as the candidate distribution. They keep their non-claims: no implicit ordinary support, no generic preparation-verifier or §14 claim, no publication of the 47 staged recordings, and "not a Core conformance claim". grep "RC2|rc.2" spec/*.md now finds only these adoption sentences and the unchanged line 25 ("RC2 evidence remains provisional…"), which is consistent.
  • Bytes.
    • conformance/, schema/ and examples/ are identical to the rc.2 tag.
    • All seven rc.1/rc.2 release files are byte-identical to their tags, and releases/v0.3.0.milestone.json is unchanged.
    • Inventories: at the top level only distribution differs, and both have the same file set. Digests differ for exactly the three pages plus the two readers, and every final pin matches the tree.
  • M1.
    • RC2_RETAINED_INVENTORY_SHA256 = b4d62b3259b24dee68d926750ab45247d974004eec2bd15814f7d24c89140160 equals both git show refs/tags/v0.3.0-rc.2:releases/v0.3.0-rc.2.retained-column.json | sha256sum and the working copy.
    • Probe 5 re-run: the case that previously passed (a leaf change re-pinned in both inventories) now refuses with "the RC2 retained-column inventory differs from the bytes RC2 published".
    • The other refusals are unchanged: _retained_column.py, verify.ts, the schema, the corpus manifest, readers naming rc.2, a releases/v0.3.0.json mapping, v0.3.1 with a mapping, and the rc.2 route on this tree.
    • The builder's allowed-to-differ set is exactly the three pages plus the two readers. A changed and re-pinned Core or Dialect page is now accepted, as A2 intends.
  • Mutants. Run in a scratch clone at the new head, which ended clean. All four were caught:
    • The narrowed allowed set (Core and Dialect dropped) fails test_final_inventory_keeps_rc2s_leaf_bytes_but_its_support_identity (error), test_closed_inventory_and_non_circular_pins and test_final_route_refuses_each_leaf_rule.
    • Removing the published-digest check fails test_final_route_refuses_each_leaf_rule.
    • Letting any file differ fails test_final_route_refuses_each_leaf_rule (3 failures).
    • A wrong digest constant (one hex digit) fails test_closed_inventory_and_non_circular_pins and test_final_inventory_keeps….
  • Bundle rebuilt with the builder CLI at the new head, using a local annotated tag in the scratch clone only.
    • Exit 0. Checksums match sha256sum, and the zip and tar contents are identical.
    • release-identity.json names v0.3.0, 0.3.0, draft and sourceCommit 1131a235….
    • Against the published rc.2 bundle it has the same 3 adds and 5 drops as before. It now differs in CHANGELOG, VERSIONING, the identity, the three pages, the two readers and retained.test.ts. The five preserved schemas are present, and there are no Policy paths.
    • Readers on the extracted bundle: Python and TypeScript both answer supported only for 0.3.0 at 1131a235, and capability-unsupported for 0.3.0 at the rc.2 source and for both rc.2 selections.
  • M2. The notes no longer contain "appended". The run id and attempt are attributed to the gate's decision record, kept as the release-evidence-<attempt> artifact, citing web/DEPLOYMENT.md "Release evidence", which documents that artifact (lines 71–72). web/ and .github/ are unchanged since cdb9f6d4.
  • M3. VERSIONING.md now reads "with no change to its normative artifacts (schemas, corpus, Core and companion rules); text and tooling that named the candidate distribution are updated to name the release, and the release's notes list those changes". This matches what the final changes, and the notes list those changes.
  • L1 and L2.
  • L4. Line 266 now says precisely what is the candidate's (the schema, the corpus and their digests) and what names the release (this page and the readers).
  • Workflow. The route and draft logic was re-run with the stub gh: plain vX.Y.Z is drafted without --prerelease, and rc, policy-v, malformed and non-evidence route values are drafted with it.
  • Suites. python -m unittest discover -s tests ran 472 tests, OK; test_site and test_profiles both pass with the flag. The TypeScript suite passes 385/385, check_retained_column.py passes, and the Policy prototype, contract and official-draft checks are OK.

Findings still open

ID Severity Where What
R1 LOW tools/build_release.py FINAL_REVISED_LEAF_FILES The allowed set is per file. Since A2, any change to the Core or Dialect page passes the comparison with rc.2's inventory (probe: changed and re-pinned, accepted). That the change was limited to the adoption sentences is shown by the diff and this review, not enforced by the builder. This is acceptable under the decision.
R2 LOW spec/judgment-pack-core.md:367 "For the proposed exact selected retained-column/1 contract only" still says "proposed" one sentence before line 371 says "adopted". This residual wording is outside the decided lines.
L3, L5, L6 LOW as above Unchanged: the relative links in the GitHub release body; files outside the brief's list, which now also include the RFC 0029 dated note (decided under A2); and the two older inconsistencies that predate this PR.

@kikashy
kikashy merged commit 6b67106 into main Oct 28, 2026
3 checks passed
@kikashy
kikashy deleted the release-v0.3.0-final branch October 28, 2026 23:29
@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:

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