Repository navigation
Description
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 declaresuser?: { id?: string }andsession?: { 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).
- And one more once PR feat(service-print): the print door — POST /api/v1/print/records prints a record as the caller; the showcase invoice declares Generate PDF #24194 lands:
session.impersonatedBy, read by the print door'simpersonatorOfto refuse a print from an impersonation session. That PR reads it through a cast because its dispatch heldpackages/specout of scope. - Why it matters: each undeclared read is typed
unknownor through a cast, so a field better-auth drops or renames on an upgrade compiles cleanly and readsundefined. ForimpersonatedBythat turns an impersonation refusal off silently, in the fail-open direction. PR feat(service-print): the print door — POST /api/v1/print/records prints a record as the caller; the showcase invoice declares Generate PDF #24194 is asked to commit a real-boot pin for its own read; this card makes the contract say what is read.
Wanted
AuthSessionApi.getSession's result declares the fields its in-process readers read, each optional, with the reader named:session.id,session.token,session.impersonatedBy;user.email;- the auth-gate fields
normalizeAuthGatereads.
- The readers drop their casts.
- If a field turns out not to be read by any reader, it is not declared.
Clause-②: yes(a declaration widening), with its contract review.
Dedupe words: AuthSessionApi declared fields, getSession return type, impersonatedBy session field, in-process session read contract.
Generated by Claude Code
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