Observation
The set of collections the runtime publish gate carries in a per-write snapshot is written down in four places, and only two of them are held to each other by the compiler:
| site |
file |
guard today |
RuntimeStackContext |
packages/lint/src/runtime-gate.ts |
the declaration |
CONTEXT_STACK_KEYS |
packages/lint/src/runtime-gate.ts |
satisfies readonly (keyof RuntimeStackContext)[] — validity, not completeness |
NAME_KEYED_STACK_KEYS |
packages/lint/src/runtime-gate.ts |
none |
CLOSURE_CONTEXT_KEY_BY_TYPE |
packages/metadata-protocol/src/runtime-authoring-gate.ts |
a satisfies clause over a record keyed by RuntimeStackContext keys — validity, not completeness |
NAME_KEYED_STACK_KEYS is the one with no guard at all, and it carries a real invariant: a collection that the CONTEXT fills and that some write type maps into (TYPE_TO_STACK_KEY) must be name-keyed, or a finding's path is a positional index into an in-memory snapshot the caller has never seen and cannot enumerate — the defect #10064 fixed for objects / permissions / books.
The invariant is currently satisfied. What is missing is anything that keeps it satisfied: the correct membership is derivable (CONTEXT_STACK_KEYS intersected with the values of TYPE_TO_STACK_KEY), and the list is hand-maintained instead.
How it surfaced
While adding the pages collection for the page-view mount work, pages had to be added to CONTEXT_STACK_KEYS, to NAME_KEYED_STACK_KEYS, to TOP_LEVEL_INDEX's alternation, to CLOSURE_CONTEXT_KEY_BY_TYPE, and to a hand-listed accumulator literal in packages/metadata-protocol/src/protocol.ts. Only the last of those five announced itself — it was the one the compiler could see, and only after the accumulator was retyped as a mapped type over RuntimePendingDeclarations. The other four were found by reading, and NAME_KEYED_STACK_KEYS in particular would have gone unnoticed: forgetting it produces correct-looking findings with paths the caller cannot resolve, and no test or gate would have gone red.
TOP_LEVEL_INDEX (a regex alternation of the same names) is a fifth spelling of the same set, one line below NAME_KEYED_STACK_KEYS.
Suggested direction (not a decision)
Derive NAME_KEYED_STACK_KEYS and TOP_LEVEL_INDEX from CONTEXT_STACK_KEYS and TYPE_TO_STACK_KEY rather than listing them, so the invariant holds by construction; keep the hand-written list only where a deliberate exception needs stating, and state it there. Nothing in this repo is wrong today — this is about what the next widening costs.
Filed unassigned from the #13216 dispatch; not fixed there, because the fix is a refactor of a seam that card only widens by one key.
Generated by Claude Code
Generated by Claude Code
Observation
The set of collections the runtime publish gate carries in a per-write snapshot is written down in four places, and only two of them are held to each other by the compiler:
RuntimeStackContextpackages/lint/src/runtime-gate.tsCONTEXT_STACK_KEYSpackages/lint/src/runtime-gate.tssatisfies readonly (keyof RuntimeStackContext)[]— validity, not completenessNAME_KEYED_STACK_KEYSpackages/lint/src/runtime-gate.tsCLOSURE_CONTEXT_KEY_BY_TYPEpackages/metadata-protocol/src/runtime-authoring-gate.tssatisfiesclause over a record keyed by RuntimeStackContext keys — validity, not completenessNAME_KEYED_STACK_KEYSis the one with no guard at all, and it carries a real invariant: a collection that the CONTEXT fills and that some write type maps into (TYPE_TO_STACK_KEY) must be name-keyed, or a finding's path is a positional index into an in-memory snapshot the caller has never seen and cannot enumerate — the defect #10064 fixed forobjects/permissions/books.The invariant is currently satisfied. What is missing is anything that keeps it satisfied: the correct membership is derivable (
CONTEXT_STACK_KEYSintersected with the values ofTYPE_TO_STACK_KEY), and the list is hand-maintained instead.How it surfaced
While adding the
pagescollection for thepage-view mount work,pageshad to be added toCONTEXT_STACK_KEYS, toNAME_KEYED_STACK_KEYS, toTOP_LEVEL_INDEX's alternation, toCLOSURE_CONTEXT_KEY_BY_TYPE, and to a hand-listed accumulator literal inpackages/metadata-protocol/src/protocol.ts. Only the last of those five announced itself — it was the one the compiler could see, and only after the accumulator was retyped as a mapped type overRuntimePendingDeclarations. The other four were found by reading, andNAME_KEYED_STACK_KEYSin particular would have gone unnoticed: forgetting it produces correct-looking findings with paths the caller cannot resolve, and no test or gate would have gone red.TOP_LEVEL_INDEX(a regex alternation of the same names) is a fifth spelling of the same set, one line belowNAME_KEYED_STACK_KEYS.Suggested direction (not a decision)
Derive
NAME_KEYED_STACK_KEYSandTOP_LEVEL_INDEXfromCONTEXT_STACK_KEYSandTYPE_TO_STACK_KEYrather than listing them, so the invariant holds by construction; keep the hand-written list only where a deliberate exception needs stating, and state it there. Nothing in this repo is wrong today — this is about what the next widening costs.Filed unassigned from the #13216 dispatch; not fixed there, because the fix is a refactor of a seam that card only widens by one key.
Generated by Claude Code
Generated by Claude Code