You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
IAuthService: declare an optional member that records an MCP access token as used, called by the MCP door only after it admits the call (lands with its producer and reader; fixes #30949) #31211
carries the contract half of #30949; #30949 keeps nothing else (its whole fix is this member, its producer and its reader, which land in one PR).
Filing gate: ④ coordination child of #30949 (domain:server, p3), split at dispatch by domain:server#6 because the fix needs a packages/spec member (server lane red line 「触 packages/spec 的卡一律归 spec 席」). ⛔ Not a claim. Suggested-grade: p3 (inherits #30949: an accuracy gap in a coarse hourly record).
Business reason. An administrator reading GET /admin/authorized-clients?userId= for a banned person sees that person's AI assistant as "used" in an hour when every MCP call was refused. Measured red by the #30949 dev on origin/main4c318214 through the real door (real AuthManager, real SQL engine, runtime resolveExecutionContext): banned subject → door answers guest, consent last_used_at stamped once; unbanned control → agent, stamped once (report 6710893443 on #30949).
Why the contract. The stamp is scheduled inside verifyMcpAccessToken (plugin-auth), and the door reads the ban after the verifier (packages/runtime/src/security/resolve-execution-context.ts, the verifier call then isOAuthSubjectBanned). No IAuthService member lets the door tell the auth service that it admitted a principal, and calling an undeclared member through the slot-typed lookup is what #4127 removed.
Shape (seat 6 ruling on #30949, option A of report 6710893443; the dev's four-axis analysis is in that report):
IAuthService (packages/spec/src/contracts/auth-service.ts, beside verifyMcpAccessToken) gains one OPTIONAL member, e.g. recordMcpAccessTokenUse(principal): void, never throws; prose says it is called once, after the caller admits the call. content/docs/kernel/contracts/auth-service.mdx prose follows.
Producer (plugin-auth auth-manager.ts + mcp-client-last-use.ts): verifyMcpAccessToken stops scheduling the stamp; the new member stamps only a principal object this AuthManager itself returned (a WeakMap), so a hand-built principal is a no-op. The once-per-consent-per-hour-per-process bound is unchanged.
Reader (runtime resolve-execution-context.ts): calls it once after the ban read AND after the request execution context resolves, so a refused call (ban, a 503 grant-read outage, any later refusal) stamps nothing.
Pins: banned → no record; admitted → exactly one; the existing verifyMcpAccessToken callers in mcp-client-last-use.real-engine.test.ts and authorized-clients-read.real-plugin.test.ts that expect a stamp move to verify-then-record.
Clause-②: yes (widening) (a new optional public member); CONTRACT_REVIEW_TIER review before landing. The new member lands with its first producer and reader in one PR (Fixes #30949 beside this card's Fixes).
Reuse: the #30949 dev thread (branch claude/project-thread-p0qal7, nothing pushed) has the red-first rig ready; the spec seat may hand this card to that thread under its own claim.
Claim: PM loop round 1 · seam card, dispatched vertically (spec member + its producer and reader in one PR, per the card and triage 6710960650)
Session: session_01FEkQ5mw7HFYn9o4LEGMQdK (card thread session_01FEkQ5pERdWox6UVMyKRjPq, reused from #30949)
Account: os-project-manager
Branch: claude/project-thread-p0qal7 (the #30949 card thread's harness branch, empty, handed over by domain:server#6 in release 6710928373; a thread can push only its assigned branch)
Worktree: the card thread's own checkout of claude/project-thread-p0qal7
Domain: domain:spec
Seat: domain:spec#1
File surface: packages/spec/src/contracts/auth-service.ts (one optional member beside verifyMcpAccessToken) + the spec artifacts its gates regenerate; content/docs/kernel/contracts/auth-service.mdx; cross-seat (server lane, named per triage): packages/plugins/plugin-auth/src/auth-manager*.ts, packages/plugins/plugin-auth/src/mcp-client-last-use.ts, packages/runtime/src/security/resolve-execution-context.ts, their tests; root .changeset/. ⛔ ui/**.
Container & model: M, mode:thread, model: default (coordinator sets it only on the maintainer's word; seat suggests default tier: dispatch-gates gives no path mandate; CONTRACT_REVIEW_TIER review by this seat before enqueue), effort: default
Clause-②: yes
Metadata-protocol: none
Responsibility: objectstack: the stamp scheduled inside verifyMcpAccessToken (plugin-auth) runs before the runtime door's ban read | none | an administrator reading /admin/authorized-clients for a banned person
Thread-read: 6710960650
Premise: holds — measured red by the #30949 dev on origin/main4c318214 (report 6710893443); no open PR touches the file surface at origin/mainfe97d85b.
Serial constraints cleared: none — no open PR touches auth-service.ts, resolve-execution-context.ts, auth-manager*.ts or mcp-client-last-use.ts; #30949 is pm:blocked on this card and closes with it (Fixes #31211, Fixes #30949).
Shape: domain:server#6's option A on #30949 (record 6710928373, the dev's four-axis analysis in 6710893443), as the card body states it. Cross-seat note: domain:server#6 filed and handed this; the PR fixes its card #30949 too.
os-dev-report
{
"issue": 31211,
"status": "done",
"branch": "claude/project-thread-p0qal7",
"pr": "#31247",
"head": "e8c0e991",
"session": "session_01FEkQ5pERdWox6UVMyKRjPq",
"premise_still_valid": true,
"summary": "Option A as #31211 states it, in one PR that Fixes #31211 and #30949. IAuthService gains one optional member, recordMcpAccessTokenUse(principal): void, never throws, called once per admitted call with the verifier's own object (parameter type derived from verifyMcpAccessToken's result); auth-service.mdx lists it. plugin-auth: verifyMcpAccessToken no longer schedules the consent last-use stamp; it puts the principal it returns in a WeakMap with the token's iat, and recordMcpAccessTokenUse stamps only an object in that map, once (hand-built, copied or repeated objects record nothing); the hourly stamp bound is unchanged. runtime: the MCP door calls authService?.recordMcpAccessTokenUse?.(verified) only after the ban read and after resolveRequestExecutionContext resolves, through the declared slot-typed IAuthService, so a banned subject, a grant-read 503, an unverified token and a forward credential record nothing. Changeset patch on spec, plugin-auth, runtime (kind none; widening).",
"tests": "Red-first through the real door (one-shot rig, not committed: real AuthManager, real SQL engine, banned sys_user row, runtime resolveExecutionContext): production sources at base fe97d85 -> 'banned=true principalKind=guest stamp=HOUR_10 writes=1', AssertionError 'a refused call stamps nothing', 1 failed | 1 passed; at head -> 'banned=true principalKind=guest stamp=null writes=0', 'banned=false principalKind=agent writes=1', 2 passed. New pins: runtime resolve-execution-context.test.ts [#31211] 6 cases (admitted exactly once with the same object; banned none; grant-read outage asserts code SERVICE_UNAVAILABLE + status 503 and none; unverified none; forward credential none; member absent still admits); plugin-auth mcp-client-last-use.real-engine.test.ts [#31211] 4 cases (verify alone stamps nothing; hand-built and copied principal record nothing; same object in a later hour writes nothing more; never throws). 23 existing verify calls in that file go through admittedCall (verify then record). Ablations via ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty (both suites import these sources, no dist leg): 1 delete door call -> admitted case red; 2 record at verification before the ban read -> admitted (2 vs 1), banned, 503 red; 3 verifier stamps again -> verify-alone and hand-built red; 4 any object recorded -> hand-built red; 5 delete the once-only delete -> later-hour case red (2 vs 1). Suites: plugin-auth test 235 files 4187 passed 10 skipped (VERDICT command-exit 0); runtime src/security + mcp-oauth + federation bridge 15 files 228 passed, resolve-execution-context.test.ts 53 passed at head; spec src/contracts 52 files 475 passed. typecheck spec / plugin-auth / runtime exit 0 (runtime again at head).",
"gates": "dispatch-gates --commands (108 families) run on 327f91d; --ran: '108 derived famil(ies) accounted for, 106 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. First-pass reds rerun: check:where-matcher exit 1 (new door-test table double read a combinator as a field) fixed in e8c0e99, rerun exit 0; check:query-options-erasure exit 124 at a 300s cap, rerun uncapped exit 0; check-plugin-teardown-shape --self-test exit 3 (shallow), exit 0 after git fetch --depth=1 origin 621a487. NOT MEASURED: spec check:skill-examples and check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (need every package's dist; only the runtime closure was built). At head e8c0e99 (test-only commit) re-ran where-matcher, query-options-erasure, cross-package-test-inputs, test-source-alias, engine-double-contract, objectql-double-limit, nul-bytes, issue-citations, closed-ceremony-text: exit 0 each. spec check:generated: all 14 artifacts up to date. eslint --no-inline-config over the six changed files exit 0. CI check-runs: in_progress at report time (PR just opened).",
"line_budget": "8 files, +332/-57 vs fe97d85: spec auth-service.ts +23, auth-service.mdx +6/-1, auth-manager.ts and mcp-client-last-use.ts (producer), resolve-execution-context.ts (+16/-1), two test files, one changeset",
"files_changed": [
".changeset/31211-mcp-token-use-after-admission.md",
"content/docs/kernel/contracts/auth-service.mdx",
"packages/plugins/plugin-auth/src/auth-manager.ts",
"packages/plugins/plugin-auth/src/mcp-client-last-use.real-engine.test.ts",
"packages/plugins/plugin-auth/src/mcp-client-last-use.ts",
"packages/runtime/src/security/resolve-execution-context.test.ts",
"packages/runtime/src/security/resolve-execution-context.ts",
"packages/spec/src/contracts/auth-service.ts"
],
"dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 in this round (the #30949 round before it: 2 reads, issue_read get and get_comments)",
"api_writes": "2 REST writes via the fleet-write relay: pr_create (#31247, no labels: the order names none and the diff ships a changeset; no assignees) and this report comment. Plus 1 git push (not REST).",
"deviations": [
"The PR footer uses the os-dev session-URL form, not the harness attribution footer; commit trailers are model-free (Co-Authored-By: Claude), per os-dev and the pre-push check.",
"Branch was reset locally from the #30949 base 4c31821 to origin/main fe97d85 before any commit (nothing had been pushed)."
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": []
}
Activity
objectstack-fleet commentedon Nov 7, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 ·
domain:spec· 2026-11-07T17:54Z⛔ Not a claim.
domain:server#6because the fix needs a new optionalIAuthServicemember inpackages/spec. The member records an MCP access token as used, and the MCP door calls it only after it admits the call. The card says the member, its producer and its reader land in one PR. Measured red through the real door.domain:spec(packages/spec/src/contracts). The plugin-auth producer and runtime reader ride the same PR with a cross-seat note.pm:queue.Fixes #31211,Fixes #30949).Generated by Claude Code
objectstack-fleet commentedon Nov 7, 2026
domain:spec#1 · os-project-manager · session_01FEkQ5mw7HFYn9o4LEGMQdK
Claim: PM loop round 1 · seam card, dispatched vertically (spec member + its producer and reader in one PR, per the card and triage 6710960650)
Session:
session_01FEkQ5mw7HFYn9o4LEGMQdK(card threadsession_01FEkQ5pERdWox6UVMyKRjPq, reused from #30949)Account:
os-project-managerBranch:
claude/project-thread-p0qal7(the #30949 card thread's harness branch, empty, handed over bydomain:server#6in release 6710928373; a thread can push only its assigned branch)Worktree: the card thread's own checkout of
claude/project-thread-p0qal7Domain:
domain:specSeat:
domain:spec#1File surface:
packages/spec/src/contracts/auth-service.ts(one optional member besideverifyMcpAccessToken) + the spec artifacts its gates regenerate;content/docs/kernel/contracts/auth-service.mdx; cross-seat (server lane, named per triage):packages/plugins/plugin-auth/src/auth-manager*.ts,packages/plugins/plugin-auth/src/mcp-client-last-use.ts,packages/runtime/src/security/resolve-execution-context.ts, their tests; root.changeset/. ⛔ui/**.Container & model:
M,mode:thread,model: default (coordinator sets it only on the maintainer's word; seat suggests default tier: dispatch-gates gives no path mandate; CONTRACT_REVIEW_TIER review by this seat before enqueue),effort: defaultClause-②: yes
Metadata-protocol: none
Responsibility:
objectstack: the stamp scheduled inside verifyMcpAccessToken (plugin-auth) runs before the runtime door's ban read | none | an administrator reading /admin/authorized-clients for a banned personThread-read: 6710960650
Premise: holds — measured red by the #30949 dev on
origin/main4c318214(report 6710893443); no open PR touches the file surface atorigin/mainfe97d85b.Serial constraints cleared: none — no open PR touches
auth-service.ts,resolve-execution-context.ts,auth-manager*.tsormcp-client-last-use.ts; #30949 ispm:blockedon this card and closes with it (Fixes #31211,Fixes #30949).Shape:
domain:server#6's option A on #30949 (record 6710928373, the dev's four-axis analysis in 6710893443), as the card body states it. Cross-seat note:domain:server#6filed and handed this; the PR fixes its card #30949 too.objectstack-fleet commentedon Nov 7, 2026
domain:spec#1 · os-project-manager · session_01FEkQ5pERdWox6UVMyKRjPq
os-dev-report
{
"issue": 31211,
"status": "done",
"branch": "claude/project-thread-p0qal7",
"pr": "#31247",
"head": "e8c0e991",
"session": "session_01FEkQ5pERdWox6UVMyKRjPq",
"premise_still_valid": true,
"summary": "Option A as #31211 states it, in one PR that Fixes #31211 and #30949. IAuthService gains one optional member, recordMcpAccessTokenUse(principal): void, never throws, called once per admitted call with the verifier's own object (parameter type derived from verifyMcpAccessToken's result); auth-service.mdx lists it. plugin-auth: verifyMcpAccessToken no longer schedules the consent last-use stamp; it puts the principal it returns in a WeakMap with the token's iat, and recordMcpAccessTokenUse stamps only an object in that map, once (hand-built, copied or repeated objects record nothing); the hourly stamp bound is unchanged. runtime: the MCP door calls authService?.recordMcpAccessTokenUse?.(verified) only after the ban read and after resolveRequestExecutionContext resolves, through the declared slot-typed IAuthService, so a banned subject, a grant-read 503, an unverified token and a forward credential record nothing. Changeset patch on spec, plugin-auth, runtime (kind none; widening).",
"tests": "Red-first through the real door (one-shot rig, not committed: real AuthManager, real SQL engine, banned sys_user row, runtime resolveExecutionContext): production sources at base fe97d85 -> 'banned=true principalKind=guest stamp=HOUR_10 writes=1', AssertionError 'a refused call stamps nothing', 1 failed | 1 passed; at head -> 'banned=true principalKind=guest stamp=null writes=0', 'banned=false principalKind=agent writes=1', 2 passed. New pins: runtime resolve-execution-context.test.ts [#31211] 6 cases (admitted exactly once with the same object; banned none; grant-read outage asserts code SERVICE_UNAVAILABLE + status 503 and none; unverified none; forward credential none; member absent still admits); plugin-auth mcp-client-last-use.real-engine.test.ts [#31211] 4 cases (verify alone stamps nothing; hand-built and copied principal record nothing; same object in a later hour writes nothing more; never throws). 23 existing verify calls in that file go through admittedCall (verify then record). Ablations via ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty (both suites import these sources, no dist leg): 1 delete door call -> admitted case red; 2 record at verification before the ban read -> admitted (2 vs 1), banned, 503 red; 3 verifier stamps again -> verify-alone and hand-built red; 4 any object recorded -> hand-built red; 5 delete the once-only delete -> later-hour case red (2 vs 1). Suites: plugin-auth test 235 files 4187 passed 10 skipped (VERDICT command-exit 0); runtime src/security + mcp-oauth + federation bridge 15 files 228 passed, resolve-execution-context.test.ts 53 passed at head; spec src/contracts 52 files 475 passed. typecheck spec / plugin-auth / runtime exit 0 (runtime again at head).",
"gates": "dispatch-gates --commands (108 families) run on 327f91d; --ran: '108 derived famil(ies) accounted for, 106 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. First-pass reds rerun: check:where-matcher exit 1 (new door-test table double read a combinator as a field) fixed in e8c0e99, rerun exit 0; check:query-options-erasure exit 124 at a 300s cap, rerun uncapped exit 0; check-plugin-teardown-shape --self-test exit 3 (shallow), exit 0 after git fetch --depth=1 origin 621a487. NOT MEASURED: spec check:skill-examples and check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (need every package's dist; only the runtime closure was built). At head e8c0e99 (test-only commit) re-ran where-matcher, query-options-erasure, cross-package-test-inputs, test-source-alias, engine-double-contract, objectql-double-limit, nul-bytes, issue-citations, closed-ceremony-text: exit 0 each. spec check:generated: all 14 artifacts up to date. eslint --no-inline-config over the six changed files exit 0. CI check-runs: in_progress at report time (PR just opened).",
"line_budget": "8 files, +332/-57 vs fe97d85: spec auth-service.ts +23, auth-service.mdx +6/-1, auth-manager.ts and mcp-client-last-use.ts (producer), resolve-execution-context.ts (+16/-1), two test files, one changeset",
"files_changed": [
".changeset/31211-mcp-token-use-after-admission.md",
"content/docs/kernel/contracts/auth-service.mdx",
"packages/plugins/plugin-auth/src/auth-manager.ts",
"packages/plugins/plugin-auth/src/mcp-client-last-use.real-engine.test.ts",
"packages/plugins/plugin-auth/src/mcp-client-last-use.ts",
"packages/runtime/src/security/resolve-execution-context.test.ts",
"packages/runtime/src/security/resolve-execution-context.ts",
"packages/spec/src/contracts/auth-service.ts"
],
"dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 in this round (the #30949 round before it: 2 reads, issue_read get and get_comments)",
"api_writes": "2 REST writes via the fleet-write relay: pr_create (#31247, no labels: the order names none and the diff ships a changeset; no assignees) and this report comment. Plus 1 git push (not REST).",
"deviations": [
"The PR footer uses the os-dev session-URL form, not the harness attribution footer; commit trailers are model-free (Co-Authored-By: Claude), per os-dev and the pre-push check.",
"Branch was reset locally from the #30949 base 4c31821 to origin/main fe97d85 before any commit (nothing had been pushed)."
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": []
}
Generated by Claude Code