fix(plugin-auth): register the auth service-composition bindings independently of registerRoutes - #14810
Conversation
…pendently of registerRoutes
`AuthPlugin` bound the outbound mail transport, the SMS transport, the
deployment email locale, the brand name and the SMS locale inside the same
`kernel:ready` hook that mounts `/api/v1/auth/*`, and that hook was gated on
`registerRoutes`.
`registerRoutes` answers a transport-mounting question. The bindings are
service composition and are true of an embedding regardless of who serves the
routes, so every routes-less embedding came up with no mail transport, no
locale on either channel and no brand binding — silently, because the
`logger.info` lines that would have reported the wiring sat inside the same
skipped block.
Split the hook: the composition block moves verbatim into its own
unconditional `ctx.hook('kernel:ready', …)`, registered before the route hook
so the ordering a routing host had is preserved. Route registration itself
stays under `if (this.options.registerRoutes)`.
This is the shape the sibling hooks in this file already use and already name
("Registered independently of `registerRoutes` so an embedding that serves no
auth routes still gets the diagnosis") — the file applied the distinction to
the diagnosis hook and not to the wiring the diagnosis exists to report on.
The `#14319` describe block now runs against both values of `registerRoutes`
rather than only the default, and pins both acceptance criteria: the
composition completes either way, and a `registerRoutes: false` kernel still
mounts no auth routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
…s row The census row for `plugin-auth`'s session-resolution middleware anchors a LINE NUMBER in `auth-plugin.ts`; the hook split moved that read site from :1353 to :1380 without changing a character of it. Re-anchored with the gate's own `--fix`, which is the repair it prescribes for pure line rot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
…th-bindings-off-registerroutes
…th-bindings-off-registerroutes
…th-bindings-off-registerroutes
📓 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:
What this run could not see
Coarse 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 dc0bd0eedb73aa3a16a76d29a8e590e919df35c1 && git checkout dc0bd0eedb73aa3a16a76d29a8e590e919df35c1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5a5336b399db2ef18dd4700f97d579a328197dda 3583196089c29cdad2433f26fd662c5ed70b91ed && git checkout -B drift-repro 5a5336b399db2ef18dd4700f97d579a328197dda && git merge --no-ff 3583196089c29cdad2433f26fd662c5ed70b91ed
node scripts/docs-audit/affected-docs.mjs --json 5a5336b399db2ef18dd4700f97d579a328197dda
|
Fixes #14724
AuthPluginregistered its service-composition bindings inside the samekernel:readyhook that mounts/api/v1/auth/*, and that hook was gated onregisterRoutes.registerRoutesanswers a transport-mounting question; the bindings are service composition and are true of an embedding regardless of who serves the routes. Every routes-less embedding — every cloud tenant environment kernel is one — therefore came up with no mail transport, no locale on either channel and no brand binding, silently, without even reading thelocalizationsettings namespace.The composition block is now its own unconditional
ctx.hook('kernel:ready', ...), registered before the route hook. Route registration itself stays gated.Comments read: 2. Ruling of record:
issuecomment-5518030363(triage, 2026-09-02 23:39:05Z). Its three binding points are all satisfied below: route registration stays gated (pinned by a test, both directions), the#14319describe block now runs both values of the flag, and the log-level question is not decided here.Reading of the 421-line churn
The gross diff on
auth-plugin.tsis +224/−197. Ignoring whitespace it is +33/−6:So ~93% of the churn is re-indentation forced by de-nesting a ~205-line block out of one
if. No statement inside the moved block changed — the-wdiff contains only the new explanatory comment, the movedif (this.options.registerRoutes) {, one added});, and one addedctx.hook(line. This is the minimum shape the fix can take in a brace-and-indent language; it is not gratuitous reformatting, and it was not widened further here.Two structural points verified rather than assumed:
dispatchHookPropagating(packages/core/src/hook-dispatch.ts:146) awaits handlers in registration order and does not catch. Composition is registered first, so it still completes before routes, and a throw in composition still aborts before any route is mounted — exactly what the single combined hook gave for free.emailservice and the manager's own view specifically so its answer is "independent of hook registration order" (its own comment), so inserting composition ahead of it changes nothing it reports.One thing worth naming explicitly: the issue lists four bindings; the moved block actually carries five —
setSmsService(#2780) sits betweensetEmailServiceand the locale rungs. It moves because it is inside the block, not as added scope; the changeset says five.Ablation
Subject resolves through a relative source import (
import { AuthPlugin } from './auth-plugin'), so the test exercisessrc/directly — nodist/leg, and no build-and-preflight pair applies. Mutation: line 747 re-gated to the pre-fix semantics, addressed by line number because the anchor text occurs 8 times in the file.Mutation confirmed on disk before measuring — blob hash moved and both unique-text counts flipped:
Results:
All 9 failures are in the
registerRoutes: falsebranch and nowhere else — the exact direction predicted:The
registerRoutes: truebranch stayed fully green under the mutation, and the route-gating test stayed green in both branches — so the ablation isolates composition from routing rather than knocking the suite over.Restore proved by blob equality plus an empty diff, not by an exit code:
The mutation script carried a
trap ... EXIT INT TERMrestore on an absolute path derived fromgit rev-parse --show-toplevel.The census row
content/docs/permissions/system-context.mdxrow 11 movedauth-plugin.ts:1353to:1380(the file grew 27 lines above that site). Confirmed tool-produced, not hand-edited, by reconstruction: restoringorigin/main's copy of the page and running the gate's own repair arm reproduces the branch file byte-for-byte.Gate green as-is on the branch:
Tests and gates
Run at
358319608(the pushed head), after mergingorigin/maine6ac0c6fd.Typecheck coverage of the edited files was measured, not assumed —
tsc --listFilesreturns 1 hit forauth-plugin.tsin the main project and 1 hit forauth-plugin.test.tsintsconfig.test.json, so neither edit is outside its program.Gate family re-derived on the merged tree with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(62 commands, derived from the merge-base change set, not a hand-written path list). 57 green. Five are NOT MEASURED locally — each by its own printed verdict, none a violation:check-test-completeness.mjsPREREQUISITE NOT MET— grades a savedturbo run testlog; the script itself says to "record this gate as NOT MEASURED"pm/check-half-states.mjsspec check:skill-examplespackages/client-react/dist holds no .d.ts declarations — the package is not builtcheck:dual-build-cjs-loadsRun pnpm build first. This is NOT a pass: nothing was measured.check:type-check-debtTopically closest gate,
pnpm check:auth-mount-ledger, is green. Exit codes were captured before any pipe (redirect, then read).Log-level fence — named, not decided
The issue asks whether the "no email service registered" branch should stay at
infonow that it becomes reachable on hosts that never saw it. Per the ruling, that is not decided here. Verified untouched: the branch is byte-identical toorigin/mainmodulo the two-space de-indent, and still readsctx.logger.info('Auth: no email service registered — transactional mail disabled'), with therequireEmailVerificationsibling still aterror.Clause 2 —
noNo new exported symbol, no new payload key, no option added (
registerRoutesalready existed), and nothing changed about what the contract accepts or rejects — the-wdiff shows zero statement changes, only the hook's registration site. The delta is runtime behaviour forregisterRoutes: falseembeddings, which is the bug being fixed rather than a contract surface move; the changeset states it explicitly for release notes.🤖 Generated with Claude Code
https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
Generated by Claude Code