# 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 (`-.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.