fix(artifacts): put release: on the plane rivet actually reads (#370) - #373
Merged
Conversation
rivet has two field planes. `release` is a CORE field read by the CLI query surface — `rivet list --release vX.Y` is the release-planning view. Separately each artifact may carry a `fields:` bag for schema-declared custom keys. Writing `release:` into the bag is valid YAML, is tolerated by the schema, and is invisible to the query. 59 of 85 assignments were in the bag. Measured A/B with one binary (rivet 0.28.0) over the same query: artifacts with a release 26 -> 86 releases populated 18 -> 29 The eleven that went from ZERO artifacts to non-zero are exactly the eleven named in #370: v0.13.0 through v0.21.0, v0.31.0, v0.32.0. Every one of those is a SHIPPED release whose scope query answered "nothing to do". That is the recurring shape — an operation that can produce "nothing happened" rendering identically to one that worked. The promotion was done by a text-level transform (so comments and block scalars survive) with per-artifact semantic verification through PyYAML: each artifact's release value must appear on the new plane, its other fields must be unchanged, and the id set must be identical. 58 in artifacts/requirements.yaml, 1 in safety/stpa/rendering-analysis.yaml. Add tools/check_release_plane.py as the regression gate, wired into the rivet-validate job (which deliberately has no `changes` filter, so it runs on every PR). rivet DID diagnose this — `INFO: field 'release' is not defined in schema`, 59 times, all correct and naming the exact ids. They sat under 276 unrelated ERROR lines from #371. A correct diagnostic under an unrelated noise floor is not a diagnostic, so the remedy is an exit code rather than better prose. The checker parses rather than greps: `release:` inside a `description: >` block scalar is prose at a deeper indent, and the obvious `grep -nE '^ {6,}release: '` would red the build for an English sentence. Stdlib-only and fail-closed, same rationale as check_human_scoped.py. The self-test asserts finding COUNTS, not just the exit code: `exit 1` on the violation fixture is equally consistent with a checker that reports every `release:` it sees — one that does not discriminate on plane at all. Four mutations (drop plane discrimination, drop block-scalar tracking, drop either fail-closed guard) each red the self-test, and each fixture kills exactly one guard. The fourth fixture exists because a mutation survived the first draft: deleting the "parsed zero artifacts" raise left all three original self-tests green, making that guard an unexercised claim inside a checker whose whole purpose is to distrust unexercised claims. Closes #370. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rivet verification gate✅ 20/20 passed
Filter: Failed artifacts(none) Updated automatically by |
This was referenced Jul 30, 2026
Open
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #370.
The defect
rivet has two field planes.
releaseis a core field, read by the CLI querysurface —
rivet list --release vX.Yis the release-planning view. Separately,each artifact may carry a
fields:bag for schema-declared custom keys (forrequirement:baseline,category,cited-source,priority,upstream-ref). Writingrelease:into the bag is valid YAML, is tolerated bythe schema, and is completely invisible to the query.
59 of 85 assignments were in the bag.
Measurement
A/B with one binary (rivet 0.28.0 local — note CI pins v0.4.3), same query,
origin/main@2b11af9vs this branch:The eleven that went from zero artifacts to non-zero are exactly the eleven
named in the issue — v0.13.0, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0,
v0.19.0, v0.20.0, v0.21.0, v0.31.0, v0.32.0. Every one is a shipped release
whose scope query answered "nothing to do".
(86 = 85 promoted/existing + 1 new artifact added by this PR.)
Why it stayed hidden
rivet diagnosed it correctly and 59 times:
INFO: field 'release' is not defined in schema, naming the exact ids. Those lines sat under 276 unrelatedERROR:lines from the status-vocabulary defect tracked as #371.
A correct diagnostic buried under an unrelated noise floor is not a diagnostic.
So the remedy here is an exit code, not better prose.
The transform
Text-level, so comments and block scalars survive; semantically verified
per-artifact through PyYAML — each artifact's release value must appear on the
new plane, all its other fields must be unchanged, and the id set must be
identical. 58 in
artifacts/requirements.yaml, 1 insafety/stpa/rendering-analysis.yaml.The gate
tools/check_release_plane.py, wired into therivet-validatejob — whichdeliberately has no
changespath filter, so it runs on every PR. Behind afilter it would be skipped by exactly the artifact-light PR that reintroduces
the bug.
It parses rather than greps:
release:inside adescription: >blockscalar is prose at a deeper indent, so the obvious
grep -nE '^ {6,}release: 'would red the build for an English sentence. Stdlib-only and fail-closed, same
rationale as
check_human_scoped.py.The self-test asserts counts, not just the exit code
exit 1on the violation fixture is necessary for "reported the nested one"but not sufficient — it is equally consistent with a checker that reports
every
release:it sees, i.e. one that does not discriminate on plane atall. So the self-test asserts
(nested, visible).Mutation-tested. Each fixture kills exactly one guard:
violation,block-scalarblock-scalarno-artifactsunsupportedThe fourth fixture exists because a mutation survived the first draft.
Deleting the
parsed zero artifactsraise left all three original self-testsgreen — making that guard an unexercised claim inside a checker whose entire
purpose is to distrust unexercised claims.
release-plane-no-artifactsclosesit.
Scope note
This does not fix #371 (the 276
ERROR:lines).rivet validatewas A/B'dbefore and after at the same error count with identical error classes; the noise
floor is unchanged and tracked separately.
Also note #137:
REQ-TSN-SYNTH-QBV-001carriesstatus: proposedwith a stalerelease: v0.23.0that is now visible to rivet for the first time. It needsre-scoping; that is deliberately not done here.
🤖 Generated with Claude Code