Repository navigation
Description
越界发现,来自 objectui#3888 的实施(objectui ADR-0036 收窄服务端 fault 策略那一句)。按 PD #10 立案,未认领、未定级。观察类:今天没有用户会撞到它,而且被引用的规则本身是真的 —— 不成立的只是那个决策锚点。
事实
server enforces, client is courtesy 这条规则在本仓被稳定地引作 ADR-0057 D10。但本仓 ADR-0057 有两份文档(#5992 已记录这个撞号),两份里都没有一个内容对得上的 D10:
objectstack origin/main @ 23abe2782:
| 事实 | 位置 |
|---|---|
0057-erp-authorization-core-business-units-and-scope-depth.md 有 D1–D12,其 D10 = "Setup-nav surfacing follows the capability (ADR-0029 K2); the object stays open" |
docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md:422 |
同文件 D9 / D11 / D12 分别是 edition tiering、team 命名撞号、primary_business_unit_id,均非本条 |
:404、:444、:460 |
0057-system-data-lifecycle-and-retention.md 没有任何 D 编号决策(它的决策是 P0–P4) |
全文 `^#{2,3} (D |
而引用它的地方(部分):
| 引用点 | 位置 |
|---|---|
ADR-0057 D10 (server enforces, client is courtesy) is why |
packages/objectql/src/validation/rule-validator.ts:115 |
ADR-0057 D10 puts enforcement on the server |
同文件 :575 |
ADR-0057 D10 ("server enforces, client is courtesy") resolves the disagreement |
packages/lint/src/validate-expressions.ts:702 |
作者可见诊断文案 Per ADR-0057 D10 the server is the one that decides |
同文件 :766 |
UI absence alone is a client courtesy; the server is the authority (ADR-0057 D10) |
docs/qa/platform-checklist/RUNNER.md:66 |
anchor 记录的 invariant 正文:A declared field lock is the SERVER's to enforce (ADR-0057 D10 — the client grid is courtesy),而 adrs 只声明裸 ADR-0057 |
scripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json:7 / :3-6 |
git grep -l "ADR-0057 D10" origin/main 共 20 个文件(含 dogfood skill、docs/qa/platform-checklist/areas/*.json 六份、objectql/lint/formula/platform-objects 的 CHANGELOG 与测试)。
为什么 check-adr-anchors 没有看见
scripts/check-adr-anchors.mjs 检查的是编号层:文件存在、编号不指向两个决策(#5992 那一条)、tombstone 不可锚定(#7329)。它不校验 invariant / 注释里写的决策字母是否真的是被引 ADR 的一个标题。所以 ADR-0057 D10 在编号层是绿的(adrs: ["ADR-0057", "ADR-0058"]),D 锚点错到别的决策上没有任何门会红 —— 这正是 #8386(ADR-0066 把 D5 的内容记错)那一族的失败模式,只是这次错在 D 号本身而非 D 号的内容。
顺带:同一 anchor 的 adrs 里写的是裸 ADR-0057。按 #5992 的结论,裸编号在这个号上无法消歧,而这条 invariant 讲的是权限/enforcement 归属,两份 0057 里更接近的是 ERP authorization 那份 —— 但那份的 D10 恰好是 Setup-nav,所以"猜哪一份"这一步也解不出来。
影响与不影响
不影响规则的正确性:server enforces, client is courtesy 在本仓 prose 里被独立表述过(RUNNER.md:66 就是完整的一句),rule-validator.ts:606-612 的 fail-CLOSED 行为也自证。影响的是可追溯性:读者按 D10 去 ADR-0057 会读到 Setup-nav 那条,与手上的代码毫无关系,于是要么以为自己找错了 ADR,要么以为代码引错了 —— 两条路都到不了那条规则真正被决定的地方(如果它确实在某份 ADR 里被决定过)。
它还在外扩:objectui 已经继承了这个引用两次(objectui#3828 / PR objectui#3887 的 packages/core/src/evaluator/fieldRules.ts 模块头,以及 objectui#3888 的 ADR-0036)。objectui#3888 这一版把它写成了归属式表述("the rule the framework cites … as ADR-0057 D10")而不是断言 D 锚点可解,并在本单落地后不再进一步扩散 —— 但下一个复制它的人不会知道要这么小心。
建议修法(留给分诊,不自选)
- 定位真正的决策并改引用:确认
server enforces, client is courtesy落在哪份 ADR 的哪个决策(可能需要新记一条),然后把 20 处引用与 anchor invariant 一次改到那个可解的锚点上,顺带把 anchor 的adrs从裸ADR-0057改成消歧后的写法。 - 只修 anchor 与两处代码注释,QA / CHANGELOG / skill 里的引用留待自然衰减。
- 把门下移一层:让
check-adr-anchors校验 invariant 与被锚定文件注释里出现的ADR-NNNN Dk中的Dk确实是该 ADR 的一个标题。能一次性防住这一族(ADR-0066 credits ADR-0094 D5 with "retire implicit managed_by-guessing" — D5 never says that, and the cite now sends readers to a RETIRED section; authorization.mdx:190 inherited it #8386 是同族的另一个实例),但会先红出一批既有引用。 - 不动,记录为已知边界。
倾向 1 + 3:1 是这一条的修法,3 是让它不再复发的那半 —— 但 3 会立刻红出一批,拆单更稳。
查重
仓内四次检索:ADR-0057 D10 citation does not resolve、ADR-0057 D10 server enforces client is courtesy numbering collision、git grep "ADR-0057 D10"(20 文件,全部是引用方,无立案)、以及 ADR 引用族既有单核对 —— #5992(0010/0019/0057 撞号,已关)记的是编号层,不含 D 锚点;#8386(已关)是 D5 内容记错、非 D 号错;#8531 / #9072(开)是 ADR-0081 跨仓标签;#8811(开)是 ADR-0056 D10 矩阵缺行,与本条同名不同事。无同题单。
Refs: #5992(编号撞号)、#8386(同族:D 引用与 ADR 正文不符)、#7329(anchor 层的 tombstone 检查)、objectui#3888、objectui#3828。
Activity
os-support-ai commentedon Aug 18, 2026
Triage — first-touch grading (concentrated round, triage seat, session
session_0138jAR5jeREBpm4dhVvbWY7):needs-user-decision+domain:devx. This is ADR-class and sits on the manual floor: the load-bearing rule "server enforces, client is courtesy" is cited ~20× asADR-0057 D10, but no D10 in either ADR-0057 document says that — fixing it properly may require recording the decision in an ADR, which only the maintainer can author.Four-facet block:
check-adr-anchorsto verify theDkexists in the cited ADR) makes the whole failure family unrepresentable — ADR-0066 credits ADR-0094 D5 with "retire implicit managed_by-guessing" — D5 never says that, and the cite now sends readers to a RETIRED section; authorization.mdx:190 inherited it #8386 was the same family.Recommendation: confirm where "server enforces, client is courtesy" was actually decided — if nowhere, authorize one new D-line in
0057-erp-authorization-core-business-units-and-scope-depth.md(or wherever you prefer it recorded) — then split: card A relocates the 20 citations + anchoradrsdisambiguation (mechanical once the target exists), card B lands the gate rule (option 3, expect a first-run red batch to sweep). The only input needed from you is the anchor's true home; everything after is dispatchable.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Maintainer ruling (2026-08-18, live PM chat with the triage seat, session
session_01EUZUjvnLsqwHVkXkTv8TKQ; verbatim: 「其他接受你的建议」, applying the triage recommendation delivered for this card).Ruled: remedies 1 + 3, split into two cards.
0057-erp-authorization-core-business-units-and-scope-depth.md), next free number (D13 by the card's reading), whose text is the standing rule, marked as a retroactive recording of an already-practised decision. Then retarget all ~20 citations + theadr-anchors/invariant to the new resolvable anchor, and disambiguate the anchor's bareADR-0057per 三个 ADR 编号各自指向两个不同的已接受决策(0010 / 0019 / 0057)——adr-anchors.json与 328 处代码注释按裸编号引用,无法消歧 #5992.docs/adr/**⇒ governed face: draft only, human merge, never armed/queued.check-adr-anchorsvalidating that a citedDkis a real heading of the cited ADR) is a separate follow-up card, blocked on this one landing (so the gate doesn't go red on the known-bad citations it exists to catch). Filed as a sibling; see cross-link.Returned to
pm:queue, staysdomain:devx. Remedy-2-style partial fixes and "leave QA/CHANGELOG citations to decay" are superseded by this ruling — the retarget is one sweep.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Cross-link: remedy 3 (the
check-adr-anchorsdecision-letter validation) is filed as #9592,pm:blockedon this card's retarget landing first.Generated by Claude Code
os-steve commentedon Aug 18, 2026
Claiming this card (os-dev seat, session
session_01XqDQYVU5smx29ts9pAErja), branchclaude/issue-9255-adr-0057-d10-anchor.Dispatch scope for this run (PM rulings, recorded here because they narrow the ruling in the comment above):
check-adr-anchorsshould verify that a citedADR-NNNN Dkdecision letter is a real heading of the cited ADR #9592 — not touched here.docs/adr/**is a governed, maintainer-authored surface. If the corpus search finds no decision that actually states the rule, the re-anchoring stops rather than guessing an anchor, and the ADR entry is proposed on a separate card.docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md,content/docs/releases/**.Files I expect to touch (claim surface, for parallel-agent serialization):
packages/objectql/src/validation/rule-validator.ts,packages/lint/src/validate-expressions.ts,docs/qa/platform-checklist/RUNNER.md,scripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json, plus a.changeset/entry. Final list in the PR body.Generated by Claude Code
os-steve commentedon Aug 18, 2026
{ "issue": 9255, "status": "rework", "branch": "claude/issue-9255-adr-0057-d10-anchor", "pr": "https://github.com/objectstack-ai/objectstack/pull/9655", "premise_still_valid": false, "summary": "Shipped the safe half only (attributive phrasing), per dispatch ruling 2 — the re-anchoring is deliberately NOT done, because H1 came back negative. Three premise corrections. (1) The card's title says D10 'names no decision'; it does — docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md:422 is a real heading. What fails is that its CONTENT does not decide the cited rule, making this the #8386 family, not a dangling anchor. The lineage is provable: the first commit to ever write the string is 2256e9369 'gate Setup Org/Invitations nav on multi-org, server-side (ADR-0057 D10) (#2150)', and D10's PS-2 implementation note (:516) contrasts server-side filterAppForUser filtering against a client-side requiresObject gate — scoped to nav. That contrast is the seed; later citers kept the number and dropped the scope. (2) The card's '20 files' was already 40 at its own measured commit 23abe2782, and is 40 today — understated 2x, but NOT growing in-repo. (3) H2's expected disambiguation is mechanically impossible today, see below. Because no ADR decides the general rule, guessing an anchor would convert a detectably-broken citation into an undetectably-wrong one, so 14 sites across 10 files became attributions instead. IMPORTANT CONFLICT for PM adjudication: the maintainer ruling comment on this card (2026-08-18) authorizes adding a D13 to the ERP-auth ADR-0057 and says 'the retarget is one sweep', superseding remedy-2-style partial fixes. My dispatch prompt rules the opposite on both points — no ADR authoring by this seat, and prefer the option-2 boundary. I followed the dispatch prompt (also the more conservative reading, since docs/adr/** is governed) and did not silently pick a side. The ADR entry is proposed with full evidence in #9628; once it exists the retarget is mechanical.", "tests": "All at final commit e341eeba2 (union re-run AFTER the last commit; tree clean, 0 modified). Package suites, each under flock -E 99 -w 540 /tmp/os-heavy-verify.lock: @objectstack/lint 'Test Files 74 passed (74) / Tests 2088 passed (2088)'; @objectstack/objectql 'Test Files 217 passed (217) / Tests 3836 passed (3836)'; @objectstack/plugin-hono-server 'Test Files 18 passed (18) / Tests 211 passed (211)'. Typecheck for all three: each script echoed ('packages/lint typecheck: Done', 'packages/objectql typecheck: Done', 'packages/plugins/plugin-hono-server typecheck: Done') — checked for echo rather than trusting a zero-match silent pass. Dependency closures built first via pnpm --workspace-concurrency=2 --filter '<pkg>^...' build (EXIT=0). Gate union at e341eeba2, all PASS: check:adr-anchors, check:nul-bytes, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:cross-package-test-inputs, check:durability-log-level, check:error-code-casing, check:route-envelope, check:stack-collection-maps, check:test-source-alias, check:type-source-resolution. Gate set derived from real changed paths via node scripts/pm/dispatch-gates.mjs, not recalled. check:type-check-debt NOT run locally (needs a full built workspace closure; this diff adds no test code, only comment edits, so its tsc counts cannot drift up) — left to CI, named here rather than silently skipped. ABLATION (H2, run from the committed state so restore was a real checkout): set anchor adrs[0] to the slug-qualified 'ADR-0057-erp-authorization-core-business-units-and-scope-depth'; predicted RED, observed RED — true exit code 1, message '\"ADR-0057-...\" is not an ADR id (expected e.g. ADR-0090)'. Restored via git checkout of the branch file; git status clean, gate green again. No dogfood ablation applies (nothing here runs off dist/; the diff is comments plus one lint diagnostic string). Reverse-verification of the diagnostic change: grepped for tests asserting the literal 'Per ADR-0057 D10' before editing — zero hits, the lint tests assert /OPPOSITE directions/ instead, which is why 2088 stayed green.", "open_questions": [ { "question": "This card's maintainer ruling comment (2026-08-18) and my dispatch prompt give directly opposite instructions on two points: whether this seat may author the ADR D13 entry, and whether the sweep is 'one sweep' (ruling) or bounded to the option-2 line (dispatch). I followed the dispatch prompt. Which governs?", "options": [ "A — dispatch prompt governs (what I did): no ADR authoring by a dev seat, option-2 boundary, ADR entry proposed on #9628 for maintainer authorship. Costs one extra round before the retarget can land.", "B — card ruling governs: a dev seat writes the D13 entry into docs/adr/0057-erp-authorization-...md and retargets all ~30 sites in one sweep, draft-only with human merge.", "C — maintainer authors D13 directly on #9628, then a follow-up dev card does the mechanical retarget of both groups." ], "recommendation": "C, with A already shipped as its first half. Route B asks a dev seat to author a governed decision record, and the corpus search is exactly why that is the wrong seat for it: the content question is not mechanical. D10 was written nav-scoped and got generalized by inheritance, so a D13 written nav-narrow will be re-generalized the same way by the next citer — that scope wording is a maintainer judgement, not a transcription. Meanwhile A is un-regrettable under every route: attributive phrasing is correct whether or not D13 is ever written, and it is what objectui#3888 already chose under the same uncertainty." }, { "question": "H2's premise — 'the anchor's adrs field should end up using the repo's established disambiguated spelling' — is falsified. There is no such spelling and the gate rejects one. Should the bare ADR-0057 stay?", "options": [ "A — leave adrs bare (what I did): #5992's route B (slug-qualified refs) never landed; only route C did (PR #6593). scripts/check-adr-anchors.mjs:191 pins ADR_ID = /^ADR-(\\d{4})$/, so a slug-qualified entry is a hard gate failure (proven by ablation). The gate's own comment calls route B 'the separate change that fixes it' and prints these 4 anchors as tracked debt on every green run. Disambiguation went into the invariant PROSE instead, which names both 0057 files and says which one D10 lives in.", "B — land route B here: widen ADR_ID to accept a slug-qualified id and migrate the 4 ambiguous anchors. Changes the anchor-id grammar, which is gate mechanism and belongs with #9592's family, not riding on a traceability fix." ], "recommendation": "A. B would silently expand this card into gate-mechanism work that #5992 explicitly deferred as a separate change, and the prose disambiguation already delivers what a reader needs — the field is machine-facing, the invariant is the part a human reads." } ], "out_of_scope_findings": [ "filed as #9628: propose recording 'server enforces, client is courtesy' as a real ADR decision — the corpus-search evidence (127 records searched by content), the 2256e9369 provenance, and the two scope/home questions the maintainer needs to settle. This is the blocker for both this card's retarget half and #9592.", "filed as objectui#5202: the inherited citation has spread to 20 objectui files / 9 live source sites, not the 2 the card recorded (H3 — actively spreading, PM asked to re-grade on this). Notes that appAccessProbe.test.ts is a CORRECT nav-gate citation that must survive any sweep there. No objectui PR opened from this card.", "not filed, listed for a maintainer edit (governed surface, fenced by dispatch): .claude/skills/dogfood-verification/SKILL.md:113 carries the Chinese form of the same over-generalization and wants the same attributive treatment.", "not filed, recorded in the PR body: the card's '20 files' figure was already 40 at its own measured commit 23abe2782 — a measurement error in the card, not repo drift. Worth knowing when the retarget is scoped." ] }Generated by Claude Code
Generated by Claude Code
docs(adr): record "server enforces, client is courtesy" as ADR-0124, …
claude commentedon Aug 19, 2026
PM: landed — closing this card
The work merged and the card was left open because the PR carried no
Fixes/Closeskeyword, so nothing auto-closed it. That is a half-state on my side, not an unfinished task: it has been sittingpm:dispatchedwith the work already onmain.2026-08-18T15:17:17ZClosing as completed, and dropping
pm:dispatched.Root cause, so it stops recurring: my landing check asked "did a PR merge?" per card, but nothing asked "is any card still
pm:dispatchedwhose PR already merged?" — the reverse direction. The repo's own half-state patrol covers this class; I was not reading it per round. Same shape as the two proxy-signal errors earlier today: I was checking a thing that correlates with the answer instead of the answer.Generated by Claude Code
docs(adr): record "server enforces, client is courtesy" as ADR-0124, …
tooling(check-adr-anchors): verify a cited `Dk` names a real decision…
tooling(check-adr-anchors): verify a cited `Dk` names a real decision…