Commit Graph

6 Commits

Author SHA1 Message Date
John Dvorak ed34df4474 feat: intermediate chain condition steps, graph-version cache invalidation, rolling-hash chain keys; fix vacuous rigor invariants
CI / benchmark (push) Successful in 48s
CI / test (push) Successful in 5m26s
CI / publish (push) Has been skipped
Chain intermediates (rule-based reachability):
- ChainRule: a condition step ({ rule, conditionStep }) at an INTERMEDIATE
  position is now EXPANDED from the current node — the rule's base edges'
  destinations, filtered by its defeaters/requirements — and traversal
  continues from each discovered node. Adds _expandRuleFromSrc / direct /
  logical(union/intersection) / defeasible / nested-chain expansion.
- RuleEvaluator: _subjectIsObject flag for unary predicate calls whose subject
  entity IS the object parameter (trusted(other) inside peer_trusted(user,
  other)); previously only subject-var unary calls (_subjectAsObject) were
  handled, so object-var unary defeaters never fired.

Graph-version cache invalidation:
- Arbiter gains a monotonic _graphVersion, incremented on every relation
  mutation. ChainRule result cache, RuleEvaluator rule-result cache, and
  DecisionCache rule cache now stamp entries with the graph version and treat
  any mismatch as a miss — graph mutations can no longer serve stale
  chain/authorization results.

Rolling-hash cache keys:
- UnifiedKeyManager.createChainKey now builds a 53-bit rolling hash (dual
  FNV-1a lanes, exact for ints/floats/strings/nested configs) instead of
  JSON.stringify — no string allocation or serialization on the chain-cache
  hot path. Composite keys stay structured strings because the direct-check
  cache pattern-invalidates by relation ID.

Rigor invariant migration (correctness):
- All 43 rigor test files' throw-based invariants ({ error, errorMessage } =>
  !error && !errorMessage) never saw fn throws — vacuous. Migrated to
  ({ actual }) => actual !== undefined, which fails on any thrown violation
  while passing legitimate null-skips. The migration immediately surfaced
  two latent bugs, now fixed:
    * node-manager/graph-indices skip paths returned bare undefined (falsy
      sentinel) — return { skipped: true }.
    * complex-graph-values-crucible expiry section rewrote values equal to the
      mutation loop's last write; the engine (by design) keeps the old
      timestamp on same-value rewrites so the pre-expiry grant never
      materialized. Now writes guaranteed-different values.
2026-08-03 13:26:42 -07:00
John Dvorak 0cb0d7c8cb js-rigor: the partial graph carries the temporal context
The partial graph is the caller's self-contained view of the world — so
the time belongs in it. partialGraph.now becomes the evaluation clock for
that check (TTL gates, proof expiry, decay all honor it), with an
explicit options.now taking precedence. The explain rerun replays the
SAME partial graph object and reproduces the original decision; the
serializer records request.temporal.now for the record.

Pins: partialGraph.now driving a challenge decision both sides of the
window, the explicit-override precedence, and the explain rerun with the
same object.
2026-08-02 10:58:49 -07:00
John Dvorak f1167d19fc js-rigor: re-entrant temporal diagnostics — pinned-clock checks + explain replay
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.
2026-08-02 10:51:50 -07:00
John Dvorak faa6485e26 js-rigor: lossless persistence — validity, decay config, and TTLs survive snapshots
The persistence probe found three silent-loss defects in the snapshot
round trip: relation validity labels (finite_sample downgraded to
heuristic after save/load!), decay configs, and the value manager's
per-relation TTL settings all vanished. The restored arbiter built its
indices directly from the condensed graph, whose edge channel carries
possibility/reliability/value only.

Format version 2 now carries per-relation metadata (validity, decayConfig)
and the valueTtls table in the snapshot payload; both the snapshot-access
layer and the CondensedGraphIndices build merge the metadata back, so a
restored arbiter is lossless end to end. Version 1 buffers are rejected
with the existing clean version error.

Pinned: a persistence round-trip test asserting validity label, decay
config, TTL, and the restored check's validity label. Suites: rigor
207/0, full 809/747/0.
2026-08-02 09:51:40 -07:00
John Dvorak 86729715f1 js-rigor: security affordances — gated provenance, audit hook, DoS hardening, explicit semantics
Per the trust-boundary direction (the caller owns evidence validation):

- Explicit possibilistic semantics module (src/core/possibility.js): the
  single authoritative home for what each operator means (max = disjunctive
  already-valid; min = unvalidified conjunctive ranking with the K-
  validification and surfaced conflict mass; product = Thm-4 heuristic;
  interior OWA = non-maxitive heuristic; reliability = adaptation, never
  conflated with plausibility).
- Provenance is opt-in (re-entrant tracing practice): default check
  results carry only {label, operator, regime}; conflictMass,
  validifiedPossibility, sources, and nonMaxitive appear only under
  includeMeta and on the explain surface. The direct-check cache now
  caches only the meta-less form — includeMeta callers always get a fresh
  full evaluation (previously a cached minimal result was served for
  includeMeta requests, silently stripping detail).
- Audit affordance: new Arbiter({ audit }) emits one record per check
  (decision, possibility, binary, partialGraphUsed, validityLabel,
  sources). The engine stores nothing — the caller owns persistence;
  zero cost when the hook is absent (and the full validity is forced only
  on audit-enabled deployments).
- DoS hardening: partial-graph size limits are enforced BEFORE the
  context allocation (the caller-supplied overlay is the per-check
  allocation point); the CondensedGraphBinary reader gained full bounds
  guards so malformed snapshot buffers fail with clean errors instead of
  RangeError crashes or oversized allocations.
- New security-affordance pins: gating, audit records, and pre-allocation
  limits.
2026-08-02 09:28:57 -07:00
John Dvorak 58e8b0e030 js-rigor: possibilistic validity layer (Cella FVN labels, conflict mass, validification)
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.
2026-08-02 08:57:05 -07:00