Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ A guard is a test whose job is to *fail* when someone later does the wrong thing
- **Two misses of the same shape mean the METHOD is wrong** — prefer "walk everything, exclude deliberately" over "list what I thought of".
- **For a pinned/golden value, prove portability** — repetition on one machine cannot detect environment leakage.
- **Prefer the boring guard** — complexity costs double when the complicated thing is the thing you are trusting.
- **Adding an entry to a registry? Find its guards by grepping the registry's READERS, never by recall.** A registry here is any list other code walks — `CliDispatcher.Commands`, `AuditActions`, an enum mirrored into `web/src/i18n/enums.ts`. Adding #534's two verbs, a recalled list produced `CliDispatcherTests` and `ProcessRoleRegistryTests` and missed `OneShotVerbMinimalConfigTests`, which walks `ProcessRoles.OneShotVerbs` and fails any verb with no minimal-config case; `grep -rn "CliDispatcher.Commands\|ProcessRoles.OneShotVerbs" tests/` returns all three in a second. This is the bullet above turned on the guards themselves: a remembered list of guards is exactly the hand-maintained list they exist to stop anyone trusting.
- **A guard that inspects call-site SYNTAX has to be read before you author the call site.** Its rule is not inferable from the code it guards, and violating it is a build failure rather than a review comment. `AuditVocabularyCoverageTests` accepts only `AuditActions.X`, or a ternary of two such references, as the action argument of an `IAuditWriter.WriteAsync` call — it fails closed on everything else and carries exactly one bespoke exemption (`IdentityProvider`'s forwarded parameter) with three companion assertions holding that exemption honest. So the obvious refactor, forwarding the action through a shared private helper's parameter, goes red on a test the author never opened; #534 caught that in a pre-dispatch review and switched to the ternary.

## Pre-commit hook (opt-in)

Expand Down Expand Up @@ -189,6 +191,7 @@ Two stages, deliberately separate: **CI publishes an image per merge; the releas
- Only commit/push when the human asks.
- **Keep phase epics in sync**: when filing a slice issue, add it to the phase epic's checklist (epic #14 = Phase 1.1, #15 = Phase 1.5); when its PR merges, check it off. Milestone assignment alone is not enough — the epics are how work is navigated.
- **Keep documentation in sync** (owner directive, 2026-07-17): every PR that adds or changes user-visible behavior updates, in the same PR, (1) `specs/product/GLOSSARY.md` when a concept appears or changes meaning, and (2) the SPA Help page + in-app glossary. Treat a missing doc update like a missing test.
- **A PR that ships work another OPEN issue claims amends that issue in the same PR.** An issue body is the spec whoever picks it up next plans, sizes and routes against, and it does not update itself. #532 shipped `AccountSuspensionService` whole — its own header naming #534 as the caller it was waiting for — while #534's body went on listing that service's revocation, epoch bump and stamp rotation as *its* scope, so #534 was picked up months later against a description wrong in most of its bullets. **Leave the body as written for history**; add an amendment note at the top pointing at a comment that says what already shipped and where, what actually remains, and which acceptance criteria the shipped code deliberately does not meet. That last part is the one that gets skipped and the one that matters: a criterion nobody will implement is either a follow-up issue or a recorded won't-fix, never a bullet quietly left to rot in a closed issue.

## Phase context

Expand Down
Loading