|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +"@objectstack/runtime": patch |
| 4 | +--- |
| 5 | + |
| 6 | +feat(spec): declare the action-body `ctx.session` contract (#5697) |
| 7 | + |
| 8 | +An action body reads `ctx.session` on every dispatch, and until now **nothing |
| 9 | +declared it**. `actionContext` is a bare `any` at both dispatch sites |
| 10 | +(`domains/actions.ts`, `action-execution.ts`), the sandbox seam types |
| 11 | +`ScriptContext.session` as `unknown`, and the one spec-side mention was an |
| 12 | +inline literal on `ActionHandlerContext` carrying a `[k: string]: unknown` |
| 13 | +catch-all. Declared-nowhere, produced-anyway: no schema, no gate, no generated |
| 14 | +reference page, and nothing the liveness ledger could reach. |
| 15 | + |
| 16 | +That is how the surface drifted without anyone noticing. Its `roles` key carries |
| 17 | +`ExecutionContext.positions` — the ADR-0090 D3 vocabulary handed to authors under |
| 18 | +the one spelling that ADR forbids — while the hook side retired its own |
| 19 | +`session.roles` at #5050. One platform, one key name, two opposite answers. |
| 20 | + |
| 21 | +**`ActionSessionSchema` (`@objectstack/spec/ui`) declares that shape as built.** |
| 22 | + |
| 23 | +```ts |
| 24 | +{ userId?: string; organizationId?: string; roles?: string[] } |
| 25 | +``` |
| 26 | + |
| 27 | +This release changes **nothing about what the runtime produces** — it is phase 1 |
| 28 | +of #5613's contract-first ruling, and declaring current reality is deliberately |
| 29 | +not the same as endorsing it: |
| 30 | + |
| 31 | +- `roles` is declared **deprecated** in its `.describe()` and its JSDoc. The |
| 32 | + rename to `positions`, with a deprecation window and an ADR-0087 semantic |
| 33 | + migration, is #5613 phase 2. There is deliberately **no `positions` key yet** — |
| 34 | + minting one before the migration would ship two live spellings of one value. |
| 35 | +- The schema is **not strict**, matching `HookContextSchema`: this is a runtime |
| 36 | + shape the platform hands a body, never authored, and closing it would turn a |
| 37 | + future engine-side enrichment into a parse failure for whoever parses a context |
| 38 | + they were given. |
| 39 | + |
| 40 | +Three facts the declaration now states, all of them previously discoverable only |
| 41 | +by reading the builder: |
| 42 | + |
| 43 | +- **Absent means the key is absent.** The builder uses conditional spreads, so |
| 44 | + `'organizationId' in ctx.session` answers `false` — not "present and |
| 45 | + `undefined`". The hook path's `input.id` on a bulk write is the opposite case |
| 46 | + (#5668); an `in` test does not port between them. |
| 47 | +- **No identity envelope yields no session at all** — `undefined`, never `{}`, so |
| 48 | + a body can tell "no caller" from "an anonymous caller" (#3712). One consequence: |
| 49 | + `roles` never appears on its own. |
| 50 | +- **`organizationId` is the blessed name** for the caller's active org; the |
| 51 | + v11-removed `session.tenantId` alias (#3280 / #3290) does not come back. |
| 52 | + |
| 53 | +Type-only on the runtime side, no behaviour change: `buildActionSession()` now |
| 54 | +declares `ActionSession | undefined` instead of `any | undefined`, and |
| 55 | +`ActionHandlerContext.session` is the schema's inferred type rather than an |
| 56 | +inline literal with a catch-all. A handler annotated with `ActionHandler` that |
| 57 | +read an undeclared key off `ctx.session` now gets a compile error naming it — |
| 58 | +that key was never produced. `ScriptContext.session` deliberately stays |
| 59 | +`unknown`: it is one seam over both body kinds, and hook and action sessions are |
| 60 | +different objects. |
| 61 | + |
| 62 | +The declaration ships with the gate it needed — |
| 63 | +`packages/runtime/src/action-session-shape-contract.test.ts` executes the real |
| 64 | +producer and asserts a non-strict parse of the built object returns it |
| 65 | +**unchanged**, so a key the builder starts producing without declaring here is |
| 66 | +stripped and the pin goes red. |
0 commit comments