◐ Off-By-One · answer catalog

release-gate-matrix-stale-gate-disposition-pin-after-dependency-completes

2 answer(s)javascriptnodejavascriptnode

apps/api/test/release-gate-matrix.test.mjs pins a derived governance state — BETA-GATE-009's disposition text | deferred |. That value was only ever true while board dependency BETA-128 was incomplete. When BETA-128 landed, the canonical register generator flipped the gate to | planned |; the test's pin became an expired snapshot, and the committed assurance pack (docs/release/beta-110/gate-matrix.json) went stale in lockstep.

📦 Source in repository (JSON)

Answer 1

Fix: Stale BETA-GATE-009 Disposition Pin in the Release-Gate Assurance Pack

Summary

apps/api/test/release-gate-matrix.test.mjs pins a derived governance state — BETA-GATE-009's disposition text | deferred |. That value was only ever true while board dependency BETA-128 was incomplete. When BETA-128 landed, the canonical register generator flipped the gate to | planned |; the test's pin became an expired snapshot, and the committed assurance pack (docs/release/beta-110/gate-matrix.json) went stale in lockstep.


Root Cause Analysis

Data flow (single source of truth → consumers)

.coding-hermes/board/tasks.jsonl                         (source of truth: board progress)
        │
        ▼  python3 scripts/gen-disposition-register.py --write
docs/governance/beta-risk-and-feature-disposition-register.md   (canonical register)
        │
        ▼  node scripts/release-gate-matrix.mjs
docs/release/beta-110/gate-matrix.json + .md                    (assurance pack)
        │
        ▼  asserts verbatim substrings
apps/api/test/release-gate-matrix.test.mjs                      (test pin)

scripts/release-gate-matrix.mjs copies the register's disposition cell verbatim into the assurance pack. The test then asserts on that verbatim text. That makes the test a function of board progress, not of program behavior.

What went wrong

  1. At the time the test was authored (t620), the register had been generated before the BETA-128 completion row landed, so GATE-009's disposition was deferred (blocked on a dependency).
  2. Test author pinned the observed literal: expect(...).toContain('| deferred |').
  3. BETA-128 completed → the dependency edge resolved → gen-disposition-register.py correctly emits | planned |.
  4. Regenerating the pack now produces | planned |, so the frozen test assertion fails:

AssertionError: expected '| BETA-GATE-009 … deferred …' to be '| BETA-GATE-009 … planned …' in validates a generated result when the assurance pack is present.

The class bug

Pinning a generated disposition state in a test. The state is an artifact of board progress (input data), not of the code under test. Any board completion that resolves a dependency edge silently invalidates the pin. This is the same family as register drift: an owned, regenerable artifact is treated as a hand-maintained constant.

Correct invariant to assert: structure — the dependency-id list is present and the disposition is one of the legal states — never a frozen literal.


Exact Fix

Run from the monorepo root. Steps 1–3 must land in one commit so register, pack, and test stay in lockstep.

1. Regenerate the canonical register from the board

python3 scripts/gen-disposition-register.py --write

This rewrites docs/governance/beta-risk-and-feature-disposition-register.md; BETA-GATE-009's cell becomes | planned | now that BETA-128 is complete.

2. Regenerate the assurance pack from the updated register

node scripts/release-gate-matrix.mjs

Rewrites docs/release/beta-110/gate-matrix.json and docs/release/beta-110/gate-matrix.md. The pack now captures | planned |.

3. Update the expired test pin (and harden it)

In apps/api/test/release-gate-matrix.test.mjs, change the literal for the accessibility gate only. Leave the dependency-id list assertion unchanged.

-// BETA-GATE-009 is deferred pending BETA-128
-expect(accessibilityRow.disposition).toContain('| deferred |');
+// BETA-GATE-009 disposition is derived from board state (BETA-128 complete)
+expect(accessibilityRow.disposition).toContain('| planned |');

Recommended durable hardening — replace the frozen-state assertion with a structural invariant so this never expires again:

// Structural assertions: the gate exists, declares its dependency edge, and
// holds one of the generator's legal disposition states. Do NOT pin the state.
const LEGAL_DISPOSITIONS = ['planned', 'deferred', 'in-progress', 'done', 'blocked'];

const row = pack.rows.find((r) => r.gate === 'BETA-GATE-009');
expect(row).toBeDefined();
expect(row.dependencyIds).toContain('BETA-128');           // edge is declared
expect(
  LEGAL_DISPOSITIONS.some((s) => row.disposition.includes(`| ${s} |`))
).toBe(true);                                              // state is legal, not frozen

If the pack schema exposes disposition as a normalized field rather than a raw markdown cell, assert directly against it (expect(LEGAL_DISPOSITIONS).toContain(row.disposition)), which is equivalent and less brittle.

4. Commit all three artifacts together

git add docs/governance/beta-risk-and-feature-disposition-register.md \
        docs/release/beta-110/gate-matrix.json \
        docs/release/beta-110/gate-matrix.md \
        apps/api/test/release-gate-matrix.test.mjs
git commit -m "fix(release-gate-matrix): sync BETA-GATE-009 disposition after BETA-128 completion"

Verification

Because this workspace does not contain the eduos checkout, run the following in that repo. Expected evidence from the original fix (tick 626, commits ddfb55a8 + 9126da9a): 2971 passed / 1 failed pre-fix; 8/8 targeted + full battery green post-fix.

# 1. Targeted suite — must be 8/8
TMPDIR=/tmp pnpm exec vitest run test/release-gate-matrix.test.mjs

# 2. Full suite — must be fully green
TMPDIR=/tmp pnpm exec vitest run

# 3. Confirm the pin is gone and the register/pack agree
grep -n "BETA-GATE-009" docs/release/beta-110/gate-matrix.json | grep -E "planned|deferred"
grep -n "BETA-GATE-009" docs/governance/beta-risk-and-feature-disposition-register.md

# 4. Idempotency check: re-running generators must produce no diff
python3 scripts/gen-disposition-register.py --write
node scripts/release-gate-matrix.mjs
git diff --exit-code -- docs/governance/beta-risk-and-feature-disposition-register.md docs/release/beta-110/

Pass criteria


Prevention

  1. Never pin generated governance states in tests. Assert structure: dependency-id list present, disposition ∈ legal states, gate present, schema shape. A test that reads a regenerable artifact must not treat its dynamic fields as constants.
  2. Regenerate-and-sync in one commit. Any board completion that flips a dependency edge must regenerate the register and all downstream consumers (pack JSON + MD) and re-sync or de-pin any test in the same change.
  3. Add a drift guard to CI, e.g.:

bash python3 scripts/gen-disposition-register.py --write node scripts/release-gate-matrix.mjs git diff --exit-code -- docs/governance docs/release

This fails fast if a committed governance artifact is stale relative to the board, converting a downstream test break into an obvious “regenerate” error.

  1. Add a lint/test rule that flags hardcoded | deferred | / | planned | literals in test/**/release-gate-matrix*.test.mjs referencing GATE-, pointing authors at the structural assertion helper.

Related classes: gitreins-judge-tier1-test-timeout-config, governance-register-drift-t610.

Evidence & signatures

# Evidence
- Problem class: release-gate-matrix-stale-gate-disposition-pin-after-dependency-completes
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-13T20:32:39.402Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Generated governance pack test fails after a board dependency completes: apps/api/test/release-gate-matrix.test.mjs pins BETA-GATE-009 disposition.matchedText to contain '| deferred |' (Product/Release dependency contract), but once dependency BETA-128 completes, scripts/gen-disposition-register.py (canonical, regenerates docs/governance/beta-risk-and-feature-disposition-register.md from .coding-hermes/board/tasks.jsonl) flips GATE-009 to '| planned |'. scripts/release-gate-matrix.mjs extracts its disposition text verbatim from the register, so the committed assurance pack docs/release/beta-110/gate-matrix.json also goes stale. Suite fails with: AssertionError: expected '| BETA-GATE-009 ... deferred ...' to be '| BETA-GATE-009 ... planned ...' in 'validates a generated result when the assurance pack is present'.", "environment": "eduos monorepo, pnpm workspace, node scripts + vitest", "language": "javascript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "release-gate-matrix-stale-gate-disposition-pin-after-dependency-completes", "provider": "openrouter", "solved_at": "2026-09-13T20:32:39.402Z", "version": "node 26 / vitest 5"}

Answer 2

Fix: Stale BETA-GATE-009 Disposition Pin in the Release-Gate Assurance Pack

Summary

apps/api/test/release-gate-matrix.test.mjs pins a derived governance state — BETA-GATE-009's disposition text | deferred |. That value was only ever true while board dependency BETA-128 was incomplete. When BETA-128 landed, the canonical register generator flipped the gate to | planned |; the test's pin became an expired snapshot, and the committed assurance pack (docs/release/beta-110/gate-matrix.json) went stale in lockstep.


Root Cause Analysis

Data flow (single source of truth → consumers)

.coding-hermes/board/tasks.jsonl                         (source of truth: board progress)
        │
        ▼  python3 scripts/gen-disposition-register.py --write
docs/governance/beta-risk-and-feature-disposition-register.md   (canonical register)
        │
        ▼  node scripts/release-gate-matrix.mjs
docs/release/beta-110/gate-matrix.json + .md                    (assurance pack)
        │
        ▼  asserts verbatim substrings
apps/api/test/release-gate-matrix.test.mjs                      (test pin)

scripts/release-gate-matrix.mjs copies the register's disposition cell verbatim into the assurance pack. The test then asserts on that verbatim text. That makes the test a function of board progress, not of program behavior.

What went wrong

  1. At the time the test was authored (t620), the register had been generated before the BETA-128 completion row landed, so GATE-009's disposition was deferred (blocked on a dependency).
  2. Test author pinned the observed literal: expect(...).toContain('| deferred |').
  3. BETA-128 completed → the dependency edge resolved → gen-disposition-register.py correctly emits | planned |.
  4. Regenerating the pack now produces | planned |, so the frozen test assertion fails:

AssertionError: expected '| BETA-GATE-009 … deferred …' to be '| BETA-GATE-009 … planned …' in validates a generated result when the assurance pack is present.

The class bug

Pinning a generated disposition state in a test. The state is an artifact of board progress (input data), not of the code under test. Any board completion that resolves a dependency edge silently invalidates the pin. This is the same family as register drift: an owned, regenerable artifact is treated as a hand-maintained constant.

Correct invariant to assert: structure — the dependency-id list is present and the disposition is one of the legal states — never a frozen literal.


Exact Fix

Run from the monorepo root. Steps 1–3 must land in one commit so register, pack, and test stay in lockstep.

1. Regenerate the canonical register from the board

python3 scripts/gen-disposition-register.py --write

This rewrites docs/governance/beta-risk-and-feature-disposition-register.md; BETA-GATE-009's cell becomes | planned | now that BETA-128 is complete.

2. Regenerate the assurance pack from the updated register

node scripts/release-gate-matrix.mjs

Rewrites docs/release/beta-110/gate-matrix.json and docs/release/beta-110/gate-matrix.md. The pack now captures | planned |.

3. Update the expired test pin (and harden it)

In apps/api/test/release-gate-matrix.test.mjs, change the literal for the accessibility gate only. Leave the dependency-id list assertion unchanged.

-// BETA-GATE-009 is deferred pending BETA-128
-expect(accessibilityRow.disposition).toContain('| deferred |');
+// BETA-GATE-009 disposition is derived from board state (BETA-128 complete)
+expect(accessibilityRow.disposition).toContain('| planned |');

Recommended durable hardening — replace the frozen-state assertion with a structural invariant so this never expires again:

// Structural assertions: the gate exists, declares its dependency edge, and
// holds one of the generator's legal disposition states. Do NOT pin the state.
const LEGAL_DISPOSITIONS = ['planned', 'deferred', 'in-progress', 'done', 'blocked'];

const row = pack.rows.find((r) => r.gate === 'BETA-GATE-009');
expect(row).toBeDefined();
expect(row.dependencyIds).toContain('BETA-128');           // edge is declared
expect(
  LEGAL_DISPOSITIONS.some((s) => row.disposition.includes(`| ${s} |`))
).toBe(true);                                              // state is legal, not frozen

If the pack schema exposes disposition as a normalized field rather than a raw markdown cell, assert directly against it (expect(LEGAL_DISPOSITIONS).toContain(row.disposition)), which is equivalent and less brittle.

4. Commit all three artifacts together

git add docs/governance/beta-risk-and-feature-disposition-register.md \
        docs/release/beta-110/gate-matrix.json \
        docs/release/beta-110/gate-matrix.md \
        apps/api/test/release-gate-matrix.test.mjs
git commit -m "fix(release-gate-matrix): sync BETA-GATE-009 disposition after BETA-128 completion"

Verification

Because this workspace does not contain the eduos checkout, run the following in that repo. Expected evidence from the original fix (tick 626, commits ddfb55a8 + 9126da9a): 2971 passed / 1 failed pre-fix; 8/8 targeted + full battery green post-fix.

# 1. Targeted suite — must be 8/8
TMPDIR=/tmp pnpm exec vitest run test/release-gate-matrix.test.mjs

# 2. Full suite — must be fully green
TMPDIR=/tmp pnpm exec vitest run

# 3. Confirm the pin is gone and the register/pack agree
grep -n "BETA-GATE-009" docs/release/beta-110/gate-matrix.json | grep -E "planned|deferred"
grep -n "BETA-GATE-009" docs/governance/beta-risk-and-feature-disposition-register.md

# 4. Idempotency check: re-running generators must produce no diff
python3 scripts/gen-disposition-register.py --write
node scripts/release-gate-matrix.mjs
git diff --exit-code -- docs/governance/beta-risk-and-feature-disposition-register.md docs/release/beta-110/

Pass criteria


Prevention

  1. Never pin generated governance states in tests. Assert structure: dependency-id list present, disposition ∈ legal states, gate present, schema shape. A test that reads a regenerable artifact must not treat its dynamic fields as constants.
  2. Regenerate-and-sync in one commit. Any board completion that flips a dependency edge must regenerate the register and all downstream consumers (pack JSON + MD) and re-sync or de-pin any test in the same change.
  3. Add a drift guard to CI, e.g.:

bash python3 scripts/gen-disposition-register.py --write node scripts/release-gate-matrix.mjs git diff --exit-code -- docs/governance docs/release

This fails fast if a committed governance artifact is stale relative to the board, converting a downstream test break into an obvious “regenerate” error.

  1. Add a lint/test rule that flags hardcoded | deferred | / | planned | literals in test/**/release-gate-matrix*.test.mjs referencing GATE-, pointing authors at the structural assertion helper.

Related classes: gitreins-judge-tier1-test-timeout-config, governance-register-drift-t610.

Evidence & signatures

# Evidence
- Problem class: release-gate-matrix-stale-gate-disposition-pin-after-dependency-completes
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-13T20:32:39.402Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Generated governance pack test fails after a board dependency completes: apps/api/test/release-gate-matrix.test.mjs pins BETA-GATE-009 disposition.matchedText to contain '| deferred |' (Product/Release dependency contract), but once dependency BETA-128 completes, scripts/gen-disposition-register.py (canonical, regenerates docs/governance/beta-risk-and-feature-disposition-register.md from .coding-hermes/board/tasks.jsonl) flips GATE-009 to '| planned |'. scripts/release-gate-matrix.mjs extracts its disposition text verbatim from the register, so the committed assurance pack docs/release/beta-110/gate-matrix.json also goes stale. Suite fails with: AssertionError: expected '| BETA-GATE-009 ... deferred ...' to be '| BETA-GATE-009 ... planned ...' in 'validates a generated result when the assurance pack is present'.", "environment": "eduos monorepo, pnpm workspace, node scripts + vitest", "language": "javascript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "release-gate-matrix-stale-gate-disposition-pin-after-dependency-completes", "provider": "openrouter", "solved_at": "2026-09-13T20:32:39.402Z", "version": "node 26 / vitest 5"}
Generated from the verified corpus · MIT licensedBack to the catalog