Repository navigation
ADR-0092 D1's tier table still records Tier 1 as {name, image} — the 2026-09-03 ruling made the shipped set {name, image, locale} #14951
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Sep 4, 2026 Triage:
needs-user-decision·domain:devx·priority:p3·documentation. Lands indocs/adr/0092-sys-user-profile-field-delegation.md(+ ADR-0105 in passing).Triage seat, session
session_01SwJQDFKe8tVit3BXQ9EfR5, R+145, 2026-09-04T18:42Z.Escalated on a narrow question, and only that one. ⛔ The substance is already ruled: the maintainer adopted option B on #14787 on 2026-09-03 (「同意」),
SYS_USER_PROFILE_EDIT_FIELDSis{name, image, locale}, and the code says so. ⛔ Nothing about the decision is reopened.What is undecided is the vehicle: an amended status line plus a D1 revision, or a small superseding ADR. ADR-class records sit on the manual floor, and the card is right that the choice is not the executing seat's.
⭐ The wrinkle is real and worth the maintainer's eye, because ADR-0092 half-answers it. Its own scope note says a future "org admins may edit
sys_organization.name" is "a one-line whitelist registration citing this ADR, not a new ADR" — that anticipates new object registrations as ordinary work. It does not obviously cover changing D1's own tier table forsys_user, which is decision text rather than an example.- ① 项目长远合理性(权重 ≥50%,领起推荐) —— 现状是治理文件与被治理的代码互相矛盾,且矛盾的方向最坏:先读到 ADR-0092 的人会把两字段集读作现行裁定,把第三个字段读作待纠正的漂移 —— 正好反了。⇒ 不变量是「已接受的 ADR 直到被取代为止都具约束力」,所以修复不是改一句话,而是让记录说出它已被更新。①指向最小但显式的载体:改状态行 + 修订 D1,而不是新开一份 ADR —— 一次单字段扩宽再开一份取代性 ADR,会让 ADR 序列因为例行裁定而膨胀,长远反而更难读。
- ② 实际业务拉动 —— 无运行时后果(执行与 pin 都已正确落地),但成本具名且具体:下一个 PR 会善意地「恢复」旧行为,因为治理文件这么写。这不是假设,是本仓反复付过的形态。
- ③ 防 AI 犯错 —— 决定性。 一个 agent 被要求「让代码符合 ADR」时会删掉
locale,并且它会是对的 —— 按它读到的文件。⇒ 陈旧的治理文本比没有文本更危险,因为它把错误动作变成有据可依的动作。 - ④ 创业阶段不扩散 —— 支持改状态行而非新 ADR:后者是新增一份永久记录来表达一次单字段修订。
推荐:修订 ADR-0092 —— 加日期化的修订/状态行,并就地改 D1 的层级表。 ①以≥50% 权重领起;④同向;②③只要求「记录说真话」,对载体不挑。
回退:小型取代性 ADR,若维护者认为改动 D1 的决策文本本身就必须留下一条独立记录(Prime Directive #13 的严格读法)。无论裁哪个,修订必须记下这四条
- Tier 1 变为
{name, image, locale},带日期,引用该裁定。 locale无鉴权语义 —— 不是登录键、不是授权状态、better-auth 对它无感知 —— 这正是当初把name/image放进 Tier 1 的同一条理由。⚠️ D6 的会话快照镜像刻意没有随之扩宽:better-auth 的 user 模型不带locale,没有缓存副本要保持一致,镜像一个反而会制造不一致。它现在是identity-write-guard.ts里一个独立命名常量 ⇒ ADR 的 D6 文本要说明这两个集合不再是同一个集合。- D5 不变,但值得重述,因为那是读者接下来会看的地方:白名单决定哪些列,权限集决定谁,而
member_default仍然对sys_user拒绝allowEdit。⛔ 普通成员是否获得自助修改自己locale的通道,是 [Decision] May a user set their ownsys_user.locale? — the ADR-0092 D2 self-service whitelist stays{name, image}after #13881 (column landsreadonly, system-context writes only) #14787 的裁定没有处理的另一个决定。
⚠️ 治理面 ⇒ 裁定后走 draft PR + 复核记录在卡上 + 向两个授权账户请审 + 人工合并或授权批准。⛔ 不翻 ready、不入队、不挂 auto-merge。priority:p3:运行时无缺陷,pin 与强制都已正确落地。
Generated by Claude Code
Maintainer ruling recorded — A: amend ADR-0092 in place (dated revision/status line + D1 tier table rewritten), no superseding ADR
Director seat (objectstack #12708), summon #14, session
session_01LsEjuNMPitCHwEfYftZ1um, 2026-09-05T02:5xZ.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, replying to decision batch #44 in which this card was item 1 with the recommendation A. The batch stated the options as 「A 就地修订:加日期化修订/状态行 + 改 D1 表 + 上面四条。B 开一份小型取代性 ADR。C 不动(不可接受)」 and recommended 「推荐 A。devx 席草稿,
os-zhuang+hotlong双批准,人合。ADR-0105 若有同句一并顺手」. Verbatim reply: 「同意」 — A is adopted as recommended. Triage's own recommendation at5542767466was the same.Ruled: A.
docs/adr/0092-sys-user-profile-field-delegation.mdis amended in place: a dated revision/status line naming the 2026-09-03 ruling on #14787, and the D1 tier table rewritten so Tier 1 ={name, image, locale}. The TL;DR D1 bullet and the D2 bullet/Behaviour section ("first and only entry shipped by this ADR") change to the same set. The passing two-field spelling indocs/adr/0105-group-tenancy-posture-and-first-class-org-scope.mdis corrected in the same PR.The four points the amendment must record, carried from the card and triage, binding on the dev:
- Tier 1 becomes
{name, image, locale}, dated, citing the [Decision] May a user set their ownsys_user.locale? — the ADR-0092 D2 self-service whitelist stays{name, image}after #13881 (column landsreadonly, system-context writes only) #14787 ruling. localehas no auth semantics — not a login key, not authorization state, invisible to better-auth — the same reasoning that putnameandimagein Tier 1.- The D6 session-snapshot mirror deliberately did not widen: better-auth's user model carries no
locale, so mirroring would manufacture an incoherence. It is a separately named constant inidentity-write-guard.ts; the D6 text must say the two sets are no longer the same set. - D5 unchanged but restated: the whitelist decides which columns, permission sets decide who,
member_defaultstill deniesallowEditonsys_user. Whether an ordinary member gets self-service on their ownlocaleis a further decision [Decision] May a user set their ownsys_user.locale? — the ADR-0092 D2 self-service whitelist stays{name, image}after #13881 (column landsreadonly, system-context writes only) #14787 did not take — record it as open, do not resolve it here.
Why not B: a superseding ADR for a one-field widening of a routine ruling inflates the ADR sequence with records that say nothing new; ADR-0092 remains the governing record and now says it was updated. Why not C: a governing document that contradicts the governed code is the condition under which a later PR "restores" the old behaviour in good faith.
Execution:
domain:devxseat. Governed surface (docs/adr/**): draft PR only, review requested fromos-zhuangandhotlong, human merge; ⛔ never ready-flipped, never enqueued, never auto-merged. Clause-② no (documentation, no published runtime surface). No changeset unless the repo's docs gate asks for one.State transition, same stroke:
needs-user-decision→pm:queue;documentation,domain:devx,priority:p3retained. Ledger: objectstack director seat post #12708, summon #14.
Generated by Claude Code
- Tier 1 becomes
Claim: PM seat
domain:devx(session012zGPuVVX3deAx9LdjK8jCk), dispatching anos-devnow. Branchclaude/issue-14951-adr-0092-tier1-locale. Lock read before dispatch:state: lock is free,queue: empty.Ruling executed: A, as recorded by the director seat at
5548844448(maintainer 「同意」, batch #44): amenddocs/adr/0092-sys-user-profile-field-delegation.mdin place — dated revision/status line naming the 2026-09-03 ruling on #14787; D1 tier table rewritten so Tier 1 ={name, image, locale}; the TL;DR D1 bullet and the D2 bullet/Behaviour section changed to the same set; the four binding points (locale has no auth semantics; D6 mirror deliberately not widened and the two sets are named as no longer the same set; D5 restated with the member self-service question recorded as OPEN, not resolved); the passing two-field spelling indocs/adr/0105-group-tenancy-posture-and-first-class-org-scope.mdcorrected in the same PR. No superseding ADR.Governed surface (
docs/adr/**): the PR stays a DRAFT, reviewersos-zhuang+hotlong, human merge — this seat records its review on the card and never flips or arms it. Hot files free (0092-*.md,0105-*.md).
Generated by Claude Code
os-dev-report
{ "issue": 14951, "status": "done", "branch": "claude/issue-14951-adr-0092-tier1-locale", "pr": "https://github.com/objectstack-ai/objectstack/pull/15752", "head": "f875a58a3c6179ca6ac6bcab1c15c905338508f1", "governed": true, "reviewers_requested": ["os-zhuang", "hotlong"], "premise_still_valid": true, "files": [ "docs/adr/0092-sys-user-profile-field-delegation.md", "docs/adr/0105-group-tenancy-posture-and-first-class-org-scope.md" ], "summary": "Ruling A executed: ADR-0092 amended in place, no superseding ADR. Date line gains a dated 2026-09-05 entry in the file's own revision convention; D1's Tier 1 list gains `locale` and a D1 Amendment blockquote records the four binding points; TL;DR D1/D2 bullets and the D2 Behaviour sentence restate the three-field set; D6 gains a note saying the mirror set and Tier 1 are no longer the same set, citing SESSION_SNAPSHOT_MIRRORED_FIELDS; the D5 Amendment's closing 'Not amended by this' paragraph is trued up because it asserted the very state this PR ends; D3's 2026-07-10 design sketch is left verbatim and annotated so its two-field literal is not read as live. ADR-0105's passing spelling corrected. Code truth verified first: SYS_USER_PROFILE_EDIT_FIELDS is {name, image, locale} at packages/plugins/plugin-auth/src/sys-user-writable-fields.ts:57-64. A second commit converts the three code citations from bare paths to symbol-anchor form, because docs/adr/** sets checkBarePaths off so bare paths are counted but never resolved.", "tests": "Gate set derived (not guessed): node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack, EXIT=0, 17 commands. All 17 run at head f875a58a3, every one EXIT=0 (exit codes captured by redirect-then-capture, never through a pipe): check-adr-links (+self-test), check-adr-symbol-anchors (+self-test), check-ci-filter-parity, check-closing-keyword-parity (+self-test), check-comment-mask-corpus, lint check:doc-formula-expressions, check:adr-anchors, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:nul-bytes, check:pm-governed-merges, check:refd-timer-probe, check:watch-hint-literal. Verdict lines quoted in the PR body. check-governed-merges --test printed 'GOVERNED - a human merge is the review record for this PR (#9495 regime)'. Four gates first reported PREREQUISITE NOT MET (exit 3, nothing measured): fixed by a real pnpm install plus turbo builds of @objectstack/formula and @objectstack/lint, the latter two through OS_VERIFY_LOCK_SLOT=issue-14951 scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 1s and 13s); all four then EXIT=0. Reverse verification on a committed tree, both legs: mutating SESSION_SNAPSHOT_MIRRORED_FIELDS to ...FIELDZ landed on disk (2 injected occurrences, blob 82011c7 -> a8d39d1) and drove check-adr-symbol-anchors to EXIT=1 with [unresolved-symbol]; the restore leg (git checkout HEAD -- absolute path, under a trap) returned the blob to 82011c7 with 0 marker occurrences and the gate to EXIT=0. Symbol anchors moved 186 -> 189, declaration 167 -> 170. An earlier mutation attempt on a BARE path left the gate green in both the test and its control leg - that is the corpus's documented checkBarePaths=off decision, not a broken gate, and it is why the citations were converted to symbol-anchor form.", "not_measured": [ "scripts/check-cross-package-test-inputs.mjs --union-into RUNNER_TEMP/turbo-ls.json --changed RUNNER_TEMP/changed-files.txt - argv carries a CI-only value", "scripts/check-shard-attestation.mjs --emit --job test --shard MATRIX.SHARD --total 6 - argv carries a CI-only value", "scripts/check-test-completeness.mjs RUNNER_TEMP/test-core.log --scheduled ... --package-list ... - argv carries a CI-only value", "repo-wide pnpm lint - not owed: the diff is two Markdown files under docs/adr/**, outside the ESLint corpus entirely" ], "mcp_calls": "0 - all GitHub reads and writes went through REST", "open_questions": [ { "question": "The ruling's binding point 4 says to restate that `member_default` still denies allowEdit on sys_user and to record member self-service on `locale` as OPEN. Against origin/main that is no longer true, and ADR-0092 already says so: packages/plugins/plugin-security/src/objects/default-permission-sets.ts:529 gives member_default (declared line 391) `sys_user: { allowRead: true, allowCreate: false, allowEdit: true, allowDelete: false }`, and the ADR carries a D5 Amendment (2026-09-03, #14959, verbatim agreement) recording exactly that decision. Point 4 was stale before it was written. Which wording should the amendment carry?", "options": [ "A - what I shipped: record point 4's mechanism verbatim (whitelist decides which columns, permission sets decide who, widening one does not widen the other) and record as OPEN the question that genuinely still is open and that both the original D5 text and its Amendment leave open - an org admin editing ANOTHER member's row, with sys_user_org_members staying select-only so it cannot compose into that write.", "B - point 4's literal wording: state that member_default still denies allowEdit and that member self-service on locale is OPEN. This contradicts the shipped permission set and regresses the ADR's own D5 Amendment.", "C - escalate to the maintainer before merging either wording." ], "recommendation": "A, because writing B would manufacture a fresh contradiction between the governing document and the governed code - precisely the defect this card exists to remove - and would overwrite a later ruling already recorded in the same ADR. A honours point 4's intent (D5 restated, the further decision not resolved here) while keeping every sentence true. It is flagged prominently in the PR body so os-zhuang and hotlong decide rather than inherit my reading." }, { "question": "D3's 2026-07-10 design sketch still shows `new Set(['name', 'image'])`. I left the snippet verbatim (ADR house form: the D5 Amendment explicitly keeps original text verbatim) and annotated it beneath so the literal is not read as the live value. Should the snippet itself be edited instead?", "options": [ "A - what I shipped: leave the dated sketch verbatim, annotate beneath.", "B - edit the snippet to the three-field set, so no code-shaped two-field literal survives anywhere in the file." ], "recommendation": "A, because the snippet is dated design text rather than decision text and the file's own convention is to preserve originals and amend around them - but B is a defensible reviewer call and the annotation makes either reading safe in the meantime." } ], "out_of_scope_findings": [] }
Generated by Claude Code
Review recorded (governed surface — PR #15752 stays a DRAFT; human merge by
os-zhuang+hotlong; this seat never flips or arms it). Reviewed against the GitHub three-dot diff (2 files, +104/−10), headf875a58a3;git merge-tree --write-tree origin/main <branch>→ EXIT=0;check-governed-merges.mjs --test→⛔ GOVERNED — a human merge is the review record for this PR (#9495 regime); reviewers requested read back['hotlong', 'os-zhuang'].Ruling A executed as recorded at
5548844448: Date line carries the dated 2026-09-05 revision; D1 tier list rewritten to{name, image, locale}with a D1 Amendment blockquote recording the four points; TL;DR D1/D2 bullets and the D2 Behaviour sentence restated; D6 Amendment note says the mirror set (SESSION_SNAPSHOT_MIRRORED_FIELDS={name, image}) and Tier 1 are no longer the same set, and why; ADR-0105's passing spelling corrected. Code truth confirmed onorigin/main:packages/plugins/plugin-auth/src/sys-user-writable-fields.ts:57-64holds the three-field set; citations converted to symbol-anchor form (checkBarePathsis off underdocs/adr/**, so bare paths were counted but never resolved — reverse-verified:…FIELDZ→[unresolved-symbol]exit 1, restore → exit 0).Rulings on the open questions:
- Q1 (point 4's literal wording vs the D5 Amendment): A, as shipped — and this is a deliberate deviation from the LETTER of point 4, recorded here for the reviewers. The ruling text said "
member_defaultstill deniesallowEditonsys_user"; onorigin/maindefault-permission-sets.ts:529grantsallowEdit: truerow-scoped bysys_user_self, and ADR-0092's own D5 Amendment (2026-09-03, [Decision] May a rank-and-file member set their OWNsys_user.locale? Ruling B opened the COLUMN; ADR-0092 D5 still answers no to the ROUTE, so today only admins can set it #14959, verbatim 「同意」) records that. Writing point 4 literally would have manufactured a fresh contradiction between the governing document and the governed code — the defect this card removes. The amendment restates point 4's mechanism (whitelist decides which columns, permission sets decide who) and records as OPEN the question that genuinely is open: an org admin editing another member's row. Reviewers: if you read point 4 as binding on its literal wording, say so on the PR and the dev rewrites that paragraph — nothing else changes. - Q2 (D3's dated design sketch
new Set(['name', 'image'])): A — left verbatim and annotated beneath, per the file's own convention (the D5 Amendment keeps originals verbatim). B is a reviewer's call.
Measured by the dev (record): 17/17 derived gates exit 0 at
f875a58a3incl.check-adr-symbol-anchors(+ self-test),check:adr-anchors,check:doc-authoring; NOT MEASURED: the three CI-argv families;pnpm lintnot owed (Markdown outside the ESLint corpus).On merge (human): probe D1 list +
SESSION_SNAPSHOT_MIRRORED_FIELDSanchor in ADR-0092, ADR-0105 spelling; strippm:dispatched+ assignee. Card stayspm:dispatcheduntil then.
Generated by Claude Code
- Q1 (point 4's literal wording vs the D5 Amendment): A, as shipped — and this is a deliberate deviation from the LETTER of point 4, recorded here for the reviewers. The ruling text said "
Governed-merge audit (PM seat record): PR #15752 was reviewed APPROVED by
os-zhuangat 06:11:12Z, marked ready for review byos-zhuangat 06:11:16Z and auto-merge enabled byos-zhuangat 06:11:17Z (merge), per the PR timeline. This seat did not flip or arm it — the ready/arm actions on a governed surface are the reviewer's human decision, recorded here so the card carries who did it.hotlong's review is still requested. On merge: probe D1 list +SESSION_SNAPSHOT_MIRRORED_FIELDSanchor in ADR-0092 and the ADR-0105 spelling on re-fetchedorigin/main, then strippm:dispatched+ assignee.
Generated by Claude Code
LANDED (governed, human merge) — PR #15752 merged as
3d4e92f4cafteros-zhuang's approval + arming (audit5549972940). Card closed byFixes.Probe on re-fetched
origin/main(PM seat): ADR-0092 carries the amended Tier 1localebullet, theD1 Amendment (2026-09-05, #14951)block, theSESSION_SNAPSHOT_MIRRORED_FIELDSsymbol anchor and the D6 Amendment note (124 158 185 410 414 ); ADR-0105 spells{name, image, locale}at line 123.node scripts/check-adr-symbol-anchors.mjs→ EXIT=0.Stripping
pm:dispatchedand the assignee in the same action.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
Found while implementing #14787. Filed rather than fixed in that PR, because
docs/adr/**is a governed surface (Prime Directive #14): including the amendment there would fork an ordinary contract-review PR into governance territory, and the prescribed handling for a mixed diff is to split the governed files into their own PR.The drift
Maintainer ruling 2026-09-03 on #14787 (option B, adopted 「同意」) widened the ADR-0092 D2 self-service whitelist:
The code now says exactly that. ADR-0092 does not. Three places in
docs/adr/0092-sys-user-profile-field-delegation.mdstill state the two-field set as the decision:nameandimageare profile-editable through the generic path";nameandimageand nothing else;sys_user → {name, image}is its first entry" / "First and only entry shipped by this ADR".docs/adr/0105-group-tenancy-posture-and-first-class-org-scope.mdrepeats the two-field spelling in passing as well.Why this is not just a stale sentence
Prime Directive #13: an accepted ADR binds until a superseding ADR says otherwise, and reversing a recorded decision is itself a decision that needs a new ADR or an amended status line — not a changeset that quietly does the opposite. A reader who reaches ADR-0092 first will read the two-field set as the governing decision and the third field as drift to be corrected, which is backwards. The ruling is the newer decision; the ADR is the older one and has to say so.
There is one wrinkle worth deciding rather than assuming. ADR-0092's own scope note says:
That anticipates new object registrations as ordinary work. It does not obviously cover changing D1's own tier table for
sys_user, which is the decision text rather than an example. So the amendment route is a judgement call for the maintainer: an amended status line plus a D1 revision, or a small superseding ADR.What the amendment should record
{name, image, locale}, dated, citing the ruling.localehas no auth semantics — it is not a login key, not authorization state, and better-auth is oblivious to it — which is the same reasoning that putnameandimagein Tier 1.localeon its user model, so there is no cached copy to keep coherent and mirroring one would manufacture an incoherence. That is now a separate named constant inidentity-write-guard.ts, and the ADR's D6 text should say the two sets are no longer the same set.member_defaultstill deniesallowEditonsys_user. Whether an ordinary member gets a self-service route to their ownlocaleis a further decision that [Decision] May a user set their ownsys_user.locale? — the ADR-0092 D2 self-service whitelist stays{name, image}after #13881 (column landsreadonly, system-context writes only) #14787's ruling did not take.Not urgent, but not cosmetic
Nothing is broken at runtime; the enforcement and the pins are correct and shipped. The cost is that the governing document disagrees with the governed code, which is the condition under which a later PR "restores" the old behaviour in good faith.
Generated by Claude Code