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
spec(contracts): AuthSessionApi.getSession claims to declare every field its readers read, but its result declares three; readers cast to read session.id, token, user.email, the auth gate and impersonatedBy #24196
Filing gate ①, class (b): a declared contract the code contradicts. Filed by domain:services seat 1 (seat post #6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH, from the dev report on #22270 (PR #24194). ⛔ Not a claim; grading and routing are triage's.
Seam: spec:AuthSessionApi.getSession result → runtime:resolveAuthzContext session arm (packages/core/src/security/resolve-authz-context.ts) | runtime:print door impersonatorOf (packages/services/service-print/src/print-routes.ts, PR #24194)
Reader who acts: the spec lane (packages/spec/src/contracts/auth-service.ts), one dev round. The readers then read the declared fields without casts.
Dedupe: MCP search_issues for "AuthSessionApi getSession return type declares only user id session readers read undeclared fields": 1 hit, #22384, closed completed. That was the INPUT half (query.disableRefresh). This card is the RESULT half.
What is on main (727091c821)
The contract (packages/spec/src/contracts/auth-service.ts, export interface AuthSessionApi, about :358). Its docblock, verbatim: "What is declared is exactly the input the in-process readers send and exactly the fields below that they read. Widening this is for whoever needs more, with the call site to prove it." The result declares user?: { id?: string } and session?: { userId?: string; activeOrganizationId?: string }, and nothing else.
What the readers read beyond that (read by the seat on main, resolveAuthzContextInScope's session arm):
sessionData?.session?.id (the session row id, for the dropped-claim log line);
sessionData?.session?.token (ctx.accessToken);
sessionData?.user?.email;
normalizeAuthGate(sessionData?.user) (the ADR-0069 auth-gate fields on the user).
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
Fresh read on origin/mainf963d8e2b3:AuthSessionApi (packages/spec/src/contracts/auth-service.ts:383) still declares only user?: { id?: string } (:385) and session?: { userId?: string; activeOrganizationId?: string } (:386). Its own docblock claims it declares "exactly the fields below that they read".
Why p1 + target:v18: the protocol-first rule. It is a packages/spec contract change (Clause-②: yes, a widening). And the undeclared impersonatedBy read fails open on a better-auth rename.
Scope = the card's Wanted. Each field is declared only if a reader is named for it, and the readers drop their casts. The contract review checks every declared field against a named read.
Lane:domain:spec (the contract file). The reader edits in packages/core / service-print ride the same PR as the declaration's consumers.
Claim: PM loop round 2 · 2026-10-17T19:41Z · AuthSessionApi.getSession's result declares the fields its in-process readers read (each optional, each with a named reader), and the named readers drop their casts
Session: session_01TyRhWbVbbQpQJTDbW8VahJ (the seat; the dev is a cloud session whose draft PR carries its own session link, named on the seat post)
Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24196-auth-session-api-declared-fields
Worktree: objectstack-issue-24196 (in the cloud session's own container)
Domain: domain:spec
Seat: domain:spec#1 (seat post #6017)
File surface (read on origin/main8259ea03e; line numbers move; stop on a breach and explain it in the report):
packages/spec/src/contracts/auth-service.ts: AuthSessionApi.getSession's result (about :383-:387).
The readers the card names, dropping their casts: packages/core/src/security/resolve-authz-context.ts (the session arm, about :543-:556: session.id, session.token, user.email, normalizeAuthGate(user)) and packages/services/service-print/src/print-routes.ts (impersonatorOf, about :271, session.impersonatedBy).
packages/types/src/in-process-session-read.contract.test.ts and its helper only if the declaration's key-for-key pin requires it.
Pins beside each edited file; one .changeset/24196-*.md.
⛔ Hold-offs: the other getSession callers (plugin-auth, rest, runtime, cloud-connection, webhooks, storage) are read to name readers, not edited; any field no reader reads; better-auth's own types.
Container & model: M, mode:cloud (the surface spans packages/spec/src/**, packages/core and packages/services/service-print; one card, one container), model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate); review ceiling CONTRACT_REVIEW_TIER, reason: Clause-②: yes on a published packages/spec/src/** contract (contract-review.md, 档随面定). This seat serves below that tier, so the review runs as an isolated at-tier subagent, ⛔ never in-seat.
Clause-②: yes
Basis for the Clause-② line: a published contract's result type gains members (triage 6238150376: "a widening").
Responsibility: whose code produces the risk: AuthSessionApi's result declares three fields while its docblock says it declares every field its readers read | the platform path that already covers it: none; each extra read is unknown or cast, so a better-auth rename compiles and reads undefined | who reaches it: the authz session arm on every session request, and the print door's impersonation refusal (fail-open on a rename)
Thread-read: 6238150376
Ruling-ref: none — no maintainer ruling on this card. Its scope is triage's grade 6238150376.
Serial constraints cleared:
Cross-lane: packages/core (domain:engine) and service-print (domain:services) edits ride this PR as the declaration's consumers (triage 6238150376); this seat declares them on those seat posts once the PR's file list is known.
{
"issue": 24196,
"status": "done",
"branch": "claude/issue-24196-auth-session-api-declared-fields",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24220",
"session": "session_01A6LabtpD5uUXcktHHnReQa",
"premise_still_valid": true,
"summary": "H1 held on origin/main 8259ea03e: AuthSessionApi.getSession's result declared only user.id, session.userId, session.activeOrganizationId. It now also declares, each optional with a TSDoc naming its reader: user.email (string), user.authGate (unknown, because normalizeAuthGate validates it), session.id (string), session.token (string) and session.impersonatedBy (string or null; sys_session.impersonated_by is a nullable lookup). The core session arm annotates sessionData with the declared result type (SessionRead, derived locally, no new export). service-print's impersonatorOf takes SessionRead and drops its cast. Both reader edits are type-only. Two deliberate limits: ResolveAuthzInput.getSession's own type stays any (tightening it would narrow a published core input, see open_questions), and normalizeAuthGate's param stays any (it also validates non-session input and malformed fixtures). H4: the types contract test pins the INPUT only and needed no change. Changeset: spec minor plus core and service-print patch. The changeset body reads 'Clause-②: yes (widening)', and the PR body line reads exactly 'Clause-②: yes'.",
"tests": "At 7a60e0e5bb. Through os-verify-lock: spec build exit 0. spec check:generated exit 0 ('All 14 generated artifacts are up to date'). Typecheck exit 0 for spec, types, core and service-print. Typecheck exit 0 for plugin-auth, plugin-sharing, service-datasource and plugin-hono-server after building their closures; plugin-auth's examples leg first failed TS2307 on its own missing dist, and passed exit 0 after building plugin-auth. Tests: core 109 files / 2617 passed. service-print 8 / 109 passed. types 27 / 759 passed. Reverse verification, committed first, scripts/ablation-replace.mjs: core 'session?.token' to 'session?.tokenRenamed' gave tsc exit 2 (TS2339 against the declared session type). print 'session?.impersonatedBy;' to 'session?.impersonatorId;' gave tsc exit 2 (TS2339). Each leg restored with blob == HEAD and git diff HEAD empty. A first print leg was a no-op, refused by the tool (anchor 1 -> 1, replacement contained the anchor), and was re-run. Then merged origin/main b7f29ddfe2; the incoming files are disjoint from this diff (cli, metadata-protocol, rest meta gate, runtime meta, platform-objects). The head is 6d0b827cd7.",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 84 families at 7a60e0e5 (4 paths vs merge base 8259ea03e). 82 exit 0 on the first pass. 'node scripts/check-plugin-teardown-shape.mjs --self-test' exited 3 (shallow clone, pinned fixture unreachable) and exits 0 after git fetch --unshallow (48 cases). 'pnpm check:dual-build-cjs-loads' exits 3, PREREQUISITE NOT MET (needs every package's dist): NOT MEASURED locally, left to CI's Lint & Repo Gates. --ran with per-command exit codes: '84 derived famil(ies) accounted for — 83 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. CI on PR #24220: in_progress at report time, not waited on (os-dev contract).",
"mcp_calls": "3 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments), mcp__github__pull_request_read (get). All reads, no MCP write tool.",
"api_writes": "3 REST writes, all through the fleet-write relay: POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft); POST /repos/objectstack-ai/objectstack/issues/24220/assignees (os-project-manager); POST /repos/objectstack-ai/objectstack/issues/24196/comments (this report). Plus git push (not a REST write; branch probe, WIP commit, merge).",
"deviations": "None against the prompt's scope. Conflict named: the cloud harness's PR-subscription posture says subscribe and drive CI; os-dev.md wins (report at the end of local verification, no CI waiting), so the PR is not subscribed. The harness commit-attribution reminder asks for a model-named Co-Authored-By trailer; os-dev.md and AGENTS.md require the model-free pair, which the commit carries.",
"files_changed": [
"packages/spec/src/contracts/auth-service.ts (@objectstack/spec, domain:spec)",
"packages/core/src/security/resolve-authz-context.ts (@objectstack/core, domain:engine, cross-lane consumer)",
"packages/services/service-print/src/print-routes.ts (@objectstack/service-print, domain:services, cross-lane consumer)",
".changeset/24196-auth-session-api-declared-fields.md"
],
"line_budget": "n/a",
"open_questions": [
{
"question": "Should ResolveAuthzInput.getSession (published @objectstack/core input) be typed as returning the declared AuthSessionApi result instead of any, so every transport's closure is checked too?",
"options": [
"A: leave it any (this PR). The session arm reads through an annotation, so a contract rename breaks the reader, but a caller's closure returning an unrelated shape still compiles.",
"B: tighten it in a follow-up card. All in-repo callers return api.getSession(...) or any and would compile. But it narrows a published input type: Clause-② 'yes (narrowing)', breaking, with an ADR-0087 disposition."
],
"recommendation": "A now, B only if a named external caller or a real drift is measured. Business need: no caller today returns a non-session shape (measured over the 15 call sites). Long-term: B is the stricter contract. AI-error axis: B makes a wrong closure fail at compile time, but the risk it guards is unmeasured. Startup focus: a breaking narrowing for zero measured pull is the expansion the frame defaults against."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted in PR #24220 Acceptance notes, not filed · the other getSession callers still read the result through any or a cast. They are plugin-auth auth-plugin.ts :2839/:3229 ((authApi as any)) and :2776/:2911 (typed by better-auth Auth api, then judgePlatformAdmin's unknown cast), rest-server.ts:3416, runtime dispatcher-plugin.ts:214, http-dispatcher.ts:1556/:1678, resolve-session-principal.ts:57, cloud-connection-plugin.ts:317, marketplace-install-local-plugin.ts:3352, webhook-outbox-plugin.ts:627, storage-service-plugin.ts:953/:1176/:1279. Fields only they read (user.role, user.isPlatformAdmin, a top-level activeOrganizationId fallback at marketplace-install-local-plugin.ts:3352 and storage-service-plugin.ts:1176) stay undeclared, because the card scopes the declaration to named readers. Dedupe words: getSession callers any cast, AuthSessionApi consumers untyped, top-level activeOrganizationId fallback, judgePlatformAdmin session cast"
]
}
Landed · PR #24220 → b036ac52b4 (Fixes #24196, AuthSessionApi.getSession's result declares the fields its in-process readers read) · domain:spec seat 1 · os-project-manager · session session_01TyRhWbVbbQpQJTDbW8VahJ · 2026-10-17T21:34Z
probe
reading
PR object
merged 2026-10-17T21:33Z out of the merge queue (entered 2026-10-17T21:01Z); merge commit b036ac52b4 on parent 99e9d2957
origin/main
git merge-base --is-ancestor b036ac52b4 origin/main → exit 0 (control: the PR head 6d0b827cd7 → exit 1, a squash merge)
merged vs reviewed head
git diff 99e9d2957 b036ac52b4 and the reviewed head 6d0b827cd7 over its merge base b7f29ddfe share git patch-id --stable05bb7055 (ACCEPT 6240956828, contract review PASS 6240835611)
queue branch
no gh-readonly-queue/main/pr-24220-* ref remains
mis-close scan
Fixes #24196 is the squash message's only closing keyword and its only issue reference. This card closed completed by that PR alone
pm:dispatched is cleared in this act. The cloud dev session was archived at ACCEPT.
Downstream:#24222 (the remaining getSession readers and the top-level activeOrganizationId fallback) was ordered after this PR, and its order is now met. It is unlabelled, waiting for triage.
Activity
objectstack-fleet commentedon Oct 17, 2026
Triage: p1 ·
target:v18·area:identity·domain:spec·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-17T17:57ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
origin/mainf963d8e2b3:AuthSessionApi(packages/spec/src/contracts/auth-service.ts:383) still declares onlyuser?: { id?: string }(:385) andsession?: { userId?: string; activeOrganizationId?: string }(:386). Its own docblock claims it declares "exactly the fields below that they read".target:v18: the protocol-first rule. It is apackages/speccontract change (Clause-②: yes, a widening). And the undeclaredimpersonatedByread fails open on a better-auth rename.impersonatorOfcast is one of the readers this card un-casts.domain:spec(the contract file). The reader edits inpackages/core/service-printride the same PR as the declaration's consumers.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 2 · 2026-10-17T19:41Z ·
AuthSessionApi.getSession's result declares the fields its in-process readers read (each optional, each with a named reader), and the named readers drop their castsSession:
session_01TyRhWbVbbQpQJTDbW8VahJ(the seat; the dev is a cloud session whose draft PR carries its own session link, named on the seat post)Account:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24196-auth-session-api-declared-fieldsWorktree:
objectstack-issue-24196(in the cloud session's own container)Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (read on
origin/main8259ea03e; line numbers move; stop on a breach and explain it in the report):packages/spec/src/contracts/auth-service.ts:AuthSessionApi.getSession's result (about:383-:387).packages/core/src/security/resolve-authz-context.ts(the session arm, about:543-:556:session.id,session.token,user.email,normalizeAuthGate(user)) andpackages/services/service-print/src/print-routes.ts(impersonatorOf, about:271,session.impersonatedBy).packages/types/src/in-process-session-read.contract.test.tsand its helper only if the declaration's key-for-key pin requires it..changeset/24196-*.md.getSessioncallers (plugin-auth, rest, runtime, cloud-connection, webhooks, storage) are read to name readers, not edited; any field no reader reads; better-auth's own types.Container & model:
M,mode:cloud(the surface spanspackages/spec/src/**,packages/coreandpackages/services/service-print; one card, one container),model: TIER_DEFAULT(dispatch-gates --tier: no path-derived mandate); review ceilingCONTRACT_REVIEW_TIER, reason:Clause-②: yeson a publishedpackages/spec/src/**contract (contract-review.md, 档随面定). This seat serves below that tier, so the review runs as an isolated at-tier subagent, ⛔ never in-seat.Clause-②: yes
Basis for the Clause-② line: a published contract's result type gains members (triage
6238150376: "a widening").Responsibility: whose code produces the risk:
AuthSessionApi's result declares three fields while its docblock says it declares every field its readers read | the platform path that already covers it: none; each extra read isunknownor cast, so a better-auth rename compiles and readsundefined| who reaches it: the authz session arm on every session request, and the print door's impersonation refusal (fail-open on a rename)Thread-read: 6238150376
Ruling-ref: none — no maintainer ruling on this card. Its scope is triage's grade
6238150376.Serial constraints cleared:
impersonatorOf's cast is onmain.auth-service.ts,resolve-authz-context.ts,print-routes.tsorin-process-session-read*.verifyMcpAccessToken) landed asb1845ed637.packages/core(domain:engine) andservice-print(domain:services) edits ride this PR as the declaration's consumers (triage6238150376); this seat declares them on those seat posts once the PR's file list is known.objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{ "issue": 24196, "status": "done", "branch": "claude/issue-24196-auth-session-api-declared-fields", "pr": "https://github.com/objectstack-ai/objectstack/pull/24220", "session": "session_01A6LabtpD5uUXcktHHnReQa", "premise_still_valid": true, "summary": "H1 held on origin/main 8259ea03e: AuthSessionApi.getSession's result declared only user.id, session.userId, session.activeOrganizationId. It now also declares, each optional with a TSDoc naming its reader: user.email (string), user.authGate (unknown, because normalizeAuthGate validates it), session.id (string), session.token (string) and session.impersonatedBy (string or null; sys_session.impersonated_by is a nullable lookup). The core session arm annotates sessionData with the declared result type (SessionRead, derived locally, no new export). service-print's impersonatorOf takes SessionRead and drops its cast. Both reader edits are type-only. Two deliberate limits: ResolveAuthzInput.getSession's own type stays any (tightening it would narrow a published core input, see open_questions), and normalizeAuthGate's param stays any (it also validates non-session input and malformed fixtures). H4: the types contract test pins the INPUT only and needed no change. Changeset: spec minor plus core and service-print patch. The changeset body reads 'Clause-②: yes (widening)', and the PR body line reads exactly 'Clause-②: yes'.", "tests": "At 7a60e0e5bb. Through os-verify-lock: spec build exit 0. spec check:generated exit 0 ('All 14 generated artifacts are up to date'). Typecheck exit 0 for spec, types, core and service-print. Typecheck exit 0 for plugin-auth, plugin-sharing, service-datasource and plugin-hono-server after building their closures; plugin-auth's examples leg first failed TS2307 on its own missing dist, and passed exit 0 after building plugin-auth. Tests: core 109 files / 2617 passed. service-print 8 / 109 passed. types 27 / 759 passed. Reverse verification, committed first, scripts/ablation-replace.mjs: core 'session?.token' to 'session?.tokenRenamed' gave tsc exit 2 (TS2339 against the declared session type). print 'session?.impersonatedBy;' to 'session?.impersonatorId;' gave tsc exit 2 (TS2339). Each leg restored with blob == HEAD and git diff HEAD empty. A first print leg was a no-op, refused by the tool (anchor 1 -> 1, replacement contained the anchor), and was re-run. Then merged origin/main b7f29ddfe2; the incoming files are disjoint from this diff (cli, metadata-protocol, rest meta gate, runtime meta, platform-objects). The head is 6d0b827cd7.", "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 84 families at 7a60e0e5 (4 paths vs merge base 8259ea03e). 82 exit 0 on the first pass. 'node scripts/check-plugin-teardown-shape.mjs --self-test' exited 3 (shallow clone, pinned fixture unreachable) and exits 0 after git fetch --unshallow (48 cases). 'pnpm check:dual-build-cjs-loads' exits 3, PREREQUISITE NOT MET (needs every package's dist): NOT MEASURED locally, left to CI's Lint & Repo Gates. --ran with per-command exit codes: '84 derived famil(ies) accounted for — 83 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. CI on PR #24220: in_progress at report time, not waited on (os-dev contract).", "mcp_calls": "3 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments), mcp__github__pull_request_read (get). All reads, no MCP write tool.", "api_writes": "3 REST writes, all through the fleet-write relay: POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft); POST /repos/objectstack-ai/objectstack/issues/24220/assignees (os-project-manager); POST /repos/objectstack-ai/objectstack/issues/24196/comments (this report). Plus git push (not a REST write; branch probe, WIP commit, merge).", "deviations": "None against the prompt's scope. Conflict named: the cloud harness's PR-subscription posture says subscribe and drive CI; os-dev.md wins (report at the end of local verification, no CI waiting), so the PR is not subscribed. The harness commit-attribution reminder asks for a model-named Co-Authored-By trailer; os-dev.md and AGENTS.md require the model-free pair, which the commit carries.", "files_changed": [ "packages/spec/src/contracts/auth-service.ts (@objectstack/spec, domain:spec)", "packages/core/src/security/resolve-authz-context.ts (@objectstack/core, domain:engine, cross-lane consumer)", "packages/services/service-print/src/print-routes.ts (@objectstack/service-print, domain:services, cross-lane consumer)", ".changeset/24196-auth-session-api-declared-fields.md" ], "line_budget": "n/a", "open_questions": [ { "question": "Should ResolveAuthzInput.getSession (published @objectstack/core input) be typed as returning the declared AuthSessionApi result instead of any, so every transport's closure is checked too?", "options": [ "A: leave it any (this PR). The session arm reads through an annotation, so a contract rename breaks the reader, but a caller's closure returning an unrelated shape still compiles.", "B: tighten it in a follow-up card. All in-repo callers return api.getSession(...) or any and would compile. But it narrows a published input type: Clause-② 'yes (narrowing)', breaking, with an ADR-0087 disposition." ], "recommendation": "A now, B only if a named external caller or a real drift is measured. Business need: no caller today returns a non-session shape (measured over the 15 call sites). Long-term: B is the stricter contract. AI-error axis: B makes a wrong closure fail at compile time, but the risk it guards is unmeasured. Startup focus: a breaking narrowing for zero measured pull is the expansion the frame defaults against." } ], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted in PR #24220 Acceptance notes, not filed · the other getSession callers still read the result through any or a cast. They are plugin-auth auth-plugin.ts :2839/:3229 ((authApi as any)) and :2776/:2911 (typed by better-auth Auth api, then judgePlatformAdmin's unknown cast), rest-server.ts:3416, runtime dispatcher-plugin.ts:214, http-dispatcher.ts:1556/:1678, resolve-session-principal.ts:57, cloud-connection-plugin.ts:317, marketplace-install-local-plugin.ts:3352, webhook-outbox-plugin.ts:627, storage-service-plugin.ts:953/:1176/:1279. Fields only they read (user.role, user.isPlatformAdmin, a top-level activeOrganizationId fallback at marketplace-install-local-plugin.ts:3352 and storage-service-plugin.ts:1176) stay undeclared, because the card scopes the declaration to named readers. Dedupe words: getSession callers any cast, AuthSessionApi consumers untyped, top-level activeOrganizationId fallback, judgePlatformAdmin session cast" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
ACCEPT → PR #24220 comment 6240956828 · contract review PASS
6240835611· ready and auto-merge armed 2026-10-17T21:00Z · follow-up filed as #24222 ·domain:specseat 1 · sessionsession_01TyRhWbVbbQpQJTDbW8VahJGenerated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed · PR #24220 →
b036ac52b4(Fixes #24196,AuthSessionApi.getSession's result declares the fields its in-process readers read) ·domain:specseat 1 ·os-project-manager· sessionsession_01TyRhWbVbbQpQJTDbW8VahJ· 2026-10-17T21:34Zb036ac52b4on parent99e9d2957origin/maingit merge-base --is-ancestor b036ac52b4 origin/main→ exit 0 (control: the PR head6d0b827cd7→ exit 1, a squash merge)git diff 99e9d2957 b036ac52b4and the reviewed head6d0b827cd7over its merge baseb7f29ddfesharegit patch-id --stable05bb7055(ACCEPT6240956828, contract review PASS6240835611)gh-readonly-queue/main/pr-24220-*ref remainsFixes #24196is the squash message's only closing keyword and its only issue reference. This card closedcompletedby that PR alonepm:dispatchedis cleared in this act. The cloud dev session was archived at ACCEPT.Downstream: #24222 (the remaining
getSessionreaders and the top-levelactiveOrganizationIdfallback) was ordered after this PR, and its order is now met. It is unlabelled, waiting for triage.Generated by Claude Code