Skip to content

refactor(plugin-auth): move AuthManager's plugin assembly helpers into auth-manager-plugin-assembly.ts (#25224 cut 6) - #25400

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25224
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160

The basis for the Clause-②: no line: this is a move. No accept set, export, route, refusal, code or answer changes. The .d.ts bar is ruling 6348891160 item 1 (on #25222, covering this card). @objectstack/plugin-auth's published dist/index.d.ts and dist/index.d.mts are raw byte-identical to base (0 diff lines, so the union-order normalisation has nothing to do), with the ruling's positive control. Its entry exports the same 155 names under ESM and CJS (measured below).

Cut 6 of the auth-manager.ts split: the plugin assembly helpers. Three AuthManager method bodies (85 lines) move unedited, re-indented one level, into one new module, packages/plugins/plugin-auth/src/auth-manager-plugin-assembly.ts. #25224 stays open for the later cuts.

This follows the amended claim 6361212912, which amends 6359424739 on the file cut 5 left (#25395, landed note 6360455119). The shape is cuts 1 to 5's, which is #23050's.

Branch: claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted from origin/main eca6b61922 after #25395 merged. It names no issue, so the card relation is this body's first line.

auth-manager.ts goes from 8,968 to 8,916 lines. The cut is +202 / −89 over 2 files: 291 changed lines, under the 3,000-line line. It is not a governed surface (check-governed-merges.mjs --branch HEAD: 0 of the 2 paths hit the register).

Why this seam

These are the three helpers buildPluginList calls to assemble better-auth's plugins:

  • addOptionalPlugin: one optional plugin whose construction throws is skipped, recorded in degradedFeatures and logged, so it cannot take down core email/password and session auth (the 15.1.0 incident its docblock records);
  • buildJwtPlugin: the jwt plugin, built with a signing algorithm this host can use (the Ed25519 probe), the signing-key reader, and the /get-session header hook made non-fatal;
  • reportJwtSigningFailure: the record and once-per-build announcement of a JWT signing failure that hook catches.

What stays, and why:

  • resolvePasswordHasher stays. Its body reads isWebContainerRuntime, a module-level function that auth-manager.ts does not export and that installWebContainerRequestStatePolyfill also reads. index.ts re-exports auth-manager.ts whole (export * from './auth-manager.js'), so exporting it there would publish it, and moving it would move a declaration the polyfill shares. Per the landed note 6360455119, a body that reads an unexported module helper comes back as needs_decision before it is cut.
  • getDegradedAuthFeatures stays: it is the public, one-line read of the record addOptionalPlugin writes.
  • buildPluginList, which calls all three, is a later cut.
  • createDatabaseConfig, resolveAuthSecret and generateSecret are the database and secret createAuthInstance hands better-auth, a different area, so they take their own cut. The seat agreed both splits.

What moved

method base signature line body lines members it reads
addOptionalPlugin :3178 15 degradedFeatures
buildJwtPlugin :3242 55 config, ed25519ProbeResult (read and written), reportJwtSigningFailure
reportJwtSigningFailure :3308 15 degradedFeatures, jwtSigningFailureReported (read and written)

Lines are base eca6b61922. The members come from a walk of every this.X in each body with the TypeScript compiler API. No body has a bare this, this[...], arguments or super, or a this inside a function or a nested class. No body reads a static (AuthManager.X) or a module-level declaration of auth-manager.ts. The two assignments to a this member are buildJwtPlugin's to ed25519ProbeResult and reportJwtSigningFailure's to jwtSigningFailureReported.

Imports. Six imported names are read only by the moved text, so they leave auth-manager.ts and the new module imports each from the same place:

  • buildJwtPluginSchema (./auth-schema-config.js, still imported by auth-manager.ts for ten other names);
  • createSigningKeyReader (./signing-key-read-cache.js, still imported there for retireHeldKeyrings and UNKNOWN_SIGNING_KEY_REREAD_FLOOR_MS);
  • createHostUsableJwksReader, probeEd25519Support, protectGetSessionJwtHook and resolveJwtSigningAlgorithm (./jwt-key-algorithm.js).

Evaluation order. auth-manager.ts no longer imports ./jwt-key-algorithm.js itself. Nothing observable moves: that module has no imports, and its top level is three as const object literals, a type alias, interfaces and function declarations, so the point at which it is evaluated cannot be observed. signing-key-read-cache.ts imports it type-only.

The shape

  • The class keeps all three methods at their places, with their names, signatures and docblocks: the published .d.ts lists each one. Each body is now a host literal of exactly the members that body reads, typed as a Pick of PluginAssemblyHost, plus .call.
  • PluginAssemblyHost lives in the new module. It names the 5 members the bodies read, each typed as the class member (AuthManager['degradedFeatures'], …). The module imports AuthManager as a type only, so there is no runtime cycle.
  • reportJwtSigningFailure goes as a late-bound closure ((...a) => this.reportJwtSigningFailure(...a)). The /get-session guard calls it at request time, long after buildJwtPlugin returned, and still reaches the method on the manager, so a spy or double set there is still the one it reaches.
  • config and degradedFeatures go as getters. The class reassigns config after construction (setRuntimeBaseUrl, applyConfigPatch), so it is read off the manager when a body reads it, as before. degradedFeatures is read the same way.
  • ed25519ProbeResult and jwtSigningFailureReported go as get/set pairs. The bodies store to both on the manager. So the Ed25519 probe still runs once per manager, the () => this.ed25519ProbeResult! thunk still reads the manager's promise when better-auth calls it, and a signing failure is still announced once per plugin-list build, which resets the flag (buildPluginList, which stays).
  • Two stubs are no longer async. Each returns the moved async function's own promise, so a caller awaits the same promise the body settles. Building the host cannot throw, so no method now throws synchronously where it used to reject. reportJwtSigningFailure was synchronous and stays so.
  • The cost: one small host object per call. buildJwtPlugin and addOptionalPlugin run once per plugin-list build; reportJwtSigningFailure runs only on a signing failure.
  • Nothing re-exports the new module, so the published surface does not move.

Path readers

None needs migrating: no reader keys on a moved line. Measured as reading no moved line:

  • api-error-code.pin.test.ts's authManagerFiles floor: no new APIError( sits in the moved bodies, so the floor reads the same sites over the same files at base and head, and the new module is not added to the list.
  • Plugin-factory scans in better-auth-schema-parity.test.ts and managed-extension-fields.test.ts: the moved text has no await import(. buildJwtPlugin takes the jwt factory as a parameter, and the factory's import stays in buildPluginList.
  • check-dispatcher-error-vocabulary.mjs, check-auth-mount-ledger.mjs and auth-manager-base-path.test.ts: none of the tokens they read sits in the moved text. check-dispatcher-error-vocabulary reads 56 sites found and 56 classified at base and at head.
  • The tenant-audit census and its two documents: the moved text holds no engine write. Compared per site, base and head read the same 237 sites with the same totals (below), so neither document is regenerated.
  • The system-context census: the moved text holds no isSystem read. The census reads 148 elevation read sites at base and at head.
  • scripts/adr-anchors/: the moved text cites no ADR, and no entry's invariant describes it. The auth-manager.ts entry (ADR-0093 and ADR-0108) is unchanged and still holds.
  • Callers through the manager: the suites that drive the three methods, among them auth-manager.jwt-eddsa-fallback.test.ts (the host without Ed25519, an existing EdDSA key hidden and never deleted, and the signing failure recorded and announced once) and auth-manager.optional-plugin-isolation.test.ts (a throwing optional plugin skipped while sign-up and get-session still answer, and the degraded state reset on rebuild), call each through the class method. All pass at head (below).
  • Every symbol anchor into the file. Before the cut, every auth-manager.ts#symbol anchor in the tracked tree outside changelogs was mapped with symbolSegmentResolution from scripts/symbol-anchors.mjs. There are 29, in ADR-0127, ADR-0134, ADR-0135 and four platform-checklist areas, and all 29 are declaration-class at base, with the 3 bodies blanked, and at head. None names a moved method. One names a token the moved text also carries: #AuthManager (ADR-0135), in the bodies' [AuthManager] log prefixes. It resolves on the class declaration, which stays.
  • check-adr-anchors' decision-letter layer (the seat asks each cut's report for this count at both ends): 2,016 citations at base and 2,016 at head. The moved text carries no ADR-NNNN Dk citation, so nothing leaves the anchored surface. The number layer reads 52,116 citations across 6,623 files at both. On origin/main 7c68b10825 (below) the layer reads 2,014 alone and 2,014 with this cut.

Proof

Builds and suites ran through os-verify-lock, and every exit code was captured before any pipe. Base eca6b61922, head eda6277866. A full turbo run build ran first. Then plugin-auth was built and its suite run at base, then built and run at head, on the same dependency dists.

1. The moved text.

  • For each of the 3 methods, the base class body and the new module's function body are the same TypeScript token sequence (scanner tokens, kind and text, trivia skipped, each template literal re-scanned at its substitutions as the parser does).
  • Every line is the base line minus exactly two leading spaces, and the parameters and return type are the same text.
  • Each class method keeps its docblock and signature, with async dropped from the two that had it.
  • Result: 3 of 3 pass, 85 body lines.
method body lines tokens
addOptionalPlugin 15 87
buildJwtPlugin 55 168
reportJwtSigningFailure 15 67
  • Negative control: one token changed in the module ('jwtSigning' → 'jwtSigned' in reportJwtSigningFailure) makes that method fail (tokens and lines DIFFER), and the run exits 1. Restored afterwards; cmp against the saved copy was clean.
  • An independent reading: git diff --color-moved=plain --color-moved-ws=allow-indentation-change eca6b61922 eda6277866 over the two source files marks 85 of the 89 deleted lines and 96 of the 202 added lines as moved.
    • The other 4 deleted lines are the two async signature lines and two import-name lines (buildJwtPluginSchema, createSigningKeyReader). The ./jwt-key-algorithm.js import block is marked moved: the module carries it verbatim.
    • The other added lines are the stubs' host literals, PluginAssemblyHost, the module header, the per-function pointer docblocks and the import blocks.

2. .d.ts and exports (ruling 6348891160 items 1 and 2).

file raw base raw head raw diff lines normalised base normalised head
dist/index.d.ts 3b3aedb6551d6324c40d87afd1cdee50b23bcb2c3384c39a9f71b771deca0472 3b3aedb6551d6324c40d87afd1cdee50b23bcb2c3384c39a9f71b771deca0472 0 d4a901d16c32f4838c14dd7fdc52d88d9d446b4b1d44afd4012c81b365ef9783 d4a901d16c32f4838c14dd7fdc52d88d9d446b4b1d44afd4012c81b365ef9783
dist/index.d.mts e2cdb2bc530cc6a554107866875910a90031e42fc89574d9adcb00d38a1becf1 e2cdb2bc530cc6a554107866875910a90031e42fc89574d9adcb00d38a1becf1 0 b2c585e677d5ac57274c22d746f6213342fbf1e7c8061b56b94175e8fe21c19d b2c585e677d5ac57274c22d746f6213342fbf1e7c8061b56b94175e8fe21c19d
  • The normaliser, verbatim (the ruling's one-liner): node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum.
  • The raw files are already byte-identical, so no union-order swap occurs in this cut: items 1(b) and 1(d) hold with nothing to account for.
  • Positive control: the head dist/index.d.ts with one private member made public (private buildJwtPlugin; → buildJwtPlugin;, line 1881, a one-line change) hashes raw 8eaebd38dabcfa33bbe0db93e6bd2c8836c91a169d81c8e6aa2ec4e78055b2de and normalised 4c5bbc92792de5db2c8f1b89018fd45d4aea96fe43b7479e3c934db9069c6169. Both differ from the readings above.
  • Export lists: ESM dist/index.mjs and CJS dist/index.js each have 155 names at base and at head, the same list (the sha256 of the sorted names is ed3ac8b902031d30… on all four).
  • The bundle: 20 files in dist/ on both sides, the same list. Only index.js, index.mjs, their two .map files and the two build-input hash stamps differ, as a move of code inside the bundle must.

3. Tests and lint.

  • pnpm exec vitest run --maxWorkers=3 in packages/plugins/plugin-auth: base Test Files 187 passed (187), Tests 3528 passed | 10 skipped (3538), VERDICT command-exit 0. Head: the same counts, VERDICT command-exit 0.
  • pnpm --filter @objectstack/plugin-auth typecheck (both tsc projects and check:test-typecheck): exit 0.
  • eslint --no-inline-config (the root lint script's binary and flags) over the 2 files: exit 0.

4. The census and the anchors, at base and at head.

  • The tenant-audit census (node scripts/tenant-audit-census.mjs --json), compared per site with the new module's file name mapped back to auth-manager.ts: 237 sites on both sides, 0 per-site differences in object, verb, tenancy, context and elevation, 0 unresolved and 0 unledgered on both, and every total the same.
  • These checkers were run by hand at base and at head, because dispatch-gates does not derive the ADR one. All six exit 0 at both, with the same reading at both:
    • check-adr-symbol-anchors: 2,384 anchors across 142 records, 466 symbol (440 declaration, 26 literal).
    • check-adr-anchors: 80 anchored files; the decision-letter layer as above.
    • check-scripts-symbol-anchors (4,061 anchors), check-spec-docblock-symbol-anchors (5,071 anchors, both read in the same built tree), check-system-context-census (148 elevation read sites) and check-platform-checklist (16 areas, 329 items).

5. Gates.

Changeset

None: skip-changeset, per ruling 6348891160 item 4. The published declarations and export lists are unchanged, and the bundle's bytes move only because code moved inside it.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr

…o auth-manager-plugin-assembly.ts

Three method bodies move unedited: addOptionalPlugin, which keeps one
optional better-auth plugin's construction failure from taking down
core auth; buildJwtPlugin, which builds the jwt plugin with an
algorithm this host can use and a non-fatal /get-session hook; and
reportJwtSigningFailure, which records and announces a signing failure
once. The class keeps all three methods, their docblocks and
signatures, and hands each body the members it reads.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

11 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7c68b108252ece317efb6672b92ef768da81fff0 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9781c84984b40c0b9888a9e301d2a3c22e60279d — the merge of head eda627786625ca08f614a270b1529f6a4444fc23 into base 7c68b108252ece317efb6672b92ef768da81fff0, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9781c84984b40c0b9888a9e301d2a3c22e60279d && git checkout 9781c84984b40c0b9888a9e301d2a3c22e60279d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7c68b108252ece317efb6672b92ef768da81fff0 eda627786625ca08f614a270b1529f6a4444fc23 && git checkout -B drift-repro 7c68b108252ece317efb6672b92ef768da81fff0 && git merge --no-ff eda627786625ca08f614a270b1529f6a4444fc23

node scripts/docs-audit/affected-docs.mjs --json 7c68b108252ece317efb6672b92ef768da81fff0

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-23T09:29Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25224 cut 6 (amended claim 6361212912, Ruling-ref: 6348891160; os-dev-report 6361689338; card session branch claude/project-thread-lg3wrj) · reviewed head eda627786625ca08f614a270b1529f6a4444fc23 · merge base eca6b61922


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 09:30
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 09:31
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 860f002 Oct 23, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lg3wrj branch October 23, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants