Repository navigation
refactor(plugin-auth): move AuthManager's MCP access-token verifier into auth-manager-mcp-access-token.ts (#25224 cut 9) - #25461
Conversation
…x.ts index.ts re-exported auth-manager.ts with export *, so any module-level helper exported there for a module split out of it would be published. It now names the 14 exports auth-manager.ts already has, in declaration order, and a new export there is published only by adding it to the list. The published declarations and both entry points' export lists are unchanged. The cut 5 module header said index.ts re-exports auth-manager.ts whole. That sentence is reworded to say why its four declarations moved with the bodies; it is a comment, and no token of the file changes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
…nto auth-manager-mcp-access-token.ts Two method bodies move unedited: verifyMcpAccessToken, the local, fail-closed check of an OAuth Bearer access token this deployment minted, and rereadKeyringForUnknownKey, the one forced keyring re-read a token signed by an unknown key earns. readPublishedJwks, which only they read, moves with them unedited. The class keeps both methods, their docblocks and signatures, and hands each body the members it reads. OAUTH_ORGANIZATION_CLAIM stays in auth-manager.ts, where the plugin assembly also reads it, and is exported there for the new module; index.ts does not publish it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a5524ffe1e7bbfb9154cc5fda4c782e14db0ce14 && git checkout a5524ffe1e7bbfb9154cc5fda4c782e14db0ce14
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 17ef0327b2b266ddb2ed2e83e6227f64761201fe 619494870ba9fd8214734413771b262da597b7a8 && git checkout -B drift-repro 17ef0327b2b266ddb2ed2e83e6227f64761201fe && git merge --no-ff 619494870ba9fd8214734413771b262da597b7a8
node scripts/docs-audit/affected-docs.mjs --json 17ef0327b2b266ddb2ed2e83e6227f64761201fe
|
席位核对 · ACCEPT · 2026-10-23T15:37Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25224
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160, 6364006498
The basis for the
Clause-②: noline: this is a move. No accept set, route, refusal, code or answer changes, and the published surface is measured unchanged at both commits:@objectstack/plugin-auth'sdist/index.d.tsanddist/index.d.mtsare raw byte-identical to base at commit 1 and at head (0 diff lines, so the union-order normalisation has nothing to do). The positive control is the one ruling6348891160asks for.The
.d.tsbar is ruling6348891160item 1 (on #25222, covering this card). Ruling6364006498is Sales's ruling A on this card, for bodies that read shared unexported helpers. This is the first cut it applies to.Cut 9 of the
auth-manager.tssplit: the MCP access-token verifier. TwoAuthManagermethod bodies (94 lines) and the one module-level declaration only they read (11 lines) move unedited, re-indented one level, into one new module,packages/plugins/plugin-auth/src/auth-manager-mcp-access-token.ts. #25224 stays open for the later cuts.This follows the amended claim
6367866058, which amends6365318480on the file cut 8 left (#25423, landed note6366862591). The shape is cuts 1 to 8's, which is #23050's.Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/main2b7c331537after #25423 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 8,790 to 8,711 lines. The cut is +247 / −112 over 4 files: 359 changed lines, under the 3,000-line line. It is not a governed surface (check-governed-merges.mjs --branch HEAD: 0 of the 4 paths hit the register).There are two commits, so the
index.tschange is measured by itself:b048a93241:index.tsnamesauth-manager.ts's published exports, and one comment sentence is reworded;619494870b: the move.Commit 1:
index.tsnames what it publishes fromauth-manager.tsRuling
6364006498, parameter 1.index.ts:16wasexport * from './auth-manager.js';, so any module-level helper exported fromauth-manager.tsfor a module split out of it would have been published. It is now an explicitexport { … } from './auth-manager.js';of the 14 namesauth-manager.tsexports 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 above it says a new export of
auth-manager.tsis published only by adding it there. The list was read off the base file with the TypeScript compiler API: 14 exported declarations, exactly these, in this order. At head the file has 15, and the fifteenth,OAUTH_ORGANIZATION_CLAIM(commit 2), is not listed.One comment-only reword, agreed with the seat. Cut 5's module header (
auth-manager-email-sms-delivery.ts:45–:49) said "index.tsre-exportsauth-manager.tswhole, 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, and the PR would otherwise ship a statement it makes false. The sentence now reads:auth-manager.tsfrom 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 (SMS_QUOTA_EXCEEDED_CODE,isSmsQuotaRefusal,smsQuotaExceededApiError,PHONE_OTP_SEND_REFUSAL_CODE) has a reader inauth-manager.ts.ADR-NNNN, with or without a decision letter: 0 and 0.'TOO_MANY_REQUESTS'→'TOO_MANY_REQUEST') makes both comparisons fail, exit 1.What commit 1 changes in the build, measured at
b048a93241:dist/index.d.tsanddist/index.d.mtsare raw byte-identical to base;index.jsandindex.mjsare byte-identical to base. Onlyindex.js.mapandindex.mjs.mapmove, becauseindex.ts's lines moved.Commit 2: the move
verifyMcpAccessToken(public):7228getApi,getAuthIssuer,getMcpResourceUrl,isMcpOAuthEnabled,rereadKeyringForUnknownKeyrereadKeyringForUnknownKey(private):7321config, andunknownKeyReread(read and written)readPublishedJwks(module-level, with its docblock):1483–:1493Lines are base
2b7c331537. The members come from a walk of everythis.Xin each body with the TypeScript compiler API. No body has a barethis,this[...],argumentsorsuper, or athisinside afunctionor a nested class. The one member a body assigns isunknownKeyReread(rereadKeyringForUnknownKey).verifyMcpAccessTokenis the local, fail-closed check of an OAuth 2.1 Bearer access token this deployment's authorization server minted: the signature against the published key set,iss,aud, the refusal of a token no human delegated, and the organization claim.rereadKeyringForUnknownKeyis the one forced keyring re-read a token signed by an unknown key earns, single-flight and floored atUNKNOWN_SIGNING_KEY_REREAD_FLOOR_MS.readPublishedJwksmoves with them, unexported. It reads the key set/jwkspublishes. 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.OAUTH_ORGANIZATION_CLAIMis exported in place (ruling6364006498, parameter 2)::1465becomesexport const OAUTH_ORGANIZATION_CLAIM = 'organization_id';. That is the only edit to text that stays.buildPluginList(:4398, which stays) mints the claim with it, andverifyMcpAccessToken(:7288) reads it back; its docblock calls it "one spelling for both ends"../auth-manager.js, besidetype AuthManager. The one read is insideverifyMcpAccessToken's body, when a token is verified. The module's top level is three imports,readPublishedJwks, the host interface and the two functions, 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 head's ESM and CJS entries do not export it, anddist/index.d.tsdoes not name it.Imports.
retireHeldKeyringsandUNKNOWN_SIGNING_KEY_REREAD_FLOOR_MS(./signing-key-read-cache.js) leaveauth-manager.ts, where their only reader wasrereadKeyringForUnknownKey's body, and that import line goes. The two docblock mentions ofUNKNOWN_SIGNING_KEY_REREAD_FLOOR_MSthat stay in the class are backticked text, not{@link}.type JSONWebKeySet(jose) stays inauth-manager.ts, for theunknownKeyRereadfield and therereadKeyringForUnknownKeysignature. The new module imports it as well.await import('jose')insideverifyMcpAccessTokenmoves with the body.Evaluation order.
signing-key-read-cache.jswas first reached throughauth-manager.ts's own import line (:146). It is now first reached throughauth-manager-plugin-assembly.js, which importscreateSigningKeyReaderfrom it (cut 6's import line,:221at head). Its top level is one type-only import, literal constants, aSet, aWeakMap, interfaces and function declarations. It imports no value and does nothing outside itself, so the change in order cannot be observed.The shape
.d.tslists each one. Each body is now a host literal of exactly the members that body reads, typed as aPickofMcpAccessTokenHost, plus.call.McpAccessTokenHostlives in the new module. It names the 7 members the bodies read, each typed as the class member (AuthManager['config'], …).(...a) => this.m(...a)), so a double or spy set on the manager is still the one a body reaches. The verifier suites replacegetApiwith a key-set double, andverifyMcpAccessTokenasksrereadKeyringForUnknownKeythrough the manager rather than calling the function directly.configgoes as a getter. The class reassigns it after construction (setRuntimeBaseUrl,applyConfigPatch), so it is read off the manager when a body reads it, as before.unknownKeyRereadgoes as a get/set pair.rereadKeyringForUnknownKeystores 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.verifyMcpAccessTokenis no longerasync. It returns the moved async function's own promise, so a caller awaits the same promise the body settles. Building the host cannot throw, so it does not now throw synchronously where it used to reject.rereadKeyringForUnknownKeywas notasyncand stays so.Path readers
No path reader needs migrating: none reads a moved line.
this.X.delete()and no elevation read.check-tenant-audit-censusexits 0 at head with no--write: 238 sites at base and at head, compared per site with line numbers ignored, 0 differences, and 2 of them inauth-manager.tsat both. So neither census document is edited.check-dispatcher-error-vocabulary.mjsandapi-error-code.pin.test.ts'sauthManagerFilesfloor: nonew APIError(sits in the moved text. The gate reads 56 sites found and 56 classified at base and at head.better-auth-schema-parity.test.tsandmanaged-extension-fields.test.tsmatchawait import('better-auth/plugins…')andawait import('@better-auth/…')only. The movedawait import('jose')is neither, and the import lines that change are local modules.check-auth-mount-ledger.mjsandauth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text.getAuthIssuerandgetMcpResourceUrlstay on the class.scripts/adr-anchors/: theauth-manager.tsentry's invariant (ADR-0093, ADR-0108) is about the organization roles and the membership reconciler, not this text.auth-manager.mcp-oauth.test.ts(local JWKS verification, fail-closed);auth-manager.mcp-verifier-keyring.test.ts(the forced re-read, its floor and single flight; it readsUNKNOWN_SIGNING_KEY_REREAD_FLOOR_MSandretireHeldKeyringsfromsigning-key-read-cache.jsitself);auth-manager.oauth-consent-organization.test.ts(the organization claim end to end);auth-manager.mcp-oauth-resource.test.ts.auth-manager.ts#symbolanchor in the tracked tree outside changelogs was mapped withsymbolSegmentResolutionfromscripts/symbol-anchors.mjs. There are 29, and all 29 aredeclaration-class at base, with the moved text blanked, and at head. None names a moved method orreadPublishedJwks. One names a token the moved text carries, and it resolves on the declaration that stays:#isMcpOAuthEnabled(docs/qa/platform-checklist/areas/ai.json:806; the moved verifier callsthis.isMcpOAuthEnabled()).The decision-letter layer does not move
The seat asks each cut's report for this count at both ends.
check-adr-anchors' decision-letter layer reads 2,001 at base2b7c331537and 2,001 at head, and the gate exits 0 at both. Nothing this PR adds or moves cites anADR-NNNN: not the moved text, not the new module's header, not theindex.tscomment, and not the reworded sentence. So the number layer reads the same at both: 52,177 citations across 6,637 files. No anchor entry is added for the new module, as for cuts 1 to 8's modules.Proof
Builds and suites ran through
os-verify-lock, and every exit code was captured before any pipe. Base2b7c331537, commit 1b048a93241, head619494870b. A fullturbo run buildran first. Thenplugin-authwas built and its suite run at base, built at commit 1, and built and its suite run at head, on the same dependency dists.1. The moved text.
asyncdropped fromverifyMcpAccessToken.readPublishedJwks's 11 lines, docblock included, are verbatim in the module and absent fromauth-manager.ts. Its 76 tokens are identical.verifyMcpAccessTokenrereadKeyringForUnknownKeyreadPublishedJwkserrors.JWKSNoMatchingKey→errors.JWKSNoMatchingKeysinverifyMcpAccessToken) makes that method fail (tokens and lines DIFFER), and the run exits 1. The module was restored afterwards, andcmpagainst the saved copy was clean.git diff --color-moved=plain --color-moved-ws=allow-indentation-change b048a93241 619494870bover the two source files marks 105 of the 107 deleted lines and 120 of the 222 added lines as moved.const OAUTH_ORGANIZATION_CLAIM = …(nowexport const) and theasync verifyMcpAccessToken(signature line.McpAccessTokenHost, the module header, the per-function pointer docblocks and the import blocks.2.
.d.tsand exports (ruling6348891160items 1 and 2).dist/index.d.tsraw12679e13f74973b0510551a65b50808e7c3936a2d273cf2f262a9196648c0737dist/index.d.tsnormalisedc7a9fa25f118cfec858d770b979767744d27d9757a9ea4063faeea6238c33c01dist/index.d.mtsrawdf57562ff950159e738d790cb7ee82a655ba0fffddafe4153bdcc50c4537336fdist/index.d.mtsnormalised68f824aea0947d9895b968ed46a7972eaae43ef0f2bda7e3ba9113002233ade6node -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.dist/index.d.tswith one private member made public (private rereadKeyringForUnknownKey;→rereadKeyringForUnknownKey;, line 3320, a one-line change) hashes raw090e9f39924a2034bc496e4fded90d5f267d90ff88aa20a92619d04019ed471band normaliseddc9fc90b5a1699dcb01263f89e1c983da7367710ea2c0f6e17ec395a9d0da579. Both differ from the readings above.dist/index.mjsand CJSdist/index.jseach have 159 names at base, at commit 1 and at head, the same list (the sha256 of the sorted names is3abb126f5babb701…on all six).OAUTH_ORGANIZATION_CLAIM,readPublishedJwksand the module's functions are not among them, anddist/index.d.tsnames none of them.dist/at all three, the same list. From base to head, onlyindex.js,index.mjsand their two.mapfiles differ, as a move of code inside the bundle must. From base to commit 1, only the two.mapfiles differ.3. Tests and lint.
pnpm exec vitest run --maxWorkers=3inpackages/plugins/plugin-auth: baseTest Files 189 passed (189),Tests 3580 passed | 10 skipped (3590), exit 0. Head: the same counts, exit 0.pnpm --filter @objectstack/plugin-auth typecheck(bothtscprojects andcheck:test-typecheck): exit 0.pnpm --filter @objectstack/runtime typecheck: exit 0.eslint --no-inline-config(the rootlintscript's binary and flags) over the 4 source files: exit 0.4. The censuses and the anchors, at base and at head.
scannedSources(725 → 726, the new module) andmeasuredAtdiffer.dispatch-gatesdoes not derive the ADR one. All six exit 0 at both, with the same reading:check-adr-symbol-anchors: 2,384 anchors across 142 records, 466 symbol (440 declaration, 26 literal);check-adr-anchors: 80 anchored files; the decision-letter layer reads 2,001, as above;check-scripts-symbol-anchors: 4,062 anchors;check-spec-docblock-symbol-anchors: 5,075 anchors (both of these read in the same built tree);check-system-context-census: 148 elevation read sites;check-platform-checklist: 16 areas, 329 items.5. Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 53 commands for the 4 paths. All 53 ran at619494870bafter the full build, and all 53 exited 0.--ranover the record with each exit code: 53 derived, 53 run, 0 NOT-MEASURED, a derived zero.check-governed-merges.mjs --branch HEAD: NOT governed, 359 changed lines.node scripts/docs-audit/affected-docs.mjs --json 2b7c331537, exit 0): 1 page,content/docs/kernel/contracts/auth-service.mdx, which states theverifyMcpAccessTokencontract signature. The class method keeps that signature, so no doc edit is owed. 0 release-owned pages.origin/mainmoved tob2c0752900during the run with fix(plugin-auth): degraded rate-limit warning names the cache registration that boots #25434, which touchesotp-send-guard.tsandrate-limit-storage.tsand its test, none of them in this surface.git merge-treeof this branch ontob2c0752900is clean.Finding, outside this cut
scripts/measure-durability-swallow-family.mjsis a measurement, not a gate. Its prose at:625and:775namesauth-manager.ts::verifyMcpAccessTokenas the one outstanding DARK member. The census does not list it on base: its oneauth-manager.tsDARK member isobserveFirstMemberships. So that prose was already stale before this cut.Its
--jsonreading is the same at base and at head: 74 members at both, every member and quiet row the same (line numbers ignored), and every statistic the same except the scanned file count (3,449 → 3,450, the new module). Neither side namesverifyMcpAccessTokenor the new module.scripts/is not edited, and the finding is for the seat to judge.Changeset
None:
skip-changeset, per ruling6348891160item 4. The published declarations and export lists are unchanged, and the bundle's bytes move only because code moved inside it.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr