What
Found during the second adversarial review of #2365, as a contrast rather than a finding against that PR.
UnobservedReason (packages/core/src/observation.ts:53) is a closed union, and UNOBSERVED_REASONS (:61) is its runtime witness — "every legal UnobservedReason, for validation and conformance checks." The two are kept in sync by hand. The reviewer added "quiesced" to the union and left the array untouched: tsc was clean and every observation test stayed green. The type now admits a value the validator and the conformance suite reject.
The same tamper against BehaviourUnpredictedReason in behaviour.ts fails at compile time at behaviour.ts(366,7), because #2365 derives its runtime array from Object.keys of a Record<BehaviourUnpredictedReason, true> — every member of the type must appear as a key or the object literal does not type-check. That construction is applied to all four closed sets in that module.
Why it matters
UnobservedReason is the vocabulary every observing lexicon's tri-state runs on, and observation-conformance.ts enforces it end to end. A reason added to the type and forgotten in the array is a reason the conformance suite refuses while the type permits — a lexicon can construct a value its own suite rejects. That is the exact desync the behaviour contract closed for itself, and it should not remain open one module over.
Do
Replace the hand-written array with the witness pattern from behaviour.ts:366: const WITNESS: Record<UnobservedReason, true> = { … } and UNOBSERVED_REASONS = Object.keys(WITNESS) as UnobservedReason[]. Check NotAttemptedReason in packages/core/src/apply.ts for the same gap — it is the other closed reason enum the conformance suite reads.
Proof
Add a member to UnobservedReason without touching the array: today tsc is clean; after, it fails in observation.ts. Shown red by the tamper, then green with the array updated.
Refs #2365, #2356.
What
Found during the second adversarial review of #2365, as a contrast rather than a finding against that PR.
UnobservedReason(packages/core/src/observation.ts:53) is a closed union, andUNOBSERVED_REASONS(:61) is its runtime witness — "every legalUnobservedReason, for validation and conformance checks." The two are kept in sync by hand. The reviewer added"quiesced"to the union and left the array untouched:tscwas clean and every observation test stayed green. The type now admits a value the validator and the conformance suite reject.The same tamper against
BehaviourUnpredictedReasoninbehaviour.tsfails at compile time atbehaviour.ts(366,7), because #2365 derives its runtime array fromObject.keysof aRecord<BehaviourUnpredictedReason, true>— every member of the type must appear as a key or the object literal does not type-check. That construction is applied to all four closed sets in that module.Why it matters
UnobservedReasonis the vocabulary every observing lexicon's tri-state runs on, andobservation-conformance.tsenforces it end to end. A reason added to the type and forgotten in the array is a reason the conformance suite refuses while the type permits — a lexicon can construct a value its own suite rejects. That is the exact desync the behaviour contract closed for itself, and it should not remain open one module over.Do
Replace the hand-written array with the witness pattern from
behaviour.ts:366:const WITNESS: Record<UnobservedReason, true> = { … }andUNOBSERVED_REASONS = Object.keys(WITNESS) as UnobservedReason[]. CheckNotAttemptedReasoninpackages/core/src/apply.tsfor the same gap — it is the other closed reason enum the conformance suite reads.Proof
Add a member to
UnobservedReasonwithout touching the array: todaytscis clean; after, it fails inobservation.ts. Shown red by the tamper, then green with the array updated.Refs #2365, #2356.