Skip to content

docs: retarget the general-rule ADR-0057 D10 citations to ADR-0124 D1 - #11780

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11501-adr-0057-d10-retarget
Aug 25, 2026
Merged

docs: retarget the general-rule ADR-0057 D10 citations to ADR-0124 D1#11780
os-steve merged 1 commit into
mainfrom
claude/issue-11501-adr-0057-d10-retarget

Conversation

@os-steve

@os-steveos-steve commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes#11501

The classification criterion

Written first, so the calls below can be checked against it rather than against my judgement.

  • ADR-0057 D10 decides: which Setup-nav entries are surfaced — they tier on capability/feature presence (requiresService, multiOrgEnabled) while the object itself stays registered open in every edition. Its PS-2 implementation note (2026-06-22) records that this nav filtering happens server-side in filterAppForUser.
  • ADR-0124 D1 decides: every access decision this platform declares is decided by the server before the answer leaves the process; a client-side check evaluating the same declaration is a usability courtesy and is never what makes the rule true. D2 makes that scope general by construction and forbids re-narrowing it to one surface.

The test applied to each citation: does the sentence carrying the citation depend on a capability/feature-gated nav or app surface? If yes, D10 is its actual subject and it stays. If the sentence would be equally true with no capability gate anywhere in the picture — because it is asserting where enforcement lives or what counts as verification — it invokes the general rule and moves.

Population — the card's number was the wrong end of it

MeasurementCountNote
Files mentioning ADR-0057 at all280the crude grep -rl figure; not the population
Files carrying the literal ADR-0057 D1042the card's ~42
Occurrences of ADR-0057 D1098the real unit of work — a file carries up to 12
Distinct spellings of a D10 citation1git grep -oE 'ADR-0057[^A-Za-z0-9]{0,4}D10|D10[^A-Za-z0-9]{0,6}(of )?ADR-0057' → 98/98 are ADR-0057 D10

⚠️ A bare-D10 grep is useless here: D10 is also a decision id in ADR-0056, 0058, 0076, 0079 and 0090 (345 non-ADR-0057 D10 hits). Every citation of this D10 is spelled with the number adjacent, so the 98 is complete.

Of 98 occurrences: 31 retargeted · 33 left on D10 (its real subject) · 34 are records not rewritten.

A · The 31 retargets

#SiteWhat the citing sentence claimsWhy it moves
1packages/objectql/src/validation/rule-validator.ts:116the standing "server enforces, client is courtesy" rule is why D5's fail-soft tier was narrowedD1names the rule verbatim; no nav or capability in the picture
2packages/objectql/src/validation/rule-validator.ts:664a lock that fails open leaves enforcement in the courtesy layerD1enforcement-location claim about a field lock; also now cites D3 (server may not delegate an undecided case)
3scripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json:7anchor invariant: "a declared field lock is the SERVER's to enforce"D1the anchor's invariant is the general rule — see the anchor note below
4packages/objectql/src/engine.ts:5394readonlyWhen is a documented server guarantee; the client grid is courtesyD1enforcement-location claim about a field lock
5packages/objectql/src/engine-readonly-when-parent.test.ts:10the rule puts enforcement on the SERVER and makes the client courtesyD1same, in the suite that pins the direction
6packages/qa/dogfood/test/showcase-readonly-when-parent.dogfood.test.ts:14makes the SERVER the enforcement point and the client courtesy; here it was invertedD1same, dogfood-level
7packages/lint/src/validate-expressions.ts:566the rule resolves a client/server disagreement in the server's favourD1enforcement-location claim; lint diagnostic docblock
8packages/lint/src/validate-expressions.test.ts:1149the rule "gives it to the server" when the two ends fault in opposite directionsD1same
9packages/lint/src/validate-expressions.test.ts:1189server enforces, client is courtesyD1verbatim statement of the rule
10packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:248the server is the authoritative gate; the client must mirror it, never divergeD1FLS map; also now cites D4 (what the client is told may not overstate enforcement)
11packages/plugins/plugin-hono-server/src/fold-wildcard-superuser.test.ts:7server enforces, client is courtesyD1same, its test
12.claude/skills/dogfood-verification/SKILL.md:113服务端是权威可见性门 —— 客户端过滤只是「礼貌」D1verbatim statement of the rule; citation swapped in place, prose untouched
13docs/qa/platform-checklist/RUNNER.md:78UI absence alone is a client courtesy; the server is the authorityD1 + D5"that rule" is D1; rule 4's own subject (both sides of every gate) is D5, so both are cited
14docs/audits/2026-07-studio-package-create-ux-dogfood.md:9"Both sides of the read-only gate were exercised"D5pure verification-method claim — see the D5 note
15.../areas/access-security.json:54"the entitled side of the same gate (both sides, RUNNER rule 4)"D5pure verification-method claim
16.../areas/access-security.json:86source entry for the RLS both-sides itemD1the source being cited is the rule itself
17.../areas/access-security.json:338the SERVER refuses the same write — UI absence never sufficesD1enforcement-location claim (read-only package meta PUT)
18.../areas/access-security.json:424every withheld cell is DENIED SERVER-SIDE — UI absence never countsD1enforcement-location claim (FLS verbs)
19.../areas/access-security.json:1296a Studio panel greying the save while a direct meta PUT widens is a FAIL — the server gate is the authorityD1OWD authoring gate; no capability gate involved
20.../areas/access-security.json:1313source ADR list for the OWD itemD1same item as 19
21.../areas/approvals.json:283the gate is server-side: a forged direct POST is rejectedD1enforcement-location claim (approval decision route)
22.../areas/approvals.json:301source: "server is the authoritative visibility gate"D1same item
23.../areas/attachments-storage.json:236UI absence of a download button is a client courtesy; the route is the authorityD1enforcement-location claim (file route)
24.../areas/identity-auth.json:214a disabled method is refused SERVER-SIDE, not merely hiddenD1enforcement-location claim (auth methods)
25.../areas/identity-auth.json:350UI-only enforcement is a FAIL — the server is the authorityD1enforcement-location claim
26.../areas/identity-auth.json:810affordance hidden but the forged endpoint succeeds is a FAILD1enforcement-location claim (org management)
27.../areas/platform-core.json:521the app-level gate is server-side, not a client courtesyD1⚠️ closest call — the gate here is App.requiredPermissions setup.access, not a capability gate, so D10 is not its subject
28.../areas/records-forms.json:1123the server is the authority, the UI drop is courtesyD1enforcement-location claim (RLS-scoped tab)
29.../areas/records-forms.json:2492"the direct non-admin PUT returns a 403-class refusal"D5pure verification-method claim
30.../areas/studio-authoring.json:76the server is the authoritative gate; the client lock is courtesyD1enforcement-location claim (read-only package write)
31.../areas/studio-authoring.json:83source: "server-side gate authority"D1same item

⚠️ Declared deviation — 4 citations point at ADR-0124 D5, not D1

The card says "retarget to ADR-0124 D1". Four of the 31 (rows 13–15, 29) do not assert where enforcement lives; they assert what counts as verification — "both sides were exercised", "the forged PUT returns a refusal". ADR-0124 D5 is that decision, exactly ("Verifying a gate means exercising the server; UI absence is not evidence"). Pointing a method claim at a location decision would be the same class of loose citation this card exists to close, so I sent them to D5 and am flagging it rather than doing it silently. Both targets satisfy the card's "Done means". If you disagree, collapsing them is a 4-token edit — they are the only D5 citations in the tree.

B · The 33 left on ADR-0057 D10 — D10 is their subject

Site×Why it stays
packages/rest/src/rest-server.ts12the requiresService capability gate itself — filterAppForUser / filterDashboardForUser, incl. the dashboard-widget gate
packages/rest/src/rest.test.ts7the tests of that gate (describe('filterAppForUser — ADR-0057 D10 requiresService gate'))
packages/rest/src/meta-app-publish-gate.test.ts3a published app gated by capability presence
packages/rest/src/meta-app-area-nav-gate.test.ts2a requiresService entry inside an area
packages/rest/src/rest-api-plugin.ts1wiring the capability probe
packages/rest/src/rest-exec-ctx-principal-kind.test.ts1the resolved kernel that probe reads
packages/platform-objects/src/apps/account.app.ts1the account app's own nav declaration — named in ADR-0124 as a "stays" case
packages/platform-objects/src/apps/account-approvals-nav.test.ts1that declaration's test
packages/spec/liveness/README.md2requiresService liveness — the "dead in the renderer, LIVE server-side" lesson
packages/spec/liveness/app.json · dashboard.json2the requiresService liveness rows
scripts/check-meta-type-normalized.mjs1"the ADR-0057 D10 widget gate never ran on the default path"

The third class exists, and it is left alone

Three of the 33 invoke both — D10's capability gate is the vehicle and the general rule is the reason: account-approvals-nav.test.ts:94 ("requiresService is stripped server-side by filterAppForUser, so a gated-off entry never reaches the browser"), meta-app-publish-gate.test.ts:125 and rest-server.ts:2299 (both on the gate's fail-open-when-unprobeable behaviour). The subject test resolves them: the sentence's subject is the capability gate, which is what D10 decides, so the citation stays and the general rule is background rather than referent. Recording them here because they are the rows most worth a second opinion.

C · The 34 not rewritten — these are records, not citing sites

Site×Why
9 package CHANGELOG.md files29shipped release records, changeset-generated, published to npm. Retargeting them would rewrite what a past release said — a different act from moving a citation.
docs/adr/0124-server-enforces-client-is-courtesy.md4⛔ out of scope by ruling. Its 4 mentions are the lineage argument (the "closest ancestor" table, the 2256e9369 commit history) and are about the old citations — they must survive for the record to make sense.
docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md1⛔ out of scope by ruling; it is D10's own heading.

Non-vacuity — pins with named controls

Measured at git rev-parse --short HEAD = 584cff6, base 5cb62d88b. Every count is git grep -o … | wc -l (occurrences, not files).

PinBeforeAfterΔReads
ADR-0057 D109867−31exactly the retargets
ADR-0124 D1 (word-bounded D1\b)129+2827 sites + RUNNER.md
ADR-0124 D504+4rows 13, 14, 15, 29
CONTROL — total ADR-0057 mentions789756−33see below
Diff ledger: - lines carrying ADR-0057 D1031
Diff ledger: + lines carrying ADR-0057 D100nothing re-spelled the old citation

The control does not close at −31, and that is correct — it is −33. The 2 extra are both in scripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json and both are required, not collateral:

  1. "ADR-0057" removed from the anchor's adrs array. check:adr-anchors asserts every listed ADR still appears in the anchored file; rule-validator.ts had only D10 mentions (2 of 2), so retargeting them left the file with no ADR-0057 at all. Leaving the entry would have turned the gate red. "ADR-0124" replaces it.
  2. The invariant's sentence "The other ADR-0057 (system data lifecycle) has no D-numbered decisions at all" — it existed to explain why the old citation was unresolvable, and is obsolete once the citation resolves.

28 + 4 = 32 citations across 31 sites because RUNNER.md row 13 carries both D1 and D5.

Gates

Run on the final commit 584cff6, each under scripts/pm/os-verify-lock.sh, exit captured before any pipe, verdict quoted from the gate's own output:

  • check:adr-anchorsOK (52 anchored file(s), every governing ADR still referenced; 123 decision number(s) …; 27918 citation(s) across 3497 file(s) resolve)
  • check:platform-checklistOK — 15 areas, 207 items (207 active)
  • check:nul-bytesOK (scanned 6572 text file(s) … no raw ASCII control bytes)
  • check:doc-authoring389 files clean — no bare metadata literals
  • check:pm-skill-ratchetdogfood-verification/SKILL.md is 155 lines (ceiling 155; headroom 0) — the skill edit is byte-for-byte line-neutral
  • check:pm-governed-prose, check:skill-frame-sync, check:agent-test-spelling — green

Declared narrowing: no local typecheck/test run. This worktree has no node_modules (the gates above are dependency-free node scripts), and installing a monorepo closure to typecheck a comments-only diff is not a good use of a shared container. The narrowing is measured, not assumed:

  1. Population read from git diff --name-only — 8 .ts files, not a guess.
  2. 0 changed lines in those files are non-comment lines (every +/- line begins *, // or /**), and /* ÷ */ counts are identical before and after in all 8 — so no comment was opened or closed.
  3. A change confined to comment interiors alters no type and no parse structure, so no untouched file's verdict can move either.

CI runs the full TypeScript Type Check job with no paths filter regardless.

Scope notes

  • skills/ (published) is not in this population at all.skills/objectstack-data/ cites ADR-0057 four times, but all four are the other ADR-0057 (0057-system-data-lifecycle-and-retention.md, lifecycle/retention) and none is a D10 citation. The only skill file touched is .claude/skills/dogfood-verification/SKILL.md — internal agent tooling.
  • content/docs/releases/ carries zero ADR-0057 D10 citations — verified, so the never-edit rule and this pass do not intersect.
  • Governed surface: the diff touches .claude/**, so the whole PR is governed under Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14. It stays draft, review requested from os-zhuang, and this seat will not flip it ready, arm auto-merge, or enqueue it. Without the one-line .claude/ edit the diff would be ungoverned — say the word if you would rather split that line into its own PR so the other 18 files can land normally.
  • No changeset, skip-changeset applied: every edit to a published package is inside a comment. The [finding] ADR-0057 D10 ("server enforces, client is courtesy") names no decision in either ADR-0057 — ~20 sites plus an adr-anchors/ invariant ride the anchor #9255 precedent in this family did ship one, but that change edited an author-visible lint diagnostic string; this one changes no runtime text.

Generated by Claude Code


Generated by Claude Code

ADR-0124 recorded the general enforcement rule ("the server is the
enforcement point; client-side gating is a usability courtesy") that ~30
sites had been citing as `ADR-0057 D10` — a number that actually decides
Setup-nav capability surfacing. This moves the general-rule citations and
leaves the capability-gate citations where they are.
Classified per citation, not rewritten mechanically: 31 of 98 occurrences
moved; 33 live citing sites that cite D10 for its own subject stay; 34
occurrences in shipped CHANGELOGs and in the two ADRs are records and are
not rewritten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 3 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/lint/src/validate-expressions.ts, packages/objectql/src/validation/rule-validator.ts, packages/plugins/plugin-hono-server/src/current-user-endpoints.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: ObjectQL (symbol, 64 pages)

Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 006c181a892e53b05db75f0b0ca4a4cec3059833packageMentionDocs.

@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT (advisory — this is governed surface, so the merge decision is os-zhuang's, not mine). Verified by content on origin/main @ 006c181a8.

My 279 was the wrong unit, and you were right to say so

probereading
files mentioning ADR-0057 anywhere (my number)279
occurrences of ADR-0057 D1098
files carrying ADR-0057 D1042

Your 98 / 42 is exact. I handed you a count of "mentions ADR-0057", labelled it the population, and flagged it as Zone 2 precisely because I suspected it was the wrong end of the problem — it was. The difference between "mentions ADR-0057" and "cites ADR-0057 D10 as a general rule" is the entire classification task, and starting from 279 would have made the card look four times larger than it is.

And D10 alone really is useless as a selector, which I had not considered at all. Measured: 443 bare D10 hits, with ADR-0090 D10 in 49 files and ADR-0056 D10 in 31. Anyone selecting on the decision number would have swept five unrelated ADRs' decisions into this card.

⭐ The control that does NOT close — this is the best thing in the report

ADR-0057 total moved -33, not the -31 the retargets account for. You did not smooth that over; you named the 2 extra and showed they were required. Verified:

packages/objectql/src/validation/rule-validator.ts
116: * why. This repo cites that rule as ADR-0057 D10 — an ATTRIBUTION, not a
665: * ADR-0057 D10 puts enforcement on the server; a lock that fails open leaves
total ADR-0057 in that file: 2 of which D10: 2

and its anchor declares adrs: ['ADR-0057', 'ADR-0058']. So that file's only two ADR-0057 mentions were both D10 — retargeting them would have left check:adr-anchors' presence assertion pointing at a string no longer in the file, and the gate would have gone red.

That is a second-order coupling the card never mentions and I would not have anticipated: a citation-retarget PR can break a presence assertion in an anchor registry simply by succeeding. Finding it, fixing it, and then reporting it as a named exception to your own arithmetic — rather than letting the control quietly not close — is exactly what the control is for.

⚠️ One small non-finding, recorded so nobody chases it: I read ADR-0057 total as 787 on today's main where you read 789 at base 5cb62d88b. Different trees and probably different probes; the −33 reasoning is what I verified, not the absolute. Not asserting either count is wrong.

The third class exists — thank you for checking rather than forcing the binary

I asked whether there might be citations invoking both the general rule and something ADR-0057-specific, and said to name them rather than force a binary. There are 3, resolved by the subject test, left on D10, and named individually for a second opinion. That is the right disposition: a judgement call made visible is reviewable; the same call made silently is not.

The D5 deviation is right, and its reasoning is the card's own principle

4 of the 31 point at D5, not D1, because their sentences assert what counts as verification ("both sides were exercised", "the forged PUT returns a refusal") rather than where enforcement lives. Your line for it:

sending a method claim to a location decision would be the same loose-citation class this card exists to close

That is the card applied to itself. A mechanical D1-for-all pass would have hit the target number and re-committed the defect on four sites. Flagging it as a declared deviation, collapsible in a 4-token edit, leaves os-zhuang a real choice instead of a fait accompli.

The 33 that stay on D10 because D10 is their subject — ADR-0124 naming that family "correct as written and stay" — is the other half of the same discipline, and 29 CHANGELOG occurrences correctly treated as records rather than rewritten.

Governed-surface handling

Not flipping ready, not arming, not queuing. Requesting os-zhuang. The narrowing you declared (no local typecheck: no node_modules, comments-only diff, 0 non-comment lines changed across the 8 .ts files, /* and */ counts identical before and after so no comment was opened or closed) is measured rather than asserted, and CI runs the full unfiltered TypeScript Type Check regardless — so the reviewer is not being asked to take the type safety on trust.

#11781 noted — the published objectstack-data skill citing bare ADR-0057 three times, to an audience with no docs/adr/ to grep. Genuinely distinct from #5992's in-repo B-work on the audience axis. And you checked the neighbouring ADR-0057 D1 at SKILL.md:651 and found it correct rather than assuming a nearby citation shared the defect — that restraint is why I believe the three.


Generated by Claude Code

os-steve pushed a commit that referenced this pull request Aug 25, 2026
automation.rollup-summary-filter clause 5 (the visual filter-editor half)
was blocked.by:fixture, tracked at #3358. The maintainer ruled NO on a
stock writable showcase package (2026-08-19, 「接受你的所有建议」) and
directed retargeting at the platform's own runtime path instead: the
runner clones the showcase base into a writable target via
POST /packages/:id/duplicate (ADR-0070 D4 "duplicate base").
Bumped revision to 3 with a history entry; removed the item-level
blocked.by:fixture entry and its matching fixtures.knownGaps line, since
the clause is runnable on every boot now. Oracle stays screenshot — the
clause specifically exercises the visual editor, corroborated by a
meta-API read of the persisted filter.
The sibling half (access-security.json, readonly-package-locks-studio)
is held by open PR #11780 and stays out of scope here — see the PR body
for the card relationship.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@os-steve
os-steve marked this pull request as ready for review August 25, 2026 04:00
@os-steve
os-steve added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 359e497Aug 25, 2026
37 of 39 checks passed
@os-steve
os-steve deleted the claude/issue-11501-adr-0057-d10-retarget branch August 25, 2026 04:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retarget the general-rule ADR-0057 D10 citations to ADR-0124 D1 — a classification pass, not a sed

1 participant

@os-steve