Files
msd-core/tests/emitted-drift-acks
Tom Boucher c933184b97 enhance(#3172): require a stated failing direction for every automated acceptance command (#3825)
* test(#3172): failing-first suite for the stated failing-direction probe

Pins the <fails_when> pairing walk, placeholder denylist, MISSING sentinel
exemption, degraded-read contract, CLI arm and the plan-authoring contract text.
RED by construction: the module exports it requires do not exist yet.
Executed on the remote runner.

* feat(#3172): require a stated failing direction for every automated acceptance command

Every runnable <automated> command now carries a <fails_when> sibling naming
what output constitutes failure. A command with no expressible failure mode is
not an acceptance test: it reads as rigour and is not falsifiable.

- verify-command-grounding gains a failing-direction probe sharing the existing
  <automated> grammar, MISSING sentinel and walk guard rather than copying them
- gsd-tools check verify-failure-directions <N> backs it; plan-phase dispatches
  it and hands the JSON to gsd-plan-checker check 8f
- Dimension 8 detail extracted to references to stay under the agent size cap

Verified on the remote runner.

* fix(#3172): close four review findings in the failing-direction probe

- MISSING_SENTINEL_RE matched an env-var assignment prefix (MISSING=1 cmd), so
  a real command was exempted from the new blocking gate. Tightened the SHARED
  constant rather than adding a second copy.
- Both token regexes scanned to EOF on unclosed openers (O(n^2), 1562ms at 40k).
  Bodies are now non-crossing; 1ms, byte-identical on well-formed input. The
  pre-existing AUTOMATED_BLOCK_RE carried the same defect and is fixed here too.
- probePhaseFailingDirections reported status 'ok' when one plan was unreadable,
  conflating 'could not look' with 'nothing to report'.
- Extracted the phase-resolution block both check arms had copied verbatim.

Also corrects a docs/AGENTS.md dimension list stale since #2401.
Verified on the remote runner.

* fix(#3172): project the planner rule onto the spawn contract, settle emitted bookkeeping

The remote runner refuted the planner-side edit. agents/gsd-planner.md is frozen
under a 49152-LF-char cap asserted by four suites and sat at 49,146 — six chars
of headroom — so the +537 of authoring rule blew it. #3297/#3645 already settled
where such a rule goes: the planner spawn contract in plan-phase.md, beside
<tracked_source_paths>. The agent file is reverted to origin/next verbatim.

- plan-phase.md gains <failing_direction_contract>; tests row 30 now asserts the
  contract there and row 30b guards the freeze in both directions
- plan-phase.md growth acknowledged by APPENDING to the 3409 fragment, per the
  precedent that two ack sources may never name the same path
- install-tree fixtures regenerated for the three new reference files

Verified on the remote runner.

* chore(#3172): backfill PR number into the changeset fragment

pr:0 -> pr:3825 now that the PR exists.

---------

Co-authored-by: sim <sim@local>
2026-08-24 19:05:11 -04:00
..

tests/emitted-drift-acks/

Per-PR acknowledgment fragments for the differential attribution check (tests/emitted-attribution.test.cjs, ADR-2719 / #2789 / #2914).

This directory being empty is the healthy steady state. A fragment appearing in a diff is the alarm; a fragment sitting here on next is spent cruft. README.md is not a fragment — every reader filters on .json — and exists so the directory (which CONTEXT.md and CONTRIBUTING.md both reference by path) survives the sweep that empties it.

The lifecycle, in three steps

  1. The gate names its own remedy. When an emitted-artifact hash moves, or a gsd-core/workflows/*.md / agents/gsd-*.md file grows, and your diff cannot explain it, the failure output tells you which key to use and prints a minimal valid document to paste. Create a NEW fragment named for your issue or PR (<NNNN>-<slug>.json) — never reuse someone else's, and never revive the legacy single tests/emitted-drift-ack.json.
  2. Note the two key spaces. The message says which one applies. An unattributable hash ripple is keyed on the emitted path (skills/gsd-add-tests/SKILL.md); growth is keyed on the bare filename as it appears under gsd-core/workflows/ or agents/ (explore.md).
  3. Delete the fragment once it has merged (#3078). Every entry is scoped to the diff that introduced it, so the moment it lands on next its prose is already at the base — it is spent and can no longer clear anything, while still owning its path keys. The guard-no-ack-on-next job reds next and prints the exact git rm. Run it.

Why the sweep exists

Fragments end the file conflict the single shared document caused. They do not end the key conflict: two ack sources may never name the same path, and that is a hard, loudly-reported error. So a fully-spent fragment left here walls off every path it owns — the next PR to grow one of them can declare it neither in the owning fragment (spent, gates nothing) nor in its own (duplicate). #2914 assumed a persisting fragment was harmless; #3078 measured the cost at 45 fragments owning 403 paths and made the guard sweep them.

A partially spent fragment is deliberately left alone. That asymmetry is what keeps the re-arm route working: appending prose to a live entry re-arms it, and re-arming deliberately costs an actual new sentence — the comparison strips zero-width characters and collapses whitespace precisely so a zero-information edit cannot fake one.

Never pin a fragment in a test

A fragment is deleted the moment it has merged (see step 3 above), so any test that asserts one exists, or asserts its contents, will fail the instant guard-no-ack-on-next sweeps it — and that failure has nothing to do with the behavior the fragment once explained. This has already cost two suites: tests/emitted-attribution.test.cjs's three #2914 migration pins, and tests/agent-tracked-source-rule.test.cjs's #3645 growth-ack pin. What a test may legitimately assert is the BEHAVIOR the ack explains, or the guard's own verdict (assertNoAllSpentFragments, assertAbsentOnNext) — never the paperwork.

Do not regenerate anything

There is no baseline file to re-run a generator over; #2724 deleted it. If you find yourself hunting for one, that is the predictable wrong guess.

See CONTRIBUTING.md → "Editing shipped content", docs/TESTING-SUITES.md, and CONTEXT.md's RULESET.EMITTED_ATTRIBUTION for the full model.