Skip to content

[maintainer] refactor(plugin-auth): split src/auth-manager.ts (10,338 lines, +1,867 in 30 days) along its AuthManager seams, one move-only slice per PR #25224

Open

Description

@objectstack-fleet

Filing gate ③: a task the maintainer dispatched, quoted verbatim below. Filed by domain:cli seat 1 (seat post #6024) · os-sales · session session_01CxBKycaiUpA1yGx1XyoGqX, as the restart act of #24586. ⛔ Not a claim.

The maintainer's words, verbatim (#24586, recorded in 6270097840): 「24586 同意A,是否建议这波分拆等 v18 发布之后再处理。」 The restart condition (npm @objectstack/spec latest ≥ 18.0.0) was read true by triage in 6291735609. The ruling's restart act: "one [maintainer] card per file over 10,000 lines into its owning lane … Each card starts with a fresh seam measurement taken at that time, in #23050's shape." This file was under the line at the ruling (8,471 lines 30 days ago) and is over it now, so the ruling's per-file rule covers it.

Reader who acts: the domain:services seat (packages/plugins/plugin-auth is that lane's), one dispatch per slice.

Measured (on origin/main 123ff6f4e, 2026-10-22)

History note: this clone's history starts 2026-09-17, so "total" commit counts are lower bounds; the 7- and 30-day windows are complete, and git blame --since=2026-09-22 agrees with a diff against the last pre-window commit fa2980341 to within 7 lines.

  • Size and churn: 10,338 lines (8,471 thirty days ago: +1,867, +22.0%). 24 commits in 7 days, 47 in 30 days. git blame --since=2026-09-22 attributes 2,277 lines (22.0%) to the last 30 days.
  • Structure: a 1,583-line module preamble (AuthManagerOptions alone is 470 lines), then one AuthManager class (8,755 lines, 181 members). Two methods dominate: createAuthInstance (1,424 lines, the better-auth config with about 30 sub-banners in its hook arms) and buildPluginList (1,336). The largest sections:
section lines lines from the last 30 days
module preamble 1,583 341
plugin assembly (resolvePasswordHasher, addOptionalPlugin, buildJwtPlugin, buildPluginList) 1,551 241
createAuthInstance 1,423 324
request routing, base paths, subject erasure, SCIM, MCP URLs, getPublicConfig, SSO 1,376 168
membership, audience admission, self-registration grants, bootstrap staging 1,206 416
password policy, lockout, auth gates, session controls 1,155 330
email/SMS delivery + locale 560 58
actor + organization guards 553 191
  • Shared state a split must pass: config (171 refs), getDataEngine (22), logDurabilityDegradation (12), auth, resolveActor, the request-scoped staging maps (pendingSelfRegistrationGrants, pendingOwnerVerifiedStamps, pendingOperatorProvisioning). createAuthInstance's hook arms call into the membership, guard, password and email sections.
  • What reads it by path: 12 scripts/ files, including the ADR anchor scripts/adr-anchors/packages__plugins__plugin-auth__src__auth-manager.ts.json (ADR-0093, ADR-0108), check-auth-mount-ledger.mjs, check-dispatcher-error-vocabulary.mjs, tenant-audit-census.mjs, publish-smoke.sh and four baselines; 19 files under content/ and docs/; 119 test files.
  • In flight: none of the 10 open PRs touch the file.
  • ⚠️ This is an authentication boundary. Move-only is the whole contract here: a slice that changes any sign-in, session or membership outcome is out of scope.

The ask (from #24586's ruling and #23050's shape)

  1. Split along the existing seams, one area per PR. Each slice is move-only: no logic changes.
  2. Shared state stays explicit: moved code reaches the class's shared members through one named context object. ⛔ Not a second copy of a helper, ⛔ not a re-derivation.
  3. Every slice proves it moved nothing: every gate, baseline, ADR anchor and docs anchor that reads the file by path is migrated in the same PR and stays green. ⛔ A gate whose target moved must not go silently empty.
  4. Serial with the file's in-flight PRs (a slice waits for an open PR on the same region, or the PR merges main after the slice).
  5. ⛔ No new gate.

Not in this card

Any behaviour change. A design change to the shared members beyond giving them an explicit context. Splitting files in other lanes (#24586 hands those to their own seats).

Dedupe

MCP search_issues on this repository, closed included: "split large file move-only service-automation engine.ts security-plugin.ts auth-manager.ts decompose modules" → 2 hits: #24586 (the ruling this card carries out) and #23050 (closed, rest-server.ts). New.


Generated by Claude Code

Activity

  1. added
    enhancementNew feature or request
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    on Oct 22, 2026
  2. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-22T13:23Z. ⛔ Not a claim. Thread-read: none

    packages/plugins/plugin-auth/src/auth-manager.ts is in flight under #25112 (claim 6341542393, the magic-link mount). The first slice of this card is cut only after #25112's PR lands, on a fresh seam reading of the merged file. Open PRs touching the file at this note: none besides that card's coming PR.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 1 (one cut = one PR, move-only, cuts strictly serial) · 2026-10-22T18:01Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-lg3wrj (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (fresh seam reading on origin/main 0f1eeee, after #25112's PR #25238 landed: auth-manager.ts is 10,349 lines, AuthManager has 176 members; stop on a breach and explain it in the report): cut 1 moves the body of AuthManager#buildPluginList (:3424-:4768, 1,345 lines, the largest single member after createAuthInstance) unedited into one new module, packages/plugins/plugin-auth/src/better-auth-plugin-list.ts, by #23050's pattern (landed note 6136102437, steps 1-6): the class keeps the private method and its docblock, and its body becomes one named host literal plus .call. The body reads 22 class members (AST walk on 0f1eeee): 2 fields read (config, reassigned after construction, so a getter; degradedFeatures, never reassigned), 2 fields written (jwtSigningFailureReported, orgRolesMap, so accessors), and 18 methods (late-bound closures). In the same PR, every reader whose reading includes a moved line is re-pointed with its sum unchanged: in-package source scanners (better-auth-schema-parity.test.ts and managed-extension-fields.test.ts read the plugin factory imports the body carries; the rest found by measurement), any docs/qa/platform-checklist/areas/*.json symbol anchor that resolves inside the moved range, and any scripts/ reader of moved lines (declared cross-lane in the PR). ⛔ Not packages/spec, not any ADR, not createAuthInstance or any other member. Later cuts each take their own amended claim on the merged file.
    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface; the proof owed is @objectstack/plugin-auth's dist/*.d.ts byte-identical to base and the same export list.
    Responsibility: n/a — not a defect card
    Thread-read: 6341869420
    Serial constraints cleared: none of the 14 open PRs touches packages/plugins/plugin-auth/src/auth-manager.ts (each PR's file list read this act). #25265 touches four other plugin-auth files (last-admin-guard.ts, default-organization-invariant.ts, ensure-default-organization.ts, tenancy-service.ts) and its own test, none of them a reader of the moved range. The serial predecessor named in 6341869420 (#25112) landed as PR #25238.
    Resume-from: none


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 1 (one cut = one PR, move-only, cuts strictly serial) · amends claim 6347670835, whose cut-1 file surface breached on measurement · 2026-10-22T19:06Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-lg3wrj (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (amended; same base 0f1eeee): the breach. 6347670835 put AuthManager#buildPluginList in cut 1. Measured before any reader was counted, that move does not stand alone: its body needs module-level helpers that only it reads (the organization slug guard, the organization-hook codes block at :1492-:1548, consentOrganizationOf) and two names it shares with the rest of the file (readBooleanEnv, OAUTH_ORGANIZATION_CLAIM), which index.ts's export * from './auth-manager.js' forbids exporting from auth-manager.ts (the published .d.ts would widen), so they need a non-re-exported shared module too. That came to about 3,075 changed lines, over the card's 3,000-line human-merge line, and the range also carries an ADR-0108 anchor. buildPluginList therefore becomes a later cut, landed by hand. Cut 1 is now the actor and organization guards: eight AuthManager methods (resolveActor, isPlatformAdminUserId, assertActorNotHeld, assertOrganizationDeletable, assertRemoveMemberPermitted, assertRevokeSessionIdentifiesRecord, assertAdminRevokeUserSessionIdentifiesRecord, resolveMembershipRole; 358 body lines) and the module-level INVITATION_ROLE_CAP_READ_FAULT_CODE / invitationRoleCapReadFaultApiError (:1464-:1490, read only by those bodies) move unedited into one new module, packages/plugins/plugin-auth/src/auth-manager-actor-guards.ts, by #23050's pattern (landed note 6136102437). The class keeps each method and its docblock; each body becomes a per-method host (Pick<ActorGuardHost, …>) plus .call. The bodies read five class members (AST walk on 0f1eeee): orgRolesMap and organizationDeleteStandingJudge (fields, handed over as getters) and computeAuthGate, getDataEngine, resolveActor (late-bound closures). Readers whose reading includes a moved line, re-pointed in the same PR with their sums unchanged: api-error-code.pin.test.ts (the per-file floor), better-auth-schema-parity.test.ts and managed-extension-fields.test.ts (the plugin factory scan: hasPermission, defaultRoles), organization-delete-read-fault.test.ts (the caller frame), and one row of packages/runtime/src/dispatcher-error-vocabulary.ts (domain:cli, declared cross-lane in the PR). No changeset: skip-changeset, per ruling 6348891160 item 4. No docs/qa/platform-checklist anchor and no ADR anchor names a moved symbol (measured). ⛔ Not packages/spec, not any ADR, not buildPluginList, createAuthInstance or any other member. Later cuts each take their own amended claim on the merged file.
    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface. The proof owed is ruling 6348891160 item 1 (on #25222, covering this card), read on @objectstack/plugin-auth's dist/index.d.ts and dist/index.d.mts, each: the same declaration set (no private member made public or protected; nothing added, removed, renamed or retyped); byte-identical to base after one normalisation only, sorting the members of each string-literal union in place; the same ESM and CJS export lists; and every raw-diff line a union-member swap. Measured at this cut's head: the raw files are byte-identical to base (0 diff lines), with the ruling's positive control in the PR and the report.
    Responsibility: n/a — not a defect card
    Thread-read: 6347670835
    Ruling-ref: 6348891160
    Serial constraints cleared: re-read this act. None of the 13 open PRs touches any of this cut's seven files (each PR head diffed against its merge base with origin/main; #24467's one file read through the API). New since 6347670835: #25268 (packages/mcp, packages/qa), #25266 (plugin-sharing, plugin-security, cli, spec, docs) and #25270 (service-automation, #25222's slice 1). #25265 still touches five other plugin-auth files, none of them in this cut; the Version Packages PR #24876 touches only plugin-auth's CHANGELOG.md and package.json. origin/main gained three commits since 0f1eeee, none touching these files.
    Resume-from: none


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25224,
    "status": "done",
    "branch": "claude/project-thread-lg3wrj",
    "pr": "#25273",
    "session": "session_01LSXBc2GCBwiAL7vV4kWkyr",
    "premise_still_valid": true,
    "summary": "Cut 1 of the auth-manager.ts split, per the amended claim 6349004758 (Ruling-ref 6348891160): eight AuthManager method bodies (resolveActor, isPlatformAdminUserId, assertActorNotHeld, assertOrganizationDeletable, assertRemoveMemberPermitted, assertRevokeSessionIdentifiesRecord, assertAdminRevokeUserSessionIdentifiesRecord, resolveMembershipRole; 358 lines) and the module-level INVITATION_ROLE_CAP_READ_FAULT_CODE block (base :1464-:1490) move unedited into packages/plugins/plugin-auth/src/auth-manager-actor-guards.ts by the landed #23050 pattern: the class keeps each method, signature and docblock, its body a per-method host of exactly the members it reads (three methods as late-bound closures, two reassigned fields as getters) plus .call, async dropped. Five path readers whose reading included a moved line were re-pointed in the same PR with their sums unchanged, one of them cross-lane (packages/runtime/src/dispatcher-error-vocabulary.ts, domain:cli, one row's file field). The first claim 6347670835 named buildPluginList for cut 1; measured, that move is about 3,075 changed lines (over the 3,000-line human-merge line) and carries an ADR-0108 anchor, so it becomes a later cut landed by hand, as the amended claim records. No sign-in, session or membership outcome changes: same tests, same published declarations, same export lists.",
    "tests": "Base 0f1eeee, head 26c8deb (source unchanged after e55cb6d; 26c8deb only drops the changeset e315a80 added). (1) Move proof, TS scanner token identity per body plus every line = base line minus two spaces plus same params/return plus stub docblock and signature kept with async dropped: 8 of 8 pass, 358 body lines; base :1464-:1490 (27 lines) verbatim in the module and absent from auth-manager.ts. git diff --color-moved=plain --color-moved-ws=allow-indentation-change marks 398 of 407 deleted and 435 of 566 added lines moved; the 9 other deleted lines are the 8 private async signature lines and one import name. (2) Ruling 6348891160 item 2 block, plugin-auth built through turbo run build at base and head: dist/index.d.ts raw base 82729d0115e4a866ed30dde301da18f96bad321e9a78fcd12b2cdd77bca3801f, raw head the same, normalised base 27d278f6825004c0b604eb60bbf9b3cd0fd8f66a89300bbe62bf7304238f6802, normalised head the same; dist/index.d.mts raw base 9665953d56da469ac783292e8406d09544f8cfe219bac7ef892625fe9a73f96a, raw head the same, normalised base 16f1ed01f0391a8bb5d7de138cbd0f9a7915b83faa9675009cc2996d8cbdb841, normalised head the same; raw diff 0 lines on both, so no union swap occurs and item 1 holds outright. Normaliser verbatim: 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. Positive control: head dist/index.d.ts with private resolveActor; made public (line 3662, one line) hashes raw 35f7e05b837ddc0f4610783c831b6857e7c5ae7b3bafd23d726ad871255ea299 and normalised 284877cf4dad17a7fe392f3ba8600f57ed6e5321c33a5109bd96b98e1bb66a29, both differ. Export lists: ESM dist/index.mjs and CJS dist/index.js 155 names each at base and head, same sorted-name hash ed3ac8b902031d30 on all four. (3) os-verify-lock -c 'pnpm exec vitest run --maxWorkers=3' in packages/plugins/plugin-auth at e55cb6d: Test Files 185 passed (185), Tests 3487 passed | 10 skipped (3497), VERDICT command-exit 0, the same counts as base. Readers red before their edit, at 1863367 (the move alone): full suite Test Files 3 failed | 182 passed (185), 7 tests (organization-delete-read-fault.test.ts 5 of 7; better-auth-schema-parity and managed-extension-fields: AUTH_MANAGER_PLUGINS names plugins auth-manager.ts no longer imports: defaultRoles, hasPermission); api-error-code.pin.test.ts run alone: expected 18 to be greater than or equal to 24, exit 1; pnpm check:dispatcher-error-vocabulary exit 1 (stale-row plus unclassified-site), exit 0 after the row moved (56 found; 56 classified). pnpm --filter @objectstack/plugin-auth typecheck exit 0; pnpm --filter @objectstack/runtime typecheck exit 0 after the full build; eslint --no-inline-config over the 7 files exit 0. No tested behaviour changed, so no ablation is owed.",
    "gates": "Local subset at 26c8deb: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 59 commands; all 59 ran after a full turbo run build and git fetch --unshallow, each exit code captured before any pipe, all 59 exit 0; --ran: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN. node scripts/pm/check-governed-merges.mjs --branch HEAD: NOT governed, 1,010 changed lines (+598/-412, 7 files) vs the 3,000-line human-merge line. Census gates base vs head: same summaries for check-system-context-census, check-tenant-audit-census, check-registry-log-declared, check:durability-log-level, check:auth-mount-ledger, check:cross-package-test-inputs; scanned-file counts move only by the new module. test:repo not run locally (no repo-project test changed and the derived list does not name it); CI runs it. CI on 26c8deb, the PR's check-runs read at report time (46): 34 success, 12 skipped (path-filtered or opt-in jobs), 0 failure, 0 pending. Build Core success; Test Core (1/6) to (6/6) success and Test Core success; TypeScript Type Check success (Type Check · workspace, · source gates, · consumer gates, · debt ledger: success); Lint & Repo Gates success (Lint · ESLint, · repo & tooling gates, · docs & code guards, · CI, release & engine gates, · PM dispatch-gates, · scoped · tooling self-tests, · scoped · query-options erasure, · scoped · slot lookup & corpus: success); Dogfood Regression Gate success (its shard matrix skipped by its filter); Check Changeset success; Governed Surface Queue Guard success; Check PR Size success; Part-of PR must not also close its card success; The card this PR closes must claim this branch success; No other open PR may claim the same issue success; No other open PR may claim the same single-writer path success; Check Documentation Links success; Flag docs affected by code changes success. mergeable_state clean. No Claude Approvals check runs on this PR. Docs Drift Check (advisory, never fails the build) comment 6349355591: re-derived with node scripts/docs-audit/affected-docs.mjs --json 0f1eeee, exit 0: 35 pages, every one listed through a literal the moved bodies carry unchanged (SERVICE_UNAVAILABLE 11, sys_member 14, sys_organization 10, sys_session 9, NOT_FOUND 1) plus one name collision (content/docs/permissions/system-context.mdx:171 names plugin-approvals' approval-service.ts #resolveActor, a different method); no page restates a rule this move changes, so no doc edit is owed.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "17 — all reads, no write tool: issue_read get 2, issue_read get_comments 3, list_pull_requests 1, search_pull_requests 3, pull_request_read get 2, pull_request_read get_files 4, pull_request_read get_check_runs 2. (claude-code-remote subscribe_pr_activity once, not a GitHub write.)",
    "api_writes": "5 relay acts (scripts/pm/ fleet-write, each read back): 1) label-write on #25224, run 39827597533, 3 actions: POST labels pm:dispatched, DELETE label pm:queue, POST assignees os-sales; 2) post-stamped claim comment 6347670835 on #25224, run 39827763009; 3) post-stamped amended claim comment 6349004758 on #25224, run 39837221540; 4) dispatch pr_create #25273 (draft, assignee os-sales, label skip-changeset; body read back identical), run 39839634890; 5) this report comment on #25224 via post-stamped. Plus git push of the branch (not REST). No other write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed — api-error-code.pin.test.ts at 1863367 failed its per-file floor run alone (expected 18 to be greater than or equal to 24) but the full plugin-auth run at the same head did not report it red; not reproduced, the re-pointed floor is green in both shapes; recorded in the PR Proof section"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 1 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-22T21:00Z

    reading value
    PR #25273 MERGED through the queue (ACCEPT 6350658338)
    origin/main carries 49185cdb8a

    State: the card stays open and dispatched (Part of); the card thread claims cut 2 afresh on the merged file, per 6349004758.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 2 (one cut = one PR, move-only, cuts strictly serial) · amends claim 6349004758 for cut 2, on the merged file · 2026-10-22T21:28Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-lg3wrj (the harness-pinned branch for this session, restarted from origin/main after cut 1's PR #25273 merged; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (fresh seam reading on origin/main 49185cdb8a, cut 1 landed per 6351216603: auth-manager.ts is 10,004 lines): cut 2 is the sign-in lockout path (ADR-0069 D2) and the last-login stamp (D7). Seven AuthManager methods (assertAccountNotLocked, recordSignInOutcome, auditRefusedSignIn, resolveSignInAccountId, findSignInAccount, resolveTwoFactorAccountLockout, stampLastLogin; 173 body lines) move unedited into one new module, packages/plugins/plugin-auth/src/auth-manager-sign-in-lockout.ts, by #23050's pattern (landed note 6136102437), the shape cut 1 landed. The class keeps each method and its docblock; each body becomes a per-method host plus .call. The bodies read five class members (AST walk on 49185cdb8a): config (a field the class reassigns in setBaseUrl and applyConfigPatch, so a getter) and findSignInAccount, getDataEngine, logDurabilityDegradation, resolveSignInAccountId (late-bound closures). No module-level declaration moves; the imports only these bodies use leave with them (accountLockedEventFor, refusalCodeOf, signInClientFingerprint, signInFailureEventFor, and the EngineQueryOptions type). Readers whose reading includes a moved line, re-pointed in the same PR with their sums unchanged: api-error-code.pin.test.ts (its sum of throws over the files AuthManager's bodies live in; the ACCOUNT_LOCKED throw moved, so the module joins the list and the sum stays 27). Measured as not reading a moved line: the two plugin-factory scans (the module imports no factory), check:dispatcher-error-vocabulary (exit 0), the query-options erasure ratchet (the moved bodies hold 0 sites; auth-manager.ts keeps its 12, so the baseline holds), and no docs/qa/platform-checklist anchor, ADR anchor or docs page names a moved symbol. Why this seam and not the whole ADR-0069 tail: the first reading took the 16 hardening methods at :8852-:9951. Five of them (assertPasswordNotReused, recordPasswordHistory, unlockUser, enforceSessionControls, enforceConcurrentCap) hold 6 of the file's 12 query-options any-erasure sites, and a site moved into a file scripts/query-options-erasure-baseline.json does not list fails pnpm lint and the shrink-only baseline, so a move cannot carry them. They stay, with a needs-decision on how later cuts carry such sites reported to the coordinator; this cut is the largest coherent seam that holds none. No changeset: skip-changeset, per ruling 6348891160 item 4. ⛔ Not packages/spec, not any ADR, not the five methods above, not buildPluginList, createAuthInstance or any other member. Later cuts each take their own amended claim on the merged file.
    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface. The proof owed is ruling 6348891160 item 1 (on #25222, covering this card), read on @objectstack/plugin-auth's dist/index.d.ts and dist/index.d.mts, each: the same declaration set (no private member made public or protected; nothing added, removed, renamed or retyped); byte-identical to base after one normalisation only, sorting the members of each string-literal union in place; the same ESM and CJS export lists; and every raw-diff line a union-member swap. Measured at this cut's head: both raw files are byte-identical to base (0 diff lines), so item 1 holds with no union swap to account for; the hashes, the normaliser and the ruling's positive control are in the PR and the report.
    Responsibility: n/a — not a defect card
    Thread-read: 6351216603
    Ruling-ref: 6348891160
    Serial constraints cleared: re-read this act. None of the 14 open PRs touches any of this cut's three files (each PR head diffed against its merge base with origin/main; #24467's one file, scripts/test-shard-timings.json, read through the API). #25286 touches plugin-auth's last-admin-guard.ts and a new test of its own, neither in this cut; the Version Packages PR #24876 touches only plugin-auth's CHANGELOG.md and package.json. #25290 (#25223's cut 1) and #25288 are plugin-security.
    Resume-from: none


    Generated by Claude Code

  8. 18 remaining items

  9. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 5 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T08:07Z

    reading value
    PR #25395 MERGED through the queue (ACCEPT 6360020819)
    origin/main carries eca6b61922

    State: the card stays open and dispatched (Part of). The card thread claims the next cut (addOptionalPlugin, buildJwtPlugin, reportJwtSigningFailure) afresh on the merged file. Cuts whose bodies read unexported module helpers come back as needs_decision, because plugin-auth's index.ts is export * from './auth-manager.js'.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 6 (one PR, cuts strictly serial: after cut 5, PR #25395, landed as eca6b61922, per the landed note 6360455119) · amends claim 6359424739 for this PR, on the merged file · 2026-10-23T08:53Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    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 Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (read on origin/main eca6b61922, where auth-manager.ts is 8,968 lines):

    (a) The move: the plugin assembly helpers. The bodies of 3 AuthManager methods move unedited from packages/plugins/plugin-auth/src/auth-manager.ts into a new module, packages/plugins/plugin-auth/src/auth-manager-plugin-assembly.ts, in cut 1's shape: the class keeps every method with its docblock and signature (async dropped from the two async stubs), and each body becomes a literal of exactly the host members it reads plus a .call.

    • addOptionalPlugin :3178: the isolation that skips an optional better-auth plugin whose construction throws, records it in degradedFeatures, and keeps core email/password and session auth up.
    • buildJwtPlugin :3242: better-auth's 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 :3308: the once-per-build record and announcement of a JWT signing failure that hook catches.
    • No module-level declaration moves. The bodies read none that auth-manager.ts declares.

    How the host is built:

    • reportJwtSigningFailure goes as a late-bound closure. The /get-session guard calls it at request time, long after buildJwtPlugin returned, and it still reaches the method on the manager.
    • config and degradedFeatures go as getters. The class reassigns config after construction (setRuntimeBaseUrl, applyConfigPatch); degradedFeatures is read the same way.
    • ed25519ProbeResult and jwtSigningFailureReported go as get/set pairs, because the bodies store to both on the manager: the probe runs once per manager, and the failure is announced once per plugin-list build, which resets the flag (buildPluginList, which stays).

    Imports that only the moved text reads move with it:

    • 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). auth-manager.ts no longer imports that module. The order of evaluation is unchanged in effect: jwt-key-algorithm.ts 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.

    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. 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 is a later cut.
    • createDatabaseConfig, resolveAuthSecret and generateSecret stay for their own cut: they are the database and secret createAuthInstance hands better-auth, a different area. The seat agreed both splits.

    (b) Path readers. None needs migrating: no reader keys on a moved line. Read, and not edited:

    • 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;
    • the 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 its 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;
    • the tenant-audit census and its two documents: the moved text holds no engine write;
    • the system-context census: the moved text holds no isSystem read;
    • scripts/adr-anchors/: the moved text cites no ADR, and no entry's invariant describes it;
    • the suites that drive the three methods through the manager, among them auth-manager.jwt-eddsa-fallback.test.ts and auth-manager.optional-plugin-isolation.test.ts, which the stubs keep working.

    ⛔ Nothing else changes:

    • No other method moves, and no body or declaration is edited.
    • No anchor, ADR or docs prose is edited.
    • No edit to eslint.config.mjs, to any baseline, or to scripts/.
    • The title goes under refactor(plugin-auth), with no !.
    • No changeset: skip-changeset, per ruling 6348891160 item 4.

    Anchor reading before the cut, on eca6b61922:

    • I mapped every auth-manager.ts#symbol anchor in the tracked tree, outside changelogs, with symbolSegmentResolution from scripts/symbol-anchors.mjs. There are 29 of them.
    • With the 3 bodies blanked, none changes class: all 29 are declaration-class at base, blanked and head.
    • No anchor names a moved method. One anchor names a token that 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: 2,016 citations at base and 2,016 at the draft. The moved text carries no ADR-NNNN Dk citation.

    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface. The proof owed:

    • token identity per moved body against this base, plus a negative control;
    • ruling 6348891160 item 2's block: raw and normalised sha256 of dist/index.d.ts and dist/index.d.mts at base and head, the normaliser verbatim, the raw diff line count, and the positive control;
    • ESM and CJS export lists;
    • plugin-auth suite counts at base and at head;
    • the census compared per site, base and head;
    • the symbol-anchor checks and check:adr-anchors (with the decision-letter count) at base and at head;
    • every derived gate.

    Premise (measured on this cut's commit eda6277866 on this base):

    • 3 of 3 bodies token-identical (85 body lines), and a one-token negative control ('jwtSigning' → 'jwtSigned' in reportJwtSigningFailure) exits 1;
    • pnpm --filter @objectstack/plugin-auth typecheck exit 0, and eslint --no-inline-config on the 2 files exit 0;
    • suite 187 files, 3528 passed and 10 skipped, the same as base;
    • dist/index.d.ts and dist/index.d.mts raw byte-identical to base, and the ESM and CJS entries export the same 155 names;
    • the census reads 237 sites at both, with 0 per-site differences and no total that moves;
    • the anchor map above.

    A diff of another kind is a fresh fork on this card.
    Responsibility: n/a — not a defect card
    Thread-read: 6360455119
    Ruling-ref: 6348891160
    Serial constraints cleared: read before the proof run and re-read this act. Of the 15 open PRs at the first reading, every head was diffed against its merge base with origin/main. One touched auth-manager.ts: #25398, which edits the docblock of enforceSessionControls (a stub cut 3 left, far below this cut's lines) and cut 3's module auth-manager-password-session-policy.ts. #25381 touched auth-plugin.ts, admin-user-endpoints.ts and the census documents; the Version Packages PR #24876 touches plugin-auth's CHANGELOG.md and package.json. No open PR touched the new module's path or the moved bodies' imports.
    Since then #25398, #25381, #25396 and #25397 merged, and origin/main is 7c68b10825. This cut stays on base eca6b61922, where every proof above was taken. On 7c68b10825 with this cut applied (git cherry-pick --no-commit, no conflict):


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25224,
    "status": "done",
    "branch": "claude/project-thread-lg3wrj",
    "pr": "#25400",
    "session": "session_01LSXBc2GCBwiAL7vV4kWkyr",
    "premise_still_valid": true,
    "summary": "Cut 6 of the auth-manager.ts split, per the amended claim 6361212912 (amends 6359424739 on the file cut 5 left; landed note 6360455119). Part of #25224, no closing keyword; #25224 stays open. Base origin/main eca6b61, head eda6277, +202/-89 over 2 files (291 changed lines). The plugin assembly helpers move unedited into packages/plugins/plugin-auth/src/auth-manager-plugin-assembly.ts in cut 1's shape: addOptionalPlugin (base :3178), buildJwtPlugin (:3242) and reportJwtSigningFailure (:3308), 85 body lines. The class keeps all three methods with docblock and signature (async dropped from two). reportJwtSigningFailure goes as a late-bound closure (the /get-session guard calls it at request time), config and degradedFeatures as getters, ed25519ProbeResult and jwtSigningFailureReported as get/set pairs (the bodies store to both on the manager). No module-level declaration moves and no body reads one, or a static. Six imported names read only by the moved text leave auth-manager.ts: buildJwtPluginSchema and createSigningKeyReader (their modules stay imported there for other names) and the four names of ./jwt-key-algorithm.js, a module with no imports whose top level is literal consts, a type alias, interfaces and function declarations, so its evaluation point is unobservable. No path reader needed migrating. Scope narrowed from the six methods first named to the seat, with the seat's agreement: createDatabaseConfig, resolveAuthSecret and generateSecret (createAuthInstance's database and secret inputs) take the next cut; resolvePasswordHasher stays because it reads the unexported shared isWebContainerRuntime, which per the landed note comes back as needs_decision (plugin-auth's index.ts is export * from auth-manager.js, so the #25223 add-export-in-place precedent would publish it). auth-manager.ts 8,968 to 8,916 lines. No sign-in, session or membership outcome changes.",
    "tests": "Base eca6b61, head eda6277; builds and suites through os-verify-lock, every exit code captured before any pipe; a full turbo run build first, then plugin-auth built and tested at base, then at head, on the same dependency dists. (1) Move proof: TS scanner token identity per body, template literals re-scanned at their substitutions, every line = base line minus two spaces, same params and return type, class stub docblock and signature kept with async dropped: 3 of 3 pass, 85 body lines (addOptionalPlugin 15 lines and 87 tokens, buildJwtPlugin 55 and 168, reportJwtSigningFailure 15 and 67). Negative control: one token changed in the module ('jwtSigning' to 'jwtSigned' in reportJwtSigningFailure) fails that method and the run exits 1; restored, cmp clean. git diff --color-moved=plain --color-moved-ws=allow-indentation-change marks 85 of 89 deleted and 96 of 202 added lines moved; the 4 unmarked deletions are the two async signature lines and two import-name lines. (2) Ruling 6348891160 item 2 block: dist/index.d.ts raw base 3b3aedb6551d6324c40d87afd1cdee50b23bcb2c3384c39a9f71b771deca0472, raw head the same, normalised base d4a901d16c32f4838c14dd7fdc52d88d9d446b4b1d44afd4012c81b365ef9783, normalised head the same; dist/index.d.mts raw base e2cdb2bc530cc6a554107866875910a90031e42fc89574d9adcb00d38a1becf1, raw head the same, normalised base b2c585e677d5ac57274c22d746f6213342fbf1e7c8061b56b94175e8fe21c19d, normalised head the same; raw diff 0 lines on both, so no union swap and item 1 holds outright. Normaliser: the ruling's one-liner, verbatim in the PR body. Positive control: head dist/index.d.ts with 'private buildJwtPlugin;' made public (line 1881, one line) hashes raw 8eaebd38dabcfa33bbe0db93e6bd2c8836c91a169d81c8e6aa2ec4e78055b2de and normalised 4c5bbc92792de5db2c8f1b89018fd45d4aea96fe43b7479e3c934db9069c6169, both differ. Export lists: ESM dist/index.mjs and CJS dist/index.js 155 names each at base and head, sorted-name hash ed3ac8b902031d30 on all four. 20 dist files on both sides, same list; only index.js, index.mjs, their maps and the two build-input hash stamps differ. (3) 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; the suites that drive the three methods through the manager (auth-manager.jwt-eddsa-fallback.test.ts, auth-manager.optional-plugin-isolation.test.ts) are among them. pnpm --filter @objectstack/plugin-auth typecheck exit 0; eslint --no-inline-config over the 2 files exit 0. (4) Tenant-audit census, base vs head per site via --json (new module mapped back to auth-manager.ts): 237 sites on both, 0 per-site differences in object, verb, tenancy, context and elevation, 0 unresolved and 0 unledgered on both, no total moves. (5) Anchors at base and at head, run by hand, all exit 0 with the same reading: check-adr-symbol-anchors (2,384 anchors, 466 symbol), check-adr-anchors (80 anchored files; decision-letter layer 2,016 at base and 2,016 at head, the moved text carries no ADR-NNNN Dk citation; number layer 52,116 citations across 6,623 files at both), check-scripts-symbol-anchors (4,061), check-spec-docblock-symbol-anchors (5,071, both read in a built tree), check-system-context-census (148 elevation read sites), check-platform-checklist (16 areas, 329 items). Anchor map before the cut: all 29 auth-manager.ts#symbol anchors outside changelogs are declaration-class at base, with the three bodies blanked, and at head; none names a moved method; #AuthManager (ADR-0135) also matches the bodies' [AuthManager] log prefixes and resolves on the class declaration. (6) Merge check on origin/main 7c68b10 (after #25398, #25381, #25396, #25397 merged) with this cut cherry-picked, no conflict: move proof against 7c68b10 3 of 3; check-tenant-audit-census, check-adr-anchors, check-adr-symbol-anchors, check-platform-checklist, check-system-context-census, check-auth-mount-ledger and check-dispatcher-error-vocabulary (56 found, 56 classified) exit 0; decision-letter layer 2,014 on 7c68b10 alone and 2,014 with this cut (the 2,016 to 2,014 step is main's own); seven suites (auth-manager.jwt-eddsa-fallback, auth-manager.optional-plugin-isolation, session-heartbeat-default-config, auth-manager, api-error-code.pin, better-auth-schema-parity, managed-extension-fields) 386 tests pass. No tested behaviour changed, so no ablation is owed.",
    "gates": "Local subset at eda6277 after the full build: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 53 commands for the 2 paths; all 53 ran, each exit code captured before any pipe, all 53 exit 0; --ran: 53 derived, 53 run, 0 NOT-MEASURED (the tool reads a command list; the per-command exit codes are in the run's record, all 0). node scripts/pm/check-governed-merges.mjs --branch HEAD: NOT governed, 0 of 2 paths hit the register, 291 changed lines vs the 3,000-line human-merge line. check-dispatcher-error-vocabulary at base and head: 56 found, 56 classified. CI on eda6277, the PR's check-runs read at report time (46): 34 success, 12 skipped, 0 failure, 0 pending. Skipped: Dogfood Verify CLI, Build Docs, Temporal Conformance (live PG + MySQL), Console Pin Gate, the Dogfood Regression Gate shard matrix, the opt-in 18.x upgrade harness and Packed-tarball smoke (twice each), and in the labeled run Auto Label, Check PR Size and Check Changeset. Success: Build Core; Test Core (1/6) to (6/6) and Test Core; TypeScript Type Check (Type Check · workspace, · source gates, · consumer gates, · debt ledger); Lint and Repo Gates (Lint · ESLint, · repo and tooling gates, · docs and code guards, · CI, release and engine gates, · PM dispatch-gates, · scoped · tooling self-tests, · scoped · query-options erasure, · scoped · slot lookup and corpus); Dogfood Regression Gate; Governed Surface Queue Guard; Check PR Size; Check Changeset; Auto Label; filter; Part-of PR must not also close its card; The card this PR closes must claim this branch; No other open PR may claim the same issue; No other open PR may claim the same single-writer path; Check Documentation Links; Flag docs affected by code changes. No Claude Approvals check runs on this PR. mergeable_state clean; the PR was opened as a draft. Docs Drift Check (advisory) comment 6361268166: nothing to list (11 anchors, no hand-written page names one), the same as node scripts/docs-audit/affected-docs.mjs --json eca6b61 (exit 0, 0 pages). Serial: open PR #25399 (#25083) touches auth-manager.ts inside createAuthInstance and buildPluginList, not a line of this cut; git merge-tree with its head is clean.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "11 since this cut began (07:33Z), all reads, no write tool: pull_request_read get 2 (#25395, #25400), pull_request_read get_check_runs 2 (#25395, #25400), pull_request_read get_status 1 (#25395), issue_read get 1 (#25224), issue_read get_comments 3 (#25224, for the landed notes 6358711845 and 6360455119), list_pull_requests 1, search_pull_requests 1. (claude-code-remote subscribe_pr_activity once on #25400, not a GitHub write.)",
    "api_writes": "3 relay acts this cut (scripts/pm/, each read back identical): 1) amended claim 6361212912 via post-stamped, run 39927841007, 10242 bytes; 2) pr_create #25400 via fleet-write, run 39928081252, 16719 bytes sent and stored, assignee os-sales, label skip-changeset; 3) this report via post-stamped. Plus git push of the branch (eda6277, a new branch after the queue deleted the old one on #25395's merge; not REST). No PR body edit. No other write.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 6 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T09:58Z

    reading value
    PR #25400 MERGED through the queue (ACCEPT 6361842970)
    origin/main carries 860f002f8b

    State: the card stays open and dispatched (Part of). The card thread claims cut 7 (createDatabaseConfig, resolveAuthSecret, generateSecret) afresh on the merged file.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 7 (one PR, cuts strictly serial: after cut 6, PR #25400, landed as 860f002f8b, per the landed note 6362338361) · amends claim 6361212912 for this PR, on the merged file · 2026-10-23T10:48Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-lg3wrj (the harness-pinned branch for this session, restarted from origin/main 860f002f8b after #25400 merged; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (read on origin/main 860f002f8b, where auth-manager.ts is 8,971 lines):

    (a) The move: the database and the auth secret createAuthInstance hands better-auth. The bodies of 3 AuthManager methods move unedited from packages/plugins/plugin-auth/src/auth-manager.ts into a new module, packages/plugins/plugin-auth/src/auth-manager-database-secret.ts, in cut 1's shape: the class keeps every method with its docblock and signature (async dropped from createDatabaseConfig), and each body becomes a literal of exactly the host members it reads plus a .call.

    • createDatabaseConfig :4670: the ObjectQL adapter factory with its created-user staging callback, or, with no data engine, the in-memory store and its warning.
    • resolveAuthSecret :4741: the auth secret, resolved once per manager.
    • generateSecret :4748: the environment read behind it, which refuses to boot production without a secret and warns in development.
    • No module-level declaration moves. The bodies read none that auth-manager.ts declares, and no static.

    How the host is built:

    • stageUserCreatedInRequest and generateSecret go as late-bound closures. The adapter's created-record callback runs stageUserCreatedInRequest inside every creating endpoint call, long after the factory was built, and still reaches the method on the manager.
    • config goes as a getter. The class reassigns it after construction (setRuntimeBaseUrl, applyConfigPatch).
    • resolvedAuthSecret goes as a get/set pair, because resolveAuthSecret stores the secret on the manager.
    • generateSecret reads no member, so its method calls the moved function directly.

    Imports: none leaves auth-manager.ts.

    • The new module imports readEnvWithDeprecation (@objectstack/types) and createObjectQLAdapterFactory (./objectql-adapter.js). auth-manager.ts keeps both import lines unchanged:
      • readEnvWithDeprecation for its other read (:3024, the CORS origin);
      • createObjectQLAdapterFactory because a docblock that stays links it ({@link createObjectQLAdapterFactory}, :895), and TypeScript resolves that link through the import. The same import line brings withSystemContext and withSystemReadContext, which the file still calls, so ./objectql-adapter.js stays imported at the same line.
    • Both modules are imported above the new module's import line, so neither module's evaluation point moves.
    • The new module's top level is three imports, an interface and three function declarations, so the point at which it is evaluated cannot be observed.
    • The await import('@better-auth/core/context') inside createDatabaseConfig's callback moves with the body, unedited. A bare specifier resolves the same from both files: they sit in the same directory and end up in the same bundle.

    What stays, and why:

    • createAuthInstance stays. Its body reads readDisableSignUpEnv, a module-level function that auth-manager.ts does not export and getPublicConfig also reads. Per the landed note 6360455119, that comes back as needs_decision before it is cut.
    • buildPluginList and resolvePasswordHasher stay for the same reason (readBooleanEnv, isWebContainerRuntime).
    • stageUserCreatedInRequest stays: it writes the request-scoped record of created users that the session seam reads, part of the membership area, a later cut.

    (b) Path readers. None needs migrating: no reader keys on a moved line. Read, and not edited:

    • The plugin-factory scans in better-auth-schema-parity.test.ts and managed-extension-fields.test.ts. The moved text carries one dynamic import their pattern matches, const { tryGetCurrentAuthEndpointContext } = await import('@better-auth/core/context'). Both scans skip every @better-auth/core specifier ("runtime utilities, not plugin factories"), so the factory set they derive is the same at base and head.
    • check-dispatcher-error-vocabulary.mjs's file-scoped import bindings (guard ③). tryGetCurrentAuthEndpointContext leaves auth-manager.ts's set. The binding matters only as the object of a member expression in an APIError argument, and no new APIError( sits in the moved text or the new module. The gate's reading is compared at base and head below.
    • 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.
    • check-auth-mount-ledger.mjs and auth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text.
    • The tenant-audit census and its two documents: the moved text holds no engine write.
    • The system-context census: the moved text holds no isSystem read.
    • scripts/adr-anchors/: no entry's invariant describes the moved text (see the decision-letter reading below).
    • The suites that drive the three methods through the manager, which the stubs keep working:
      • auth-manager.test.ts: "createDatabaseConfig – adapter wrapping", and "generateSecret – production secret guard (P0-1)", which calls generateSecret() on the manager;
      • user-created-in-request.test.ts, which awaits createDatabaseConfig() on the manager;
      • auth-route-ledger.conformance.test.ts (the in-memory fallback);
      • first-session-membership-ordering.test.ts (the created-user staging).

    ⛔ Nothing else changes:

    • No other method moves, and no body or declaration is edited.
    • No anchor, ADR or docs prose is edited.
    • No edit to eslint.config.mjs, to any baseline, or to scripts/.
    • The title goes under refactor(plugin-auth), with no !.
    • No changeset: skip-changeset, per ruling 6348891160 item 4.

    Anchor reading before the cut, on 860f002f8b:

    • I mapped every auth-manager.ts#symbol anchor in the tracked tree, outside changelogs, with symbolSegmentResolution from scripts/symbol-anchors.mjs. There are 29 of them.
    • With the 3 bodies blanked, none changes class: all 29 are declaration-class at base, blanked and head.
    • No anchor names a moved method. One anchor names a token that the moved text also carries: #AuthManager (ADR-0135), in the in-memory warning's text. It resolves on the class declaration, which stays.

    The decision-letter layer of check-adr-anchors drops by one, and the gate stays green.

    • createDatabaseConfig's callback comment cites ADR-0093 D7 (the adapter as the one place that sees the created user's id inside the creating call).
    • That layer checks ADR-NNNN Dk citations only in anchored files. auth-manager.ts is anchored and the new module is not, as cuts 1 to 6's modules are not.
    • So the layer reads one citation fewer at head: 2,015 at base 860f002f8b and 2,014 at head, and the gate exits 0 at both. On origin/main 2dfeb1a772 (below) it reads 2,005 alone and 2,004 with this cut.
    • auth-manager.ts's anchor entry names ADR-0108 and ADR-0093, and its invariant describes the role map and the D2 membership seams, not D7. Its required text stays in auth-manager.ts.
    • No anchor entry is added for the new module. An entry would need new invariant text, which a move-only cut does not add; the seat ruled the same for cut 5's ADR-0131 D7.

    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface. The proof owed:

    • token identity per moved body against this base, plus a negative control;
    • ruling 6348891160 item 2's block: raw and normalised sha256 of dist/index.d.ts and dist/index.d.mts at base and head, the normaliser verbatim, the raw diff line count, and the positive control;
    • ESM and CJS export lists;
    • plugin-auth suite counts at base and at head;
    • the census compared per site, base and head;
    • the symbol-anchor checks and check:adr-anchors (with the decision-letter count) at base and at head;
    • every derived gate.

    Premise (measured on this cut's commit 83f5b0249e on this base):

    • 3 of 3 bodies token-identical (97 body lines), and a one-token negative control ('sys_user' → 'sys_users' in createDatabaseConfig's callback) exits 1;
    • pnpm --filter @objectstack/plugin-auth typecheck exit 0, and eslint --no-inline-config on the 2 files exit 0;
    • suite 189 files, 3580 passed and 10 skipped, the same as base;
    • dist/index.d.ts and dist/index.d.mts raw byte-identical to base, and the ESM and CJS entries export the same 159 names;
    • the census reads 238 sites at both, with 0 per-site differences and no total that moves;
    • the plugin-factory scan finds the same 16 factories at both; check-dispatcher-error-vocabulary reads 56 found and 56 classified at both;
    • the anchor map and the decision-letter reading above.

    A diff of another kind is a fresh fork on this card.
    Responsibility: n/a — not a defect card
    Thread-read: 6362338361
    Ruling-ref: 6348891160
    Serial constraints cleared: read before the proof run. 13 PRs were open, and each head was diffed against its merge base with origin/main. None touches packages/plugins/plugin-auth/src, the new module's path, dispatcher-error-vocabulary.ts, the plugin-auth anchor entries, the census, or the readers named in (b). The Version Packages PR #24876 touches plugin-auth's CHANGELOG.md and package.json only.
    Since then #25401 (#25223's cut, plugin-security only) merged, and origin/main is 2dfeb1a772. This cut stays on base 860f002f8b, where every proof above was taken. On 2dfeb1a772 with this cut applied (git cherry-pick --no-commit, no conflict):

    • the move proof re-run against 2dfeb1a772 passes 3 of 3;
    • check-tenant-audit-census, check-adr-anchors, check-adr-symbol-anchors, check-platform-checklist, check-system-context-census, check-auth-mount-ledger and check-dispatcher-error-vocabulary (56 found, 56 classified) exit 0;
    • the decision-letter layer reads 2,005 on 2dfeb1a772 alone and 2,004 with this cut, the same drop of one;
    • the seven suites that drive the moved methods or read the file (auth-manager, user-created-in-request, auth-route-ledger.conformance, first-session-membership-ordering, api-error-code.pin, better-auth-schema-parity, managed-extension-fields) pass, 397 tests.
    • Re-read this act: 12 PRs are open, the same heads as before less refactor(plugin-security): move the RLS filter layers out of security-plugin.ts (#25223 cut 7) #25401, and still none touches the files above.
      Resume-from: none

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25224,
    "status": "done",
    "branch": "claude/project-thread-lg3wrj",
    "pr": "#25404",
    "session": "session_01LSXBc2GCBwiAL7vV4kWkyr",
    "premise_still_valid": true,
    "summary": "Cut 7 of the auth-manager.ts split, per the amended claim 6363170079 (amends 6361212912 on the file cut 6 left; landed note 6362338361). Part of #25224, no closing keyword; #25224 stays open. Base origin/main 860f002, head 83f5b02, +192/-91 over 2 files (283 changed lines). The database and the auth secret createAuthInstance hands better-auth move unedited into packages/plugins/plugin-auth/src/auth-manager-database-secret.ts in cut 1's shape: createDatabaseConfig (base :4670), resolveAuthSecret (:4741) and generateSecret (:4748), 97 body lines. The class keeps all three methods with docblock and signature (async dropped from createDatabaseConfig). stageUserCreatedInRequest and generateSecret go as late-bound closures (the adapter's created-record callback runs the first inside every creating endpoint call), config as a getter, resolvedAuthSecret as a get/set pair (resolveAuthSecret stores the memo on the manager); generateSecret reads no member, so its stub calls the function directly. No module-level declaration moves and no body reads one, or a static. No import leaves auth-manager.ts: readEnvWithDeprecation stays for its CORS-origin read, createObjectQLAdapterFactory because a staying docblock links it, on the import line that still brings withSystemContext and withSystemReadContext; both modules are imported above the new module's line, so no evaluation point moves. No path reader needed migrating: the moved dynamic import of @better-auth/core/context matches the plugin-factory scans' pattern but both scans skip @better-auth/core, so they find the same 16 factories at base and head. The decision-letter layer of check-adr-anchors drops by one (ADR-0093 D7 leaves the anchored file with createDatabaseConfig's callback comment); the gate stays green and no anchor entry is added, as the seat ruled for cut 5. auth-manager.ts 8,971 to 8,909 lines. No sign-in, session or membership outcome changes. Correction to the claim: it lists first-session-membership-ordering.test.ts among the suites that drive the three methods; that suite drives stageUserCreatedInRequest, the member the callback's closure reaches, directly, and the PR body says so.",
    "tests": "Base 860f002, head 83f5b02; builds and suites through os-verify-lock, every exit code captured before any pipe; a full turbo run build first, then plugin-auth built and tested at base, then at head, on the same dependency dists. (1) Move proof: TS scanner token identity per body, template literals re-scanned at their substitutions, every line = base line minus two spaces, same params and return type, class stub docblock and signature kept with async dropped: 3 of 3 pass, 97 body lines (createDatabaseConfig 66 lines and 192 tokens, resolveAuthSecret 6 and 32, generateSecret 25 and 79). Negative control: one token changed in the module ('sys_user' to 'sys_users' in createDatabaseConfig's callback) fails that method and the run exits 1; restored, cmp clean. git diff --color-moved=plain --color-moved-ws=allow-indentation-change marks 90 of 91 deleted and 100 of 192 added lines moved; the one unmarked deletion is createDatabaseConfig's async signature line. (2) Ruling 6348891160 item 2 block: dist/index.d.ts raw base 12679e13f74973b0510551a65b50808e7c3936a2d273cf2f262a9196648c0737, raw head the same, normalised base c7a9fa25f118cfec858d770b979767744d27d9757a9ea4063faeea6238c33c01, normalised head the same; dist/index.d.mts raw base df57562ff950159e738d790cb7ee82a655ba0fffddafe4153bdcc50c4537336f, raw head the same, normalised base 68f824aea0947d9895b968ed46a7972eaae43ef0f2bda7e3ba9113002233ade6, normalised head the same; raw diff 0 lines on both, so no union swap and item 1 holds outright. Normaliser: the ruling's one-liner, verbatim in the PR body. Positive control: head dist/index.d.ts with 'private createDatabaseConfig;' made public (line 1842, one line) hashes raw 4def12da7d94e4d585fad40a829795f80a7fdfeec9d8d4936f68d770a8e3e53d and normalised f98f154135c7fb43f7716274364a3263a4a40d749febc84653c2ad97a4e6f19d, both differ. Export lists: ESM dist/index.mjs and CJS dist/index.js 159 names each at base and head, sorted-name hash 3abb126f5babb701 on all four. 18 dist files on both sides, same list; only index.js, index.mjs and their maps differ. (3) pnpm exec vitest run --maxWorkers=3 in packages/plugins/plugin-auth: base Test Files 189 passed (189), Tests 3580 passed | 10 skipped (3590), VERDICT command-exit 0; head the same counts, VERDICT command-exit 0; the suites that drive the three methods through the manager (auth-manager.test.ts adapter wrapping and production secret guard, user-created-in-request.test.ts, auth-route-ledger.conformance.test.ts) are among them. pnpm --filter @objectstack/plugin-auth typecheck exit 0; pnpm --filter @objectstack/runtime typecheck exit 0; eslint --no-inline-config over the 2 files exit 0. The plugin-factory scan run as both tests run it: 16 factories at base and the same 16 at head; the @better-auth/core imports it skips go 3 to 2, the one that left being tryGetCurrentAuthEndpointContext. (4) Tenant-audit census, base vs head per site via --json (new module mapped back to auth-manager.ts): 238 sites on both, 0 in the new module, 0 per-site differences in object, verb, tenancy, context and elevation, 0 unresolved and 0 unledgered on both, no total moves. (5) Anchors at base and at head, run by hand, all exit 0: check-adr-symbol-anchors (2,384 anchors, 466 symbol, at both); check-adr-anchors (80 anchored files at both; decision-letter layer 2,015 at base and 2,014 at head, the moved ADR-0093 D7; number layer 52,147 citations at both, across 6,630 files at base and 6,631 at head); check-scripts-symbol-anchors (4,061), check-spec-docblock-symbol-anchors (5,071, both read in a built tree), check-system-context-census (148 elevation read sites), check-platform-checklist (16 areas, 329 items), the same at both. check-dispatcher-error-vocabulary: 56 found, 56 classified at both (scope 3,394 to 3,395 files, the new module). Anchor map before the cut: all 29 auth-manager.ts#symbol anchors outside changelogs are declaration-class at base, with the three bodies blanked, and at head; none names a moved method; #AuthManager (ADR-0135) also matches the in-memory warning's text and resolves on the class declaration. (6) Merge check on origin/main 2dfeb1a (after #25401, plugin-security only) with this cut cherry-picked, no conflict: move proof against 2dfeb1a 3 of 3; check-tenant-audit-census, check-adr-anchors, check-adr-symbol-anchors, check-platform-checklist, check-system-context-census, check-auth-mount-ledger and check-dispatcher-error-vocabulary (56 found, 56 classified) exit 0; decision-letter layer 2,005 on 2dfeb1a alone and 2,004 with this cut; seven suites (auth-manager, user-created-in-request, auth-route-ledger.conformance, first-session-membership-ordering, api-error-code.pin, better-auth-schema-parity, managed-extension-fields) 397 tests pass. No tested behaviour changed, so no ablation is owed.",
    "gates": "Local subset at 83f5b02 after the full build: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 53 commands for the 2 paths; all 53 ran, each exit code captured before any pipe, all 53 exit 0; --ran over that record with each exit code: 53 derived, 53 run, 0 NOT-MEASURED, a derived zero. node scripts/pm/check-governed-merges.mjs --branch HEAD: NOT governed, 0 of 2 paths hit the register, 283 changed lines vs the 3,000-line human-merge line. CI on 83f5b02, the PR's check-runs read at report time (46): 34 success, 12 skipped, 0 failure, 0 pending. Skipped: Dogfood Verify CLI, Build Docs, Temporal Conformance (live PG + MySQL), Console Pin Gate, the Dogfood Regression Gate shard matrix, the opt-in 18.x upgrade harness and Packed-tarball smoke (twice each), and in the labeled run Auto Label, Check PR Size and Check Changeset. Success: Build Core; Test Core (1/6) to (6/6) and Test Core; TypeScript Type Check (Type Check · workspace, · source gates, · consumer gates, · debt ledger); Lint and Repo Gates (Lint · ESLint, · repo and tooling gates, · docs and code guards, · CI, release and engine gates, · PM dispatch-gates, · scoped · tooling self-tests, · scoped · query-options erasure, · scoped · slot lookup and corpus); Dogfood Regression Gate; Governed Surface Queue Guard; Check PR Size; Check Changeset; Auto Label; filter; Part-of PR must not also close its card; The card this PR closes must claim this branch; No other open PR may claim the same issue; No other open PR may claim the same single-writer path; Check Documentation Links; Flag docs affected by code changes. No Claude Approvals check runs on this PR. mergeable_state clean against main 2dfeb1a; the PR was opened as a draft. Docs Drift Check (advisory) comment 6363232145: 8 hand-written pages and 3 release-owned, the same 11 as node scripts/docs-audit/affected-docs.mjs --json 860f002 (exit 0), every one listed through a literal the moved text carries unchanged (OS_AUTH_SECRET, AUTH_SECRET and BETTER_AUTH_SECRET in generateSecret, sys_oauth_resource in a comment in createDatabaseConfig); no page restates a rule this move changes, so no doc edit is owed and no reply was posted. Serial: none of the 13 open PRs at the first reading, nor the 12 at the claim, touches plugin-auth/src or a reader of this cut.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "7 since this cut began (after #25400 merged at 09:58Z), all reads, no write tool: issue_read get_comments 2 and issue_read get 1 (#25224, for the landed note 6362338361), list_pull_requests 2, pull_request_read get_check_runs 1 and pull_request_read get 1 (#25404). (claude-code-remote subscribe_pr_activity once on #25404, not a GitHub write.)",
    "api_writes": "3 relay acts this cut (scripts/pm/, each read back identical): 1) amended claim 6363170079 via post-stamped, run 39941652790, 11384 bytes; 2) pr_create #25404 via fleet-write, run 39941976857, 19366 bytes sent and stored, assignee os-sales, label skip-changeset; 3) this report via post-stamped. Plus git push of the branch (83f5b02, a new branch after the queue deleted the old one on #25400's merge; not REST). No PR body edit. No other write.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  15. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: human ruling by Sales (os-sales) in the project chat, not a batch · letter A · maintainer 「同意」 (2026-10-23 11:32:30Z, after 「具体解释。」 at 11:30:15Z) · recorded 2026-10-23T11:38Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr (the #25224 card thread, domain:services seat 1, project run by the Sales account), recording at the project coordinator's request. The ruling is Sales's; this comment only records it.

    The question

    The remaining large bodies of packages/plugins/plugin-auth/src/auth-manager.ts each read a module-level helper or constant that the file does not export and that another method also reads. A move-only cut can only reach such a name by adding export in place (the #23050 shape the series uses). But packages/plugins/plugin-auth/src/index.ts:16 is export * from './auth-manager.js', so any such export would join the published surface of @objectstack/plugin-auth. The services seat (07:49Z) asked for this to come back as needs_decision before any such cut, and the card thread laid out three ways in the project chat: list the entry's exports by name in index.ts and export the helpers in place; move the helpers into a new shared module that nothing re-exports (blocked for isScimProtocolPath and SCIM_PROTOCOL_PATH_PREFIX, which ADR-0134 anchors by declaration, and refused earlier by the seat as a new structure); or leave those bodies in the class and end the series near 8,000 lines.

    Measured on 83f5b0249e (cut 7's head), the bodies this holds are createAuthInstance (1,448 lines), buildPluginList (1,335), getPublicConfig (206), handleRequest (200), verifyMcpAccessToken (76), rereadKeyringForUnknownKey (20) and resolvePasswordHasher (35), about 3,320 lines, the bulk of the card. The helpers they read: readDisableSignUpEnv, readBooleanEnv, isWebContainerRuntime, OAUTH_ORGANIZATION_CLAIM, isScimProtocolPath, readPublishedJwks.

    What Sales ruled

    Letter A: index.ts names the exports of auth-manager.ts instead of export *, and the shared helpers get export in place. Every declaration and every anchor stays where it is.

    Execution parameters (one pass, as laid out to Sales)

    1. index.ts replaces export * from './auth-manager.js' with the 14 names auth-manager.ts exports today: AuthManager, DEFAULT_AUTH_BASE_PATH, assertScimAdminCoherence, authEmailLocaleFromRequest, ipMatchesRange, isOAuthEligibleBaseUrl, normalizeAuthEmailLocale, readInvitationPlacementIntent, readMcpServerEnabledEnv, resolveDcrEnabled, resolveOidcProviderEnabled, resolveScimEnabled, and the types AuthManagerOptions and InvitationPlacementServiceLike. The list lands in the first cut that exports a helper, in the same PR. From then on a new export of auth-manager.ts is published only by adding it to the list, the rule plugin-security's entry already follows.
    2. A helper gets export in place in the cut whose moved body first reads it, not before. A moved body imports it by value and reads it only at call time, the shape already agreed for exported functions.
    3. Proof, every cut: dist/index.d.ts and dist/index.d.mts raw byte-identical to base (ruling 6348891160 item 1), and the ESM and CJS entry export lists the same (159 names today). The first cut under A also shows the index.ts change alone against base.
    4. Any change to the published .d.ts or to an export list, from the list or from an in-place export, stops the cut and comes back as a fresh needs_decision. A is not read as permission to move the published surface.
    5. Unchanged by this ruling: bodies that read a private static of AuthManager stay in the class (the seat agreed: reading them through a host edits the body); reconcileScimUserLifecycle stays while scripts/tenant-audit-census.mjs keys it by path; no new gate; one area per PR, cuts strictly serial; a PR over 3,000 changed lines merges by hand (buildPluginList will be one).

    Workload under A: the index.ts list is about 15 lines in one file of this lane, plus one export keyword per helper as its first reader moves. It opens the seven bodies above to the series' later cuts.

    Prior rulings read: export-star,index.ts,re-export,published-surface → 4 hits; ADR-0029 D6, ADR-0076 D1, ADR-0087 D7, ADR-0131 D13; thread: 1 ruling(s) (6351993113); repo: objectstack-ai/objectstack

    None of the four ADR decisions answers this question: they cover other packages' decompositions and retirements, and ADR-0087 D7 (a published runtime TS interface is compiler-carried) is kept by parameter 3. Ruling 6351993113 on this thread is the any-erasure sweep, a different question.


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 7 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T11:56Z

    reading value
    PR #25404 MERGED through the queue (ACCEPT 6363871819), head 83f5b0249e
    origin/main carries f0fdd45bbc

    State: the card stays open and dispatched (Part of). The card thread claims cut 8 (validateAudienceAdmission … settleMembership) afresh on the merged file, under Ruling-ref: 6364006498 (ruling A on the shared unexported helpers).


    Generated by Claude Code

  17. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 8 (one PR, cuts strictly serial: after cut 7, PR #25404, landed as f0fdd45bbc, per the landed note 6364312052) · amends claim 6363170079 for this PR, on the merged file · 2026-10-23T12:52Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-lg3wrj (the harness-pinned branch for this session, restarted from origin/main f0fdd45bbc after #25404 merged; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (read on origin/main f0fdd45bbc, where auth-manager.ts is 8,909 lines):

    (a) The move: who may join, and what joining settles. The bodies of 4 AuthManager methods move unedited from packages/plugins/plugin-auth/src/auth-manager.ts into a new module, packages/plugins/plugin-auth/src/auth-manager-membership-admission.ts, in cut 1's shape: the class keeps every method with its docblock and signature (async dropped from the three that had it), and each body becomes a literal of exactly the host members it reads plus a .call.

    • settleMembership :4899: the membership reconciler's one assembly of inputs, which both creation seams call. Reads config, getMembershipPolicy.
    • validateAudienceAdmission :5158: the audience gate better-auth's user.validateUserInfo runs on every user creation. Reads audienceLogError, config, enterpriseOAuthProviderIds, getAudience, hasPendingInvitationFor, isBootstrapCreation, isOperatorProvisioning, selfRegistrationSetResolvable, stageOwnerVerifiedStamp, stageSelfRegistrationGrant.
    • landSelfRegistrationGrant :5894: writes the self-registration grant, owned by the membership's organization, idempotently. Reads config.
    • reportUngrantedSelfRegistrant :6057: the durability-level report of a grant that did not land. Reads audienceLogError.
    • No module-level declaration moves, and no body reads one.

    How the host is built:

    • Every method goes as a late-bound closure, so a double or spy set on the manager is still the one a body reaches: the bootstrap, invitation and operator-provisioning probes stay on the class, as do the stagings and getMembershipPolicy (a live platform setting).
    • config goes as a getter. The class reassigns it after construction (setRuntimeBaseUrl, applyConfigPatch).

    The one value read of AuthManager. validateAudienceAdmission reads the public static AuthManager.OPERATOR_PROVISIONING_TICKET_FIELD (base :5183), so the new module imports AuthManager by VALUE from ./auth-manager.js, the shape agreed for public statics. That is the only value read of the import: the module's other AuthManager mentions are the host interface's indexed-access types and comments. The read sits inside the function body and runs only when better-auth calls the gate. The module's top level is six imports, an interface and four function declarations, so nothing reads the class while the two modules are first evaluated.

    Imports that leave auth-manager.ts (each name now has no reader there):

    • from ./audience-posture.js: classifyCreationMethod, decideAudienceAdmission, AUDIENCE_CONFIG_ERROR, type AudienceCreationClass (the import line stays for its six other names);
    • shouldStampOwnerVerifiedAtCreation (./walled-owner-operator-stamp.js) and GRANT_SET_NAME_FIELD (./grant-set-name.js): their import lines go;
    • reconcileMembership (./reconcile-membership.js): the line stays for type MembershipPolicy and type ReconcileOutcome; the second is still settleMembership's return type on the class.
    • The new module imports each of these from the same place, plus withSystemReadContext from ./objectql-adapter.js, which auth-manager.ts keeps importing.
    • Evaluation order: the three modules whose value import leaves are now first reached through the new module's import line, a little later in auth-manager.ts's import list. Each module's top level is declarations and literal constants, and every module they import is imported by auth-manager.ts earlier, so the change in order cannot be observed.

    What stays, and why (narrowed from the 6 bodies the seat agreed):

    • settleSelfRegistrationGrant stays. Its body does this.pendingSelfRegistrationGrants.delete(email) (:5857). The census reads a this. receiver's type from class property declarations only, so in the new module that call is a receiver it cannot read, and check-tenant-audit-census fails with an untyped-receiver problem. Placing it would need a new UNTYPED_RECEIVERS row in scripts/tenant-audit-census.mjs, a ledger that is ⛔ SHRINK-ONLY. Kept in the class, the census reads it as before. (The failure was measured on a draft that moved it.)
    • sendExistingAccountSignUpNotice stays. auth-email-locale.test.ts describes its send as one of "the five sendTemplate call sites" and says no other file under plugin-auth/src calls one. Moving another send would add to that description's drift (finding below).
    • The methods that read a private static stay (isBootstrapCreation, hasPendingInvitationFor, hasExistingUserFor, persistSelfRegistrationGrantClaim, observeFirstMemberships, settleDeferredSelfRegistrationGrant, the three prune*), as the seat agreed.
    • No shared unexported helper is read by these bodies, so ruling A's index.ts list does not land in this cut: it lands with the first cut that exports a helper (ruling 6364006498, parameter 1).

    (b) Path readers.

    • The tenant-audit census and its two documents, migrated in this PR. The moved landSelfRegistrationGrant carries one census site, the sys_user_permission_set insert (base :5907). Compared per site, base and head read the same 238 sites with the same object, verb, tenancy, context and elevation, and every total the same; the site now sits in the new module. node scripts/tenant-audit-census.mjs --write moves its row in docs/audits/2026-08-tenant-audit-write-call-sites.counts.md from auth-manager.ts to the new file, and re-measures the not-enforced corpus-scale block in both documents (content/docs/permissions/tenant-audit-census.mdx too). No prose figure moves.
    • find-envelope-limb-removal.test.ts's B5 site: label, which names auth-manager.ts as the file of the sys_user_permission_set existence read. That read moves with landSelfRegistrationGrant, so the label is re-pointed to the new file. It is a test-title string only: no gate or test reads it back.
    • Read, and not edited:
      • check-dispatcher-error-vocabulary.mjs and api-error-code.pin.test.ts's authManagerFiles floor: no new APIError( sits in the moved bodies.
      • check-auth-mount-ledger.mjs and auth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text.
      • The plugin-factory scans in better-auth-schema-parity.test.ts and managed-extension-fields.test.ts: the imports that leave are local modules, not plugin factories.
      • The system-context census: it reads 148 elevation read sites at base and at head.
      • scripts/adr-anchors/: no entry's invariant describes the moved text.
    • The suites that drive the four methods through the manager, which the stubs keep working: audience-posture.test.ts, logger-receiver-detach.test.ts, find-envelope-limb-removal.test.ts, self-registration-grant-first-membership.test.ts, dev-admin-seed-credential-gate.test.ts (which also reads AuthManager.OPERATOR_PROVISIONING_TICKET_FIELD) and signup-existing-address-refusal.test.ts.

    ⛔ Nothing else changes:

    • No other method moves, and no body or declaration is edited.
    • No anchor, ADR or docs prose is edited; the two census documents change only as --write writes them.
    • No edit to eslint.config.mjs, to any baseline, to index.ts, or to scripts/.
    • The title goes under refactor(plugin-auth), with no !.
    • No changeset: skip-changeset, per ruling 6348891160 item 4.

    Anchor reading before the cut, on f0fdd45bbc:

    • I mapped every auth-manager.ts#symbol anchor in the tracked tree, outside changelogs, with symbolSegmentResolution from scripts/symbol-anchors.mjs. There are 29.
    • With the 4 bodies blanked, none changes class: all 29 are declaration-class at base, blanked and head.
    • No anchor names a moved method. Two name a token the moved text also carries, and both resolve on declarations that stay: #AuthManager (ADR-0135; the moved text reads AuthManager.OPERATOR_PROVISIONING_TICKET_FIELD) and #isBootstrapCreation (identity-auth.json:1810; the gate calls this.isBootstrapCreation).

    The decision-letter layer of check-adr-anchors does not move. It reads 2,004 at base f0fdd45bbc and 2,004 at head, and the gate exits 0 at both. The moved text cites ADR-0078 with no decision letter, so only the number layer sees it: 52,156 citations at both, across 6,632 files at base and 6,633 at head.

    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface. The proof owed:

    • token identity per moved body against this base, plus a negative control;
    • ruling 6348891160 item 2's block: raw and normalised sha256 of dist/index.d.ts and dist/index.d.mts at base and head, the normaliser verbatim, the raw diff line count, and the positive control;
    • ESM and CJS export lists;
    • plugin-auth suite counts at base and at head;
    • the census compared per site, base and head;
    • the symbol-anchor checks and check:adr-anchors (with the decision-letter count) at base and at head;
    • every derived gate.

    Premise (measured on this cut's commits 487d57fedd and fa0187ad1f on this base):

    • 4 of 4 bodies token-identical (163 body lines), and a one-token negative control ('sys_user_permission_set' → 'sys_user_permission_sets' in landSelfRegistrationGrant's insert) exits 1;
    • pnpm --filter @objectstack/plugin-auth typecheck exit 0, pnpm --filter @objectstack/runtime typecheck exit 0, and eslint --no-inline-config on the 3 source files exit 0;
    • suite 189 files, 3580 passed and 10 skipped, the same as base;
    • dist/index.d.ts and dist/index.d.mts raw byte-identical to base, and the ESM and CJS entries export the same 159 names;
    • the census reads 238 sites at both, with 0 per-site differences and no total that moves;
    • check-dispatcher-error-vocabulary reads 56 found and 56 classified at both;
    • 89 derived gates run at head, all exit 0 (--ran: a derived zero);
    • the anchor map and the decision-letter reading above.

    A diff of another kind is a fresh fork on this card.

    Finding, outside this cut: the population comments in auth-email-locale.test.ts (around :44, :179 and :857–:865) say the auth mail surface is five sendTemplate call sites and that no other file under plugin-auth/src calls one. On base there are six: five in auth-manager.ts and one in auth-manager-email-sms-delivery.ts (moved there by cut 5). This cut does not touch either, and the comments are not a gate. I report it for the seat to triage.
    Responsibility: n/a — not a defect card
    Thread-read: 6364312052
    Ruling-ref: 6348891160 (the .d.ts bar), 6364006498 (ruling A on this card)
    Serial constraints cleared: read before the proof run. 11 PRs are open, and each head was diffed against its merge base with origin/main (#24467 read through its file list). None touches packages/plugins/plugin-auth/src, the new module's path, the census script or its two documents, dispatcher-error-vocabulary.ts, the plugin-auth anchor entries, identity-auth.json, or the readers named in (b). The Version Packages PR #24876 touches plugin-auth's CHANGELOG.md and package.json and two changesets only. origin/main is still f0fdd45bbc.
    Resume-from: none


    Generated by Claude Code

  18. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25224,
    "status": "done",
    "branch": "claude/project-thread-lg3wrj",
    "pr": "#25423",
    "session": "session_01LSXBc2GCBwiAL7vV4kWkyr",
    "premise_still_valid": true,
    "summary": "Cut 8 of the auth-manager.ts split, per the amended claim 6365318480 (amends 6363170079 on the file cut 7 left; landed note 6364312052), under Ruling-ref 6348891160 (the .d.ts bar) and 6364006498 (Sales's ruling A on this card; no body here reads a shared unexported helper, so its index.ts name list does not land in this cut). Part of #25224, no closing keyword; #25224 stays open. Base origin/main f0fdd45, head fa0187a (move commit 487d57f, then the census-documents commit), +321/-173 over 5 files (494 changed lines). The audience gate and membership admission move unedited into packages/plugins/plugin-auth/src/auth-manager-membership-admission.ts in cut 1's shape: settleMembership (base :4899), validateAudienceAdmission (:5158), landSelfRegistrationGrant (:5894) and reportUngrantedSelfRegistrant (:6057), 163 body lines. The class keeps all four methods with docblock and signature (async dropped from the three that had it). Every method goes as a late-bound closure, config as a getter; MembershipAdmissionHost names the 11 members read. The new module imports AuthManager by value for one read, the public static AuthManager.OPERATOR_PROVISIONING_TICKET_FIELD inside validateAudienceAdmission (base :5183), at call time only; the module's top level is six imports, an interface and four function declarations. Imports leaving auth-manager.ts: classifyCreationMethod, decideAudienceAdmission, AUDIENCE_CONFIG_ERROR and type AudienceCreationClass (audience-posture), shouldStampOwnerVerifiedAtCreation, GRANT_SET_NAME_FIELD, and the value reconcileMembership (its line stays for two types); the modules reached later are declarations and literal constants whose own imports auth-manager.ts imports earlier, so no evaluation order is observable. Path readers migrated in this PR: the tenant-audit census row for the sys_user_permission_set insert moves to the new file (tenant-audit-census.mjs --write, which also re-measures the not-enforced corpus-scale block in both documents), and find-envelope-limb-removal.test.ts's B5 site label (a test-title string) names the new file. Scope narrowed from the 6 bodies the seat agreed to 4: settleSelfRegistrationGrant stays because its this.pendingSelfRegistrationGrants.delete call becomes a receiver the census cannot read from the host, and placing it would need a new row in the shrink-only UNTYPED_RECEIVERS ledger (measured on a draft that moved it); sendExistingAccountSignUpNotice stays because auth-email-locale.test.ts describes the mail surface by its call sites. Decision-letter layer of check-adr-anchors: 2,004 at base f0fdd45 and 2,004 at head; on current main 77392e9, 2,001 alone and 2,001 with this cut. auth-manager.ts 8,909 to 8,790 lines. No sign-in, session or membership outcome changes.",
    "tests": "Base f0fdd45, head fa0187a; builds and suites through os-verify-lock, every exit code captured before any pipe; a full turbo run build first, then plugin-auth built and tested at base, then at head, on the same dependency dists. (1) Move proof: TS scanner token identity per body, template literals re-scanned at their substitutions, every line = base line minus two spaces, same params and return type, class stub docblock and signature kept with async dropped: 4 of 4 pass, 163 body lines (settleMembership 24 lines and 93 tokens, validateAudienceAdmission 112 and 466, landSelfRegistrationGrant 19 and 154, reportUngrantedSelfRegistrant 8 and 22). Negative control: one token changed in the module ('sys_user_permission_set' to 'sys_user_permission_sets' in landSelfRegistrationGrant's insert) fails that method and the run exits 1; restored, cmp clean. git diff --color-moved=plain --color-moved-ws=allow-indentation-change over the two source files marks 161 of 165 deleted and 167 of 313 added lines moved; the 4 unmarked deletions are the reconcileMembership import line and the three async signature lines. (2) Ruling 6348891160 item 2 block: dist/index.d.ts raw base 12679e13f74973b0510551a65b50808e7c3936a2d273cf2f262a9196648c0737, raw head the same, normalised base c7a9fa25f118cfec858d770b979767744d27d9757a9ea4063faeea6238c33c01, normalised head the same; dist/index.d.mts raw base df57562ff950159e738d790cb7ee82a655ba0fffddafe4153bdcc50c4537336f, raw head the same, normalised base 68f824aea0947d9895b968ed46a7972eaae43ef0f2bda7e3ba9113002233ade6, normalised head the same; raw diff 0 lines on both, so no union swap and item 1 holds outright. Normaliser: the ruling's one-liner, verbatim in the PR body. Positive control: head dist/index.d.ts with 'private validateAudienceAdmission;' made public (line 2107, one line) hashes raw a44eb00dde9098cdcb14efc26e659d46f940a6df42d3cc1759bb78ad6d0f1db8 and normalised df635741a7c0a9d7d3f34be34bc52a32ea6406d9ada1112acd0de9f924990750, both differ. Export lists: ESM dist/index.mjs and CJS dist/index.js 159 names each at base and head, sorted-name hash 3abb126f5babb701 on all four. 18 dist files on both sides, same list; only index.js, index.mjs and their maps differ. (3) pnpm exec vitest run --maxWorkers=3 in packages/plugins/plugin-auth: base Test Files 189 passed (189), Tests 3580 passed | 10 skipped (3590), exit 0; head the same counts, exit 0; the suites that drive the four methods through the manager (audience-posture, logger-receiver-detach, find-envelope-limb-removal, self-registration-grant-first-membership, dev-admin-seed-credential-gate, signup-existing-address-refusal) are among them. pnpm --filter @objectstack/plugin-auth typecheck exit 0; pnpm --filter @objectstack/runtime typecheck exit 0; eslint --no-inline-config over the 3 source files exit 0. (4) Tenant-audit census, base vs head per site via --json (new module mapped back to auth-manager.ts): 238 sites on both, 1 in the new module at head, 0 per-site differences in object, verb, tenancy, context and elevation, 0 unresolved and 0 unledgered on both, no total moves; check-tenant-audit-census exit 0 after --write. (5) Anchors at base and at head, run by hand, all exit 0: check-adr-symbol-anchors (2,384 anchors, 466 symbol, at both); check-adr-anchors (80 anchored files at both; decision-letter layer 2,004 at base and 2,004 at head; the moved text cites ADR-0078 with no letter, so only the number layer sees it: 52,156 citations at both, across 6,632 files at base and 6,633 at head); check-scripts-symbol-anchors (4,061), check-spec-docblock-symbol-anchors (5,071, both read in a built tree), check-system-context-census (148 elevation read sites), check-platform-checklist (16 areas, 329 items), the same at both. check-dispatcher-error-vocabulary: 56 found, 56 classified at both. Anchor map before the cut: all 29 auth-manager.ts#symbol anchors outside changelogs are declaration-class at base, with the four bodies blanked, and at head; none names a moved method; #AuthManager (ADR-0135) and #isBootstrapCreation (identity-auth.json:1810) name tokens the moved text carries and resolve on declarations that stay. (6) Merge check on current origin/main 77392e9 (after #24947, #25406, #25247, #25255, #25418 and #25393; none touches plugin-auth, the census or its documents, or a reader of this cut) with both commits cherry-picked, no conflict: move proof against 77392e9 4 of 4; check-tenant-audit-census, check-adr-anchors, check-adr-symbol-anchors, check-platform-checklist, check-system-context-census, check-auth-mount-ledger and check-dispatcher-error-vocabulary exit 0; decision-letter layer 2,001 on 77392e9 alone and 2,001 with this cut; ten suites (the six above plus api-error-code.pin, better-auth-schema-parity, managed-extension-fields and auth-email-locale) 250 tests pass, on the dependency dists built for f0fdd45. No tested behaviour changed, so no ablation is owed.",
    "gates": "Local subset at fa0187a after the full build: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 89 commands for the 5 paths; all 89 ran, each exit code captured before any pipe, all 89 exit 0; --ran over that record with each exit code: 89 derived, 89 run, 0 NOT-MEASURED, a derived zero. node scripts/pm/check-governed-merges.mjs --branch HEAD: NOT governed, 0 of 5 paths hit the register, 494 changed lines vs the 3,000-line human-merge line. CI on fa0187a, the PR's check-runs read at report time (47): 36 success, 11 skipped, 0 failure, 0 pending. Skipped: Dogfood Verify CLI, Temporal Conformance (live PG + MySQL), Console Pin Gate, the Dogfood Regression Gate shard matrix, the opt-in 18.x upgrade harness and Packed-tarball smoke (twice each), and in the labeled run Auto Label, Check PR Size and Check Changeset. Success: Build Core; Build Docs; Test Core (1/6) to (6/6) and Test Core; TypeScript Type Check (Type Check · workspace, · source gates, · consumer gates, · debt ledger); Lint and Repo Gates (Lint · ESLint, · repo and tooling gates, · docs and code guards, · CI, release and engine gates, · PM dispatch-gates, · scoped · tooling self-tests, · scoped · query-options erasure, · scoped · slot lookup and corpus); Dogfood Regression Gate; Governed Surface Queue Guard; Spec property liveness; Check PR Size; Check Changeset; Auto Label; filter; Part-of PR must not also close its card; The card this PR closes must claim this branch; No other open PR may claim the same issue; No other open PR may claim the same single-writer path; Check Documentation Links; Flag docs affected by code changes. No Claude Approvals check runs on this PR. mergeable_state clean; the PR was opened as a draft. Docs Drift Check (advisory) comment 6365390201: 6 hand-written pages and 6 release-owned, the same 12 as node scripts/docs-audit/affected-docs.mjs --json f0fdd45 (exit 0), every one listed through sys_user_permission_set, a literal landSelfRegistrationGrant carries unchanged; no page restates a rule this move changes, so no doc edit is owed and no reply was posted. Serial: none of the 11 open PRs at the claim touches plugin-auth/src, the census or its documents, or a reader of this cut.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "9 since cut 7's report (11:21Z), all reads, no write tool: issue_read get_comments 2 (#25224: the freshness read before the ruling record, and the landed note 6364312052), pull_request_read get 2 (#25404 state; #25423 at report time), pull_request_read get_status 1 (#25404), list_pull_requests 2 (open PRs for the serial read; this branch's PR), pull_request_read get_files 1 (#24467, whose head ref would not fetch), pull_request_read get_check_runs 1 (#25423). (claude-code-remote subscribe_pr_activity once on #25423, not a GitHub write.)",
    "api_writes": "4 relay acts since cut 7's report (scripts/pm/, each read back identical): 1) the record of Sales's ruling A, comment 6364006498 via post-stamped, run 39947470946, 4745 bytes, at the coordinator's request; 2) amended claim 6365318480 via post-stamped, run 39956604344, 12323 bytes; 3) pr_create #25423 via fleet-write, run 39956982796, 18496 bytes sent and stored, assignee os-sales, label skip-changeset; 4) this report via post-stamped. Plus git push of the branch (fa0187a, a new branch after the queue deleted the old one on #25404's merge; not REST). No PR body edit. No other write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "auth-email-locale.test.ts's population comments (around :44, :179 and :857 to :865) say the auth mail surface is five sendTemplate call sites and that no other file under plugin-auth/src calls one. Base f0fdd45 has six: five in auth-manager.ts and one in auth-manager-email-sms-delivery.ts (moved there by cut 5). Not a gate; this cut touches neither the sends nor the comments. For the seat to triage."
    ]
    }


    Generated by Claude Code

  19. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 8 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T14:09Z

    reading value
    PR #25423 MERGED through the queue (ACCEPT 6366175708), head fa0187ad1f
    origin/main carries 70ff9983c9

    State: the card stays open and dispatched (Part of). The card thread claims cut 9 (verifyMcpAccessToken + rereadKeyringForUnknownKey, with readPublishedJwks moving unexported) afresh on the merged file, under Ruling-ref: 6364006498.


    Generated by Claude Code

  20. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 9 (one PR, cuts strictly serial: after cut 8, PR #25423, landed per the landed note 6366862591) · the first cut under ruling 6364006498 (A) · amends claim 6365318480 for this PR, on the merged file · 2026-10-23T14:59Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-lg3wrj (the harness-pinned branch for this session, restarted from origin/main 2b7c331537 after #25423 merged; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (read on origin/main 2b7c331537, where auth-manager.ts is 8,790 lines; packages/plugins/plugin-auth/ is unchanged since cut 8's head fa0187ad1f):

    Two commits, in this order, so that the index.ts change is measured by itself.

    (a) Commit 1: index.ts names auth-manager.ts's published exports (ruling 6364006498, parameter 1).

    • packages/plugins/plugin-auth/src/index.ts:16, export * from './auth-manager.js';, becomes an explicit export { … } from './auth-manager.js'; of the 14 names auth-manager.ts exports at base, in declaration order: DEFAULT_AUTH_BASE_PATH, readMcpServerEnabledEnv, resolveOidcProviderEnabled, resolveDcrEnabled, resolveScimEnabled, assertScimAdminCoherence, isOAuthEligibleBaseUrl, type InvitationPlacementServiceLike, readInvitationPlacementIntent, type AuthManagerOptions, ipMatchesRange, normalizeAuthEmailLocale, authEmailLocaleFromRequest, AuthManager. A four-line comment says a new export of auth-manager.ts is published only by adding it there.
    • One comment-only reword, agreed with the seat. auth-manager-email-sms-delivery.ts:45–:49 (cut 5's module header) says "index.ts re-exports auth-manager.ts whole, so exporting them from there would add them to the published surface, and a value import of that file from here would be a cycle." This commit makes the first half false. The sentence becomes:

      The four module-level declarations move with the bodies rather than being imported back, because only these bodies read them. (index.ts lists the names it publishes from auth-manager.ts one by one, so a helper exported there in place for a module split out of it stays off the published surface.) Nothing re-exports this module, so the published surface does not move.

      • Rationale: this PR would otherwise ship a statement it makes false.
      • The cycle half is dropped. A value import of auth-manager.ts from a split module is a cycle, but cuts 8 and 9 take one by design, read only inside function bodies. So it is no longer a reason. "Only these bodies read them" is measured: none of the four names has a reader in auth-manager.ts.
      • The old and new wording carry no ADR-NNNN citation, with or without a decision letter (0 and 0).
      • The file's token sequence is unchanged (1,427 tokens at both, template-aware scanner, trivia skipped; a comment-free print is identical; negative control: one string token changed exits 1).

    (b) Commit 2: the move. The MCP access-token verifier. Two AuthManager method bodies and one module-level declaration move unedited from packages/plugins/plugin-auth/src/auth-manager.ts into a new module, packages/plugins/plugin-auth/src/auth-manager-mcp-access-token.ts, in cut 1's shape. The class keeps both methods with their docblocks and signatures (async dropped from verifyMcpAccessToken), and each body becomes a literal of exactly the host members it reads, plus a .call.

    • verifyMcpAccessToken :7228 (public): the local, fail-closed check of an OAuth 2.1 Bearer access token this deployment's authorization server minted. It reads getApi, getAuthIssuer, getMcpResourceUrl, isMcpOAuthEnabled and rereadKeyringForUnknownKey.
    • rereadKeyringForUnknownKey :7321 (private, not async, stays so): the one forced keyring re-read a token signed by an unknown key earns. It reads config, and reads and writes unknownKeyReread.
    • readPublishedJwks moves with them, unexported, its declaration and docblock (:1483–:1493, 11 lines) unedited. Its only readers are the two moved bodies (:7238, :7329). No anchor anywhere in the tree names it, and no test reads it. Its docblock cites [#24186] and no ADR-NNNN.

    How the host is built:

    • Every method goes as a late-bound closure, so a double or spy set on the manager is still the one a body reaches. The verifier suites replace getApi with a key-set double, and verifyMcpAccessToken asks rereadKeyringForUnknownKey through the manager.
    • config goes as a getter. The class reassigns it after construction (setRuntimeBaseUrl, applyConfigPatch).
    • unknownKeyReread goes as a get/set pair. rereadKeyringForUnknownKey stores the re-read it starts on the manager, so the floor and the read in flight stay the manager's, shared by every token it verifies.

    OAUTH_ORGANIZATION_CLAIM is exported in place (ruling 6364006498, parameter 2).

    • :1465 becomes export const OAUTH_ORGANIZATION_CLAIM = 'organization_id';. That is the only edit to it.
    • It does not move: buildPluginList (:4398, which stays) mints the claim with it, and verifyMcpAccessToken (:7288) reads it back. Its docblock calls it "one spelling for both ends".
    • The new module imports it by VALUE from ./auth-manager.js, beside type AuthManager. The one read is inside verifyMcpAccessToken's body, when a token is verified. The module's top level is three imports, one function declaration, an interface and two function declarations, so nothing reads the binding while the two modules are first evaluated.
    • index.ts (commit 1) does not list it, so it is not published. The proof below measures that.

    Imports:

    • retireHeldKeyrings and UNKNOWN_SIGNING_KEY_REREAD_FLOOR_MS (./signing-key-read-cache.js) leave auth-manager.ts. Their only readers were rereadKeyringForUnknownKey's body, and the import line goes. The two docblock mentions of UNKNOWN_SIGNING_KEY_REREAD_FLOOR_MS that stay in the class are backticked text, not {@link}.
    • type JSONWebKeySet (jose) stays in auth-manager.ts, for the unknownKeyReread field and the rereadKeyringForUnknownKey signature. The new module imports it too.
    • The await import('jose') inside verifyMcpAccessToken moves with the body.
    • Evaluation order: signing-key-read-cache.js was first reached through auth-manager.ts's own import line (:146). It is now first reached through auth-manager-plugin-assembly.js, which imports createSigningKeyReader from it (cut 6's import line, :221 at head). Its top level is one type-only import, literal constants, a Set and a WeakMap, interfaces and function declarations. It imports no value and does nothing outside itself, so the change in order cannot be observed.

    What stays:

    • Every other method. The private-static readers and reconcileScimUserLifecycle stay, per the ruling.

    (c) Path readers.

    • No census site moves. The moved text has no engine write, no this.X.delete(), and no elevation read. check-tenant-audit-census exits 0 at head with no --write: 238 sites at base 2b7c331537 and at head, compared per site (line-blind): 0 differences, and the auth-manager.ts count is 2 at both. So neither census document is edited.
    • scripts/measure-durability-swallow-family.mjs (a measurement, not a gate): its prose at :625 and :775 names auth-manager.ts::verifyMcpAccessToken as the one outstanding DARK member. The census does not list it on base: its one auth-manager.ts DARK member is observeFirstMemberships. So that prose was already stale before this cut. Its --json reading is the same at base and at head: 74 members at both, every member and quiet row the same (line-blind), and every statistic the same except the scanned file count (3,449 → 3,450, the new module). Neither side names verifyMcpAccessToken or the new module. scripts/ is not edited. This is a finding for the seat to judge at ACCEPT.
    • Read, and not edited:
      • check-dispatcher-error-vocabulary.mjs and api-error-code.pin.test.ts's authManagerFiles floor: no new APIError( sits in the moved text.
      • check-auth-mount-ledger.mjs and auth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text. getAuthIssuer and getMcpResourceUrl stay on the class.
      • The plugin-factory scans in better-auth-schema-parity.test.ts and managed-extension-fields.test.ts: they match await import('better-auth/plugins…' | '@better-auth/…') only. The moved await import('jose') is neither.
      • signing-key-read-cache.ts:45: it names AuthManager.verifyMcpAccessToken, which stays on the class.
      • scripts/adr-anchors/: the auth-manager.ts entry's invariant (ADR-0093, ADR-0108) describes the organization roles and the membership reconciler, not this text.
    • The suites that drive the two methods through the manager, which the stubs keep working: auth-manager.mcp-oauth.test.ts, auth-manager.mcp-verifier-keyring.test.ts (it reads UNKNOWN_SIGNING_KEY_REREAD_FLOOR_MS and retireHeldKeyrings from signing-key-read-cache.js itself), auth-manager.oauth-consent-organization.test.ts and auth-manager.mcp-oauth-resource.test.ts.

    ⛔ Nothing else changes:

    • No other method moves, and no moved body or declaration is edited.
    • The only edit to stay-behind text is export on OAUTH_ORGANIZATION_CLAIM. The only comment edit outside the moved text is the reword in (a).
    • No anchor, ADR or docs prose is edited.
    • No edit to eslint.config.mjs, to any baseline, or to scripts/.
    • The titles go under refactor(plugin-auth), with no !.
    • No changeset: skip-changeset, per ruling 6348891160 item 4.

    Anchor reading before the cut, on 2b7c331537:

    • I mapped every auth-manager.ts#symbol anchor in the tracked tree, outside changelogs, with symbolSegmentResolution from scripts/symbol-anchors.mjs. There are 29.
    • With the 2 bodies and the readPublishedJwks block blanked, none changes class: all 29 are declaration-class at base, blanked and head.
    • No anchor names a moved method or readPublishedJwks. One names a token the moved text also carries, and it resolves on the declaration that stays: #isMcpOAuthEnabled (docs/qa/platform-checklist/areas/ai.json:806; the moved verifier calls this.isMcpOAuthEnabled()).

    The decision-letter layer of check-adr-anchors does not move. It reads 2,001 at base 2b7c331537 and 2,001 at head, and the gate exits 0 at both. Neither the moved text, the new module's header, the index.ts comment nor the reworded sentence cites an ADR-NNNN, so the number layer does not move either: 52,177 citations across 6,637 files at both.

    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface. The published surface is measured, not argued, for both commits. The proof owed:

    • token identity per moved body and for the moved declaration against this base, plus a negative control;
    • ruling 6348891160 item 2's block: raw and normalised sha256 of dist/index.d.ts and dist/index.d.mts at base, at commit 1 and at head, the normaliser verbatim, the raw diff line count, and the positive control;
    • ESM and CJS export lists at all three;
    • plugin-auth suite counts at base and at head;
    • the census and the durability measurement at base and head;
    • the symbol-anchor checks and check:adr-anchors (with the decision-letter count) at base and at head;
    • every derived gate.

    Premise (measured on this cut's commits b048a93241 and 619494870b on this base):

    • 2 of 2 bodies token-identical: verifyMcpAccessToken 74 lines and 434 tokens, rereadKeyringForUnknownKey 20 lines and 130 tokens. The readPublishedJwks block is 11 lines, verbatim, and its 76 tokens are identical. Negative control: one token changed in the module (errors.JWKSNoMatchingKey → errors.JWKSNoMatchingKeys) exits 1.
    • The reworded file: 1,427 tokens at both, identical.
    • pnpm --filter @objectstack/plugin-auth typecheck exits 0, and so does pnpm --filter @objectstack/runtime typecheck. eslint --no-inline-config on the 4 source files exits 0.
    • Suite: 189 files, 3,580 passed and 10 skipped at base and at head.
    • dist/index.d.ts and dist/index.d.mts are raw byte-identical at base, at commit 1 and at head. The ESM and CJS entries export the same 159 names at all three, and the list does not include OAUTH_ORGANIZATION_CLAIM.
    • Commit 1 alone: index.js and index.mjs are byte-identical to base. Only the two .map files move.
    • The census and the durability measurement read as stated in (c).
    • check-dispatcher-error-vocabulary reads 56 found and 56 classified at both.
    • 53 derived gates run at head, all exit 0 (--ran: a derived zero).
    • The anchor map and the decision-letter reading above.

    A diff of another kind is a fresh fork on this card.

    Responsibility: n/a — not a defect card
    Thread-read: 6366862591
    Ruling-ref: 6348891160 (the .d.ts bar), 6364006498 (ruling A on this card)
    Serial constraints cleared: read after the proof run. 25 PRs are open, and each head was diffed against its merge base with origin/main. None touches auth-manager.ts, index.ts, auth-manager-email-sms-delivery.ts, the new module's path, signing-key-read-cache.ts, measure-durability-swallow-family.mjs, the plugin-auth anchor entry, ai.json, auth-service.mdx, or the readers named in (c). The Version Packages PR #24876 touches plugin-auth's package.json and a changeset only. origin/main moved to b2c0752900 during the run. #25434 landed in that move: it touches otp-send-guard.ts and rate-limit-storage.ts and its test. None of those is in this cut's surface, and git merge-tree of this branch onto b2c0752900 is clean. The report will re-read the gates on the merge.
    Resume-from: none


    Generated by Claude Code

  21. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25224,
    "status": "done",
    "branch": "claude/project-thread-lg3wrj",
    "pr": "#25461",
    "session": "session_01LSXBc2GCBwiAL7vV4kWkyr",
    "premise_still_valid": true,
    "summary": "Cut 9 of the auth-manager.ts split, per the amended claim 6367866058 (amends 6365318480 on the file cut 8 left; landed note 6366862591), under Ruling-ref 6348891160 (the .d.ts bar) and 6364006498 (Sales's ruling A on this card). This is the first cut under ruling A. Part of #25224, no closing keyword; #25224 stays open. Base origin/main 2b7c331, two commits, +247/-112 over 4 files (359 changed lines). Commit 1 b048a93: index.ts:16 export * from ./auth-manager.js becomes an explicit export list of the 14 names auth-manager.ts exports at base, in declaration order (read off the base file with the compiler API; at head the file has 15, and the fifteenth, OAUTH_ORGANIZATION_CLAIM, is not listed). The same commit rewords one comment sentence the list made false, as the seat agreed: cut 5's module header (auth-manager-email-sms-delivery.ts:45-49) said index.ts re-exports auth-manager.ts whole; it now says the four declarations moved with the bodies because only those bodies read them, and that index.ts lists what it publishes by name. The cycle half is dropped, because cuts 8 and 9 take that cycle by design. 0 ADR citations in the old and new wording; the file's 1,427 tokens are identical. Commit 2 6194948: verifyMcpAccessToken (base :7228, 74 body lines) and rereadKeyringForUnknownKey (:7321, 20 lines) move unedited into packages/plugins/plugin-auth/src/auth-manager-mcp-access-token.ts in cut 1's shape, with readPublishedJwks and its docblock (:1483-1493, 11 lines; read only by those two bodies; no anchor or test names it) moved unexported. The class keeps both methods with docblock and signature (async dropped from verifyMcpAccessToken). McpAccessTokenHost names 7 members: config as a getter, unknownKeyReread as a get/set pair (the re-read stores its floor and in-flight read on the manager), the other 5 as late-bound closures. OAUTH_ORGANIZATION_CLAIM (:1465) is exported in place, its only edit, because buildPluginList (:4398, which stays) mints the claim with it. The new module imports it by value beside type AuthManager, for one read inside verifyMcpAccessToken at call time; the module's top level is three imports and four declarations. retireHeldKeyrings and UNKNOWN_SIGNING_KEY_REREAD_FLOOR_MS leave auth-manager.ts's imports. signing-key-read-cache.js is now first reached through auth-manager-plugin-assembly.js (cut 6), and its top level imports no value and has no outside effect. No path reader needed migrating: no census site moves. Decision-letter layer of check-adr-anchors: 2,001 at base 2b7c331 and 2,001 at head; on current main 17ef032, 2,001 alone and 2,001 with this cut. auth-manager.ts 8,790 to 8,711 lines. No sign-in, session or membership outcome changes.",
    "tests": "Base 2b7c331, commit 1 b048a93, head 6194948. Builds and suites ran through os-verify-lock, with every exit code captured before any pipe. A full turbo run build ran first. Then plugin-auth was built and tested at base, built at commit 1, and built and tested at head, on the same dependency dists. (1) Move proof: TS scanner token identity per body, with template literals re-scanned at their substitutions. Every line is the base line minus two spaces, with the same params and return type, and the class stub keeps its docblock and signature with async dropped. 2 of 2 bodies pass: verifyMcpAccessToken 74 lines and 434 tokens, rereadKeyringForUnknownKey 20 and 130. The readPublishedJwks block is verbatim in the module and absent from auth-manager.ts, and its 76 tokens are identical. Negative control: errors.JWKSNoMatchingKey changed to errors.JWKSNoMatchingKeys in the module fails verifyMcpAccessToken, and the run exits 1; restored, cmp clean. git diff --color-moved=plain --color-moved-ws=allow-indentation-change b048a93 6194948 over the two source files marks 105 of 107 deleted and 120 of 222 added lines moved. The 2 unmarked deletions are the OAUTH_ORGANIZATION_CLAIM line (now export const) and the async verifyMcpAccessToken( signature line. The reword: 1,427 tokens at both and an identical comment-free print; a one-token negative control exits 1. (2) Ruling 6348891160 item 2 block, identical at base, commit 1 and head. dist/index.d.ts: raw 12679e13f74973b0510551a65b50808e7c3936a2d273cf2f262a9196648c0737, normalised c7a9fa25f118cfec858d770b979767744d27d9757a9ea4063faeea6238c33c01. dist/index.d.mts: raw df57562ff950159e738d790cb7ee82a655ba0fffddafe4153bdcc50c4537336f, normalised 68f824aea0947d9895b968ed46a7972eaae43ef0f2bda7e3ba9113002233ade6. Raw diff 0 lines at commit 1 and at head, so there is no union swap. Normaliser: the ruling's one-liner, verbatim in the PR body. Positive control: head dist/index.d.ts with 'private rereadKeyringForUnknownKey;' made public (line 3320) hashes raw 090e9f39924a2034bc496e4fded90d5f267d90ff88aa20a92619d04019ed471b and normalised dc9fc90b5a1699dcb01263f89e1c983da7367710ea2c0f6e17ec395a9d0da579; both differ. Export lists: ESM dist/index.mjs and CJS dist/index.js have 159 names each at all three points, sorted-name hash 3abb126f5babb701 on all six. OAUTH_ORGANIZATION_CLAIM, readPublishedJwks and the module's functions are not exported, and dist/index.d.ts names none of them. 18 dist files at all three. Base to head, only index.js, index.mjs and their maps differ. Base to commit 1, only the two maps differ. (3) pnpm exec vitest run --maxWorkers=3 in packages/plugins/plugin-auth: base Test Files 189 passed (189), Tests 3580 passed | 10 skipped (3590), exit 0; head the same counts, exit 0. Among them are the suites that drive both methods through the manager: auth-manager.mcp-oauth, auth-manager.mcp-verifier-keyring, auth-manager.oauth-consent-organization and auth-manager.mcp-oauth-resource. pnpm --filter @objectstack/plugin-auth typecheck exit 0; pnpm --filter @objectstack/runtime typecheck exit 0; eslint --no-inline-config over the 4 source files exit 0. (4) Tenant-audit census --json, compared per site with line numbers ignored: 238 sites at base and head, 0 differences, 2 in auth-manager.ts at both, 0 unresolved and 0 unledgered; only scannedSources (725 to 726) and measuredAt differ. check-tenant-audit-census exits 0 at both with no --write, so neither document is edited. measure-durability-swallow-family --json: 74 members at both, every member and quiet row the same, and every statistic the same except files (3,449 to 3,450). (5) Anchors at base and at head, run by hand, all exit 0 with the same reading: check-adr-symbol-anchors (2,384 anchors, 466 symbol); check-adr-anchors (80 anchored files; decision-letter layer 2,001 at both; number layer 52,177 citations across 6,637 files at both, because nothing added or moved cites an ADR); check-scripts-symbol-anchors (4,062); check-spec-docblock-symbol-anchors (5,075); check-system-context-census (148 elevation read sites); check-platform-checklist (16 areas, 329 items). check-dispatcher-error-vocabulary: 56 found and 56 classified at both. Anchor map before the cut: all 29 auth-manager.ts#symbol anchors outside changelogs are declaration-class at base, with the moved text blanked, and at head. None names a moved method or readPublishedJwks. #isMcpOAuthEnabled (ai.json:806) names a token the moved verifier calls and resolves on the method that stays. (6) Merge check on current origin/main 17ef032, with both commits cherry-picked and no conflict. It runs after #25434, #25431, #25435, #25441, #25437, #25409, #25394, #25385, #25407, #25449 and #25440; only #25434 touches plugin-auth (otp-send-guard.ts, rate-limit-storage.ts and its test), none of them in this surface. Results: move proof against 17ef032 is 2 of 2 plus the block. check-tenant-audit-census, check-adr-anchors, check-adr-symbol-anchors, check-platform-checklist, check-system-context-census, check-auth-mount-ledger, check-dispatcher-error-vocabulary and check-scripts-symbol-anchors all exit 0. Decision-letter layer: 2,001 on 17ef032 alone and 2,001 with this cut. Number layer: 52,192 citations across 6,643 files at both. The durability measurement is the same, except files 3,449 to 3,450. Eleven suites pass, 288 tests: the four verifier suites, auth-manager-base-path, better-auth-schema-parity, managed-extension-fields, api-error-code.pin, auth-manager.mcp-oauth-dcr-infer, rate-limit-storage and auth-email-locale, on the dependency dists built for 2b7c331. No tested behaviour changed, so no ablation is owed.",
    "gates": "Local subset at 6194948 after the full build: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 53 commands for the 4 paths. All 53 ran, each exit code captured before any pipe, and all 53 exit 0. --ran over that record: 53 derived, 53 run, 0 NOT-MEASURED, a derived zero. dispatch-gates --tier: no path-derived mandate. node scripts/pm/check-governed-merges.mjs --branch HEAD: NOT governed, 0 of 4 paths hit the register, 359 changed lines against the 3,000-line human-merge line. CI on 6194948, the PR's check-runs read at report time (46): 34 success, 12 skipped, 0 failure, 0 pending. Skipped: Dogfood Verify CLI, Temporal Conformance (live PG + MySQL), Console Pin Gate, Build Docs, the Dogfood Regression Gate shard matrix, the opt-in 18.x upgrade harness and Packed-tarball smoke (twice each), and in the labeled run Auto Label, Check PR Size and Check Changeset. Success: Build Core; Test Core (1/6) to (6/6) and Test Core; TypeScript Type Check (Type Check · workspace, · source gates, · consumer gates, · debt ledger); Lint and Repo Gates (Lint · ESLint, · repo and tooling gates, · docs and code guards, · CI, release and engine gates, · PM dispatch-gates, · scoped · tooling self-tests, · scoped · query-options erasure, · scoped · slot lookup and corpus); Dogfood Regression Gate; Governed Surface Queue Guard; Check PR Size; Check Changeset; Auto Label; filter; Part-of PR must not also close its card; The card this PR closes must claim this branch; No other open PR may claim the same issue; No other open PR may claim the same single-writer path; Check Documentation Links; Flag docs affected by code changes. No Claude Approvals check runs on this PR. No review threads. The PR was opened as a draft. Docs Drift Check (advisory), comment 6367942023: 1 hand-written page, content/docs/kernel/contracts/auth-service.mdx, listed through verifyMcpAccessToken, getApi and getMcpResourceUrl. It matches node scripts/docs-audit/affected-docs.mjs --json 2b7c331 (exit 0, 0 release-owned pages). The page states the verifyMcpAccessToken contract signature, which the class method keeps, so no doc edit is owed and no reply was posted. Its unanchored file is the reworded comment's file, whose tokens do not change. Serial: none of the 25 open PRs at the claim touches this cut's files or a reader of it.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "12 since cut 8's report (6366045042), all reads, no write tool: pull_request_read get 2 (#25423 state twice), pull_request_read get_status 2 (#25423; #25461), list_pull_requests 1 (open PRs for the serial read), pull_request_read get_files 2 (#25434 and #25448, before the git-based serial read), pull_request_read get_check_runs 3 (#25461), pull_request_read get_comments 1 (#25461), pull_request_read get_review_comments 1 (#25461). (claude-code-remote subscribe_pr_activity once on #25461, not a GitHub write.)",
    "api_writes": "3 relay acts since cut 8's report (scripts/pm/, each read back identical): 1) amended claim 6367866058 via post-stamped, run 39974328194, 14451 bytes; 2) pr_create #25461 via fleet-write, run 39974683057, 20951 bytes sent and stored, assignee os-sales, label skip-changeset; 3) this report via post-stamped. Plus git push of the branch (6194948, a new branch after the queue deleted the old one on #25423's merge; not REST). No PR body edit. No other write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "scripts/measure-durability-swallow-family.mjs (a measurement, not a gate; check:swallow-census-controls runs only its self-test controls) names auth-manager.ts::verifyMcpAccessToken in prose at :625 and :775 as the one outstanding DARK member. The census on base 2b7c331 does not list it: its one auth-manager.ts DARK member is observeFirstMemberships. So the prose was stale before this cut. This cut leaves the census reading the same at base and head (74 members, every row the same, files 3,449 to 3,450). scripts/ is not edited. For the seat to judge at ACCEPT, as agreed."
    ]
    }


    Generated by Claude Code

  22. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed cut 9 · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T15:57Z

    reading value
    PR #25461 MERGED through the queue (the seat's ACCEPT is on the PR), head 619494870b
    origin/main carries 05cc3ee953

    State: the card stays open and dispatched (Part of). The card thread claims cut 10 (request routing: handleRequest, ownsRoute, betterAuthEndpointPath; isScimProtocolPath exported in place under ruling A) afresh on the merged file, under Ruling-ref: 6364006498. The seat has agreed the scope.


    Generated by Claude Code

  23. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · series card, cut 10 (one PR, cuts strictly serial: after cut 9, PR #25461, landed as 05cc3ee953, per the landed note 6369059246) · the second cut under ruling 6364006498 (A) · amends claim 6367866058 for this PR, on the merged file · 2026-10-23T16:43Z
    Session: session_01LSXBc2GCBwiAL7vV4kWkyr
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-lg3wrj (the harness-pinned branch for this session, restarted from origin/main 05cc3ee953 after #25461 merged; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
    Worktree: objectstack-issue-25224 (a linked worktree in this session's container)
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (read on origin/main 05cc3ee953, where auth-manager.ts is 8,711 lines). One commit, 27f04eaa1c.

    (a) The move: request routing. Three AuthManager method bodies move unedited from packages/plugins/plugin-auth/src/auth-manager.ts into a new module, packages/plugins/plugin-auth/src/auth-manager-request-routing.ts, in cut 1's shape. The class keeps all three methods with their docblocks and signatures (async dropped from the two that had it), and each body becomes a literal of exactly the host members it reads, plus a .call.

    • handleRequest :6369 (public, 200 body lines): the one seam every better-auth request passes through on its way to the vendor handler and back. It reads betterAuthEndpointPath, getOrCreateAuth, resolveClientAddress and runSubjectErasureAtomically.
    • ownsRoute :6739 (public): whether better-auth routes a request at all. It reads betterAuthEndpointPath and getOrCreateAuth, and reads and writes routeOwnership and routeOwnershipFor.
    • betterAuthEndpointPath :6766 (private, not async, stays so): the endpoint path both key on. It reads getBasePath.

    How the host is built:

    • Every method goes as a late-bound closure, so a double or spy set on the manager is still the one a body reaches. handleRequest and ownsRoute ask betterAuthEndpointPath through the manager.
    • routeOwnership and routeOwnershipFor (the private fields at :6755–:6756, which stay) go as get/set pairs. ownsRoute stores the table it builds, and the instance it built it from, on the manager, so the memo stays the manager's and is rebuilt when that instance is replaced.
    • handleRequest reassigns its own request parameter, as before. Its two await import(…) calls (@better-auth/core/context, ./scim-connection-service.js) move with the body, and so do its two console.error('[AuthManager] better-auth returned error:', …) lines, text unchanged.

    isScimProtocolPath is exported in place (ruling 6364006498, parameter 2).

    • :1240 becomes export function isScimProtocolPath(…). That is the only edit to text that stays.
    • It does not move: docs/adr/0134-env-side-scim-provisioning.md:122 anchors it as auth-manager.ts#isScimProtocolPath, beside #SCIM_PROTOCOL_PATH_PREFIX (:121), which it reads and which stays too.
    • The new module imports it by VALUE from ./auth-manager.js, beside type AuthManager. The one read is inside handleRequest's body, when a request is handled. The module's top level is imports, the host interface and three function declarations, so nothing reads the binding while the two modules are first evaluated.
    • index.ts lists auth-manager.ts's published names one by one (cut 9), and it does not list this one, so it is not published. The proof below measures that.

    Imports:

    • Ten imported names leave auth-manager.ts, because their only readers were the moved bodies: runWithAuthActorScope, SESSION_ERASURE_PATHS, envelopeVendorAdminRefusal, refuseAnonymousSession, buildBetterAuthRouteOwnership, ADMIN_SESSION_COOKIE_KEY, STOP_IMPERSONATING_PATH, withBearerAdminSessionRecovery, withVendorClientAddress and clientAddressStateEntries. Each line keeps the names that stay (setAuthActorResolver, rotateCallerBearerOnImpersonation, vendorClientAddressOptions, readRequestClientAddress); the three single-name lines go.
    • type BetterAuthRouteOwnership stays, for the routeOwnership field.
    • ORGANIZATION_DELETE_PATH is imported by both files: the global before-hook in auth-manager.ts (:2379) reads it too.
    • Evaluation order: three import lines go whole.
      • session-tombstone.js is still first reached earlier, through objectql-adapter.js (:70).
      • vendor-admin-refusal-envelope.js and anonymous-session-refusal.js have no other importer, so they are now first reached through the new module, whose import line is auth-manager.ts's last. Each one's top level is literal constants, one new Set([401, 403]), function declarations and two value imports. One is ./platform-admin-gate.js, which an earlier import line of auth-manager.ts already reaches (admin-impersonate-endpoint.js, :95, imports it by value). The other is @objectstack/spec/api, which nothing else in plugin-auth imports by value, so its first load moves later too: in the CJS bundle its first require goes from line 4,372 to 7,535, and ESM hoists it either way.
      • @objectstack/spec declares "sideEffects": false, and neither module does anything outside itself, so the later order cannot be observed.

    What stays:

    • Every other method, including getBasePath, getOrCreateAuth, resolveClientAddress and runSubjectErasureAtomically, which the bodies reach through the host. The private-static readers and reconcileScimUserLifecycle stay, per the ruling.

    (b) The decision-letter layer drops by one, as the seat agreed. handleRequest's first comment cites ADR-0069 D7. auth-manager.ts is an anchored file, and the new module is not, so check-adr-anchors' decision-letter layer reads 1,978 at base 05cc3ee953 and 1,977 at head. The gate exits 0 at both. That is the one letter citation in the moved text; its other ADR citation is ADR-0112 with no letter. No anchor entry is added for the new module: the auth-manager.ts entry's two paragraphs (ADR-0093 D2, the membership reconciler; ADR-0108, the organization roles) describe neither moved method. The new module's header cites no ADR-NNNN, so the number layer reads 52,223 citations at both, across 6,651 files at base and 6,652 at head (the new module).

    (c) Path readers.

    • No census site moves. The moved text has no engine write, no this.X.delete() and no elevation read. check-tenant-audit-census exits 0 at head with no --write: 238 sites at base and at head, compared per site (line-blind): 0 differences, 2 in auth-manager.ts at both, 0 in the new module. So neither census document is edited.
    • scripts/measure-durability-swallow-family.mjs (a measurement, not a gate): 74 members and 88 quiet rows at both, the same line-blind. Two statistics move. The scanned file count goes 3,454 → 3,455. Its droppedNotAwaited diagnostic goes 245 → 242 (delete 158 → 156, update 25 → 24). Measured with an instrumented scratch copy, never committed: at base, ownsRoute's try (:6742) walked into getOrCreateAuth's same-file body and met three un-awaited write-shaped calls there (:5544, :5742, :6961). In the new module that try cannot follow this.getOrCreateAuth() into another file, so it no longer meets them. They were dropped at base too, so no member moves. The other try that reaches the same three calls (:5097 at base) still does.
    • Read, and not edited:
      • check-dispatcher-error-vocabulary.mjs and api-error-code.pin.test.ts's authManagerFiles floor: no new APIError( sits in the moved text. The gate reads 56 found and 56 classified at both.
      • check-auth-mount-ledger.mjs and auth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text. getBasePath, configuredBasePath and rootedBasePath stay on the class.
      • The plugin-factory scans in better-auth-schema-parity.test.ts and managed-extension-fields.test.ts: they match await import('better-auth/plugins…' | '@better-auth/…') only. The moved await import('@better-auth/core/context') is not a plugin factory, and the gates pass at head.
      • Prose that names AuthManager.betterAuthEndpointPath or handleRequest (better-auth-route-ownership.ts:103, anonymous-session-refusal.ts:89, vendor-admin-refusal-envelope.ts:83, comments in auth-manager.ts): each names a method that stays on the class.
    • The suites that drive the three methods through the manager, which the stubs keep working: auth-catchall-fallthrough.test.ts, auth-catchall-yield.test.ts, auth-manager-base-path.test.ts, oidc-discovery-sync-mount.test.ts, scim-transaction-scope.test.ts and the full plugin-auth suite.

    ⛔ Nothing else changes:

    • No other method moves, and no moved body is edited.
    • The only edit to stay-behind text is export on isScimProtocolPath.
    • No anchor, ADR or docs prose is edited.
    • No edit to eslint.config.mjs, to any baseline, or to scripts/.
    • The title goes under refactor(plugin-auth), with no !.
    • No changeset: skip-changeset, per ruling 6348891160 item 4.

    Anchor reading before the cut, on 05cc3ee953:

    • I mapped every auth-manager.ts#symbol anchor in the tracked tree, outside changelogs, with symbolSegmentResolution from scripts/symbol-anchors.mjs. There are 29.
    • With the 3 bodies blanked, none changes class: all 29 are declaration-class at base, blanked and head.
    • No anchor names a moved method. Two name a token the moved text also carries, and each resolves on the declaration that stays: #isScimProtocolPath (ADR-0134 :122; the moved handleRequest calls it) and #AuthManager (ADR-0135 :104; the moved log text spells [AuthManager]).

    Container & model: L (series), card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Basis for the Clause-② line: a move-only cut widens no accept set and no public surface. The published surface is measured, not argued. The proof owed:

    • token identity per moved body against this base, plus a negative control;
    • ruling 6348891160 item 2's block: raw and normalised sha256 of dist/index.d.ts and dist/index.d.mts at base and at head, the normaliser verbatim, the raw diff line count, and the positive control;
    • ESM and CJS export lists at both;
    • plugin-auth suite counts at base and at head;
    • the census and the durability measurement at base and head;
    • the symbol-anchor checks and check:adr-anchors (with the decision-letter count) at base and at head;
    • every derived gate.

    Premise (measured on 27f04eaa1c on this base):

    • 3 of 3 bodies token-identical: handleRequest 200 lines and 465 tokens, ownsRoute 14 and 90, betterAuthEndpointPath 12 and 86. Negative control: one token changed in the module ('production' → 'productio' in handleRequest) exits 1.
    • pnpm --filter @objectstack/plugin-auth typecheck exits 0, and so does pnpm --filter @objectstack/runtime typecheck. eslint --no-inline-config on the 2 source files exits 0.
    • Suite: 189 files, 3,580 passed and 10 skipped at base and at head.
    • dist/index.d.ts and dist/index.d.mts are raw byte-identical at base and at head. The ESM and CJS entries export the same 159 names at both, and the list does not include isScimProtocolPath or the module's functions.
    • The census and the durability measurement read as stated in (c).
    • 53 derived gates run at head, all exit 0 (--ran: a derived zero).
    • The anchor map and the decision-letter reading above.

    A diff of another kind is a fresh fork on this card.

    Responsibility: n/a — not a defect card
    Thread-read: 6369059246
    Ruling-ref: 6348891160 (the .d.ts bar), 6364006498 (ruling A on this card)
    Serial constraints cleared: read after the proof run. 19 PRs are open, and each head was diffed against its merge base with origin/main. None touches auth-manager.ts, index.ts, the new module's path, the ten modules it imports, measure-durability-swallow-family.mjs, the plugin-auth anchor entry, ADR-0134, auth-service.mdx, authentication.mdx, or the readers named in (c). The Version Packages PR #24876 touches plugin-auth's package.json, its CHANGELOG.md and changesets only.
    Resume-from: none


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedenhancementNew feature or requestpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions