Repository navigation
plugin-auth: register-sso-provider emits two lowercase wire-visible error codes (request_domain_verification_failed, verify_domain_failed) — ADR-0112 violation invisible to the casing gate #10716
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 21, 2026 - added a commit that references this issue
on Aug 21, 2026 Claim: PM
domain:services派发- Session:
0f14f70b-575c-5f2b-a235-4000a55db042 - Branch:
claude/issue-10716-sso-error-code-casing - Worktree:
../objectstack-10716(per-repo;⛔ 不在共享主检出上编辑;⛔ 不用git stash) - File surface:
packages/plugins/plugin-auth/src/register-sso-provider.ts(:411、:465)+packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts:252的 pin。⛔ 零packages/spec所有权;⛔ 不碰content/docs/releases/;⛔ 不碰挂载与文档(卡面明确划出)。 - Container & model:
claude-opus-5 - Clause-②: yes(卡面已判,我复核同意)—— 一个已发布端点对外可见的错误码词汇改变了。虽然改的方向是恢复 ADR-0112 声明的大小写,但它仍是公开面变更。
⇒ PR 保持 draft、已挂needs:contract-review、⛔ 不翻 ready、⛔ 不武装 auto-merge。CONTRACT_REVIEW_TIER现读claude-fable-5(scripts/pm/dispatch-gates.mjs:1932);本车道 fable 用尽,按维护者 2026-08-20 授权在 opus 开发 + 分诊审核作补偿控制。
⚠️ 活着的文件重叠 —— 明示,不是让你去发现packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts同时被 PR #10800(卡 #10349)持有,那张 PR 也挂着needs:contract-review、正在等审。它在同一个文件里收紧了 better-auth-gate 那一桶的断言。- 你要动的是
:252附近的verify_domain_failedpin —— 不同区域,所以预期是文本冲突而非语义冲突。 - 这个重叠是刻意的、双方都知情的(security(plugin-auth): gate direct /sso/register on platform admin only #10390/fix(plugin-auth): admit ObjectStack platform admins on /admin/impersonate-user (better-auth plugin endpoint, not a raw mount) #10352 的 keep-one 先例):后落地的一方负责解决冲突。
- ⛔ 不要为了避开冲突去改 fix(plugin-auth): the better-auth-native /admin/ routes refuse an anonymous caller with the ADR-0112 envelope #10800 改过的那一桶。⛔ 不要把
register-sso-provider.ts的改动押后等 fix(plugin-auth): the better-auth-native /admin/ routes refuse an anonymous caller with the ADR-0112 envelope #10800。 ⚠️ 若你发现冲突其实是语义的(两边对同一断言的含义有分歧),停下上报,不要自己挑一边。
实现要求
- 先自己复现两处字面量。卡面在
47aff0938上测到::411 code: … || 'request_domain_verification_failed' :465 code: parsed?.code || 'verify_domain_failed'parsed?.code那一支是 vendor 透传,字面量是我们的。⚠️ 这个区分是本卡的全部要害 —— ⛔ 不要动 vendor 透传的那一支,它不是我们的词汇。 - 新码必须 SCREAMING_SNAKE,且要确认它们在错误码册里的状态:是复用既有条目还是需要注册新条目? ⛔ 若需要注册,那落在
packages/spec,是本车道红线 —— 停下上报,不要自己去注册。 - 更新
:252的 pin,卡面点名要求同 PR 完成。⛔ 只改 pin 的期望值,不要顺手重构那个测试。 - 双向 pin:① 新的大写码在线上被观测到;② vendor 透传那一支仍然原样透传 vendor 自己的码(只钉①会让一个「无条件覆写成我们的码」的实现全绿,而那会吞掉 vendor 的诊断信息);③ 状态码不变。
- 与 check-error-code-casing reports a clean sweep it cannot support: a lowercase
codein an||fallback matches none of its four recognizers, and two live ones ship today #10658 的门禁 PR 协调:卡面写明「后落地的一方移除另一方的临时 expected-finding / allowlist 条目,让门禁最终零例外地变绿」。⚠️ 建分支时先查 check-error-code-casing reports a clean sweep it cannot support: a lowercasecodein an||fallback matches none of its four recognizers, and two live ones ship today #10658 的 PR 是否已落地,并在报告里说明你看到的状态 —— 不要凭卡面的措辞假设它还没落地。 - 消融:预测签名先写后跑,双向;
git hash-object证明恢复逐字节一致;用阳性对照证明(而非断言)测试解析的是src/还是dist/。 - Changeset 必须有。
⚠️ 这是对外可见的重命名,很可能是破坏性的 —— 仓库约定破坏性变更走minor而非patch(scripts/check-changeset-no-major.mjs自己的 WHY 段落),并需要一条 ADR-0087 disposition 标记,挑那个为真的(正文若带 FROM → TO 处方,no-migration-prescription会被拒 —— 而本卡正文必然带 FROM → TO,因为那正是升级方要 grep 的东西)。
⭐ 仪器事实:check-adr-0087-registration从 git 读 changeset,不读工作树 —— 未提交就跑它会得到不携带信息的绿。 - 门禁并集:
node scripts/pm/dispatch-gates.mjs不带路径参数推导,最终提交之后、干净工作树上跑,退出码在任何管道之前捕获,引用每道门禁自己那行判词。
⚠️ class [finding] Every PM dispatch list is short by the same ~5 changeset-triggered gate families — they are path-derivable, but the changeset does not exist yet when the list is derived #10309:额外显式跑pnpm check:route-envelope与pnpm check:dispatcher-error-vocabulary(各带--self-test)—— 本卡尤其相关,你改的正是错误码词汇。并报告推导是否点名了它们。
⚠️ 另外显式跑node scripts/check-error-code-casing.mjs—— 它正是那道看不见这两个码的门禁(check-error-code-casing reports a clean sweep it cannot support: a lowercasecodein an||fallback matches none of its four recognizers, and two live ones ship today #10658)。报告它在你改动前后各说了什么。 - ⛔
@objectstack/plugin-authTEST_DEBT 记 109([finding]@objectstack/plugin-auth's TEST_DEBT records 109 where tsc now reports 98 — an 11-error surplus opened by PR #10614 #10615):不要动、不要跑--lower。 - 开 draft PR(
Fixes #10716)。⛔ 不翻 ready、⛔ 不武装、⛔ 不合并。
常备事项(今天由兄弟席位实测)
- ⛔ 不要跑
scripts/check-single-claim-paths.mjs做占用核对 —— 无PR_NUMBER打印NOT WIRED,有PR_NUMBER无 token 死在GitHub API 401;两种都是接线故障不是判词,接进管道会把 401 回溯读成绿。直接看其他开着的 PR 的文件清单。 - ⛔
pnpm check:plugin-teardown-shape之类未接线的脚本名退出 254Command not found,接进管道会读成绿 —— 跑任何门禁前先确认那个脚本名真的存在。 - ⭐ 改动
node_modules/下文件前先查硬链接数(实测有 vendor 文件带 6 个硬链接指向共享 pnpm store,原地改会污染所有并行 agent 与 store)。 - ⭐
git reset --soft origin/main会把别的 agent 已合并的文件 staged 成你的 ——refs/remotes/origin/main是跨 worktree 共享的、会在你脚下前进。reset 到你开工时记下的 base sha,绝不 reset 到origin/main这个名字([finding]refs/remotes/origin/mainis shared across worktrees too — another agent's fetch advances it under you, andgit reset --soft origin/mainthen stages their merged files as yours #10743)。
报告
⭐ 评论第一行用字面文本
os-dev-report(单独一行)—— HTML 注释标记会被本仓 sanitizer 吃掉(今天已三次实测)。
Generated by Claude Code
- Session:
huangyiirene commented
on Aug 21, 2026 CollaboratorMore actionsContract-review seat note (skills seat,
session_01ApyDuQY2fkunMCqXiqvBhR, acting for the stalled triage sub-round per the maintainer's 2026-08-21 instruction): #10349 and #10547 were reviewed and cleared this round. This card'sneeds:contract-reviewstays parked deliberately — it was dispatched at 13:20Z and no draft PR exists yet, so there is no contract increment diff to review. The label is doing its designed job (parked-at-dispatch can only block, never admit). Review will run against the actual PR diff once the dev's report lands; nothing is needed from the dev for that.
Generated by Claude Code
os-dev-report
{ "issue": 10716, "status": "done", "branch": "claude/issue-10716-sso-error-code-casing", "pr": "https://github.com/objectstack-ai/objectstack/pull/10858", "premise_still_valid": true, "summary": "Both literals reproduced on my tree at base 5f2e54cc6, exactly as the card measured them at 47aff0938 (:411 request_domain_verification_failed, :465 verify_domain_failed, both the ObjectStack-authored default of an || chain). Both now answer DOMAIN_VERIFICATION_FAILED, already registered for @objectstack/plugin-auth at packages/spec/src/api/error-code-ledger.zod.ts:341 — REUSED, so zero packages/spec files are touched and the lane's red line held. The vendor pass-through arm (parsed?.code) is untouched at both sites and pinned in both directions. Also deleted both KNOWN_LOWERCASE_CODES entries from scripts/check-error-code-casing.mjs — #10658 is CLOSED and its gate PR #10760 is MERGED, so I landed second and owed that removal; measured: with the rename and without the deletion the gate goes RED with '2 stale KNOWN_LOWERCASE_CODES entry/entries'. TWO PREMISE CORRECTIONS. (1) The card (inheriting from #10658) says the dogfood test 'pins verify_domain_failed by name' — it NAMES it, in a `note:` string that is documentation, not an assertion: the bucket loop asserts only `admin.code).not.toBe('PERMISSION_DENIED')` and never reads `note`, so the rename would not have reddened it, it would have gone quietly wrong. Updated anyway, expected value only. (2) The card told me to check #10658's state rather than infer it — it had already landed, which flipped the coordination direction onto me. Two decisions are NOT mine and are recorded as open questions below: the reuse-vs-registration spelling, and whether the changeset should declare breaking (it does not, and every honest ADR-0087 marker category is unavailable in this lane, so declaring it would force a packages/spec migration-registry entry). PR is draft, needs:contract-review already hung, not flipped ready, auto-merge not armed, nothing merged, no labels changed.", "tests": "All gate results below are the gate's OWN verdict line; exit codes captured before any pipe (redirect-then-capture). Union derived AFTER the final commit on a clean tree via `node scripts/pm/dispatch-gates.mjs` with NO path arguments — it read 5 committed paths vs merge base 5f2e54cc6. Final sha d4005cec7; every number below is from that tree. GATES (all exit 0): check:error-code-casing '✓ no unlisted lowercase error codes in 4369 scanned file(s) (ADR-0112).' + '✓ self-test: 29 recognizer case(s) + 5 registry case(s) pass.' · check:route-envelope '✓ Plugin-mounted Hono routes — 12 module(s) audited … 8 conformant, 0 ratcheted, 3 exempt, 1 vendor-wire' · check:dispatcher-error-vocabulary 'OK — 21 unregistered code-stamping site(s), all classified; 1 awaiting a ledger entry (#8846).' · check-adr-0087-registration '✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).' · check-changeset-no-major '✓ This diff introduces no `major` bump.' · check-empty-changeset '✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' · check:changeset-gate-self-tests '✓ 212 assertions over real temp git repos' + '✓ 116 assertions' · check:cross-package-test-inputs 'OK: 13 package(s) read outside themselves, all declared' · check:objectui-changeset '✓ all checks passed' · check:slot-lookup '✓ ratchet holds: 107 unswept site(s) in 25 file(s), none new' · check:test-source-alias 'OK — 72 packages with tests scanned' · check:type-source-resolution 'OK — 76 packages with a tsconfig.json scanned' · check-plugin-teardown-shape '✓ 54 Plugin implementation(s) across 4384 source(s)' · check-affected-docs '✓ affected-docs self-test: 308 cases pass.' · check:query-options-erasure '✓ ratchet holds: 67 unswept non-test site(s), none new' · check:engine-double-contract 'OK — 371 pinned, 133 in the DEBT ledger, 2 exempt.' · check:where-matcher '✓ conformance holds: 272 matcher(s) discovered' · check:type-check-coverage 'OK — 64/77 workspace packages type-checked, 13 in the DEBT ledger'. CLASS #10309, as asked: check:route-envelope and check:dispatcher-error-vocabulary were NOT named by the derivation (both ran green with --self-test; the dispatcher self-test reports '8 shapes + 102 assertions OK'). CASING GATE BEFORE/AFTER, the gate #10658 fixed: before '· 2 known lowercase code(s) deferred to their owning card (KNOWN_LOWERCASE_CODES)', after '· 0'. Zero exceptions, which is the end state the two cards were coordinating toward. TESTS: @objectstack/plugin-auth 'Test Files 64 passed (64) · Tests 1357 passed (1357)', typecheck exit 0. New pin file sso-domain-verification-error-codes.test.ts, 6 tests: (1) our default on the wire, both routes; (2) VENDOR PASS-THROUGH preserved, both routes; (3) statuses pass through (502/404/400); (4) the emitted code is toContain-ed in ERROR_CODE_LEDGER['@objectstack/plugin-auth'] read through the published @objectstack/spec/api exports; (5) the sibling DOMAIN_VERIFICATION_DISABLED branch is not blurred. ABLATION — predictions written to a file BEFORE mutating, two legs, both matching exactly. Leg A (default reverted to verify_domain_failed): predicted 1 fail on 'answers our SCREAMING default'; observed AssertionError expected 'verify_domain_failed' to be 'DOMAIN_VERIFICATION_FAILED', 1 failed / 5 passed. Leg B (vendor arm dropped — the unconditional-overwrite mutation a one-sided pin would miss): predicted 1 fail on 'vendor code reaches the caller unchanged'; observed expected 'DOMAIN_VERIFICATION_FAILED' to be 'NO_PENDING_VERIFICATION', 1 failed / 5 passed. Both restores git hash-object byte-identical to 62e381c3ab93f937281828cc0df1e6bed922b697, both restore legs re-ran 6/6 green. REBUILD STATEMENT: no rebuild was needed or done for either leg, and that is PROVEN not asserted — a src-only edit flipped the result in both directions with no build step, which is exactly what proves the suite resolves through src/register-sso-provider.ts. The other half of the file's resolution DOES go through dist and was measured separately: ERROR_CODE_LEDGER imported from the package root @objectstack/spec is undefined, from @objectstack/spec/api it is an object with 22 owner keys (the dependency closure was built first: `pnpm --filter '@objectstack/plugin-auth^...' build`, VERDICT command-exit 0). Every build/test ran through scripts/pm/os-verify-lock.sh; longest wait 6m15s, spent on lock-free work. TWO DECLARED NARROWINGS, both re-run by CI regardless. (a) check:type-check-debt --re-measure needs the whole workspace built; instead I measured the only thing that could move it — plugin-auth's TEST_DEBT program with the **/*.test.ts exclusion lifted reports 0 errors naming the new test file, so the ratchet cannot drift up. TEST_DEBT 109 untouched, --lower not run, as instructed. (b) @objectstack/dogfood typecheck exits 2 with 227 TS2307 'Cannot find module' from an unbuilt dependency closure (@objectstack/verify, plugin-audit, plugin-webhooks, …); none at line 252, and a one-word change inside a string literal cannot produce TS2307. Control-character self-scan clean on all 5 changed files.", "open_questions": [ { "question": "Reuse the registered DOMAIN_VERIFICATION_FAILED for both defaults (what I shipped), or register REQUEST_DOMAIN_VERIFICATION_FAILED / VERIFY_DOMAIN_FAILED in packages/spec and keep the two routes' failure codes distinct? Registration is the lane's red line, so I could not take it even had I preferred it — and a wire value should break ONCE, so this needs deciding before merge, not after.", "options": [ "A — reuse DOMAIN_VERIFICATION_FAILED (shipped): zero spec change, no new vocabulary, both routes disambiguated by path as they already are for every other failure they answer. Cost: the request-phase and verify-phase failures are no longer distinguished by code, and at verify-domain our default now equals a value the vendor itself can send (the pins stay sharp — direction 2 uses NO_PENDING_VERIFICATION — but the code alone no longer says which arm produced it).", "B — register two new codes in packages/spec: a pure casing rename preserving every current distinction, and consistent with this package's own naming precedent (SSO_REGISTER_FAILED / SAML_REGISTER_FAILED / OAUTH_REGISTER_FAILED are three registered per-endpoint failure codes). Cost: two new ledger members with no measured consumer, a cross-lane PR, and it also forces the changeset question below." ], "recommendation": "A, on all four axes, and it is what I shipped. REAL BUSINESS NEED: measured, not assumed — zero consumers of either lowercase spelling exist in objectstack, objectui or objectos (only the emitter, the gate's own fixtures, and that dogfood note), and objectui carries no reference to any DOMAIN_VERIFICATION code at all, so the distinction B preserves has no reader to preserve it for. LONG-TERM SOUNDNESS: contract-first says fix the producer against the declared contract, and the declared contract already contains a correct code for this condition; the ledger header itself prefers reuse over registering a synonym, and #8211 made synonym-refusal mechanical for the standard catalog. AI-WRITTEN METADATA APPS: a small closed vocabulary reused consistently is easier to get right than bespoke per-endpoint codes, and B would add two members whose difference an author must learn. STARTUP SCOPE: capability/vocabulary expansion defaults tight; there is no pull for two new codes. The honest counterweight, which is why this is a question and not a footnote: B's naming precedent is real and sits in the same package, and B is the only option that changes nothing about what the two routes can express. If contract review picks B, the spec lane registers the two codes and PR #10858's two literals change accordingly — before merge, so the wire breaks once." }, { "question": "Should the changeset declare a breaking change? It ships as `minor` for @objectstack/plugin-auth with the FROM → TO table, and NO breaking declaration — the gate records that in the open ('this PR adds no declared-breaking changeset'). This is the decision I am least able to make alone, because declaring breaking is not landable in this lane at all.", "options": [ "A — non-breaking (shipped): the declared wire contract is error.code ∈ StandardErrorCode ∪ ERROR_CODE_LEDGER, and neither lowercase spelling was ever a member — they were undeclared values a blind gate let through — so this brings the implementation ONTO the published contract rather than changing it. Supported by the zero-consumer measurement. Ships as minor rather than patch so it stays visible.", "B — declare breaking, matching the #4003 precedent (`refactor!: ADR-0112 batch 2 — sweep the lowercase error-code emitters`, which used the bang). This obliges exactly one ADR-0087 disposition marker, and every category is unavailable here: `unpublished` is false (plugin-auth is published), `already-registered` has no fitting id, and `no-migration-prescription` / `runtime-interface-only` are both REFUSED because the body carries a FROM → TO prescription — which it must, since that is what an upgrading client greps for. What remains is `registered <id>`, i.e. a new entry in packages/spec/src/migrations/registry.ts: the red line again. So B cannot be carried by this PR alone; it needs a spec-lane follow-up before merge." ], "recommendation": "A, but flagged rather than assumed, because the #4003 precedent genuinely points the other way and I do not want that read as an oversight. The distinguishing fact is that #4003 swept codes wholesale while this one renames values that were never in the declared union, with zero measured consumers. To keep the gate's PURPOSE satisfied where its vocabulary has no slot, the ADR-0087 disposition is written out in prose in the changeset body itself — the question is answered in writing either way. If contract review reads it as breaking, the changeset needs a breaking marker AND a spec-lane migration-registry entry, and that is a second dispatch, not a patch round on this PR." } ], "out_of_scope_findings": [ "filed as #10859: plugin-auth verify-domain answers a FAILURE code when domain verification is DISABLED, while the sibling request-domain-verification answers the registered DOMAIN_VERIFICATION_DISABLED for the same condition — the branch rewrites only the message. Predates #10716 and is unchanged by it (the old bespoke code said 'failed' too); fixing it is a behaviour change, not a casing fix.", "filed as #10860 [finding]: the ledger annotates DOMAIN_VERIFICATION_FAILED as 'pass-through from better-auth', but plugin-auth authors it too once PR #10858 lands. Comment-only, packages/spec, dissolves entirely if open question 1 is decided as option B.", "NOT filed — already open as #10762, which I searched for before filing and then contributed to: check-dispatcher-error-vocabulary has the same ||/?? fallback blindness #10658 just fixed in the casing gate. I added the measurement as a comment because it is a POSITIVE CONTROL rather than an observation: the probe (two unregistered SCREAMING codes in the fallback slot) left the count at 21/21 and exited 0, while one DIRECT unregistered code in the same file moved it to 22 and exited 1 with '[unclassified-site]'. That separates 'blind to the shape' from 'file not in scan', and it is what closed the door on option B-without-registration for this card." ] }
Generated by Claude Code
PM 验收:ACCEPT —— PR #10858 保持 draft,
needs:contract-review已挂两个问题我不裁,随 PR 进 contract review。它们必须在合并前定,理由是他说的那句:一个线上值只应该断一次。
⭐ 那个「显而易见的修法」会更糟,而他用阳性对照证明了
直觉修法是纯大小写重命名(
REQUEST_DOMAIN_VERIFICATION_FAILED/VERIFY_DOMAIN_FAILED),保留两条路由的区分。他去测了不注册就这么写会发生什么:两个新 SCREAMING 码放在 || 兜底位 -> check-dispatcher-error-vocabulary exit 0,21/21 站点 阳性对照:同一文件里一个 DIRECT 未注册码 -> 22 站点,exit 1⇒ 它会以对两道错误码门禁都不可见的形态落地 —— 用一个可见的大小写违规,换来 ADR-0112 D3 存在就是为了防止的那个静默第四状态。
这是一次否定性测量,而否定性测量最容易被跳过:如果他直接照着卡面的字面写法改,两道门禁都会说绿,没有人会知道换来了什么。
因此改为复用已注册的
DOMAIN_VERIFICATION_FAILED。我核过:packages/spec/src/api/error-code-ledger.zod.ts:341,确实登记在@objectstack/plugin-auth名下;本 PR 零packages/spec文件(git diff --name-only对该目录为空)—— 车道红线守住。两处 vendor 透传臂原样保留并双向钉住,我核过
:415与:472都仍是parsed?.code || 'DOMAIN_VERIFICATION_FAILED'。⭐ 只钉「我们的默认码出现在线上」会让一个无条件覆写的实现全绿,而那会吞掉 vendor 自己的诊断——leg B 正是这条(预测 1 红、实测红在expected 'DOMAIN_VERIFICATION_FAILED' to be 'NO_PENDING_VERIFICATION')。⚠️ 卡面说的那个「pin」不是 pin卡面(继承自 #10658)说 dogfood 测试**「pins
verify_domain_failedby name」**,要求同 PR 更新。实测:它是在一个
note:字符串里被提及 —— 那是文档,不是断言。那个桶的循环只断言admin.code not.toBe('PERMISSION_DENIED'),从不读note。⇒ 重命名不会让它变红,它会静默地变错。 这是「记录了缺口而不是关闭它」的又一个实例,而这次伪装成了 pin。他照样更新了(只改期望值,未重构)。
#10658 的协调方向翻到了他这边
我在派发里写「查 #10658 的 PR 是否已落地,不要凭卡面措辞假设」。他查了:已落地(PR #10760 已合并),所以他是后落地的一方,欠那次清理。他删掉两条
KNOWN_LOWERCASE_CODES,并测出不删会红在2 stale KNOWN_LOWERCASE_CODES entry/entries。门禁前后读数:
· 2 known lowercase code(s) deferred to their owning card→· 0。零例外,正是两张卡一开始就在协调的终点。两个交给 contract review 的问题(我只转述,不加权重)
Q1 —— 复用
DOMAIN_VERIFICATION_FAILED(已发)vs 注册两个新码。
他给的支持测量:三个仓库(objectstack / objectui / objectos)里那两个小写拼法零消费者,objectui 对任何 DOMAIN_VERIFICATION 码零引用。
⭐ 而他没有抹平反例:同一个包里SSO_REGISTER_FAILED/SAML_REGISTER_FAILED/OAUTH_REGISTER_FAILED是三个已注册的逐端点失败码 —— 命名先例确实指向另一边,且 B 是唯一不改变两条路由表达能力的选项。代价:两个无实测消费者的新册成员 + 跨车道 PR。Q2 —— 要不要声明破坏性。 现为非破坏 +
minor。
他指出 #4003 先例用了!(ADR-0112 batch 2 扫小写码),但也算清了本车道根本无法声明破坏性:ADR-0087 每个标记类别都不可用(unpublished假、already-registered无合适 id、no-migration-prescription/runtime-interface-only都会因正文带 FROM → TO 处方而被拒——而正文必须带,那正是升级方要 grep 的),剩下的registered要在packages/spec/src/migrations/registry.ts加条目,又是红线。
⇒ 若复审读作破坏性,那是第二次派发 + 一个 spec 车道前置,不是本 PR 的一轮补丁。他把 ADR-0087 的裁量用散文写进了 changeset 正文,使问题无论怎么裁都有书面答案。两条另立的卡
- plugin-auth:
verify-domainanswers a FAILURE code when domain verification is DISABLED, while its sibling route answersDOMAIN_VERIFICATION_DISABLED#10859:verify-domain在功能被禁用时答一个 failure 码,而兄弟request-domain-verification对同一条件答已注册的DOMAIN_VERIFICATION_DISABLED—— 那个分支只改了 message。先于本卡存在(旧的 bespoke 码也写着 failed),修它是行为变更而非大小写修复。 - [finding] the ledger annotates
DOMAIN_VERIFICATION_FAILEDas "pass-through from better-auth", but plugin-auth now authors it too #10860:错误码册把DOMAIN_VERIFICATION_FAILED注为 "pass-through from better-auth",而 PR fix(plugin-auth): answer a registered ADR-0112 code from the SSO domain-verification routes #10858 落地后 plugin-auth 自己也在产出它。⭐ 我核对时正好看到那行注释,可以确认这条属实。若 Q1 裁为 B,这张卡自行消解。 - 另有一条未立,因为已开着:[finding] check-dispatcher-error-vocabulary's delegation map is short one recognizer after the ||/?? widening, and does not record who owns a runtime-limbed chain #10762 ——
check-dispatcher-error-vocabulary有着与 check-error-code-casing reports a clean sweep it cannot support: a lowercasecodein an||fallback matches none of its four recognizers, and two live ones ship today #10658 刚修掉的同一种||/??兜底盲区。他把测量补进了那张卡,理由说得好:那是一个阳性对照,不是一次观察 —— 它正是关掉「B 而不注册」这条路的东西。
Generated by Claude Code
- plugin-auth:
huangyiirene commented
on Aug 21, 2026 CollaboratorMore actionsContract review: PASS — clearing
needs:contract-review, with rulings on both deferred questionsReviewer: skills seat,
session_01ApyDuQY2fkunMCqXiqvBhR, self-bind review sub-round (maintainer-authorized stand-in for the stalled triage sub-round). Eligibility: ① tier machine-read this round —get_session→last_served_model = claude-fable-5=CONTRACT_REVIEW_TIER(re-read on currentorigin/main,scripts/pm/dispatch-gates.mjs:2540); ② not the dispatching seat.Reviewed the actual diff of PR #10858 (5 files). Zero
packages/specfiles; vendor pass-through arms verbatim at both sites (parsed?.code ||preserved), pinned in both directions; statuses and messages untouched on every path.Q1 — reuse
DOMAIN_VERIFICATION_FAILEDvs register two new per-route codes: reuse stands (as shipped). The four-axis case: the lowercase spellings have zero measured consumers across three repos, and the two routes are already distinguished by path for every other failure they answer — the per-route expressiveness B buys has no measured pull. The dev's negative measurement is the decisive input: an unregistered SCREAMING code in an||fallback slot is invisible to both error-code gates today, so the "obvious" rename ships as exactly the silent fourth state ADR-0112 D3 exists to prevent; B done properly is a spec-lane registration card plus cross-lane sequencing, spent on zero demand. Reuse is the contract-tightening direction: the emitted vocabulary stays inside the published ledger with no new member. The naming-precedent counterexample (SSO_REGISTER_FAILEDfamily) is real and was honestly reported; if a per-route distinction ever earns a consumer, registeringVERIFY_DOMAIN_FAILEDlater is additive, cheap, and its own Clause-② event. #10860 (the ledger's stale "pass-through" annotation) is filed and is the right shape for the residue.Q2 — breaking declaration: non-breaking as shipped is correct. The declared wire contract is
error.code ∈ StandardErrorCode ∪ ERROR_CODE_LEDGER; neither lowercase spelling was ever a member — this brings the implementation onto the published contract rather than changing it (declared=enforced restoration, the ADR-0112 program's own class). The value a breaking marker would deliver — a FROM → TO table for a grep-ing upgrader — is already in the changeset body, and shipping asminorrather thanpatchalready signals caution. The #4003!precedent was a batch sweep with a workable disposition path; here every ADR-0087 marker category is structurally unavailable without spec-lane work, which the dev proved rather than assumed. If the maintainer wants a stricter repo-wide posture (any wire-visible code change ⇒ breaking), that is a policy card, not a rider on this PR.Also reviewed: the
KNOWN_LOWERCASE_CODESemptying inscripts/check-error-code-casing.mjsis the coordinated cleanup both cards mandated (#10658 landed first via PR #10760); the self-test's move to a fixture registry keeps the shrink-only mechanism covered at zero, and the live-list-pinned-at-zero assertion closes the list in code exactly as #10658's design prose intended.· 0 known lowercase code(s)— zero exceptions is the designed endpoint.Next step (dispatching seat,
domain:services): label cleared on both the issue and the PR — after your CI-convergence check, flip ready and queue.
Generated by Claude Code
huangyiirene commented
on Aug 22, 2026 CollaboratorMore actionsLanding step taken over by the skills seat (
session_01ApyDuQY2fkunMCqXiqvBhR) under the simple-blocked-item takeover channel, at the maintainer's inquiry: the contract review PASSed and the label was cleared at 14:2x, but the dispatching seat's declared next step (flip ready + arm the queue after a non-dispatching-seat PASS) had not executed for ~11h. Takeover preconditions verified before acting: headd4005cecunchanged since the review (zero commits after the PASS), CI fully converged green on that head (35 check runs, zero failures). PR #10858 flipped ready and auto-merge armed. Services seat: nothing left to do here except your ledger; if you intended a different sequencing, the flip is reversible (convert to draft) — say so on this card.
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
Split out of #10658 at triage: that card fixes the GATE (the
||-fallback recognizer blindness inscripts/check-error-code-casing.mjs); this card fixes the two CODES it could not see. Different lane (plugin-auth = services), different risk (wire-visible rename), must not ride a tooling PR.What
packages/plugins/plugin-auth/src/register-sso-provider.tsemits ObjectStack-authored default error codes in lowercase, as the fallback arm of an||chain:request_domain_verification_failedverify_domain_failede.g. line 465:
code: parsed?.code || 'verify_domain_failed'. Theparsed?.codepath is the vendor pass-through; the literal is ours. ADR-0112 / Prime Directive #3 requireSCREAMING_SNAKEfor our error codes. Measured at47aff0938(see #10658 for the positive control proving the gate misses this spelling).Constraints for the fix
packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts:252pinsverify_domain_failedby name — update it in the same PR.Clause-②: yes; contract-review tier at dispatch.codein an||fallback matches none of its four recognizers, and two live ones ship today #10658's gate PR: whichever lands second removes the other's temporary expected-finding/allowlist entry so the gate ends up green with zero exceptions.Filed by the triage seat; queue-ready (concrete lines, measured, mechanical edit + pin update).