Commit Graph

16 Commits

Author SHA1 Message Date
John Dvorak ff6e52111d js-rigor: reliability crucibles across the campaigns; denied-decision leak fixed
The reliability gap found last round was invisible to every parity mirror
(they compared possibility only). Hardened the existing campaigns so the
mirrors carry reliability too:

- batch-order-parity: batch ops carry reliability; the mirror tracks
  last-write-wins reliability and the crucible asserts engine reliability
  parity (mirror corrected: add-on-existing preserves reliability, it does
  not reset it).
- rule-kind-partial-parity: the TTU differential property now generates
  per-edge reliabilities and asserts the winning intermediate's
  reliability (tupleset.reli * computed.reli); a new chain reliability
  differential property does the same for 2-step chains.
- snapshot-quantization-parity: edges carry deterministic reliabilities and
  the round-trip pins the codec's reliability channel (product-aware
  tolerance: chain reliability multiplies two quantized inputs).
- model-based-graph: the reference model tracks reliability per tuple and
  checks it alongside possibility for direct and chain queries.

The model crucible immediately caught a real bug: the direct-check fast
path returned the relation's reliability on a DENIED decision (possibility
0), while the rule-collection path zeroes it — denied results leaked
reliability. Both fast-path branches (direct match and threshold_not_met)
now report reliability 0 when the decision is denied.
2026-08-01 11:18:20 -07:00
John Dvorak 4fd4e20bd0 js-rigor: reliability flows through every rule kind; multi_hop value collection fixed
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.
2026-08-01 09:52:31 -07:00
John Dvorak f0dc14fb72 js-rigor: TTU operand values flow; modify honors changed_last_at override
Two issues found by the extended probe sweep:

- A relational_comparator operand backed by a tuple_to_userset rule always
  denied: the TTU rule collected only the intermediate KEY, which the
  operand extraction skips as non-numeric, so no value was ever available.
  The TTU rule now emits a value-carrying collected entry when the
  tupleset edge carries a numeric value (entityKey = tuple src, relation =
  tupleset relation), keeping the bare intermediate key when there is no
  value. Comparator-with-TTU-operand now allows/denies on the tuple value
  through both persistent and partial contexts.

- _modifyRelation ignored the changed_last_at override that the add path
  honors: value-changing modifies stamped fresh Date.now() regardless of
  the pin, so replay/restore tools pinning timestamps got different
  semantics via modify vs add. The override now applies to refresh events
  (value/reliability/possibility change) and is ignored for value-unchanged
  writes, preserving the TTL parity contract that identical replays never
  un-expire old values.

Verified clean: intersection through partial, defeasible with logical
when, challenge subject object/session with sessionKey, non-binary
minAllowPossibility threshold, batch value updates, explain agreement
under partial.
2026-08-01 08:08:36 -07:00
John Dvorak 02d7a5dd75 js-rigor: check() no longer strips reliability; TTU reliability pins
Bug 34: ArbiterChecks.check destructured { reliability, ...rest } out of
every normal (and binary) check result since the initial commit — the
engine computes reliability faithfully (TTU tupleset.reli * computed.reli,
direct relation reliability) and explain() preserved it, but the public
check() API silently dropped it. explain() kept it, so exposing it in
check() is the intended contract. The destructuring is removed; direct
and TTU checks now return reliability (0.6 / 0.72 in the pins).

Campaign pins added: TTU multi-path fusion (max over intermediates of
min(legs)) with reliability propagation through both persistent and
partial contexts, the maxIntermediates circuit breaker through partial,
and snapshot-restored arbiters evaluating TTU.
2026-07-31 23:52:48 -07:00
John Dvorak 30fc7e5017 js-rigor: TTU reverse+tuplesetDirection-in now honors direction
The reverse branch of TupleToUsersetRule ignored tuplesetDirection: it
looked up the tupleset relation as outgoing-from-user even when 'in' was
set, while the join honored 'in' by using t.src as the intermediate —
the documented shape (intermediates hold the relation TO the user) never
matched, and only a degenerate join-on-user shape produced results. The
same gap existed in the compiled direct_join optimization.

Fixed the fallback tupleset lookup (including the graph-neighbor path and
the computed-join tupleEdge direction) and the optimized direct_join to
honor tuplesetDirection in reverse mode. Matrix pins reverse-in (both
evaluation paths, persistent + partial), multi_hop reverse, chain-in,
union-with-chain-child, defeasible split legs, TTU value flow, and
challenge-via-binary.
2026-07-31 18:54:57 -07:00
John Dvorak 0149926344 js-rigor: fix TTU computed-join possibility loss and logical cache staleness
Two real bugs found by the new rule-kind x partial-graph parity campaign:

- TupleToUsersetRule 'computed' join mode (computed side has fewer
  intermediates than the tupleset side) pushed path objects carrying
  combinedPossibility, but _buildFinalResult reads path.possibility —
  every valid TTU grant in that mode silently returned 0, in persistent
  and partial contexts alike.

- _collectRelationUsages only registered explicit type:'direct' children,
  so shorthand logical operands ({ relation: 'owner' } inside union/
  intersection/exclusion) left the dependency index empty: writes to a
  base relation never invalidated cached logical decisions, and a check
  performed before an add kept serving its stale result forever.

New campaign rule-kind-partial-parity.test.js pins the full kind x
persistent/partial matrix (direct, chain, multi_hop, TTU out/in/reverse,
parent, computed, defeasible, union, exclusion, comparator, challenge,
binary) plus seeded differential properties for TTU, comparator, and
exclusion; artifact persistence disabled to avoid disk bloat.
2026-07-31 17:09:46 -07:00
John Dvorak ad9aa22225 js-rigor: partial-graph policy limits now enforce; binary-partial pins
- Arbiter constructor and setPartialGraphPolicy dropped maxNodes,
  maxRelations, and reservedRelations — the partial-graph context always
  saw the 1000/2000 defaults, silently disabling configured DoS guards.
  Both paths now carry the limits through; policy limits test pins
  constructor + setter enforcement and custom reservedRelations.
2026-07-31 15:34:45 -07:00
John Dvorak 8f863275c2 js-rigor: fix binary mode dropping partial graphs; binary-partial parity campaign
The binary branch of AuthorizationChecker.check rebuilt its options with a
fixed six-field object, silently discarding partialGraphContext (and any
other caller option) — binary checks denied grants the normal path
allowed. Now spreads all caller options through. binary-partial-parity
pins: partial grants above the threshold allow, below deny, persistent
wins over partial, and binary decisions agree with normal decisions on
the same overlay.
2026-07-31 15:31:34 -07:00
John Dvorak e12cc651d0 js-rigor: partial-graph overlay parity campaign
Pins the partial-graph contract: persistent-over-partial direct overlay,
layer-trust conflict precedence (token_projection > partial), registry-
gated per-relation reducers (strongest/weakest/first on listed relations;
unlisted relations reject the reducer with a first-kept fallback),
conflict_mode strict throws, reserved relations skipped, chain traversal
merging persistent+partial edges incl. partial-only temp nodes, and
read-only contexts (persistent state untouched).
2026-07-31 15:23:00 -07:00
John Dvorak 14f762d4c4 js-rigor: transitive dependency-index invalidation; multi_hop/parent usage tracking
- invalidateRuleResultCacheByRelation now walks the transitive closure of
  the dependency index (owner -> is_owner -> computed can_read): mutating
  a base relation left computed/multi_hop results stale
- _collectRelationUsages registers multi_hop and parent rule relations
  (parentRelation + subject relation) so their caches invalidate on
  base-relation mutations
- advanced-rule-kinds.test.js: multi_hop (min-over-path, depth-limited),
  computed (userset alias), parent (subject relation on the target's
  parent) through the full check() path under random mutations
2026-07-31 15:11:42 -07:00
John Dvorak 15e9728d00 js-rigor: tx atomicity (poison at any position rolls back), batch add validation, batch+PLTC
- _addRelationInternal now validates possibility like the single-add path
  (batch adds could store out-of-range values and make the transactional
  wrapper report success:true after a partial apply)
- tx-rollback-parity.test.js: poison at every batch position rolls back
  fully; clean batches apply in order; batch+PLTC reachability reflects
  batch writes immediately
2026-07-31 15:02:12 -07:00
John Dvorak 5c7ec95344 js-rigor: extend fuzzer campaign to 6 config kinds (intersection, defeasible)
Confirms min-intersection and when*unless defeasible semantics hold under
fuzzed config transitions and mutation bursts.
2026-07-31 14:27:13 -07:00
John Dvorak f5690d4777 js-rigor: fix batch operation ordering (last-write-wins), batch-order campaign
updateRelationsBatch previously pre-sorted ops remove->modify->add, which
changed the final state whenever one tuple was touched by mixed kinds:
[add, remove] left the tuple present, [modify, add, modify] ended with
the middle value. Now ops apply strictly in the given order via the
dedup-aware _addRelationInternal (in-place last-write-wins) with upfront
validation, post-batch PLTC edge updates, and per-relation arbiter-level
cache invalidation.

batch-order-parity.test.js pins the contract with an in-order mirror
(modify-of-missing is a silent no-op).
2026-07-31 14:25:07 -07:00
John Dvorak 7ffa5045e6 js-rigor: batch cache staleness, tx-batch crash, TTL bypass; value freshness campaigns
Engine fixes:
- RelationUpdates.updateRelationsBatch: invalidate arbiter-level caches
  (rule result cache, ChainRule caches, direct-check cache) per affected
  relation — batch updates bypassed Arbiter.addRelation and served stale
  decisions after batch modify/swap with warm caches
- updateRelationsBatchTransactional rollback: new Map(Set) crashed with
  'Iterator value is not an entry object' — fixed to new Set
- RelationalComparatorRule: value extraction (direct-list and cached
  direct paths) now gates on valueManager._isValueExpired — TTL-expired
  values no longer feed comparator decisions

Campaigns:
- value-freshness-parity.test.js: batch modify/swap/tx rollback freshness
  with comparator mirror (batch MODIFY of a missing relation is a silent
  no-op — pinned)
- ttl-expiry-parity.test.js: injected-clock TTL expiry through the
  comparator path (exact parity with caching off; bounded staleness with
  caching on), faithful changed_last_at mirror semantics
2026-07-31 14:21:22 -07:00
John Dvorak 2de5faa7c9 js-rigor: greybox fuzzer campaign, config validation, lifecycle positional gates
- fuzzer-mutation-needles.test.js: first greybox-fuzzer campaign (mutation
  + replay-near-failure strategies) hunting cache staleness across config
  kind transitions; pins canonical config shapes ({union:{rules}},
  {exclusion:[a,b]} with probabilistic P(A)*(1-P(B)) semantics) and a
  mirror faithful to the engine's phantom-node no-op rule
- ArbiterConfig.setRelationConfig: clean validation error for non-object
  configs (was cryptic internal TypeError under fuzzed args)
- protocol-snapshot-lifecycle.test.js: beforeStep/afterStep positional
  gates (virgin state, mirror-engine tuple sync)
- dropped complexity-verification attempt: wall-clock e-process too noisy
  at sub-ms operation scale
2026-07-31 13:59:02 -07:00
John Dvorak 717ae1031e initial commit: @arbiter/core authorization engine with js-rigor hardening
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/
binary modes, condensed snapshots, value relations) with 39 rigor test
campaigns. Includes fixes for snapshot binary writer/reader format
mismatch (snapshot-of-snapshot corruption), possibility write-boundary
validation, empty-graph snapshot serialization, relation lookup cache
direction collision, config-redefinition cache invalidation, binary
threshold semantics, defeasible compiled routing, and comparator
reason whitelisting.
2026-07-31 13:44:06 -07:00