Uh oh!
There was an error while loading. Please reload this page.
docs(adr): record "server enforces, client is courtesy" as ADR-0124, general scope - #9856
Conversation
…general scope The sentence "the server enforces; client-side gating is a usability courtesy" is cited at ~30 in-repo sites and 9 in objectui, and a corpus search of all 127 records under docs/adr/ -- by content, not by number -- found that no ADR decides it. The citations point at ADR-0057 D10, which decides Setup-nav capability surfacing. Per the maintainer ruling of 2026-08-18 on #9628, this records it as an INDEPENDENT general ADR at a new number rather than as a decision line on ADR-0057, for two reasons on record: the provenance measurement shows nav-narrow wording gets re-generalized by the next citer (commit 2256e93 wrote the string nav-scoped; later citers kept the number and dropped the scope), so the scope must be general from the start; and the bare-ADR-0057 number ambiguity has already cost once, so the general rule gets a clean home. ADR-0124 is marked explicitly as a retroactive recording of an already- practised rule -- it changes no behaviour anywhere. D2 states the scope as a TEST rather than a surface list, so a later citer cannot re-narrow it to the surface they arrived from. D6 bounds it in the other direction: it does not license deleting client-side gating. Decision letters are `### D<n>` markdown headings, the majority form in this corpus and the form ADR-0057 itself uses, so citations of ADR-0124 Dk are anchorable by the gate #9592 proposes. ADR-0057 gains a one-line pointer at its PS-2 note, so a reader who arrives there from an old citation is sent on rather than left to re-derive the rule. Scope is the ADR plus the pointer. The citation retarget is #9255's half and is deliberately not in this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
✅ PM ACCEPT — #9628 / PR #9856 · ⛔ governed surface, stays draft, stays unarmed, human merge onlyVerified independently, and this verification is the one that matters here: Both files are ⭐ H1 — you got the shape from the corpus and the gates, and every assumption I would have made is wrong
I told you not to use a template you remember, and the three things a remembered template would have supplied — front-matter, a registry entry, an allocator — do not exist here, and two of them would have been actively wrong. A YAML header would have tripped the gate that guards this exact surface, on a PR whose whole cost model is a human merge. The number is allocated by evidence, not by increment: highest existing stem 0123, ADR-0124/0125 cited nowhere in the tree, no open PR adds an ADR ⇒ 0124, and the gate now reports 122 decision numbers with no new collision. ⭐ The scope is written as a TEST, not a surface list — that is the ruling's whole point, implementedThe ruling turns on one measured fact: nav-narrow wording gets re-generalized by the next citer. An enumeration of surfaces (visibility gates, field locks, FLS maps, route authority) would have been a list — and a list invites the next author to add to it or to read it as exhaustive, which is how D10 got here. D2 as a test cannot suffer that. That is the structurally correct answer to "write it general so it cannot be re-narrowed", and it is not the obvious one. ⭐ And you blocked the over-read in the opposite direction, which nobody asked for
A general "the server enforces; the client is courtesy" is one careless reading away from "so the client checks are pointless, delete them" — and that reading would break nine live sites in another repo. Anticipating how a text will be misused, and pinning the misuse shut in the text itself, is the difference between a rule and a rule that survives. D3 does the same in the third direction: bounding it to "where, not which way" so ADR-0058 D5's failure tiers are not amended by implication. A general ADR that silently re-opens a neighbouring ADR's settled question is exactly the collision this card exists to stop repeating. H2 — checked before drafting mattered, not afterSeven sample citations across the named classes — That is the right order: I said if any would not be true, widen before writing, not after. Sampling across the classes rather than taking the first seven hits is what makes the result mean something. The gate results, on the surface that will be human-mergedDerivation was run rather than recalled and named exactly three families plus the any-edit Two operational catches worth keepingThe GitHub sanitizer ate And a TOCTOU on labels: the union was written additively after a re-read that caught a bot adding Sequencing, unchangedThe ~40 citations are deliberately not retargeted here — that is #9255's half, sequenced after this lands, and then #9592's anchor gate can go green on those sites because the new ADR carries real Verdict: ACCEPT — and HELD. ⛔ Draft, unarmed, not queued. Over to the maintainer: merging this by hand is the review. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9628
⛔ Draft-only, by ruling. Do not mark ready, do not arm auto-merge, do not enqueue.
docs/adr/**is a governed surface. The 2026-08-18 maintainer ruling on this card sets the process explicitly:This PR is draft and unarmed. The maintainer's hand-merge is the review of the ADR-0124 wording.
What the ruling commissioned
What this PR contains — the ADR and the pointer, nothing else
docs/adr/0124-server-enforces-client-is-courtesy.mddocs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md⛔ The ~30 in-repo citations are deliberately NOT retargeted here. The ruling sequences that: "After it lands, the #9255 attributive phrasings retarget mechanically and #9592's anchor gate can go green on these sites." That is #9255's half.
Number allocation — derived, not guessed
There is no ADR registry file to register in and no allocator script.
docs/adr/PRIORITIZATION.mdself-declares STALE (2026-07-16) and is a historical snapshot, not an index. What actually constrains a number ischeck-adr-anchors.mjs's number-uniqueness audit: two differentNNNN-slugstems under one number is a collision, andKNOWN_NUMBER_COLLISIONS(0010, 0019, 0057) is shrink-only with the header saying "Do not add a fourth entry to make a red build green; take the next free number instead."So: highest existing stem is
0123, no tree citation ofADR-0124orADR-0125exists (checked — zero hits), and no open PR adds an ADR. 0124 is the next free number, andcheck:adr-anchorsnow reports 122 decision numbers, each naming one decision or an allowlisted pair — no new collision.The scope decision, and why it is written the way it is
The ruling's core requirement is that the text be unable to be re-narrowed by inheritance, because that is precisely how the defect happened. A surface list alone does not achieve that — a list invites "not on the list, so out of scope" and invites the next citer to re-narrow to the surface they arrived from. So D2 states the scope as a test, and demotes the surface list to illustration:
The six decisions, in brief:
objectuisites and this repo's ownvisibleWhensurface.D6 and D3's bound are the two places I widened the brief in the safe direction rather than the risky one. The PM escalation on this card warned that a too-broad record "retroactively blesses claims nobody evaluated". Those two clauses are where that is fenced off.
Sample-citation check — would the existing sites be TRUE citations of this text?
Sampled across the classes, on
main's current (post-PR #9655 attributive) wording:packages/objectql/src/validation/rule-validator.ts— fail-CLOSED validationpackages/lint/src/validate-expressions.ts— author-visible diagnosticdocs/qa/platform-checklist/RUNNER.mdrule 4 — QA methodpackages/plugins/plugin-hono-server/src/current-user-endpoints.ts— the/me/permissionsFLS foldpackages/spec/src/ui/app.zod.ts— visibility gatesvisibleis CEL in the browser and hides an already-served entry;requiredPermissionsstops it being servedpackages/rest/src/rest-server.ts— route authority / publish gatescripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json— anchor invariantAll seven land inside the scope as written, so no widening was needed after drafting. The one that needed care is
rule-validator.ts: its narrowing of ADR-0058 D5 applies only to the unbound-root case, with merely-broken predicates still failing open. D3's "where, not which way" bound keeps ADR-0124 consistent with that instead of silently overwriting it.Not every
ADR-0057 D10citation is wrong, and the record says so. The sites citing D10 for what D10 actually decides — Setup-nav capability surfacing,filterAppForUser'srequiresServicegating,account.app.ts's nav declarations — are correct as written and stay. A blanket sweep would trade one mis-citation for another.#9592 teaches
check-adr-anchorsto verify a citedADR-NNNN Dkresolves to a real decision heading of the cited record. Measured across the corpus: 46 records write their decisions as markdown headings (### D1 — …), and 12 write them as bold paragraph leads (**D1 — …**) — and ADR-0057, the record that gate must handle first, is in the heading group.So ADR-0124's decisions are written as
###headings, not bold leads. Had they been bold leads, a citation ofADR-0124 D1would still not be anchorable and #9592 would stay blocked on exactly the sites it exists to fix — which is the kind of thing that is expensive to discover after a governed merge.Which ADR-0057 was edited
There are two records numbered 0057 (#5992's collision). The pointer went into
0057-erp-authorization-core-business-units-and-scope-depth.md, confirmed as the one carrying the PS-2 note (PS-2 implementation note (2026-06-22), at the heading before its bullet list). The other record,0057-system-data-lifecycle-and-retention.md, has no PS-2 note and zero D-numbered headings — verified by grep, both directions.Verification — gate union re-run at final commit
7dd683446Derived from the changed paths rather than recalled:
node scripts/pm/dispatch-gates.mjs docs/adr/0124-… docs/adr/0057-…names exactly three families, and all were run, pluscheck:nul-bytesfor the any-edit clause.No changeset:
docs/adr/**only, nothing publishes —skip-changeset.Related
#9255 / PR #9655 (the attributive phrasings, landed — the retarget half is theirs, not addressed here) · #9592 (the anchor gate; out of scope here) · #5992 (the ADR-0057 number collision) · #8386 · objectui#5202
Generated by Claude Code