Repository navigation
Commit b036ac5
feat(spec): AuthSessionApi.getSession declares the result fields its readers read (session.id, token, impersonatedBy, user.email, authGate) (#24220)
Fixes #24196
Clause-②: yes
## What
`AuthSessionApi.getSession`
(`packages/spec/src/contracts/auth-service.ts`) says its result declares
exactly the fields its in-process readers read, but it only declared
`user.id`, `session.userId` and `session.activeOrganizationId`. This PR
declares the other fields the card's readers read. Each one is optional,
carries a TSDoc naming its reader, and is typed the way that reader
takes it:
| Declared | Type | Reader |
|---|---|---|
| `user.email` | `string` | `resolveAuthzContext` session arm
(`@objectstack/core`) → `ctx.email` |
| `user.authGate` | `unknown` | `normalizeAuthGate` from the session
arm. It validates the value against the closed posture vocabulary, so
the contract declares it unvalidated, which is how that reader takes it
|
| `session.id` | `string` | session arm, dropped-claim log line (the row
id, never the token) |
| `session.token` | `string` | session arm → `ctx.accessToken` |
| `session.impersonatedBy` | `string` or `null` | print door
`impersonatorOf` (`@objectstack/service-print`). The stored column
`sys_session.impersonated_by` is a nullable lookup, and better-auth's
admin plugin types it `string` or `undefined` |
The two readers now read through the declared type. Both changes are
type-only:
- **`packages/core/src/security/resolve-authz-context.ts`**: the session
arm's `sessionData` is annotated with the type
`AuthSessionApi.getSession` declares it answers (`SessionRead`, derived
locally with `Awaited`/`ReturnType`, so no new export). Before, it was
the `any` that `getSession`'s input type returns. ⛔
`ResolveAuthzInput.getSession`'s own type is **left at `any`**.
Tightening it would narrow a published input of `@objectstack/core`,
which is a `(narrowing)` arm and breaking, and this card is a widening.
That option goes back to the seat as an open question.
- **`packages/services/service-print/src/print-routes.ts`**:
`impersonatorOf` takes the declared `SessionRead` instead of `unknown`.
That drops the `as { session?: { impersonatedBy?: unknown } }` cast. The
same branches run on the same values: the optional chain replaces
cast-then-chain, and the `typeof by === 'string'` narrowing stays.
`packages/types/src/in-process-session-read.contract.test.ts` (H4) pins
the declaration's INPUT key for key (`DeclaredInput`), never the result.
It needed no change and passes unchanged.
## Verification
All of this ran at `7a60e0e5bb`. The later merge of `origin/main`
(`b7f29ddfe2`) brought only cli / metadata-protocol / rest / runtime /
platform-objects files, none of which this diff touches.
- `pnpm --filter @objectstack/spec build` → exit 0. `check:generated` →
exit 0, "All 14 generated artifacts are up to date". No artifact
changed: the result type is inline in an existing export.
- Typecheck, downstream of `@objectstack/spec`, after building each
package's closure: `@objectstack/spec`, `@objectstack/types`,
`@objectstack/core`, `@objectstack/service-print` → exit 0. Then the
four packages that implement or read `AuthSessionApi`:
`@objectstack/plugin-auth` (its `AuthManager.getApi()` answers
better-auth's own API, which still fits the widened result),
`@objectstack/plugin-sharing`, `@objectstack/service-datasource`,
`@objectstack/plugin-hono-server` → exit 0. plugin-auth's examples leg
resolves plugin-auth's own `dist`, so it was re-run after building it.
- Tests: `@objectstack/core` 109 files / 2617 tests passed.
`@objectstack/service-print` 8 / 109 passed. `@objectstack/types` 27 /
759 passed.
- Reverse verification, committed first, mutation via
`scripts/ablation-replace.mjs`, each leg restored and checked as `blob
== HEAD`, `git diff HEAD` empty:
- core `session?.token` → `session?.tokenRenamed`: `tsc` exit 2,
`TS2339: Property 'tokenRenamed' does not exist on type '{ userId?…;
id?…; token?…; impersonatedBy?… }'`.
- print `session?.impersonatedBy;` → `session?.impersonatorId;`: `tsc`
exit 2, TS2339 on the same declared type.
- A first print attempt used a replacement that contained its own
anchor. The tool refused it (anchor 1 → 1) and restored the file. That
attempt measured nothing, and the second leg above is the reading.
- `dispatch-gates --commands` (84 families, change set derived against
merge base `8259ea03e`): 83 exit 0. The one exception is `pnpm
check:dual-build-cjs-loads`, exit 3, PREREQUISITE NOT MET: it reads
every package's `dist`, and this box built only the closures above. It
is NOT MEASURED locally and is CI's reading. `node
scripts/check-plugin-teardown-shape.mjs --self-test` first exited 3 on a
shallow clone. After `git fetch --unshallow` it exits 0 (48 cases).
`--ran` reconciliation: 84 derived, 83 run, 1 NOT-MEASURED (derived from
the recorded exit 3).
## Acceptance notes
The other `getSession` callers were **read, not edited** (the hold-off).
Their reads of the result still go through `any` or a cast. Read on this
branch:
- `plugin-auth/src/auth-plugin.ts`: `:2839` and `:3229` call `(authApi
as any).getSession(…)`. `:3229` also casts the result to `{ user?: {
id?: unknown } }`. `:2776` and `:2911` are typed by better-auth's
`Auth['api']`, not `AuthSessionApi`. All four hand the session to
`judgePlatformAdmin` / `isPlatformAdminUser`, which take `unknown` and
cast. They read `user.isPlatformAdmin`, `user.role`, `user.email`,
`user.id` and `user.authGate`.
- `rest/src/rest-server.ts:3416`: `gatedSession: any` →
`normalizeAuthGate(gatedSession?.user)`. The closure at `:3202` goes to
`resolveAuthzContext`.
- `runtime/src/dispatcher-plugin.ts:214`: `const session: any` →
`evaluateAuthGate`. `runtime/src/http-dispatcher.ts:1556` (`let session:
any` → `evaluateAuthGate`) and `:1678` (implicitly `any`; reads
`user.id`, `session.userId`, `session.activeOrganizationId`).
`runtime/src/security/resolve-session-principal.ts:57`: `api: any`; its
callers read `user.id` / `session.userId`.
- `cloud-connection-plugin.ts:317` (`any` → `evaluateAuthGate`, then
`resolveAuthzContext`). `marketplace-install-local-plugin.ts:3352` reads
`session.activeOrganizationId` **or a top-level
`activeOrganizationId`**.
`service-storage/src/storage-service-plugin.ts:1176` reads the same
top-level fallback.
- `plugin-webhooks/src/webhook-outbox-plugin.ts:627` and
`service-storage` `:953` / `:1279`: `any`, then `evaluateAuthGate` and
`user.id` / `session.activeOrganizationId`.
- `normalizeAuthGate` / `evaluateAuthGate`
(`packages/core/src/security/auth-gate.ts`) keep their `any` parameter.
They also validate non-session input (`{ authGate: raw }` from
`resolveUserAuthGates`, and deliberately malformed test fixtures).
Typing them is a separate change, not this card's.
The fields only those callers read (`user.role`, `user.isPlatformAdmin`,
the top-level `activeOrganizationId`) are **not** declared here. The
card scopes the declaration to its named readers, and each of those
callers would have to drop its cast for the declaration to have a
reader.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01A6LabtpD5uUXcktHHnReQa)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 99e9d29 commit b036ac5
4 files changed
Lines changed: 62 additions & 10 deletions
File tree
- .changeset
- packages
- core/src/security
- services/service-print/src
- spec/src/contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
| 66 | + | |
66 | 67 | | |
67 | 68 | | |
68 | 69 | | |
| |||
146 | 147 | | |
147 | 148 | | |
148 | 149 | | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
149 | 153 | | |
150 | 154 | | |
151 | 155 | | |
| |||
539 | 543 | | |
540 | 544 | | |
541 | 545 | | |
542 | | - | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
543 | 549 | | |
544 | 550 | | |
545 | 551 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
135 | 138 | | |
136 | 139 | | |
137 | 140 | | |
138 | | - | |
| 141 | + | |
139 | 142 | | |
140 | 143 | | |
141 | 144 | | |
| |||
226 | 229 | | |
227 | 230 | | |
228 | 231 | | |
229 | | - | |
| 232 | + | |
230 | 233 | | |
231 | | - | |
| 234 | + | |
232 | 235 | | |
233 | 236 | | |
234 | 237 | | |
| |||
267 | 270 | | |
268 | 271 | | |
269 | 272 | | |
270 | | - | |
271 | | - | |
272 | | - | |
| 273 | + | |
| 274 | + | |
273 | 275 | | |
274 | 276 | | |
275 | 277 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
379 | 379 | | |
380 | 380 | | |
381 | 381 | | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
382 | 388 | | |
383 | 389 | | |
384 | 390 | | |
385 | | - | |
386 | | - | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
387 | 418 | | |
388 | 419 | | |
0 commit comments