feat: intermediate chain condition steps, graph-version cache invalidation, rolling-hash chain keys; fix vacuous rigor invariants
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.
This commit is contained in:
@@ -103,7 +103,7 @@ function makeOracle() {
|
||||
const rel = opRel !== undefined ? opRel : r.rel;
|
||||
const directKey = key(srcId, rel, dstId);
|
||||
const stored = direct.get(directKey);
|
||||
if (!stored) return;
|
||||
if (!stored) return { skipped: true };
|
||||
direct.delete(directKey);
|
||||
const srcRel = key(stored.src, stored.rel);
|
||||
const dstRel = key(stored.dst, stored.rel);
|
||||
@@ -180,7 +180,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
const report = await rigor.campaign(
|
||||
[rigor.fn('check', check, rigor.args(opGen))],
|
||||
rigor.crucible([
|
||||
rigor.invariant('getDirectRelation-matches-oracle', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('getDirectRelation-matches-oracle', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 1500, seed: 'graph-indices-direct-a' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
@@ -243,7 +243,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
const report = await rigor.campaign(
|
||||
[rigor.fn('check', check, rigor.args(opGen))],
|
||||
rigor.crucible([
|
||||
rigor.invariant('getRelationsFromSrc-matches-oracle', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('getRelationsFromSrc-matches-oracle', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 1500, seed: 'graph-indices-direct-b' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
@@ -303,7 +303,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
const report = await rigor.campaign(
|
||||
[rigor.fn('check', check, rigor.args(opGen))],
|
||||
rigor.crucible([
|
||||
rigor.invariant('getRelationsToDst-matches-oracle', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('getRelationsToDst-matches-oracle', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 1500, seed: 'graph-indices-direct-c' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
@@ -363,7 +363,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
const report = await rigor.campaign(
|
||||
[rigor.fn('check', check, rigor.args(opGen))],
|
||||
rigor.crucible([
|
||||
rigor.invariant('getRelationsByName-matches-oracle', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('getRelationsByName-matches-oracle', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 1500, seed: 'graph-indices-direct-d' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
@@ -419,7 +419,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
)
|
||||
)],
|
||||
rigor.crucible([
|
||||
rigor.invariant('addRelation-tuple-idempotent', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('addRelation-tuple-idempotent', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 800, seed: 'graph-indices-src' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
@@ -457,7 +457,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
const report = await rigor.campaign(
|
||||
[rigor.fn('check', check, rigor.args(relGen))],
|
||||
rigor.crucible([
|
||||
rigor.invariant('addRelation-idempotent', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('addRelation-idempotent', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 800, seed: 'graph-indices-dst' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
@@ -500,7 +500,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
const report = await rigor.campaign(
|
||||
[rigor.fn('check', check, rigor.args(relGen))],
|
||||
rigor.crucible([
|
||||
rigor.invariant('clear-empties-indexes', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('clear-empties-indexes', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 800, seed: 'graph-indices-name' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
@@ -547,7 +547,7 @@ describe('GraphIndices indexes (rigor)', () => {
|
||||
const report = await rigor.campaign(
|
||||
[rigor.fn('check', check, rigor.args(relGen))],
|
||||
rigor.crucible([
|
||||
rigor.invariant('add-remove-cycle', ({ error, errorMessage }) => !error && !errorMessage)
|
||||
rigor.invariant('add-remove-cycle', ({ actual }) => actual !== undefined)
|
||||
])
|
||||
).run({ effort: 800, seed: 'graph-indices-cycle' , artifacts: { dir: '', persist: 'never' }});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user