Skip to content

FEAT-096: gate the release-ordering inversion that was found by hand once - #194

Merged
avrabe merged 2 commits into
mainfrom
gate-release-ordering
Aug 28, 2026
Merged

FEAT-096: gate the release-ordering inversion that was found by hand once#194
avrabe merged 2 commits into
mainfrom
gate-release-ordering

Conversation

@avrabe

@avrabe avrabe commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

The gap

v3.3.0's FEAT-064 is satisfiable only via FEAT-077 and FEAT-087 — both scheduled v3.4.0, both already shipped. FEAT-069 depends on FEAT-095, scheduled v3.5.0. So v3.3.0 cannot be cut before v3.4.0, and no work on its remaining items changes that.

The previous PR made those dependencies visible by typing them as depends-on links instead of prose buried in an acceptance criterion. This PR makes them checked.

Visible is not checked. The inversion was found by an ad-hoc Python snippet I ran by hand once, and nothing re-ran it — the same defect as a gate that never fails: it reads as rigour and enforces nothing. Same distinction FEAT-091 drew for commit trailers and FEAT-093 for the required-check set.

The allowlist is the design, not an escape hatch

Three inversions are real, understood, and recorded on #187. Failing on them would put main red for a known condition, so each is listed with its reason and anything new fails.

Critically, a stale entry — one naming an inversion that is no longer real — also fails. The three known pairs are therefore three facts the detection logic must keep re-deriving from live artifacts on every run.

Mutation 3 showed a consequence I had not designed for: flipping the comparison operator turns all three entries stale and the gate red. The allowlist is a permanent live test of the checker — the answer to who checks the checker for a gate whose normal state is green. A suppression list you can only ever add to is how gates rot; this one costs something to keep.

Mutation-checked against real artifacts, not fixtures

mutant expected measured
FEAT-057 given depends-on a v3.5.0 item (structural YAML edit, applied-count asserted) fail exit 1, named it
allowlist entry for a nonexistent FEAT-999 -> FEAT-998 fail exit 1, "no longer describes a real inversion"
comparison > flipped to < fail exit 1, all three reported stale
control — unmutated pass exit 0

Plus a --self-test of 6 cases over the pure verdict() function, run before the real check in CI, covering earlier-release and same-release dependencies, an unknown target, and two-new-inversions-both-reported.

A step, not a job

It runs on rivet-validate: same artifacts, same PyYAML as the goal guard beside it, and that context is already required — so the gate binds the moment this merges, with no ruleset edit to forget. A new required context needs a post-merge admin step, and a forgotten post-merge admin step is #130. Required set unchanged at 12, verified with --against-file.

What this does not do

It does not resolve the inversions — that is the planning decision still open on #187 (move FEAT-064/REQ-020 to v3.4.0, accept that v3.3.0 ships after it, or re-cut). If the answer is "move them", the gate will fail until the allowlist entries are deleted, which is the correct pressure.

It checks only typed links. A dependency stated in prose stays invisible, which is exactly how these three hid.

Refs: FEAT-096 · scry#187

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc

…once

v3.3.0's FEAT-064 is satisfiable only via FEAT-077 and FEAT-087, both
scheduled v3.4.0 and both already shipped; FEAT-069 depends on FEAT-095,
scheduled v3.5.0. v3.3.0 therefore cannot be cut before v3.4.0, and no
work on its remaining items changes that.

The previous commit made those dependencies VISIBLE by typing them as
`depends-on` links, replacing prose buried in an acceptance criterion.
This makes them CHECKED. Visible is not checked: the inversion was found
by an ad-hoc script run by hand exactly once, and nothing re-ran it,
which is the same defect as a gate that never fails — it reads as rigour
and enforces nothing.

The allowlist is the design. Three inversions are real, understood, and
recorded on scry#187, so failing on them would put main red for a known
condition; each is listed with its reason and anything new fails. A STALE
entry also fails, which makes the three known pairs three facts the
detection logic must keep re-deriving from live artifacts. Mutation 3
demonstrated the consequence and I had not designed for it: flipping the
comparison operator turns all three stale and the gate red. The allowlist
is a permanent live test of the checker, which is the answer to "who
checks the checker" for a gate whose normal state is green.

Mutation-checked against REAL artifacts, not fixtures — a structural YAML
edit gave FEAT-057 a dependency on a v3.5.0 item and the gate named it.
Plus a stale entry, the inverted comparison, and a clean control.

Runs as a step on rivet-validate rather than a job of its own: same
artifacts, same PyYAML, and that context is already required, so the gate
binds on merge with no ruleset edit to forget. A forgotten post-merge
ruleset edit is scry#130. Required set unchanged at 12, verified.

This does not RESOLVE the inversions — that is the planning decision still
open on scry#187. If the answer is "move FEAT-064 and REQ-020 to v3.4.0",
the gate will fail until the allowlist entries are deleted, which is the
correct pressure.

Refs: FEAT-096

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

📐 rivet artifact delta

PR: #194 Base SHA: 33b29255

Validation

head — `rivet validate` result
  SR-11 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-12 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-13 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-2 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-3 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-4 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-5 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-6 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-7 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-8 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-9 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SYS-1 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-2 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-3 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-4 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-5 (system-req, status: accepted) — missing: sys-integration-verification
  → run `rivet validate --explain SR-1` to see which link type and source types satisfy a gap

Result: PASS (185 warnings)
Schemas: common@0.3.0 (embedded), dev@0.3.0 (embedded), research@0.1.0 (embedded), research-ext@0.1.0 (on-disk), safety-case@0.1.0 (embedded), aspice@0.2.0 (embedded)
base — `rivet validate` result (for comparison)
  SR-11 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-12 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-13 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-2 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-3 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-4 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-5 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-6 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-7 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-8 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SR-9 (sw-req, status: accepted) — missing: sw-integration-verification, unit-verification
  SYS-1 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-2 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-3 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-4 (system-req, status: accepted) — missing: sys-integration-verification
  SYS-5 (system-req, status: accepted) — missing: sys-integration-verification
  → run `rivet validate --explain SR-1` to see which link type and source types satisfy a gap

Result: PASS (176 warnings)
Schemas: common@0.3.0 (embedded), dev@0.3.0 (embedded), research@0.1.0 (embedded), research-ext@0.1.0 (on-disk), safety-case@0.1.0 (embedded), aspice@0.2.0 (embedded)

Artifact stats

base head
Total artifacts 265 266
full stats — head
Artifact summary:
  academic-reference               24
  competitive-analysis             11
  design-decision                  22
  feature                          96
  market-finding                    7
  requirement                      21
  safety-context                    3
  safety-goal                       5
  safety-justification              4
  safety-solution                   6
  safety-strategy                   1
  stakeholder-req                   3
  sw-req                           13
  sw-verification                  13
  sys-verification                  5
  system-req                        5
  technology-evaluation            12
  verification                     15
  TOTAL                           266

Orphan artifacts (no links): 12
  CA-001
  CA-002
  CA-003
  CA-004
  CA-005
  CA-006
  CA-007
  CA-008
  CA-009
  CA-010
  CA-011
  FEAT-078

Diagnostics: 0 error(s), 185 warning(s), 29 info(s)

Diff (base → head)

+ FEAT-096  v3.4 — A release cannot be cut before one it depends on, and now something checks (scry#187)

1 added, 0 removed, 0 modified, 265 unchanged

~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-064' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-077' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-087' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-069' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-095' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-091' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-093' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'FEAT-001' but no typed link to it; add a link in `links:` or remove the mention
~ NEW    WARN: [FEAT-096] prose mentions 'REQ-020' but no typed link to it; add a link in `links:` or remove the mention
0 new errors, 0 resolved errors, 9 new warnings, 0 resolved warnings

AADL model — head

spar/scry.aadl: OK

Posted by the rivet-delta workflow. Informational only — does not gate the PR.

…lled safe

Two holes, both found by review of text that was about to ship.

1. FAIL-OPEN ON THE SOURCE SIDE. An unparseable release mapped to a
   (99,99,99) sentinel, and the residual claimed that "sorts last, so it
   biases toward false alarms, the safe direction". True when the garbage
   is the TARGET. False when it is the SOURCE: nothing can exceed
   (99,99,99), so an artifact with a garbage release became a source
   under which no inversion could ever be reported. The residual asserted
   a safety property the code did not have — the overclaim shape this
   repo keeps catching. release_key now returns None and every caller
   fails. Mutation-checked on a real artifact: FEAT-064's release set to
   `v3.3` exits 1.

2. LAST-GLOB-WINS ACROSS 23 FILES. release_of is flat, so a second
   declaration of the same id silently won. G-005 living outside
   safety-case.yaml is the precedent check-undeveloped-goals.py exists
   for. Now fails naming both releases; mutation-checked with a second
   FEAT-064 declaring v9.9.9.

Also measured the gate's REACH, which was not stated: 59 of 266
artifacts carry a release, because the axis arrived with rivet 0.22 and
FEAT-001..031 predate it. 37 depends-on edges have an unscheduled source,
all shipped history. That leaves one asymmetric case undetected — a
SCHEDULED artifact depending on an UNSCHEDULED one is equally uncuttable
and the gate skips it, a skip the self-test codifies as intended, which
is how it would have stayed invisible. Probed and measured at zero today,
and now named in the residual rather than implied absent.

Self-test 6 -> 13 cases, now covering collect() as well as verdict().
All five mutants red, control green.

Refs: FEAT-096

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkNzkNYzPh7366DkNijeNc
@avrabe
avrabe merged commit 35cf316 into main Aug 28, 2026
13 checks passed
@avrabe
avrabe deleted the gate-release-ordering branch August 28, 2026 04:09
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