Uh oh!
There was an error while loading. Please reload this page.
feat(client,plugin-auth): bind set-initial-password into the SDK and ledger it as an sdk mount - #11360
Conversation
…itial-password route
`AuthPlugin` mounts `POST /api/v1/auth/set-initial-password` on the raw Hono
app, but no `ObjectStackClient` method built the URL — measured zero for both
`setInitialPassword` and `set-initial-password` across `packages/client/src`,
against four sibling auth members returning non-zero on the same corpus.
The method is shaped like its namespace siblings (`getConfig`,
`changePassword`, `changeEmail`): `this.getRoute('auth')` + `this.fetch`, POST
with a JSON body, returning the parsed envelope.
The route's own accept/reject behaviour, admit set and server-side guards are
untouched. This binds a client to an already-mounted route.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 23 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 8ee1fceec3f103fd6f239c6858e6ad62ecb390bf && git checkout 8ee1fceec3f103fd6f239c6858e6ad62ecb390bf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9337cd8104cb4640b5616a7227c55b9fb5a1222e fbd9af2da4b8d16ca92a466899526cba906e99ee && git checkout -B drift-repro 9337cd8104cb4640b5616a7227c55b9fb5a1222e && git merge --no-ff fbd9af2da4b8d16ca92a466899526cba906e99ee
node scripts/docs-audit/affected-docs.mjs --json 9337cd8104cb4640b5616a7227c55b9fb5a1222e
|
os-zhuang
commented
Aug 23, 2026
PM: this red is known, diagnosed and ruled. Fix is in flight — no action needed from a reader.
Not a defect in this PR's code.#10974's body predicted this leg would go green on weak wildcard evidence and asked the dev to name the matched pattern. It does not: the assertion bounds wildcard-only matches at ⭐ The dev did not reach for any of the ways to make this green cheaply: no Maintainer ruling, 2026-08-23 — option A, combine. The exact Gate status unchanged while that lands: Generated by Claude Code |
…k mount Second half of the maintainer's option-C ruling, landed in the same PR as the first half by the follow-up ruling of 2026-08-23 (combine). `AUTH_ROUTE_LEDGER` gains the exact row for `POST /api/v1/auth/set-initial-password` — `family: 'objectstack-mount'`, `source: 'objectstack'`, `disposition: 'sdk'`, `client: 'auth.setInitialPassword'` — shaped like the two ObjectStack `sdk` mounts it sits beside. The `:171` pin (`the objectstack-mounted rows are the ones auth-plugin.ts serves itself`) goes 11 → 12 BY ADDITION: the assertion, the pin and the `live.has(route)` loop are untouched. Both of the pin's own terms hold for the new entry — auth-plugin.ts mounts it directly on the raw app ahead of the catch-all, and better-auth does not publish it. `scripts/check-auth-mount-ledger.mjs`'s PENDING_DISPOSITION entry for this route is deleted, which is that shrink-only ratchet coming down: the gate fails `resolved-pending` if an entry survives its disposition landing, and the entry said so itself. PENDING_MAX is NOT changed. ⛔ No guard was weakened to reach this: the `wildcardOnly` bound stays at 0, the method is not parked in `NON_HTTP`, `gap`/`mismatch` stay at 0, and the route's accept/reject behaviour, admit set and server-side guards are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
os-zhuang
commented
Aug 23, 2026
PM note for the contract reviewer: A gate script inside a Clause-② PR should make a reviewer tense up, so here is the check I ran rather than the dev's word for it. What changed: the single entry in Why the deletion is required, from the gate's own source ( if(ledgered.has(p.route)||vendor.has(p.route)){findings.push({kind: 'resolved-pending',text: `PENDING_DISPOSITION entry ${p.route} now HAS a ledger row (${p.issue} landed). `+`Delete the entry -- that is how the ratchet comes down.`,});}Once the ⭐ Stronger still: the gate's self-test at [{route: p[0].route,source: 'objectstack',disposition: 'sdk',client: 'auth.setInitialPassword'}],…'a pending entry whose route is now ledgered passed -- the ratchet cannot come down'— so today's landing is the scripted outcome the gate rehearsed in advance. The deleted entry's own text also said "This entry is deleted by #10975." Direction check: shrink-only lists are weakened by growing. This one shrank 1 → 0. Nothing was exempted, no threshold moved, no assertion loosened. The Lane crossings, named rather than glossed: this PR now reaches Gates unchanged: Out of scope and expected: the docs-drift advisory names Generated by Claude Code |
os-zhuang
commented
Aug 23, 2026
PM: green at Every job ⭐ That green is itself the proof the displacement happened. The assertion is So maintainer ruling A (combine) is confirmed the right call on evidence: the two halves were jointly unsatisfiable apart, and together they close on exact evidence rather than weak. Still owed by the dev, and I will hold the review note until it lands: the matched-pattern name before and after, which the dispatch demanded as a top-level field. The ratchet reaching 0 establishes that an exact route matched; the pattern name establishes which, and a card whose whole subject is "weak evidence is not evidence" should not settle for the inference when the reading is available. Gates unchanged and this is not a hand-off to merge: Prior PM notes on this PR stand: the Generated by Claude Code |
sdk mountUh oh!
There was an error while loading. Please reload this page.
Fixes#10974
Fixes#10975
One statement in one PR: the method exists · the ledger declares it · the URL resolves to an enumerated route.
POST /api/v1/auth/set-initial-passwordhas been mounted byAuthPluginfor as long as the SSO-onboarding flow has existed, with no SDK method and no ledger row. This binds it intoObjectStackClientasauth.setInitialPasswordand records the exactAUTH_ROUTE_LEDGERrow that names that method.The ruling this PR carries
Maintainer's option C (2026-08-22) settled the disposition: bind the route into the SDK, then ledger it
sdk. It was filed as two cards, #10974 then #10975, and the SDK half landed here first — at which point the split turned out to be measurably unsatisfiable.client-url-conformance.test.tsbounds wildcard-only matches at zero ("ZERO IS THE POINT: … Raising this bound reintroduces the one kind of evidence this audit family has caught being wrong"), so the method matching through the dispatcher's* /auth/**row was a hard failure, not the weak pass #10974's body predicted. The only thing that turns it green is the exact row — which #10974's own fence excluded.Maintainer ruling A — combine (2026-08-23): both halves land here. That changes the packaging, not the surface — option C had already authorised exactly one method and exactly one row.
needs:contract-reviewstays hung, the PR stays draft, no ready-flip, no auto-merge, no merge-queue enqueue on the dispatching seat's authority, and the reviewing seat must not be the dispatching seat.The matched pattern, named before and after
A green suite either way would not show the displacement; the pattern name does. Both readings come from the conformance test's own
matches(), via a temporary probe that was restored by trap after each run, so no probe is in the diff. Each probe's injection was confirmed on disk before its run (anchor and marker counts, byte delta) and the tree was verified clean again afterwards.BEFORE — at
f9f809d2, from the unmodified test's own failing assertion, no probe needed:and, naming the compiled pattern's source:
AFTER — at
fbd9af2d, printed by vitest itself from a probe assertion:* /auth/**(compiled from the dispatcher ledger,source: 'dispatcher') →POST /api/v1/auth/set-initial-password(compiled fromAUTH_ROUTE_LEDGER,source: 'auth'). The sort atclient-url-conformance.test.ts:132put the exact row ahead of the wildcard family exactly as its comment says it would.wildcardOnlyis now empty and its bound is untouched attoBe(0).The
:171pin — extended by ADDITION, argued on its own termsthe objectstack-mounted rows are the ones auth-plugin.ts serves itselfpins thesource: 'objectstack'set to a hand-written literal list. The card said 3 → 4; re-derived on this branch it is 11 → 12 — the pin grew through #10534's follow-ups after #10975 was written. The assertion, its shape and thelive.has(route)loop are unchanged; one string literal was added, in the position.sort()puts it.The pin's own two terms, both measured:
1.
auth-plugin.tsmounts it directly. Re-derived rather than trusted — #10975 measured line 1716, it is now 1741:A raw-app mount ahead of the catch-all, so better-auth's wire table cannot publish it and the enumeration (which reads
.path) never sees it.2.
live.has(route)isfalse. The assertion's own reading, printed by vitest, with a positive control so afalsefrom an empty set cannot pass for a measurement:131 live routes enumerated, the control route present, ours absent. It does not come from better-auth.
⛔ Not reached by loosening the assertion, deleting the pin, or replacing the literal list with a computed one.
The five gates, individually — all at
fbd9af2dauth-route-ledger.conformance.test.ts:152everysdkrow names a client methodevery 'sdk' entry names its client method; every non-sdk entry carries a rationale:160no route ledgered twice, every row under the auth base pathno route is ledgered twice, and every row is under the auth base path:171the objectstack pinthe objectstack-mounted rows are the ones auth-plugin.ts serves itself:189gapandmismatchonly shrinkgap and mismatch counts only shrink— both still 0; nogaprow was added, and no ratchet movedclient-url-conformance.test.ts:363every client method classifiedNON_HTTP, noDRIVEoverrideclient-url-conformance.test.ts:373every URL matches a mounted routeWhole files:
auth-route-ledger.conformance.test.ts10 passed (10);@objectstack/client23 files / 314 tests passed (was 22/1-failed and 313/1-failed atf9f809d2— the one failure was:373).One file outside the two halves, and why it is not scope creep
scripts/check-auth-mount-ledger.mjscarried aPENDING_DISPOSITIONentry for this exact route, whose own text reads "This entry is deleted by #10975." The gate reconciles that list in both directions and raisesresolved-pendingwhen an entry's route acquires a ledger row — so landing the row without deleting the entry is a red gate, by design. The entry is deleted;PENDING_MAXis not changed (it stays 1, and lowering it would break the gate's own self-test case that exercises a one-entry list). Two sentences of prose that the deletion made false were corrected in the same file. The ratchet coming down, in the gate's own words:Nothing was weakened to get here
⛔ The
wildcardOnlybound stays at0(option D was presented and not taken). ⛔ The method is not inNON_HTTP. ⛔gapandmismatchstay at 0. ⛔ The route's accept/reject behaviour, its admit set and its server-side guards are untouched — this binds a client and records a row; it does not change what the mount allows. ⛔ Nothing else inpackages/plugins/plugin-auth/**is touched.Verification — all at
fbd9af2d, clean treeGate families derived from the real change set with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no hand-built path list; the script read the merge-base change set of 5 paths and asserted the repo against this checkout'sorigin). 18 path-matched families plus the 6 convention-triggered ones for a touched test file, every exit code captured before any pipe:plugin-authauth-route-ledger.conformance.test.ts(10/10) ·@objectstack/clientfull suite (23 files / 314 tests)@objectstack/client(incl.check:test-typecheck— "0 file(s) / 0 error(s)") ·@objectstack/plugin-authcheck:auth-mount-ledger·check:engine-double-contract·check:where-matcher·check:query-options-erasure·check:type-check-coverage·check:nul-bytes·check:ratchet-remedy-authority·check:cross-package-test-inputs·check:test-source-alias·check:type-source-resolution·check:published-files·check:slot-lookup·check:entry-guard·check:parse-guard·check:pnpm-filter-targets·check:changeset-gate-self-tests·check:objectui-changesetcheck-adr-0087-registration·check-changeset-no-major·check-ci-filter-parity·check-cross-package-test-inputs·check-empty-changeset·check-plugin-teardown-shape·check-affected-docspnpm lint(eslint . --no-inline-config) — run in full, not narrowedcheck:type-check-debt --re-measureon a fully built workspace closure: "33 ledger entr(ies) re-measured … 1897 raw tsc error(s) total, none above its recorded number" (its first invocation refused with NOT MEASURED on an unbuilt worktree — its designed refusal; the closure was built and it was re-run)Changeset present and extended to both packages (
@objectstack/clientminor,@objectstack/plugin-authpatch).Refs
Part of #10534, which remains open until all 17 of its mounts are accounted for — its ledger leg for this route is now clear, and its docs leg is #10660's. Also #10050 · #3563 / #3642 (the conformance guards) · #3656 (the auth ledger's charter) · #9941 (the pin's
add-memberprecedent) · #3528 (the coverage-lie failure the combined order avoids: the row and the method exist in the same commit).Out-of-scope finding #11359 (the auth ledger's
clientnames are never resolved against a real client, unlike every sibling ledger) is filed, unassigned, and is not fixed here.Generated by Claude Code