The errors-skill's two-pass model applied without a journal: the caller
owns the temporal context. Every decision-flipping temporal feature (the
clock behind TTL gates, challenge-proof expiry, value decay) is now
parameterized as options.now, so an explain rerun that replays the
original temporal parameters reproduces the original decision exactly.
Threaded now through: the comparator's operand value paths
(_getCachedDirectValue/_extractValues/isWithinTTL), the challenge proof
lookup, and the multi_hop value collection (isWithinTTL + blur). All four
result caches bypass cached decisions when the clock is pinned (rule-
result cache, the checker's rule/direct caches, and the comparator's
derived operand cache) — interleaved pinned-clock checks are per-time
with no cross-contamination.
The explain serializer records request.temporal.now so the caller knows
exactly what to replay. The happy path stays minimal and fast (no now ->
no parameter, no cache changes); the rerun (explain with the temporal
context) carries the full diagnostics.
Pins: interleaved fresh/expired/expired-again comparator checks, the
explain replay of both decisions + the recorded temporal context, and
challenge-proof expiry replay.
The compiled evaluation path was never a performance win and was a
double-implementation liability: every semantics fix had to land twice
(CompiledEvaluator + LogicalOperators/handlers), and several bugs lived
only in one copy. A warm benchmark shows the compiled path at parity at
best (the apparent 7x chain regression was cold-cache confound).
Removed the runtime compiled dispatch entirely: RuleEvaluator evaluates
every rule through the single fallback path (logical operators + rule
handlers). The RuleCompiler remains as the config VALIDATOR only
(_compileErrors/_compileWarnings + _needsValues + the _compiled metadata
carried by snapshots). CompiledEvaluator.js deleted.
Fixes surfaced by removing the mask:
- The defeasible fallback wrap produced the wrong component shape
({rules} instead of {union:{rules}}/{intersection:{rules}}) — the
compiled path always ran for defeasible configs, so the fallback had
never executed; now wrapped correctly.
- Defeasible configs had no normal-dispatch routing (the compiled
evaluator handled them); routed to evaluateDefeasible.
- The binary defeasible path forced the binary mode's internal 0.5
threshold, while the compiled path always ran normal mode — the binary
decision is now the thresholded normal combination (preserving the
pinned contract).
- The fallback union/intersection/exclusion results now carry the
validity blocks (previously only the compiled versions did).
Probe sweep of the OWA surfaces found three real defects:
- reliabilityWeighting was a silent no-op everywhere: every implementation
scaled possibilities by metas[i].reliability, but no child meta ever
carried a reliability field (the compiled direct omitted it and the
DirectRule handler omitted it too), so the weighting was always x1.0.
All weighting branches now use the tracked child reliabilities, and the
DirectRule handler + its meta now carry the relation's reliability.
- The compiled union and the direct_list fast path had no
reliabilityWeighting branch at all; both now apply it.
- Shorthand children ({ relation: 'editor' }) dispatch to the direct
handler but carry no type, so _getRuleResultCacheKey derived the generic
'rule' suffix for every shorthand child of a logical rule — the first
child's cached result was served for all of them (the fallback path
returned the owner's 0.8 for the editor). The key derivation now matches
the shorthand dispatch. The RuleEvaluator also treats shorthand operands
as direct rules instead of unknown_rule_type on the non-compiled path.
New pins: an OWA differential property (custom weights, max/min/average
aggregators, reliabilityWeighting, compiled path) and a multi_hop
pathAggregation=owa fixed pin with reliability propagation.