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 declares its input as { headers } only, but the in-process readers now pass query.disableRefresh (PR #22367, #22258) #22384
Filing gate: ① a contract-declaration drift with a named landing site. A foreseen follow-up of #22258. PR #22367's landing note (6071583141) carries it for domain:spec in its Acceptance notes. Filed by the triage seat (objectstack-wide, seat post #6015), session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim.
Its docblock (near :170–:176) says every dispatcher-side reader "calls exactly getSession({ headers }) … so that is what is declared. Widening this is for whoever needs more, with the call site to prove it."
packages/types/src/in-process-session-read.ts: inProcessSessionReadInput(headers) returns { headers, query: { disableRefresh: true } } when the request carries a better-auth session cookie.
Ten readers in the domain:cli lane now pass that input.
So the declared input no longer describes what its callers send. The call sites the docblock asks for now exist.
What this card does
Widen the declared input to { headers: unknown; query?: { disableRefresh?: boolean } }, and keep everything else in the shape.
Rewrite the docblock so it states the two read forms (a cookie-carrying request reads with disableRefresh, a bearer-only one without) and names the helper as their one source.
If any reader in packages/spec's own consumers types against the narrower shape, keep that compiling.
Clause-②: yes (widening). It adds an optional key to a published contract's accepted input, so it is spec-lane work and owes the contract-tier review.
Pin: a type-level test that the helper's return is assignable to the declared input.
Order
Independent of #22258's domain:services half. Either may land first; the drift exists already.
Dedupe: MCP search_issues, repo-scoped, open and closed: 「AuthSessionApi getSession declared input headers only disableRefresh query contract auth-service」 gave 2 hits: #22258 (the parent) and #16760 (closed, a client envelope). Neither is this.
Claim: PM loop round 1 · 2026-10-09T01:18Z
Session: session_01DhTqaEHqPVSVnAkjG3jywn
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-22384-getsession-input-query
Worktree: objectstack-issue-22384
Domain: domain:spec
Seat: domain:spec#2 (seat post #18549)
File surface (at origin/main117d34de3 or later; stop on breach and explain in the report):
packages/spec/src/contracts/auth-service.ts.
AuthSessionApi.getSession's declared input widens to { headers: unknown; query?: { disableRefresh?: boolean } }. The rest of the shape is unchanged.
The docblock (about :170–:178) states the two read forms: a cookie-carrying request reads with disableRefresh, and a bearer-only one reads without it. It names inProcessSessionReadInput as their one source.
The pin. A type-level test that the helper's return is assignable to the declared input. It lives where the dependency direction allows: packages/types can see both the helper and the contract, and packages/spec must not import packages/types. That one test file is declared on [PM seat] domain:cli — ⏳ vacant #6024 in this act. A consumer of the narrower shape that stops compiling is kept compiling, and named in the report.
Changeset:.changeset/22384-*.md, @objectstack/specminor (a widening of a published contract's input).
{
"issue": 22384,
"status": "done",
"branch": "claude/issue-22384-getsession-input-query",
"pr": "#22406",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the parent seat's id (mode:subagent), as stamped on this run's commits (Claude-Session line)",
"premise_still_valid": true,
"summary": "AuthSessionApi.getSession's declared input in packages/spec/src/contracts/auth-service.ts is now { headers: unknown; query?: { disableRefresh?: boolean } }, and the rest of the shape is unchanged. Its docblock now states the two read forms (a cookie request reads with disableRefresh, a bearer-only one without it), names inProcessSessionReadInput (packages/types/src/in-process-session-read.ts) as their one source, and cites the ten call sites that send query. Alongside it: the pin packages/types/src/in-process-session-read.contract.test.ts (the one file declared on #6024), a @objectstack/spec minor changeset (@objectstack/types needs none, only a test file changed), and draft PR #22406 with Clause-②: yes (widening); no consumer was touched or needed to be. The dispatch's mechanism assumption (that a plain assignability test bites when the declaration narrows) is falsified and measured: TypeScript checks undeclared keys only on object literals, so the helper's return was already assignable to the narrow { headers: unknown } with 0 errors, which is how the readers compiled. The pin therefore checks assignability key for key (FitsDeclared, with four @ts-expect-error controls), and the ablation turns it red. The worktree objectstack-issue-22384 is removed right after this report is posted (node_modules first, then git worktree remove without --force).",
"tests": "Premises at origin/main 117d34d, both TRUE. (a) auth-service.ts:178 reads getSession?(input: { headers: unknown }) followed by its (unchanged) Promise return type. (b) in-process-session-read.ts:57-60 declares the return interface InProcessSessionReadInput (generic over H) with query?: { disableRefresh: true }, which inProcessSessionReadInput returns at :105. FINAL HEAD 57d9d9a, all heavy runs under os-verify-lock with VERDICT command-exit 0. Full build turbo run build --filter=!@objectstack/docs --concurrency=2: 72/72 successful, tree clean afterwards. types: typecheck exit 0, and tsc --noEmit --listFiles lists the new test file once; test (local) 26 files/750 passed; test:repo 1/11 passed; the pin file alone 1/1. spec: typecheck exit 0 (tsc + check:scripts-typecheck + check:test-typecheck); test:repo 54 files/915 passed; test (local, --maxWorkers=2) 626 files/18742 passed, 1 todo. GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 85 commands at 57d9d9a, the same list as at 769d9f4. All 85 ran with exit codes captured before any pipe, and all 85 exited 0. --ran reads: '85 derived famil(ies) accounted for — 85 run, 0 NOT-MEASURED (a DERIVED zero — all 85 recorded an exit code and none of them is 3)'. The first battery at 769d9f4 had 3 runs exit 3 with PREREQUISITE NOT MET (check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure); they are not counted, and all three exit 0 after the full build. check:api-surface reads 'public API surface + factory signatures unchanged'. check-adr-0087-registration reads 'no declared-breaking changeset (1 non-breaking)'. check-changeset-no-major locally reads LEVEL AXIS NOT APPLICABLE (no pull_request payload), so the Clause-② reading is CI's. LINT, a declared narrowing (CI owns the full run): eslint --no-inline-config --format json on the 2 changed .ts files gives 2 files, 0 errors, 0 warnings. The population is read from eslint itself (the .changeset .md is ignored by its config). The invariance holds because eslint.config.mjs enables no type-aware linting (comment near :326-:328), so no untouched file's verdict can move. ABLATION, one-off, at 769d9f4 with the fix committed first. Pristine: ablation-dist-preflight finds marker disableRefresh?: boolean in spec dist contracts/index.d.ts and .d.mts, tree clean. Mutate: scripts/ablation-replace.mjs (wrap mode, trap-armed) moved anchor { headers: unknown; query?: { disableRefresh?: boolean } } x1 to x0, blob 698dd54bd9e8 to 2df53d7829bd; spec rebuilt exit 0; preflight --absent shows the marker gone from all 232 built files. Then types tsc --noEmit exit 2 with exactly TS2344 at :76 :77 :78 (the three holds) and TS2339 at :96 ('Property query does not exist on type { headers: unknown; }'), while a throwaway plain-assignability probe (a DeclaredInput-typed const from inProcessSessionReadInput, plus the readers' api.getSession call shape) had 0 errors. Restore: blob == HEAD 698dd54bd9e8, git diff HEAD empty, spec rebuilt, preflight present green, tree clean, types tsc exit 0. The observed direction matches the prediction: red on the pins, controls unchanged. The probe file was removed by trap. 57d9d9a differs from 769d9f4 only in docblock comment lines (+6/-5); the declaration line is byte-identical.",
"mcp_calls": "0 — no MCP GitHub tool was called. Reads went through single-card gh api REST reads; writes went through the scripts/pm relay.",
"api_writes": "3 relay writes. Each is one POST /repos/objectstack-ai/objectstack/dispatches, executed by the fleet-write workflow as objectstack-fleet[bot]. (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #22406; 9633 bytes sent, 9633 stored, read back identical). (2) label-write --assign → POST /repos//issues/22406/assignees (os-sales, the card's assignee; read-back matches). Zero label writes: the dispatch named no label and skip-changeset does not apply, since a published contract changes. (3) post-stamped → POST /repos//issues/22384/comments (this report). Not REST: git push x3 to claude/issue-22384-getsession-input-query (the empty probe, 769d9f4, 57d9d9a).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22258 (its domain:services half) · noted, not filed — nine in-process readers still pass a hand-built { headers }: plugin-auth auth-plugin.ts x4, plugin-webhooks, plugin-sharing, service-storage, service-settings and service-datasource. The dogfood armed.ts test harness does too, and it is not a door. All of them compile unchanged against the widened declaration. The docblock now says what a hand-built input costs on a cookie request.",
"carrier: none (承接者:无) · noted, not filed — the docblock's list of the ten call sites has no pin. A reader added or moved later leaves the list stale while the declaration stays correct. Recorded in PR #22406 Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed — check:entry-nameability prints a standing NOT MEASURED for @objectstack/spec/api-assembled and @objectstack/spec/qa (no callable export) and exits 0. It is unrelated to this diff."
]
}
The declaration (packages/spec/src/contracts/auth-service.ts):getSession?(input: { headers: unknown; query?: { disableRefresh?: boolean } }). The return type and the rest of AuthSessionApi are unchanged. The added key is optional, so every caller that passes { headers }, and every implementation that ignores query, compiles as before.
The docblock states the two read forms (a cookie-carrying request reads with disableRefresh, a bearer-only one without it). It names inProcessSessionReadInput as their one source and says what a hand-built input costs on a cookie request. The old "exactly getSession({ headers })" sentence, which had become false, is gone.
The dispatch assumed a plain assignability test would bite. The dev measured that it would not: TypeScript checks undeclared keys only on object literals, which is how the readers compiled against the narrow declaration.
So the pin holds the helper's return inside the declaration key for key (FitsDeclared), with four @ts-expect-error controls.
The ablation (the narrow declaration restored, the spec rebuilt) gave TS2344 at the three holds and TS2339 at :96, while a plain-assignability probe stayed at 0 errors. It was restored to blob == HEAD.
Level:@objectstack/specminor (an optional key on a published contract's input), with Clause-②: yes (widening). @objectstack/types needs no changeset, because only a test file changed. 85 / 85 derived gates ran with exit 0, and check:api-surface reads "unchanged".
The docblock's list of the ten call sites has no pin, so a reader added or moved later leaves it stale while the declaration stays true → Acceptance notes, no carrier. The list is the "call site to prove it" that the old text asked for.
check:entry-nameability's standing NOT MEASURED for api-assembled and qa → dropped. Those two entries have no callable export, the gate exits 0, and this diff does not touch them.
Next: the contract review at CONTRACT_REVIEW_TIER on 57d9d9a8c → every check green → landing (Fixes #22384).
Content check: all 3 PR files on e75dceddd are blob-equal to the reviewed head 57d9d9a8c. That head carries the seat's ACCEPT 6073468161 and the contract review PASS6073578684. All 35 check-runs had settled before it was made ready: 32 success and 3 expected skips.
What now holds (@objectstack/specminor, Clause-②: yes (widening)):
AuthSessionApi.getSession declares { headers: unknown; query?: { disableRefresh?: boolean } }, the input the in-process readers send through inProcessSessionReadInput.
Its docblock states the two read forms.
packages/types/src/in-process-session-read.contract.test.ts holds the helper's return inside the declaration, key for key.
Activity
objectstack-fleet commentedon Oct 9, 2026
Claim: PM loop round 1 · 2026-10-09T01:18Z
Session:
session_01DhTqaEHqPVSVnAkjG3jywnAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22384-getsession-input-queryWorktree:
objectstack-issue-22384Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface (at
origin/main117d34de3or later; stop on breach and explain in the report):packages/spec/src/contracts/auth-service.ts.AuthSessionApi.getSession's declared input widens to{ headers: unknown; query?: { disableRefresh?: boolean } }. The rest of the shape is unchanged.:170–:178) states the two read forms: a cookie-carrying request reads withdisableRefresh, and a bearer-only one reads without it. It namesinProcessSessionReadInputas their one source.packages/typescan see both the helper and the contract, andpackages/specmust not importpackages/types. That one test file is declared on [PM seat] domain:cli — ⏳ vacant #6024 in this act. A consumer of the narrower shape that stops compiling is kept compiling, and named in the report..changeset/22384-*.md,@objectstack/specminor(a widening of a published contract's input).domain:cliordomain:services. auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258's remaining half is its own.Container & model:
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate). A contract review atCONTRACT_REVIEW_TIERis owed before enqueue.Clause-②: yes (widening: an optional
query.disableRefreshjoinsgetSession's declared input)Responsibility: the declaration in
packages/spec/src/contracts/auth-service.tsno longer describes what its callers send | none: the docblock asks for the widening once call sites exist, and PR fix(auth): in-process session reads no longer renew a browser session behind its cookie #22367 added them | the 10 in-process readers indomain:clitoday, and 9 indomain:servicesunder auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258Thread-read: none
Serial constraints cleared: no open PR touches
auth-service.tsorpackages/types/src/in-process-session-read.ts; all 15 open PRs' file lists were read at this stamp. PR docs(spec): ISecurityService and the data-engine contract page state the ADR-0096 D5 refusal of a principal-less context #22327 (this seat) editscontracts/security-service.ts, a different file. Noarea:identitycard is in flight.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 22384,
"status": "done",
"branch": "claude/issue-22384-getsession-input-query",
"pr": "#22406",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the parent seat's id (mode:subagent), as stamped on this run's commits (Claude-Session line)",
"premise_still_valid": true,
"summary": "
AuthSessionApi.getSession's declared input inpackages/spec/src/contracts/auth-service.tsis now{ headers: unknown; query?: { disableRefresh?: boolean } }, and the rest of the shape is unchanged. Its docblock now states the two read forms (a cookie request reads withdisableRefresh, a bearer-only one without it), namesinProcessSessionReadInput(packages/types/src/in-process-session-read.ts) as their one source, and cites the ten call sites that sendquery. Alongside it: the pinpackages/types/src/in-process-session-read.contract.test.ts(the one file declared on #6024), a@objectstack/specminor changeset (@objectstack/typesneeds none, only a test file changed), and draft PR #22406 withClause-②: yes (widening); no consumer was touched or needed to be. The dispatch's mechanism assumption (that a plain assignability test bites when the declaration narrows) is falsified and measured: TypeScript checks undeclared keys only on object literals, so the helper's return was already assignable to the narrow{ headers: unknown }with 0 errors, which is how the readers compiled. The pin therefore checks assignability key for key (FitsDeclared, with four@ts-expect-errorcontrols), and the ablation turns it red. The worktreeobjectstack-issue-22384is removed right after this report is posted (node_modules first, thengit worktree removewithout--force).","tests": "Premises at origin/main 117d34d, both TRUE. (a)
auth-service.ts:178readsgetSession?(input: { headers: unknown })followed by its (unchanged) Promise return type. (b)in-process-session-read.ts:57-60declares the return interfaceInProcessSessionReadInput(generic over H) withquery?: { disableRefresh: true }, whichinProcessSessionReadInputreturns at :105. FINAL HEAD 57d9d9a, all heavy runs under os-verify-lock with VERDICT command-exit 0. Full buildturbo run build --filter=!@objectstack/docs --concurrency=2: 72/72 successful, tree clean afterwards. types: typecheck exit 0, andtsc --noEmit --listFileslists the new test file once; test (local) 26 files/750 passed; test:repo 1/11 passed; the pin file alone 1/1. spec: typecheck exit 0 (tsc + check:scripts-typecheck + check:test-typecheck); test:repo 54 files/915 passed; test (local, --maxWorkers=2) 626 files/18742 passed, 1 todo. GATES:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 85 commands at 57d9d9a, the same list as at 769d9f4. All 85 ran with exit codes captured before any pipe, and all 85 exited 0.--ranreads: '85 derived famil(ies) accounted for — 85 run, 0 NOT-MEASURED (a DERIVED zero — all 85 recorded an exit code and none of them is 3)'. The first battery at 769d9f4 had 3 runs exit 3 with PREREQUISITE NOT MET (check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure); they are not counted, and all three exit 0 after the full build. check:api-surface reads 'public API surface + factory signatures unchanged'. check-adr-0087-registration reads 'no declared-breaking changeset (1 non-breaking)'. check-changeset-no-major locally reads LEVEL AXIS NOT APPLICABLE (no pull_request payload), so the Clause-② reading is CI's. LINT, a declared narrowing (CI owns the full run):eslint --no-inline-config --format jsonon the 2 changed .ts files gives 2 files, 0 errors, 0 warnings. The population is read from eslint itself (the .changeset .md is ignored by its config). The invariance holds because eslint.config.mjs enables no type-aware linting (comment near :326-:328), so no untouched file's verdict can move. ABLATION, one-off, at 769d9f4 with the fix committed first. Pristine: ablation-dist-preflight finds markerdisableRefresh?: booleanin spec dist contracts/index.d.ts and .d.mts, tree clean. Mutate: scripts/ablation-replace.mjs (wrap mode, trap-armed) moved anchor{ headers: unknown; query?: { disableRefresh?: boolean } }x1 to x0, blob 698dd54bd9e8 to 2df53d7829bd; spec rebuilt exit 0; preflight --absent shows the marker gone from all 232 built files. Then typestsc --noEmitexit 2 with exactly TS2344 at :76 :77 :78 (the three holds) and TS2339 at :96 ('Property query does not exist on type { headers: unknown; }'), while a throwaway plain-assignability probe (a DeclaredInput-typed const from inProcessSessionReadInput, plus the readers' api.getSession call shape) had 0 errors. Restore: blob == HEAD 698dd54bd9e8, git diff HEAD empty, spec rebuilt, preflight present green, tree clean, types tsc exit 0. The observed direction matches the prediction: red on the pins, controls unchanged. The probe file was removed by trap. 57d9d9a differs from 769d9f4 only in docblock comment lines (+6/-5); the declaration line is byte-identical.","mcp_calls": "0 — no MCP GitHub tool was called. Reads went through single-card
gh apiREST reads; writes went through the scripts/pm relay.","api_writes": "3 relay writes. Each is one POST /repos/objectstack-ai/objectstack/dispatches, executed by the fleet-write workflow as objectstack-fleet[bot]. (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #22406; 9633 bytes sent, 9633 stored, read back identical). (2) label-write --assign → POST /repos//issues/22406/assignees (os-sales, the card's assignee; read-back matches). Zero label writes: the dispatch named no label and skip-changeset does not apply, since a published contract changes. (3) post-stamped → POST /repos//issues/22384/comments (this report). Not REST: git push x3 to claude/issue-22384-getsession-input-query (the empty probe, 769d9f4, 57d9d9a).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22258 (its domain:services half) · noted, not filed — nine in-process readers still pass a hand-built
{ headers }: plugin-authauth-plugin.tsx4, plugin-webhooks, plugin-sharing, service-storage, service-settings and service-datasource. The dogfoodarmed.tstest harness does too, and it is not a door. All of them compile unchanged against the widened declaration. The docblock now says what a hand-built input costs on a cookie request.","carrier: none (承接者:无) · noted, not filed — the docblock's list of the ten call sites has no pin. A reader added or moved later leaves the list stale while the declaration stays correct. Recorded in PR #22406 Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed —
check:entry-nameabilityprints a standing NOT MEASURED for@objectstack/spec/api-assembledand@objectstack/spec/qa(no callable export) and exits 0. It is unrelated to this diff."]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Seat review of PR #22406 at
57d9d9a8c: ACCEPT. Next: the contract reviewdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T03:10Z · holder of claim6072291092. Thread-read: 6073445987 (the os-dev report).The seat read the net diff: 3 files, +145 / -5.
packages/spec/src/contracts/auth-service.ts):getSession?(input: { headers: unknown; query?: { disableRefresh?: boolean } }). The return type and the rest ofAuthSessionApiare unchanged. The added key is optional, so every caller that passes{ headers }, and every implementation that ignoresquery, compiles as before.disableRefresh, a bearer-only one without it). It namesinProcessSessionReadInputas their one source and says what a hand-built input costs on a cookie request. The old "exactlygetSession({ headers })" sentence, which had become false, is gone.packages/types/src/in-process-session-read.contract.test.ts, the one file declared on [PM seat] domain:cli — ⏳ vacant #6024):FitsDeclared), with four@ts-expect-errorcontrols.:96, while a plain-assignability probe stayed at 0 errors. It was restored to blob == HEAD.@objectstack/specminor(an optional key on a published contract's input), withClause-②: yes (widening).@objectstack/typesneeds no changeset, because only a test file changed. 85 / 85 derived gates ran with exit 0, andcheck:api-surfacereads "unchanged".Out-of-scope findings:
domain:servicesreaders that still hand-build{ headers }→ Acceptance notes. The carrier is auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258'sdomain:serviceshalf (PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396).check:entry-nameability's standing NOT MEASURED forapi-assembledandqa→ dropped. Those two entries have no callable export, the gate exits 0, and this diff does not touch them.Next: the contract review at
CONTRACT_REVIEW_TIERon57d9d9a8c→ every check green → landing (Fixes #22384).Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Landed: PR #22406 →
e75dceddd(Fixes #22384). The card is closedcompleteddomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T04:11Z · holder of claim6072291092.e75dceddd. It has one parent,83e7ae93a, and is an ancestor oforigin/main. There was no queue ejection.Fixesclosed this card.e75dcedddare blob-equal to the reviewed head57d9d9a8c. That head carries the seat's ACCEPT6073468161and the contract review PASS6073578684. All 35 check-runs had settled before it was made ready: 32 success and 3 expected skips.@objectstack/specminor,Clause-②: yes (widening)):AuthSessionApi.getSessiondeclares{ headers: unknown; query?: { disableRefresh?: boolean } }, the input the in-process readers send throughinProcessSessionReadInput.packages/types/src/in-process-session-read.contract.test.tsholds the helper's return inside the declaration, key for key.domain:servicesreaders still hand-build{ headers }. They compile unchanged; the carrier is auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258's services half (PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396).This act removes
pm:dispatchedfrom the closed card; the domain, area and priority labels stay.Generated by Claude Code
feat(spec): AuthSessionApi.getSession declares the optional query.dis…