The principle: time is caller-provided (options.now / partialGraph.now);
the wall clock is only the fallback for unpinned callers, never a hidden
decision input. Remaining clock leaks:
- getBlurredValue gained an optional now param threaded to _isValueExpired;
ChainRule (2 sites), MultiHopRule (2 sites), RelationManager (3 sites)
now pass the caller clock. Previously a pinned-clock caller's chain/
multi-hop value TTL used the WALL clock (wall in 2026, pinned T0 in
2001 -> values wrongly expired).
- MultiHopRule's TTL gate used valueFilters.ttl || 24h instead of the
valueManager's per-relation TTL (inconsistent with chain/comparator);
now valueManager.getTTL is the authority, valueFilters.ttl the override.
- QualitativeRelationalComparatorRule decay (_calculatePeriodsElapsed)
and value timestamps used the wall clock, so qualitative possibility
decay ignored the pinned clock; now threaded through _evaluateOperand.
- ValueManager decay internals (getDecayedRelation, _calculateSeparated
Decay, _calculateBlurredValue) accept a now param (background worker
still passes none -> wall clock is correct there).
- PartialGraphContext._addChallengeProof/_addRelation used Date.now()
instead of the context's own this.now (the partial graph's time).
- Arbiter gained an injectable clock (options.clock) driving unpinned
cache-entry freshness in DecisionCache, RuleEvaluator, ChainRule, and
RelationalComparatorRule; DecisionCache explicit clock still wins.
- Collected-value timestamps in DirectRule and RelationalComparatorRule
honor the caller clock.
Pinned-clock chain probe: values fresh at T0, expired at T0+61s, with the
wall clock in 2026. Rigor 251/251, full suite 853/791/0.
Two remaining clock/shape inconsistencies from the audit:
1. The direct fast path emitted a bare collected-value object
{value, source, relation, userKey, objectKey} — no possibility, no
path, no metadata. Value consumers (comparators, chains) rely on the
self-describing shape the rule paths emit. The fast path now emits
the standard shape (value/possibility/path/source/metadata), matching
DirectRule's existing _createCollectedValue contract.
2. Collected-value timestamps fell back to the WALL clock (Date.now())
even for pinned-clock callers in BaseRule._createCollectedValue,
ChainRule, and TupleToUsersetRule. The metadata timestamp now honors
options.now when pinned (changed_last_at wins, then pinned now, then
wall clock). ValueContext's collectedAt remains metadata-only.
Pinned by ttl-contract.test.js: the fast-path collected value carries
the full shape and its timestamp honors the pinned clock. Rigor 251/251,
full suite 853/791/0.
Three related findings from the nervous-item audit:
1. TTL contract pinned (ttl-contract.test.js + README): TTL is a
VALUE-FRESHNESS gate, not an access-expiry mechanism. Direct grants
are timeless; expired values deny comparators and drop from collected
values. The direct fast path collected values WITHOUT the TTL gate
(comparators skipped expired relations, the direct path did not) —
now gated identically.
2. ChainRule cache served pinned-clock callers (ChainRule.js): a chain
result captured at one time (with then-fresh values) was served to
callers asking about another time. The chain cache now bypasses
reads AND writes when options.now is pinned, matching the rule-result
cache contract.
3. Decision caches bundled stale values (AuthorizationChecker.js):
the direct-check cache stored collectedValues alongside the timeless
decision; an unpinned caller past wall-clock expiry got the stale
value. Value-carrying results are now never cached (the decision is
timeless, the values are not). The rule-result cache is unchanged —
it serves snapshots under explicit write-invalidation (its own
contract, asserted by cache-invalidation tests).
Rigor 250/250, full suite 852/790/0.
Adds an epistemic validity layer in the spirit of the zig-contour fusion
spec: every check result now carries a validity block {label, operator,
regime, sources, conflictMass, validifiedPossibility, nonMaxitive}.
- Relations accept a validity label (default heuristic = unlabeled input).
- Labels propagate through fusion: identity/max preserve the weakest
source label (max is already valid under arbitrary dependence); min
(conjunctive: intersection, chain, TTU, multi_hop, parent) is
approximate at best, surfaces the conflict mass (1 - possibility) that
was previously dropped, and exposes the arbitrary-regime validification
min(1, K*gamma); product-style operators (exclusion, defeasible) and
interior OWA averaging are always heuristic, with nonMaxitive flagged.
- Reliability and validity are now explicitly distinct: reliability stays
the scalar confidence adaptation; validity tracks the epistemic label.
- The hottest paths attach a shared frozen default block instead of
allocating (perf A/B shows no regression: ~300k ops/s direct both ways).
- Pre-existing fixes surfaced while wiring: the array-form logical config
dropped top-level aggregator/owaWeights (average union compiled as max),
and _createStandardResult dropped unknown fields (validity never
survived rule results).
New campaign validity-parity.test.js pins the label taxonomy, conflict
mass, validification, weakest-propagation, and the reliability/validity
separation. Suites: rigor 203/0, full 803/741/0.
Systemic reliability gap found by the probe sweep: the compiled evaluation
paths never emitted the reliability the engine computes.
- Compiled _evaluateDirect omitted the relation's reliability, and the
chain/multi_hop rules hardcoded reliability: 1.0 — so check() results
reported 1.0 for any rule whose decision came through a chain, multi_hop,
union, intersection, exclusion, or defeasible combination.
- The chain and multi_hop traversals now track per-path reliability (product
of edge reliabilities) and report the winning path's value; the compiled
and fallback logical operators (union/intersection/exclusion, direct_list
fast path, early exits) report the selected child's reliability
(max/min child or OWA trace index; exclusion multiplies both legs), and
normal-mode defeasible combines base x requires x defeater reliabilities.
- The checker's logical fast path dropped collectedValues from union/
intersection/exclusion results; it now passes them through.
- MultiHopRule.valueManager was read off relationManager where the real
arbiter keeps it on the arbiter — collectValues: true on a multi_hop rule
with a value-carrying edge crashed the evaluation (error result, silent
denial). Now resolved at the arbiter level with a relationManager
fallback for stubs.
Campaign pins: reliability per kind (chain/multi_hop product, union/intersection
selected child, exclusion/defeasible product), and multi_hop value collection
through persistent and partial contexts.