Repository navigation
Commit e75dced
feat(spec): AuthSessionApi.getSession declares the optional query.disableRefresh its readers send (#22406)
Fixes #22384
Clause-②: yes (widening)
`AuthSessionApi.getSession` now declares the optional
`query.disableRefresh` that the in-process readers send. A type-level
pin in `packages/types` keeps the helper's return inside that
declaration, key for key.
## What changes
- **`packages/spec/src/contracts/auth-service.ts`.**
- The declaration: `getSession?(input: { headers: unknown; query?: {
disableRefresh?: boolean } })`. It was `{ headers: unknown }`. The
return type and the rest of `AuthSessionApi` are unchanged.
- The docblock now states the two read forms. A request carrying a
better-auth session cookie reads with `{ headers, query: {
disableRefresh: true } }`. A bearer-only request reads with `{ headers
}` alone.
- It names `inProcessSessionReadInput`
(`packages/types/src/in-process-session-read.ts`) as the one source of
both forms, and says what a hand-built `{ headers }` costs on a cookie
request.
- The old line "Widening this is for whoever needs more, with the call
site to prove it" stays. The docblock now cites the ten call sites from
PR #22367 that send `query`: `rest-server.ts` (x2), `http-dispatcher.ts`
(x2), `resolve-session-principal.ts`, `resolve-execution-context.ts`,
`current-user-endpoints.ts`, `cloud-connection-plugin.ts` and
`marketplace-install-local-plugin.ts` (x2).
- **`packages/types/src/in-process-session-read.contract.test.ts`**
(new). This is the pin, and it is the one file declared on #6024
(comment `6072295794`). It is described below.
- **`.changeset/22384-auth-session-api-getsession-input-query.md`.**
`@objectstack/spec` `minor`. `@objectstack/types` gets no entry, because
the only file changed there is a test, and `files[]` ships only `dist`,
`README.md` and `CHANGELOG.md`.
No consumer was touched. No reader changed in `domain:cli` or
`domain:services`.
## The pin, and a dispatch assumption it falsified
The dispatch asked for a type-level test that the helper's return is
*assignable* to the declared input, and asked that narrowing the
declaration back should make it fail. **Plain assignability cannot fail
here.** TypeScript refuses an undeclared key only on an object literal.
A non-literal value with an extra optional key is assignable to a type
that omits that key. That is how all ten readers compiled against `{
headers: unknown }` while sending `query`.
This was measured in the ablation below. With the declaration narrowed
back, a throwaway probe compiled with **0 errors**. The probe held
`const x: DeclaredInput = inProcessSessionReadInput(new Headers())` and
the readers' own call shape,
`api.getSession?.(inProcessSessionReadInput(...))`.
So the pin checks assignability **key for key**. `FitsDeclared` applies
the object-literal rule to a type:
- the value is assignable;
- it carries no key the declaration does not name;
- this holds at every depth where the declaration names a shape
(`headers` is `unknown`, so it is not looked into);
- a union is judged one member at a time, never by its common keys.
The file carries:
- three `holds(true)` lines, each typed with `FitsDeclared` applied to
`HelperInput` of a header type and `DeclaredInput`. There is one line
per header shape the readers hand the helper: Web `Headers`, a Node
header record, and `unknown`;
- four `@ts-expect-error` controls that prove the instrument can fail at
all. If `FitsDeclared` ever went vacuous, each directive would stop
matching an error and tsc would report TS2578;
- one runtime case: an implementer typed by the contract reads
`input.query?.disableRefresh`. That read is itself a compile-time check,
and it asserts a cookie read does not renew while a bearer read does.
**Mechanism.** The test runs under the package's existing `typecheck`
script (`tsc --noEmit`). `packages/types/tsconfig.json` includes
`src/**/*`, tests included, and `--listFiles` lists the new file once.
CI's `TypeScript Type Check` runs it, and it reads `@objectstack/spec`
from its built `.d.ts`. This follows the `@ts-expect-error` compile-time
pins already in `response-envelope.test.ts`. No new runner was added.
## Reverse verification (one-off; nothing kept)
The run is at `769d9f4db`, after the fix was committed. It used
`scripts/ablation-replace.mjs` in wrap mode and
`scripts/ablation-dist-preflight.mjs`, under the shared verify lock. The
predicted direction was red on the three `holds` and on the `query`
read, with the controls unchanged. That is what happened.
| leg | reading |
|---|---|
| pristine dist | marker `disableRefresh?: boolean` present in
`dist/contracts/index.d.ts` and `.d.mts`; tree clean |
| mutate | anchor `{ headers: unknown; query?: { disableRefresh?:
boolean } }` x1 -> x0; blob `698dd54bd9e8` -> `2df53d7829bd`; spec
rebuilt (exit 0); preflight `--absent`: marker absent from all 232 built
files |
| `tsc --noEmit` in `packages/types`, mutated | **exit 2**: `TS2344` at
`:76`, `:77`, `:78` (the three `holds`), `TS2339` at `:96` (`Property
'query' does not exist on type '{ headers: unknown; }'`); **0** errors
in the plain-assignability probe |
| restore | blob after restore `698dd54bd9e8` == HEAD; `git diff HEAD`
empty; spec rebuilt; preflight (present) green; tree clean |
| `tsc --noEmit` in `packages/types`, restored | **exit 0** |
The head after that run (`57d9d9a8c`) changes only docblock text in
`auth-service.ts`: 6 lines added and 5 removed, all inside the comment.
The declaration line is byte-identical.
## Consumers
Every consumer that types against `AuthSessionApi` keeps compiling, and
none was edited:
- The ten readers pass `inProcessSessionReadInput(...)`. That input is
now declared instead of tolerated.
- The readers that still pass `{ headers }` by hand: `plugin-auth` (x4),
`plugin-webhooks`, `plugin-sharing`, `service-storage`,
`service-settings`, `service-datasource`, and the dogfood `armed.ts`
harness. `query` is optional, so they type as before. Moving them to the
helper is #22258's remaining half and is not done here.
- An implementer of `getSession` that declares `{ headers: unknown }`
still satisfies the contract, because the wider input is assignable to
it.
The `typecheck` of both edited packages is green (below). The downstream
consumer typecheck is left to CI's `TypeScript Type Check`.
## Verification
At `57d9d9a8c` (final head):
- **Build.** `pnpm exec turbo run build --filter='!@objectstack/docs'
--concurrency=2`: 72 of 72 tasks successful, and `git status
--porcelain` was empty afterwards.
`packages/spec/dist/contracts/index.d.ts` carries the new declaration
and docblock.
- **`@objectstack/types`.**
- `typecheck`: exit 0. `tsc --noEmit --listFiles` lists
`in-process-session-read.contract.test.ts` once.
- `test` (local): 26 files, 750 tests passed.
- `test:repo`: 1 file, 11 tests passed.
- The pin file run alone: 1 file, 1 test passed.
- **`@objectstack/spec`.**
- `typecheck`: exit 0. This covers `tsc --noEmit`,
`check:scripts-typecheck` and `check:test-typecheck`.
- `test:repo`: 54 files, 915 tests passed.
- `test` (local, `--maxWorkers=2`): 626 files, 18742 tests passed, 1
todo.
- **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 85 commands at `57d9d9a8c`. All 85
ran, each exit code captured before any pipe, and all 85 exited 0. The
`--ran` reconciliation 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)". Selected verdict lines:
- `check:api-surface`: "public API surface + factory signatures
unchanged".
- `check-adr-0087-registration`: "this PR adds no declared-breaking
changeset (1 non-breaking changeset(s) seen)".
- `check:nul-bytes`: OK.
- `check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:doc-formula-expressions` were measured after the full build.
Their earlier runs at `769d9f4db` exited 3 ("prerequisite not met");
those runs are not counted.
- **`check-changeset-no-major --base origin/main`.** Run locally, it
prints "LEVEL AXIS: NOT APPLICABLE" because there is no `pull_request`
payload. The reading of `Clause-②: yes (widening)` against `minor` is
CI's on this PR.
- **Lint, narrowed (CI owns the full run).** I ran `pnpm exec eslint
--no-inline-config --format json` on the two changed `.ts` files: 2
files, 0 errors, 0 warnings. The `.changeset` file falls outside
eslint's config (eslint reports it as ignored). `eslint.config.mjs`
enables no type-aware linting (no `parserOptions.project`; see its
comment near `:326`), so this diff cannot change a verdict on any
untouched file.
- **Base.** The branch is 8 commits behind `origin/main` (`11d119ab1`).
None of those commits touches `packages/spec/src/contracts/` or
`packages/types/`, and the merge queue rebuilds the merged generation.
## Acceptance notes
- **The declaration's value is `boolean`, but the helper only ever sends
`true`.** This follows the card and the claim. `FitsDeclared` accepts
`true` against `boolean`. A helper that started sending `false` would
also fit, and `false` would mean renewal.
- **The docblock names files rather than lines.** The call sites move
often, so a line number would go stale on the next edit. The pin names
the helper, not the readers, and nothing checks the list of ten. A
reader added or moved later leaves the list stale but the declaration
correct.
- `check:entry-nameability` prints a standing `NOT MEASURED` for
`@objectstack/spec/api-assembled` and `@objectstack/spec/qa` (no
callable export). This is unrelated to `contracts`. The gate exits 0.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 83e7ae9 commit e75dced
3 files changed
Lines changed: 145 additions & 5 deletions
File tree
- .changeset
- packages
- spec/src/contracts
- types/src
Lines changed: 11 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
169 | 169 | | |
170 | 170 | | |
171 | 171 | | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
176 | 199 | | |
177 | 200 | | |
178 | | - | |
| 201 | + | |
179 | 202 | | |
180 | 203 | | |
181 | 204 | | |
| |||
Lines changed: 106 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
0 commit comments