Found while implementing #6216 (PR #7259). Filed unassigned; deliberately not fixed there, because declaring a field on ExecutionContextSchema is a spec change with its own review surface, not a refactor rider.
The fact
The ADR-0069 authentication-policy gate rides on the execution context:
- written in
packages/rest/src/rest-server.ts computeExecCtx — the assembled envelope is spread with ...(authGate ? { authGate } : {}) behind an as any;
- read in the same file's
enforceAuth (const gate = context?.authGate;), which blocks protected resources for a gated user;
- not declared anywhere in
packages/spec/src/kernel/execution-context.zod.ts — grep -n authGate packages/spec/src returns nothing.
Why it matters now specifically
#6216's whole point is that the ExecutionContext field set is closed by type, so a field cannot reach one transport and miss another. That closure is derived from keyof ExecutionContext, i.e. from the schema. A field that lives only in an as any spread is, by construction, outside the closure: ENTRY_EXECUTION_CONTEXT_FIELDS cannot list it, ExecutionContextEntryFields cannot demand it, and the runtime pin that reconciles the closed set against ExecutionContextSchema.shape cannot see it either. It is exactly the blind spot the new gate is meant to remove, sitting one as any outside the gate.
Nothing is broken today: the producer and the consumer are ten lines apart in one file, so they cannot drift the way #6071's two files did. The cost is that a second transport wiring up the ADR-0069 gate has nothing to tell it the field exists.
Suggested shape if it is taken up
Declare authGate on ExecutionContextSchema (shape: { code, message } per packages/core/src/security/auth-gate.ts), drop the as any, and let #6216's closed set pick it up — at which point every face must decide it, which is the intended outcome. Note the ADR-0087 / authorable-surface consequences of adding a key: pnpm --filter @objectstack/spec build regenerates packages/spec/authorable-surface/kernel.json, and that regeneration is itself gated.
Related: #6216 / PR #7259, ADR-0069.
Found while implementing #6216 (PR #7259). Filed unassigned; deliberately not fixed there, because declaring a field on
ExecutionContextSchemais a spec change with its own review surface, not a refactor rider.The fact
The ADR-0069 authentication-policy gate rides on the execution context:
packages/rest/src/rest-server.tscomputeExecCtx— the assembled envelope is spread with...(authGate ? { authGate } : {})behind anas any;enforceAuth(const gate = context?.authGate;), which blocks protected resources for a gated user;packages/spec/src/kernel/execution-context.zod.ts—grep -n authGate packages/spec/srcreturns nothing.Why it matters now specifically
#6216's whole point is that the
ExecutionContextfield set is closed by type, so a field cannot reach one transport and miss another. That closure is derived fromkeyof ExecutionContext, i.e. from the schema. A field that lives only in anas anyspread is, by construction, outside the closure:ENTRY_EXECUTION_CONTEXT_FIELDScannot list it,ExecutionContextEntryFieldscannot demand it, and the runtime pin that reconciles the closed set againstExecutionContextSchema.shapecannot see it either. It is exactly the blind spot the new gate is meant to remove, sitting oneas anyoutside the gate.Nothing is broken today: the producer and the consumer are ten lines apart in one file, so they cannot drift the way #6071's two files did. The cost is that a second transport wiring up the ADR-0069 gate has nothing to tell it the field exists.
Suggested shape if it is taken up
Declare
authGateonExecutionContextSchema(shape:{ code, message }perpackages/core/src/security/auth-gate.ts), drop theas any, and let #6216's closed set pick it up — at which point every face must decide it, which is the intended outcome. Note the ADR-0087 / authorable-surface consequences of adding a key:pnpm --filter @objectstack/spec buildregeneratespackages/spec/authorable-surface/kernel.json, and that regeneration is itself gated.Related: #6216 / PR #7259, ADR-0069.