Uh oh!
There was an error while loading. Please reload this page.
docs(plugin-auth): ledger and document the unaccounted ObjectStack auth mounts - #10660
Conversation
…th mounts auth-plugin.ts mounts 17 routes directly on the raw Hono app ahead of the better-auth catch-all. Nine were in neither half of auth-route-ledger.ts and six carried no literal wire path in the hand-written docs. Ledger: eight gain reviewed `source: 'objectstack'` rows, all `server-only`, each measured to have zero ObjectStackClient callers and exactly one real caller (a declarative metadata action target or a Console wizard). The conformance pin over the objectstack-sourced set grows 3 -> 11 in the same edit, so the enlarged set stays reviewable. set-initial-password is deliberately left unledgered: its peer group makes it read as `gap`, not `server-only`, and `gap` is ratcheted to zero — so a disposition there would be a false declaration written to dodge a ratchet. Escalated on the issue instead. Docs: bootstrap-status, set-initial-password, admin/unban-user, admin/sso/register and the two admin/sso domain-verification routes now carry their literal wire paths. admin/sso/register was NOT documented before — the census's substring test had credited it to its longer sibling register-saml. No route's mounting, behaviour or accept/reject set changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not seeCoarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6a2e2d6df472be799b0eb8270396510465bd5ce9 && git checkout 6a2e2d6df472be799b0eb8270396510465bd5ce9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9dd192d48b17e1d60abc47f16bd9d3c0212d237b 3115f7939f7517da5de14df074b15937f2493ff1 && git checkout -B drift-repro 9dd192d48b17e1d60abc47f16bd9d3c0212d237b && git merge --no-ff 3115f7939f7517da5de14df074b15937f2493ff1
node scripts/docs-audit/affected-docs.mjs --json 9dd192d48b17e1d60abc47f16bd9d3c0212d237b
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32550339300 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Part of #10534
Scope is the card's follow-ups 1 and 2 only, per the triage ruling and the claim comment. Follow-up 3 (the
REGISTRAR_FILE_REbridge) and follow-up 4 (the proposed gate) are untouched — see Out of scope below, including a state correction on the reason follow-up 3 was fenced.1. The census, re-run — and one number the card got wrong
Re-measured on
origin/mainat47aff0938(the card measured4544be260), running the card's own expression rather than trusting it.Population: still 17.
rawApp.mounts spelled with a template literal on${basePath}inauth-plugin.ts, excluding therawApp.allcatch-all, therawApp.useIP-gate middleware, and the.well-known/*mounts that are not underbasePath.Leg 1 — documented? 6 undocumented, not 5
The card's test — does the literal wire path appear anywhere in the hand-written docs corpus — is a substring test, and one route is a prefix of another:
/admin/sso/registerwas credited as documented on the strength of the only occurrence in the corpus beingregister-saml's URL atcontent/docs/permissions/sso.mdx:222. Re-run with a right boundary (a negative lookahead for word characters and hyphen):/admin/sso/registerSo the card's "5 remaining" was one short, and the missing one was hidden by the measurement rather than by the corpus. All 6 are documented here.
Positive controls (leg 1), because two of the numbers above are zeros:
The card's own control —
organization/add-memberpresent in the undocumented set — now reportsfalse, correctly: #10050's PR documented it. It is replaced above rather than dropped, since a control that has been fixed is no longer a control.Leg 2 — ledgered? 9, exactly as the card found
9 of 17 in neither
AUTH_ROUTE_LEDGERnorBETTER_AUTH_MOUNTED_SURFACE, matching the card member-for-member. Positive control (leg 2): the same per-route grep returns1for/configand1for/organization/add-member.After this PR
2. Follow-up 1 — a verdict per route, not a bulk edit
The card permits documenting or recording a deliberate server-only, not user-facing verdict, and names
bootstrap-statusas a likely candidate. All six are documented; none earned the server-only verdict, and the reason differs per route:GET /bootstrap-statusclient.auth.bootstrapStatus()is a shippedObjectStackClientmethod, ledgereddisposition: 'sdk'. A public SDK method IS user-facing surface; an undocumented one is the declared-is-not-documented gap, not a server internal.POST /set-initial-passwordauthentication.mdx("offers set-initial-password") with no URL. That mention now carries the literal path and links to the new section.POST /admin/unban-usercreate-user,set-user-password,import-users,unlock-user,ban-user) are all documented. A ban that documents no way back is the gap.POST /admin/sso/registerPOST /admin/sso/request-domain-verificationOS_SSO_DOMAIN_VERIFICATION) had zero mentions anywhere incontent/docsordocs— not the URLs, not the env var.POST /admin/sso/verify-domain"Mentioned" is not "documented" was treated as the bar throughout: every route above gets its literal wire path plus what it actually does, refuses, and requires.
404when the feature is off. Readingregister-sso-provider.tsinstead of the mount's comment: step 1 translates the inner 404 into 400DOMAIN_VERIFICATION_DISABLED, step 2 passes the 404 through. The docs now state the asymmetry and tell readers to match on the code, not the status.3. Follow-up 2 — 8 rows, and one deliberate refusal
Eight mounts gain reviewed
source: 'objectstack'rows, alldisposition: 'server-only'. Disposition was decided, not defaulted —server-onlymeans "deliberately not SDK surface", which is a claim about intent. It was tested per route by measuring who builds the URL:Positive control on the SDK grep (
packages/client/src), so the zeros below are real:Every one of the eight came back with zero
ObjectStackClientcallers and exactly one real caller that is a declarative surface — a metadata actiontargetonsys_user/sys_sso_provider/sys_oauth_application, or the Console import wizard (objectuiapp-shell/src/views/identityImport.ts). That is theorganization/add-memberprecedent exactly. Their peer routes are uniformly SDK-absent too, so "the SDK deliberately does not express platform-operator user administration" is the surface's real shape, not a story written to fit a row.⛔
POST /api/v1/auth/set-initial-passwordis NOT ledgered — escalated insteadIt is the ninth unledgered mount and the one place I stopped. It fails the test above in a way none of the other eight do:
@object-ui/auth'screateAuthClient, which builds exactly four auth URLs:/config,/get-session,/list-accounts,/set-initial-password.sdkwithObjectStackClientmethods (auth.getConfig,auth.me,auth.accounts.list). Only this one has none.changePassword— is ledgeredsdk.That is the shape of
gap("should be in the SDK and is not"), notserver-only. Andgapis ratcheted to zero by this file's own conformance suite. So the two available moves were: writeserver-only(a false declaration of intent, chosen to dodge a ratchet) or writegap(turns the suite red). Neither is mine to pick — whetherObjectStackClientshould expresssetInitialPasswordis a product decision. It is in the report'sopen_questionswith options and a recommendation. The refusal is recorded in both the ledger and the conformance test so the next reader is not left wondering.Contrast that with
/admin/import-users, whose caller is also a hand-written objectui client rather than a metadata action: its peer group (create-user,ban-user,set-user-password) is uniformly SDK-absent, so it is consistent, andserver-onlyis honest. The discriminator is the peer group, not convenience.The conformance pin moves in the same edit
'the objectstack-mounted rows are the ones auth-plugin.ts serves itself'pins thesource: 'objectstack'set exactly — it grows 3 to 11. That is the closest mechanical check that exists today for "an ObjectStack mount with no ledger row", and its comment now says so, along with what it still cannot catch (it is hand-written, so it sees a row that disappears, not a mount that never got one).4. Ablation
Signature predicted before running, then observed:
Observed:
No rebuild is involved, and that is proven rather than asserted: the suite imports the ledger relatively —
from './auth-route-ledger'atauth-route-ledger.conformance.test.ts:27— so resolution cannot go through the packageexportsfield and nodist/sits between the edit and the run. Mutation confirmed on disk (the row grepped to 0 occurrences), not from an editor's exit code.Restore proven byte-identical, not merely "looks right":
5. Gates
All run at the final commit
3115f7939on a clean working tree. Family derived bynode scripts/pm/dispatch-gates.mjswith no path arguments (5 paths, 20 matched families plus the convention-triggered set). Exit codes captured before any pipe (cmd > file 2>&1; EXIT=$?), never after atail.plugin-authfull suiteTest Files 61 passed (61)·Tests 1359 passed (1359)plugin-auth typechecktsc --noEmit, exit 0check:affected-docs✓ affected-docs self-test: 281 cases pass.check:docs-audit-scope✓ docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).✓ release-owned pages are in scope and read-only: 9 page(s) ... review-onlycheck:doc-anchors✅ ... slug parity, custom ids, duplicate counters ... (277 live fragment links)check:docs✅ 229 generated files in sync with packages/speccheck:nul-bytesOK (scanned 6212 text file(s) ... no raw ASCII control bytes).check:route-envelope(+--self-test)✓ Plugin-mounted Hono routes — 12 module(s) audited ... 8 conformant, 0 ratchetedcheck:dispatcher-error-vocabulary(+--self-test)8 shapes + 102 assertions OKcheck:where-matcher✓ where-matcher conformance holds: 271 matcher(s) discovered, 271 answer the combinator battery correctlycheck:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declaredcheck:type-check-coverage✓ --self-test — 23 semantic + 24 observation + 25 re-measure + 28 built-closure + 9 auto-lowering case(s) hold.check:engine-double-contract·check:query-options-erasure·check:slot-lookup·check:test-source-alias·check:type-source-resolution·check:role-word·check:docs-redirects·check:published-readme-links·check:objectui-changeset·check:changeset-gate-self-tests·check-changeset-no-major·check-empty-changeset·check-adr-0087-registration· speccheck:empty-state/check:liveness/check:strictness-ledger/check:variant-docscheck:route-envelopeandcheck:dispatcher-error-vocabulary, both of which biteplugin-auth— were run explicitly. Confirmed: neither appears in the derivation's output for this change set. That is a live instance of the class, recorded here rather than only in the report.One declared narrowing:
check:type-check-debt --re-measurewas not run. It refuses on an unbuilt worktree and needs the whole workspace closure built, a full-workspace build under a container-shared verification lock. In its place,pnpm --filter @objectstack/plugin-auth typecheck(tsc --noEmit) passes at exit 0, and the diff adds only string-literal array entries to an existing test file. CI runs the ratchet regardless.@objectstack/plugin-authTEST_DEBT (card #10615): untouched. The number was not edited,--lowerwas not run, and nothing here re-measures it. The edited test file is pre-existing (auth-route-ledger.conformance.test.ts), so no package gains or loses test-file coverage.Out of scope
Follow-up 3 — a state correction. The card and the claim comment both fence it on the grounds that
scripts/docs-audit/affected-docs.mjsis held by open PR #10501. Verified directly rather than taken on trust: #10501 is merged (closed2026-08-21T02:46:52Z,merged: true, commit4e7229a6). The stated blocker has therefore cleared. It is still not touched here, because the scope ruling admits follow-ups 1 and 2 only — but the reason it was deferred no longer holds, which is the PM's call to make.Follow-up 4 — not built, as instructed. Concrete evidence about how such a gate should be written came out of this work and is in the report; the one worth repeating here is that a mount-vs-docs gate must match the wire path with a right boundary, or it silently credits a prefix route to its longer sibling — the exact artifact that made this card's leg 1 read 5 instead of 6.
Filed unassigned, not fixed:#10658 —
check-error-code-casingprints✓ no lowercase error codes in 4346 scanned file(s)while two ObjectStack-authored lowercase codes ship inregister-sso-provider.ts, because a literal reached through an||fallback matches none of its four recognizers (verified with a positive control on the gate's own regex). Same "partial read reported as complete" class #10501 just closed for a different scanner. Not fixed here: different defect class.No route's mounting, behaviour, or accept/reject set is changed by this PR (Clause-②
no). Nothing undercontent/docs/releases/**orpackages/specis touched.Generated by Claude Code