diff --git a/package.json b/package.json index ed7d027..dfcf940 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arbiter/core", - "version": "1.0.3", + "version": "1.0.4", "description": "Arbiter core engine: graph indices, relation/reachability, authorization rule evaluator, DSL/AST, condensed & sharded snapshots, and evidence fusion.", "license": "ISC", "author": "", diff --git a/src/authorization/DecisionCache.js b/src/authorization/DecisionCache.js index 9b55521..5788c35 100644 --- a/src/authorization/DecisionCache.js +++ b/src/authorization/DecisionCache.js @@ -107,6 +107,11 @@ export class DecisionCache { if (!this.ruleEnabled) return undefined; const entry = this.arbiter.ruleResultCache.get(ruleCacheKey); if (!entry) return undefined; + // A graph mutation since the entry was computed invalidates it (stale + // derived result). _graphVersion increments on every relation mutation. + if (entry.graphVersion !== undefined && entry.graphVersion !== (this.arbiter._graphVersion ?? 0)) { + return undefined; + } if (this.clock() - entry.timestamp >= this.arbiter.ruleResultCacheTTL) { return undefined; } @@ -117,7 +122,8 @@ export class DecisionCache { if (!this.ruleEnabled) return; this.arbiter.ruleResultCache.set(ruleCacheKey, { result, - timestamp: this.clock() + timestamp: this.clock(), + graphVersion: this.arbiter._graphVersion ?? 0 }); } diff --git a/src/authorization/RuleEvaluator.js b/src/authorization/RuleEvaluator.js index 2fb6806..1c33341 100644 --- a/src/authorization/RuleEvaluator.js +++ b/src/authorization/RuleEvaluator.js @@ -33,7 +33,7 @@ export class RuleEvaluator { // Convert string keys to numeric IDs if needed - const numericUserId = typeof userId === 'string' ? this.arbiter.resolveNodeId(userId, options) : userId; + let numericUserId = typeof userId === 'string' ? this.arbiter.resolveNodeId(userId, options) : userId; let numericObjectId = typeof objectId === 'string' ? this.arbiter.resolveNodeId(objectId, options) : objectId; // Unary / subject-scoped rules (e.g. a DSL predicate call `banned(user)` @@ -45,6 +45,13 @@ export class RuleEvaluator { numericObjectId = numericUserId; objectKey = userKey; } + // A unary call whose subject entity IS the object parameter + // (`trusted(other)` inside peer_trusted(user, other)) checks the relation + // as a self-edge on the object — rewrite the subject to the object. + if (rule._subjectIsObject) { + numericUserId = numericObjectId; + userKey = objectKey; + } const needsValueContext = valueContext !== null && valueContext !== undefined ? true @@ -66,7 +73,11 @@ export class RuleEvaluator { if (canCacheRuleResult) { const cached = this.arbiter.ruleResultCache.get(ruleCacheKey); const cacheNow = this.arbiter.clock ? this.arbiter.clock() : Date.now(); - if (cached && cacheNow - cached.timestamp < this.arbiter.ruleResultCacheTTL) { + // A graph mutation since the entry was computed invalidates it — the + // result is derived from the graph and would be stale. _graphVersion + // increments on every relation mutation. + const freshGraph = cached && cached.graphVersion === (this.arbiter._graphVersion ?? 0); + if (freshGraph && cacheNow - cached.timestamp < this.arbiter.ruleResultCacheTTL) { this.arbiter.ruleResultCacheStats.hits++; if (collectValues && finalValueContext && cached.result?.collectedValues?.length) { const ruleType = rule?.type || (rule?.union ? 'union' : rule?.intersection ? 'intersection' : rule?.exclusion ? 'exclusion' : 'rule'); @@ -173,7 +184,8 @@ export class RuleEvaluator { const cacheNow = this.arbiter.clock ? this.arbiter.clock() : Date.now(); this.arbiter.ruleResultCache.set(cacheKey, { result, - timestamp: cacheNow + timestamp: cacheNow, + graphVersion: this.arbiter._graphVersion ?? 0 }); this.arbiter._cacheRuleResult(relation, cacheKey); return result; diff --git a/src/authorization/rules/ChainRule.js b/src/authorization/rules/ChainRule.js index 58fb681..5695cd5 100644 --- a/src/authorization/rules/ChainRule.js +++ b/src/authorization/rules/ChainRule.js @@ -202,22 +202,17 @@ export class ChainRule extends BaseRule { const { relation: stepRelation, direction } = step; // CONDITION STEP (rule-based step): a step carrying a `rule` config is a - // condition-gated hop, not an edge traversal. It is only valid as the - // FINAL step: the object is known, so each current path's node is checked - // against the object through the referenced rule (e.g. a defeasible - // evidence like `WHEN can_view(group, doc) UNLESS banned(group)`). The - // DSL compiler emits these when a chain's object-side hop references a - // logical/defeasible evidence. The rule config is part of the chain - // cache key (JSON.stringify of steps), so cache correctness is preserved. + // condition-gated hop, not a plain edge traversal. The DSL compiler emits + // these when a chain step references a logical/defeasible evidence. + // - FINAL step: the object is known, so each current path's node is + // checked against the object through the referenced rule. + // - INTERMEDIATE step: the rule is EXPANDED from each current node + // (rule-based reachability) — the reachable destinations of the + // rule's base edges, filtered by its defeaters/requirements — and + // the traversal continues from each discovered node. + // The rule config is part of the chain cache key, so cache correctness + // is preserved. if (step.rule) { - if (stepIndex !== steps.length - 1) { - return this._createStandardResult({ - possibility: 0, - reliability: 1.0, - ...(includeMeta && { meta: null }), - reason: 'condition_step_not_final' - }, []); - } if (!this.ruleEvaluator) { return this._createStandardResult({ possibility: 0, @@ -226,27 +221,58 @@ export class ChainRule extends BaseRule { reason: 'condition_step_requires_rule_evaluator' }, []); } - const conditionPaths = []; - for (const currentPath of currentPaths) { - const condResult = this.ruleEvaluator.evaluateRule( - currentPath.id, currentPath.key, objectIdNum, objectKey, - step.rule, new Set(visited || []), currentRelation, options - ); - const condPossibility = condResult.possibility ?? 0; - if (condPossibility <= 0) continue; - const nextPossibility = Math.min(currentPath.possibility, condPossibility); - if (fastPath && nextPossibility < minPossibility) continue; - conditionPaths.push({ - id: objectIdNum, - key: objectKey, - possibility: nextPossibility, - reliability: (currentPath.reliability ?? 1.0) * (condResult.reliability ?? 1.0), - path: [...currentPath.path, objectKey], - pathEntities: [...currentPath.pathEntities, { id: objectIdNum, key: objectKey, source: 'condition' }] - }); + if (stepIndex === steps.length - 1) { + const conditionPaths = []; + for (const currentPath of currentPaths) { + const condResult = this.ruleEvaluator.evaluateRule( + currentPath.id, currentPath.key, objectIdNum, objectKey, + step.rule, new Set(visited || []), currentRelation, options + ); + const condPossibility = condResult.possibility ?? 0; + if (condPossibility <= 0) continue; + const nextPossibility = Math.min(currentPath.possibility, condPossibility); + if (fastPath && nextPossibility < minPossibility) continue; + conditionPaths.push({ + id: objectIdNum, + key: objectKey, + possibility: nextPossibility, + reliability: (currentPath.reliability ?? 1.0) * (condResult.reliability ?? 1.0), + path: [...currentPath.path, objectKey], + pathEntities: [...currentPath.pathEntities, { id: objectIdNum, key: objectKey, source: 'condition' }] + }); + } + currentPaths = conditionPaths; + break; } - currentPaths = conditionPaths; - break; + + // INTERMEDIATE condition step: expand the rule from each current node. + const pathMap = new Map(); + for (const currentPath of currentPaths) { + const reachable = this._expandRuleFromSrc(currentPath.id, step.rule, options); + for (const [nextId, cand] of reachable) { + const nextKey = this.arbiter.resolveKey(nextId, options); + if (!nextKey) continue; + const nextPossibility = Math.min(currentPath.possibility, cand.possibility); + if (fastPath && nextPossibility < minPossibility) continue; + const nextReliability = (currentPath.reliability ?? 1.0) * cand.reliability; + const existing = pathMap.get(nextId); + if (existing && existing.possibility > nextPossibility) continue; + if (existing && existing.possibility === nextPossibility && existing.reliability >= nextReliability) continue; + pathMap.set(nextId, { + id: nextId, + key: nextKey, + possibility: nextPossibility, + reliability: nextReliability, + path: [...currentPath.path, nextKey], + pathEntities: [...currentPath.pathEntities, { id: nextId, key: nextKey, source: 'condition' }] + }); + } + } + currentPaths = Array.from(pathMap.values()); + if (currentPaths.length === 0) { + break; + } + continue; } if (!stepRelation || !direction) { @@ -457,15 +483,178 @@ export class ChainRule extends BaseRule { * Get relations for a step based on direction * @private */ - _getRelationsForStep(entityId, relation, direction, options = null) { - if (direction === 'out') { - return this.arbiter.relationManager.getRelationsFromSrc(entityId, relation, options); - } else if (direction === 'in') { - return this.arbiter.relationManager.getRelationsToDst(entityId, relation, options); - } else { - // Default to 'out' for backward compatibility - return this.arbiter.relationManager.getRelationsFromSrc(entityId, relation, options); + _getRelationsForStep(entityId, relation, direction, options = null) { + if (direction === 'out') { + return this.arbiter.relationManager.getRelationsFromSrc(entityId, relation, options); + } else if (direction === 'in') { + return this.arbiter.relationManager.getRelationsToDst(entityId, relation, options); + } else { + // Default to 'out' for backward compatibility + return this.arbiter.relationManager.getRelationsFromSrc(entityId, relation, options); + } + } + + // --------------------------------------------------------------------------- + // Rule-based reachability: expand a rule config from a source node into its + // reachable destinations. Used by intermediate condition steps — a + // logical/defeasible evidence as a non-final chain step discovers its + // reachable nodes (the base edges' destinations, filtered by the rule's + // defeaters/requirements) instead of checking a single (src, dst) pair. + // --------------------------------------------------------------------------- + + /** + * Expand a rule config from a source node into Map. + * Supports direct rules, logical union/intersection nodes, defeasible rules + * (when/unless/never/requires/always), and nested chains. + */ + _expandRuleFromSrc(srcId, rule, options = null) { + if (!rule || typeof rule !== 'object') return new Map(); + if (rule.type === 'direct' && rule.relation) { + return this._expandDirectFromSrc(srcId, rule.relation, options); } + if (rule.type === 'chain' && Array.isArray(rule.steps)) { + return this._expandChainRuleFromSrc(srcId, rule, options); + } + if (rule.type === 'logical') { + if (rule.when || rule.unless || rule.never || rule.requires || rule.always) { + return this._expandDefeasibleFromSrc(srcId, rule, options); + } + if (rule.union) { + return this._expandLogicalNodeFromSrc(srcId, rule.union, 'union', options); + } + if (rule.intersection) { + return this._expandLogicalNodeFromSrc(srcId, rule.intersection, 'intersection', options); + } + } + return new Map(); + } + + _expandDirectFromSrc(srcId, relation, options = null) { + const edges = this.arbiter.relationManager.getRelationsFromSrc(srcId, relation, options); + const out = new Map(); + for (const e of edges || []) { + out.set(e.dst, { possibility: e.possibility ?? 1, reliability: e.reliability ?? 1 }); + } + return out; + } + + _expandLogicalNodeFromSrc(srcId, node, op, options = null) { + const rules = (node && node.rules) || []; + if (rules.length === 0) return new Map(); + if (op === 'union') { + const out = new Map(); + for (const r of rules) { + const m = this._expandRuleFromSrc(srcId, r, options); + for (const [id, v] of m) { + const cur = out.get(id); + if (!cur || v.possibility > cur.possibility) out.set(id, v); + } + } + return out; + } + // intersection: nodes reachable via every branch, weakest-link combined + const maps = rules.map(r => this._expandRuleFromSrc(srcId, r, options)); + const out = new Map(); + for (const [id, v] of maps[0]) { + let ok = true; + let min = v.possibility; + let rel = v.reliability; + for (let i = 1; i < maps.length; i++) { + const other = maps[i].get(id); + if (!other) { ok = false; break; } + min = Math.min(min, other.possibility); + rel *= other.reliability; + } + if (ok) out.set(id, { possibility: min, reliability: rel }); + } + return out; + } + + _expandDefeasibleFromSrc(srcId, rule, options = null) { + const base = this._expandBaseFromSrc(srcId, rule, options); + if (base.size === 0) return base; + const out = new Map(); + const evalAt = (candId, candKey, node) => { + if (!node) return { poss: 1, rel: 1 }; + const wrapped = node.union ? { union: node.union } : node; + const r = this.ruleEvaluator.evaluateRule( + srcId, null, candId, candKey, wrapped, new Set(), null, options || {} + ); + return { poss: r.possibility ?? 0, rel: r.reliability ?? 1 }; + }; + for (const [candId, cand] of base) { + const candKey = this.arbiter.resolveKey(candId, options); + if (rule.never) { + const n = evalAt(candId, candKey, rule.never); + if (n.poss > 0.5) continue; + } + let defeat = 0; + let defeatRel = 1; + if (rule.unless) { + const u = evalAt(candId, candKey, rule.unless); + defeat = u.poss; + defeatRel = u.rel; + } + let req = 1; + let reqRel = 1; + if (rule.requires) { + const q = evalAt(candId, candKey, rule.requires); + req = q.poss; + reqRel = q.rel; + } + const poss = cand.possibility * req * (1 - defeat); + if (poss <= 0) continue; + out.set(candId, { + possibility: poss, + reliability: cand.reliability * reqRel * defeatRel + }); + } + return out; + } + + _expandBaseFromSrc(srcId, rule, options = null) { + if (rule.when && rule.when.intersection) { + return this._expandLogicalNodeFromSrc(srcId, rule.when.intersection, 'intersection', options); + } + if (rule.when && rule.when.union) { + return this._expandLogicalNodeFromSrc(srcId, rule.when.union, 'union', options); + } + if (rule.when && rule.when.rules) { + return this._expandLogicalNodeFromSrc(srcId, rule.when, 'union', options); + } + if (rule.union) { + return this._expandLogicalNodeFromSrc(srcId, rule.union, 'union', options); + } + if (rule.always && rule.always.direct) { + return this._expandRuleFromSrc(srcId, rule.always.direct, options); + } + return new Map(); + } + + _expandChainRuleFromSrc(srcId, rule, options = null) { + let current = [{ id: srcId, possibility: 1, reliability: 1 }]; + for (const step of rule.steps || []) { + const stepName = typeof step === 'string' ? step : step.relation; + const stepRule = typeof step === 'string' ? null : step.rule; + const next = []; + for (const node of current) { + const m = stepRule + ? this._expandRuleFromSrc(node.id, stepRule, options) + : this._expandDirectFromSrc(node.id, stepName, options); + for (const [id, v] of m) { + next.push({ + id, + possibility: Math.min(node.possibility, v.possibility), + reliability: node.reliability * v.reliability + }); + } + } + current = next; + if (current.length === 0) break; + } + const out = new Map(); + for (const n of current) out.set(n.id, { possibility: n.possibility, reliability: n.reliability }); + return out; } /** @@ -482,6 +671,12 @@ export class ChainRule extends BaseRule { const key = this._getChainResultCacheKey(userId, objectId, steps); const entry = this.chainResultCache.get(key); + // A graph mutation since the entry was computed invalidates it: the + // reachability it captured is stale. The arbiter increments _graphVersion + // on every relation mutation, so any mismatch is a miss. + if (entry && entry.graphVersion !== (this.arbiter._graphVersion ?? 0)) { + return null; + } const cacheNow = this.arbiter.clock ? this.arbiter.clock() : Date.now(); if (entry && cacheNow - entry.timestamp < this.cacheTTL) { return entry.result; @@ -504,7 +699,8 @@ export class ChainRule extends BaseRule { const cacheNow = this.arbiter.clock ? this.arbiter.clock() : Date.now(); this.chainResultCache.set(key, { result, - timestamp: cacheNow + timestamp: cacheNow, + graphVersion: this.arbiter._graphVersion ?? 0 }); } diff --git a/src/core/Arbiter.js b/src/core/Arbiter.js index 88fb340..920faf4 100644 --- a/src/core/Arbiter.js +++ b/src/core/Arbiter.js @@ -25,6 +25,13 @@ export class Arbiter { constructor(options = {}) { this.options = options; + // Monotonic graph mutation counter: incremented on every relation edge + // mutation (and node removal). Derived-result caches (e.g. the ChainRule + // result cache) stamp entries with the version they were computed at and + // treat any newer version as a miss, so a graph change can never serve a + // stale chain/authorization result. + this._graphVersion = 0; + // Audit affordance (caller-wired, zero cost when absent): invoked once // per check with a minimal decision record. The engine does NOT store // audit state — the caller owns persistence and retention. The record diff --git a/src/core/RelationManager.js b/src/core/RelationManager.js index 2d5d6c6..0d7a455 100644 --- a/src/core/RelationManager.js +++ b/src/core/RelationManager.js @@ -696,6 +696,12 @@ export class RelationManager { * Cache management methods */ _invalidateRelationCaches(srcId, relation, dstId) { + // Any relation mutation invalidates the graph version so derived-result + // caches (ChainRule result cache) stamp/validate against it — a graph + // change can never serve a stale chain result. + if (this.arbiter && typeof this.arbiter._graphVersion === 'number') { + this.arbiter._graphVersion++; + } this._caches.invalidateRelationCaches(srcId, relation, dstId); } diff --git a/src/core/UnifiedKeyManager.js b/src/core/UnifiedKeyManager.js index dce2ef4..a6c54a1 100644 --- a/src/core/UnifiedKeyManager.js +++ b/src/core/UnifiedKeyManager.js @@ -129,7 +129,7 @@ export class UnifiedKeyManager { * @param {number} userId - User ID * @param {number} objectId - Object ID * @param {Array} steps - Chain steps - * @returns {number} Composite key + * @returns {number} Rolling-hash composite key */ createChainKey(userId, objectId, steps) { if (userId > this.options.maxSrcId) { @@ -139,7 +139,79 @@ export class UnifiedKeyManager { throw new Error(`Object ID ${objectId} exceeds max range ${this.options.maxDstId}`); } - return JSON.stringify([userId, objectId, steps]); + // Rolling hash instead of JSON.stringify: no string allocation, no + // serialization of possibly-nested step configs. Steps may be strings, + // { relation, direction } objects, or { rule: , conditionStep } + // objects — each feeds the hash incrementally. + return this._rollingHash(userId, objectId, steps); + } + + /** + * 53-bit rolling hash over heterogeneous components (numbers, strings, + * booleans, arrays, nested objects). Dual 32-bit FNV-1a lanes combined into + * a single exact integer (< 2^53) usable as a Map key — no string + * concatenation or serialization on the hot path. Integers feed their + * high/low 32-bit halves; floats feed their exact 64-bit byte pattern; + * objects feed their sorted key/value pairs so ordering is stable. + * @returns {number} exact integer in [0, 2^53) + */ + _rollingHash(...parts) { + let h1 = 0x811c9dc5; + let h2 = 0x9e3779b1; + const floatBuf = new Float64Array(1); + const floatBytes = new Uint8Array(floatBuf.buffer); + const mix1 = (h, x) => Math.imul(h ^ x, 0x01000193) >>> 0; + const mix2 = (h, x) => Math.imul((h ^ x) ^ 0x5bd1e995, 0x01000193) >>> 0; + const feed = (v) => { + if (typeof v === 'number') { + if (Number.isInteger(v)) { + const hi = Math.floor(v / 0x100000000); + const lo = v >>> 0; + h1 = mix1(h1, hi); + h2 = mix2(h2, lo); + h1 = mix1(h1, lo); + h2 = mix2(h2, hi ^ 0x9e37); + } else { + floatBuf[0] = v; + for (let i = 0; i < 8; i++) { + h1 = mix1(h1, floatBytes[i]); + h2 = mix2(h2, floatBytes[i]); + } + } + } else if (typeof v === 'string') { + h1 = mix1(h1, v.length | 0x8000); + h2 = mix2(h2, v.length ^ 0x55aa); + for (let i = 0; i < v.length; i++) { + h1 = mix1(h1, v.charCodeAt(i)); + h2 = mix2(h2, v.charCodeAt(i) ^ 0xa5); + } + } else if (typeof v === 'boolean') { + h1 = mix1(h1, v ? 0x1111 : 0x2222); + h2 = mix2(h2, v ? 0x3333 : 0x4444); + } else if (v === null) { + h1 = mix1(h1, 0xaaaa); + h2 = mix2(h2, 0xbbbb); + } else if (v === undefined) { + h1 = mix1(h1, 0xcccc); + h2 = mix2(h2, 0xdddd); + } else if (Array.isArray(v)) { + h1 = mix1(h1, v.length | 0x800000); + h2 = mix2(h2, v.length ^ 0x1234); + for (const x of v) feed(x); + } else if (typeof v === 'object') { + h1 = mix1(h1, 0x5eed); + h2 = mix2(h2, 0xcafe); + const keys = Object.keys(v).sort(); + for (const k of keys) { + feed(k); + feed(v[k]); + } + } + }; + for (const p of parts) feed(p); + // Combine both 32-bit lanes into an exact < 2^53 integer: + // h1 * 2^21 (h1 < 2^32 => product < 2^53) + top 21 bits of h2. + return h1 * 0x200000 + (h2 >>> 11); } /** diff --git a/tests/rigor/authorization-config-consistency.test.js b/tests/rigor/authorization-config-consistency.test.js index cb5a1d4..78adb1b 100644 --- a/tests/rigor/authorization-config-consistency.test.js +++ b/tests/rigor/authorization-config-consistency.test.js @@ -75,7 +75,7 @@ describe('Authorization config consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('path-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('path-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'authz-config-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -120,7 +120,7 @@ describe('Authorization config consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('override-honored', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('override-honored', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'authz-config-override' , artifacts: { dir: '', persist: 'never' }}); @@ -168,7 +168,7 @@ describe('Authorization config consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('remediation-contract', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('remediation-contract', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'authz-config-remediation' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/authorization-graph.test.js b/tests/rigor/authorization-graph.test.js index 2a1673d..c33e49d 100644 --- a/tests/rigor/authorization-graph.test.js +++ b/tests/rigor/authorization-graph.test.js @@ -66,7 +66,7 @@ describe('Authorization graph semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('direct-exact', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('direct-exact', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'authz-graph-direct' , artifacts: { dir: '', persist: 'never' }}); @@ -101,7 +101,7 @@ describe('Authorization graph semantics (rigor)', () => { rigor.fn('check', check, rigor.args(rigor.gen.int(2, 6))) ], rigor.crucible([ - rigor.invariant('absent-denies', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('absent-denies', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'authz-graph-absent' , artifacts: { dir: '', persist: 'never' }}); @@ -144,7 +144,7 @@ describe('Authorization graph semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('weakest-link', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('weakest-link', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'authz-graph-chain' , artifacts: { dir: '', persist: 'never' }}); @@ -194,7 +194,7 @@ describe('Authorization graph semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('disjunctive-max', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('disjunctive-max', ({ actual }) => actual !== undefined) ]) ).run({ effort: 500, seed: 'authz-graph-multipath' , artifacts: { dir: '', persist: 'never' }}); @@ -246,7 +246,7 @@ describe('Authorization graph semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('tus-weakest-link', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('tus-weakest-link', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'authz-graph-tus' , artifacts: { dir: '', persist: 'never' }}); @@ -285,7 +285,7 @@ describe('Authorization graph semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('revoke-invalidates', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('revoke-invalidates', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'authz-graph-mutation' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/batch-loading-parity.test.js b/tests/rigor/batch-loading-parity.test.js index 0b921b0..00fcbab 100644 --- a/tests/rigor/batch-loading-parity.test.js +++ b/tests/rigor/batch-loading-parity.test.js @@ -129,7 +129,7 @@ describe('Batch loading consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('batch-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('batch-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'batch-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -177,7 +177,7 @@ describe('Batch loading consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('batch-mutation-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('batch-mutation-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'batch-mutation-parity' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/binary-mode-parity.test.js b/tests/rigor/binary-mode-parity.test.js index b028a2c..88b08f6 100644 --- a/tests/rigor/binary-mode-parity.test.js +++ b/tests/rigor/binary-mode-parity.test.js @@ -290,7 +290,7 @@ describe('Binary (threshold) mode parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('binary-normal-agreement', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('binary-normal-agreement', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1200, seed: 'binary-mode-config-matrix' , artifacts: { dir: '', persist: 'never' }}); @@ -349,7 +349,7 @@ describe('Binary (threshold) mode parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('mutation-freshness', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('mutation-freshness', ({ actual }) => actual !== undefined) ]) ).run({ effort: 800, seed: 'binary-mode-mutation-parity' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/cache-parity.test.js b/tests/rigor/cache-parity.test.js index 582e56d..eb80955 100644 --- a/tests/rigor/cache-parity.test.js +++ b/tests/rigor/cache-parity.test.js @@ -113,7 +113,7 @@ describe('Cache correctness under mutation (rigor)', () => { rigor.fn('check', check, rigor.args(opGen)) ], rigor.crucible([ - rigor.invariant('cache-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('cache-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 600, seed: 'cache-onoff-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -159,7 +159,7 @@ describe('Cache correctness under mutation (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('override-cache-fresh', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('override-cache-fresh', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'cache-override-freshness' , artifacts: { dir: '', persist: 'never' }}); @@ -205,7 +205,7 @@ describe('Cache correctness under mutation (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('ttl-contract', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('ttl-contract', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'cache-ttl-contract' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/chain-rule.test.js b/tests/rigor/chain-rule.test.js index 5b4b9c6..d50b9db 100644 --- a/tests/rigor/chain-rule.test.js +++ b/tests/rigor/chain-rule.test.js @@ -45,7 +45,7 @@ describe('ChainRule evaluation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('empty-steps', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('empty-steps', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'chain-rule-empty-steps', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -82,7 +82,7 @@ describe('ChainRule evaluation (rigor)', () => { rigor.args(rigor.gen.float({ min: 0, max: 1 })) )], rigor.crucible([ - rigor.invariant('possibility-bounded', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-bounded', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'chain-rule-possibility-bounded', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -117,7 +117,7 @@ describe('ChainRule evaluation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('no-path', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('no-path', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'chain-rule-no-path', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -154,7 +154,7 @@ describe('ChainRule evaluation (rigor)', () => { rigor.args(rigor.gen.float({ min: 0.01, max: 1 })) )], rigor.crucible([ - rigor.invariant('one-step-pos', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('one-step-pos', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'chain-rule-one-step', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -204,7 +204,7 @@ describe('ChainRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('two-step-chain', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('two-step-chain', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'chain-rule-two-step', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/challenge-proof.test.js b/tests/rigor/challenge-proof.test.js index 940901a..6e53d88 100644 --- a/tests/rigor/challenge-proof.test.js +++ b/tests/rigor/challenge-proof.test.js @@ -108,7 +108,7 @@ describe('PartialGraphContext.getChallengeProof (rigor)', () => { rigor.crucible([ rigor.invariant( 'oracle-matches-brute-force', - ({ error, errorMessage }) => !error && !errorMessage + ({ actual }) => actual !== undefined ) ]) ).run({ seed: 'challenge-proof-oracle', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -150,7 +150,7 @@ describe('PartialGraphContext.getChallengeProof (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('no-expired', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('no-expired', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-proof-expired', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -225,7 +225,7 @@ describe('PartialGraphContext.getChallengeProof (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('most-recent', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('most-recent', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-proof-most-recent', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -281,7 +281,7 @@ describe('PartialGraphContext.getChallengeProof (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('within-window', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('within-window', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-proof-within-ms', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/challenge-rule.test.js b/tests/rigor/challenge-rule.test.js index 14c13ab..c5936e5 100644 --- a/tests/rigor/challenge-rule.test.js +++ b/tests/rigor/challenge-rule.test.js @@ -67,7 +67,7 @@ describe('ChallengeRule._resolveSubjectKey (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('subjectKey-wins', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('subjectKey-wins', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-rule-subject-key-wins', effort: 1000 , artifacts: { dir: '', persist: 'never' }}); @@ -116,7 +116,7 @@ describe('ChallengeRule._resolveSubjectKey (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('subject-mapping', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('subject-mapping', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-rule-subject-mapping', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -197,7 +197,7 @@ describe('ChallengeRule._resolveWithinMs (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('within-units', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('within-units', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-rule-within-units', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -260,7 +260,7 @@ describe('ChallengeRule._resolveWithinMs (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('within-priority', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('within-priority', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-rule-within-priority', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -293,7 +293,7 @@ describe('ChallengeRule._resolveWithinMs (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('null-when-absent', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('null-when-absent', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-rule-null-when-absent', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -333,7 +333,7 @@ describe('ChallengeRule._buildRequirement (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('buildRequirement', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('buildRequirement', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'challenge-rule-build-requirement', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/check-explain-agreement.test.js b/tests/rigor/check-explain-agreement.test.js index 8f1f380..5125758 100644 --- a/tests/rigor/check-explain-agreement.test.js +++ b/tests/rigor/check-explain-agreement.test.js @@ -78,7 +78,7 @@ describe('check/explain agreement (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('check-explain-agree', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('check-explain-agree', ({ actual }) => actual !== undefined) ]) ).run({ effort: 500, seed: 'explain-agreement' , artifacts: { dir: '', persist: 'never' }}); @@ -148,7 +148,7 @@ describe('check/explain agreement (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('used-facts-consistent', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('used-facts-consistent', ({ actual }) => actual !== undefined) ]) ).run({ effort: 600, seed: 'explain-used-facts' , artifacts: { dir: '', persist: 'never' }}); @@ -196,7 +196,7 @@ describe('check/explain agreement (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('remediation-consistent', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('remediation-consistent', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'explain-remediation' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/comparator-full-path.test.js b/tests/rigor/comparator-full-path.test.js index adae72d..1b7bc6c 100644 --- a/tests/rigor/comparator-full-path.test.js +++ b/tests/rigor/comparator-full-path.test.js @@ -113,7 +113,7 @@ describe('Relational comparator full-path parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('comparator-full-path', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('comparator-full-path', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1200, seed: 'comparator-full-path-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -162,7 +162,7 @@ describe('Relational comparator full-path parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('comparator-aggregation', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('comparator-aggregation', ({ actual }) => actual !== undefined) ]) ).run({ effort: 500, seed: 'comparator-aggregation' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/complex-graph-batch-crucible.test.js b/tests/rigor/complex-graph-batch-crucible.test.js index 31d84fd..5b27cc9 100644 --- a/tests/rigor/complex-graph-batch-crucible.test.js +++ b/tests/rigor/complex-graph-batch-crucible.test.js @@ -154,10 +154,10 @@ describe('Complex-graph batch/cache crucibles (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('batch-sequential-parity', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[parity]')), - rigor.invariant('batch-mutation', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[mutation]')), - rigor.invariant('cache-interaction', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[cache]')), - rigor.invariant('fixture-size', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[fixture]')) + rigor.invariant('batch-sequential-parity', ({ actual }) => actual !== undefined), + rigor.invariant('batch-mutation', ({ actual }) => actual !== undefined), + rigor.invariant('cache-interaction', ({ actual }) => actual !== undefined), + rigor.invariant('fixture-size', ({ actual }) => actual !== undefined) ]) ).run({ effort: 150, seed: 'complex-graph-batch-crucible', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/complex-graph-crucible.test.js b/tests/rigor/complex-graph-crucible.test.js index 261bd45..38f11de 100644 --- a/tests/rigor/complex-graph-crucible.test.js +++ b/tests/rigor/complex-graph-crucible.test.js @@ -127,7 +127,7 @@ describe('Complex-graph crucibles (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'complex-graph-parity', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/complex-graph-mutation-crucible.test.js b/tests/rigor/complex-graph-mutation-crucible.test.js index c823ac8..83a6787 100644 --- a/tests/rigor/complex-graph-mutation-crucible.test.js +++ b/tests/rigor/complex-graph-mutation-crucible.test.js @@ -91,7 +91,7 @@ describe('Complex-graph mutation crucibles (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('mutation-freshness', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('mutation-freshness', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'complex-graph-mutation-freshness', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/complex-graph-overlay-crucible.test.js b/tests/rigor/complex-graph-overlay-crucible.test.js index c2a69c8..efd5c30 100644 --- a/tests/rigor/complex-graph-overlay-crucible.test.js +++ b/tests/rigor/complex-graph-overlay-crucible.test.js @@ -145,10 +145,10 @@ describe('Complex-graph overlay crucibles (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('overlay-surfaces', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[surfaces]')), - rigor.invariant('persistent-wins', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[wins]')), - rigor.invariant('overlay-binary-parity', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[binary]')), - rigor.invariant('overlay-on-complex', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[complex]')) + rigor.invariant('overlay-surfaces', ({ actual }) => actual !== undefined), + rigor.invariant('persistent-wins', ({ actual }) => actual !== undefined), + rigor.invariant('overlay-binary-parity', ({ actual }) => actual !== undefined), + rigor.invariant('overlay-on-complex', ({ actual }) => actual !== undefined) ]) ).run({ effort: 200, seed: 'complex-graph-overlay-crucible', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/complex-graph-reachability-crucible.test.js b/tests/rigor/complex-graph-reachability-crucible.test.js index 6b46f9b..049b437 100644 --- a/tests/rigor/complex-graph-reachability-crucible.test.js +++ b/tests/rigor/complex-graph-reachability-crucible.test.js @@ -148,9 +148,9 @@ describe('Complex-graph PLTC reachability crucibles (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('verdict-parity', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[verdict]')), - rigor.invariant('fast-fail-soundness', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[soundness]')), - rigor.invariant('null-defer-contract', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[vacuity]')) + rigor.invariant('verdict-parity', ({ actual }) => actual !== undefined), + rigor.invariant('fast-fail-soundness', ({ actual }) => actual !== undefined), + rigor.invariant('null-defer-contract', ({ actual }) => actual !== undefined) ]) ).run({ effort: 200, seed: 'complex-graph-reachability-crucible', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/complex-graph-ttl-crucible.test.js b/tests/rigor/complex-graph-ttl-crucible.test.js index 79bf5f2..ddd81fe 100644 --- a/tests/rigor/complex-graph-ttl-crucible.test.js +++ b/tests/rigor/complex-graph-ttl-crucible.test.js @@ -170,9 +170,9 @@ describe('Complex-graph value-TTL crucibles (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('freshness-parity', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[freshness]')), - rigor.invariant('mutation-with-time', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[mutation]')), - rigor.invariant('snapshot-preserves-ttl', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[snapshot]')) + rigor.invariant('freshness-parity', ({ actual }) => actual !== undefined), + rigor.invariant('mutation-with-time', ({ actual }) => actual !== undefined), + rigor.invariant('snapshot-preserves-ttl', ({ actual }) => actual !== undefined) ]) ).run({ effort: 250, seed: 'complex-graph-ttl-crucible', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/complex-graph-values-crucible.test.js b/tests/rigor/complex-graph-values-crucible.test.js index 9ad61f1..93a5c35 100644 --- a/tests/rigor/complex-graph-values-crucible.test.js +++ b/tests/rigor/complex-graph-values-crucible.test.js @@ -125,10 +125,15 @@ describe('Complex-graph value/comparator crucibles (rigor)', () => { } // TTL-EXPIRY-ON-COMPARATOR: both operands past TTL -> deny, mirror agrees. + // Use values NOT in VALUE_SET (137 > 30): the engine keeps the OLD + // timestamp when a rewrite does not change the value, so a value equal + // to whatever the mutation loop last wrote would NOT refresh freshness + // and the pre-expiry grant would (correctly) not materialize. Writing + // guaranteed-different values forces a timestamp refresh. const kExp = keys[keys.length - 1]; engineNow = BASE_NOW + 2_000_000; - write(kExp, 'balance', 100); - write(kExp, 'price', 50); + write(kExp, 'balance', 137); + write(kExp, 'price', 30); if (checkAt(kExp).normal !== 1) fail(`[expiry] pre-expiry grant missing (seed=${seed})`); engineNow += TTL + 1; const expired = checkAt(kExp); @@ -149,9 +154,9 @@ describe('Complex-graph value/comparator crucibles (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('comparator-parity', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[parity]')), - rigor.invariant('value-mutation', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[mutation]')), - rigor.invariant('ttl-expiry-on-comparator', ({ error, errorMessage }) => !error || !String(errorMessage).startsWith('[expiry]')) + rigor.invariant('comparator-parity', ({ actual }) => actual !== undefined), + rigor.invariant('value-mutation', ({ actual }) => actual !== undefined), + rigor.invariant('ttl-expiry-on-comparator', ({ actual }) => actual !== undefined) ]) ).run({ effort: 200, seed: 'complex-graph-values-crucible', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/computed-rule.test.js b/tests/rigor/computed-rule.test.js index e880aeb..b8e939e 100644 --- a/tests/rigor/computed-rule.test.js +++ b/tests/rigor/computed-rule.test.js @@ -70,7 +70,7 @@ describe('ComputedRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('possibility-passthrough', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-passthrough', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'computed-rule-possibility-passthrough', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -106,7 +106,7 @@ describe('ComputedRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('reason-default', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('reason-default', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'computed-rule-reason-default', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -143,7 +143,7 @@ describe('ComputedRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('reason-passthrough', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('reason-passthrough', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'computed-rule-reason-passthrough', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -186,7 +186,7 @@ describe('ComputedRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('meta-contract', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('meta-contract', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'computed-rule-meta-contract', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -235,7 +235,7 @@ describe('ComputedRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('collected-values-passthrough', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('collected-values-passthrough', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'computed-rule-collected-values', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -271,7 +271,7 @@ describe('ComputedRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('possibility-fallback-zero', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-fallback-zero', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'computed-rule-possibility-fallback', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -309,7 +309,7 @@ describe('ComputedRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('options-passthrough', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('options-passthrough', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'computed-rule-options-passthrough', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/config-redefinition.test.js b/tests/rigor/config-redefinition.test.js index d103c53..913b717 100644 --- a/tests/rigor/config-redefinition.test.js +++ b/tests/rigor/config-redefinition.test.js @@ -145,7 +145,7 @@ describe('Config redefinition semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('redefine-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('redefine-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1200, seed: 'config-redefinition-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -192,7 +192,7 @@ describe('Config redefinition semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('redefine-binary-fastpath', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('redefine-binary-fastpath', ({ actual }) => actual !== undefined) ]) ).run({ effort: 800, seed: 'config-redefinition-binary' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/direct-rule.test.js b/tests/rigor/direct-rule.test.js index 3f4ece2..f7267eb 100644 --- a/tests/rigor/direct-rule.test.js +++ b/tests/rigor/direct-rule.test.js @@ -82,7 +82,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('no-relation-fallback', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('no-relation-fallback', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-no-relation', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -136,7 +136,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('relation-strength-preserved', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('relation-strength-preserved', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-strength', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -184,7 +184,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('reverse-routing', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('reverse-routing', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-reverse', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -234,7 +234,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('fastPath-early-exit', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('fastPath-early-exit', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-fast-path', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -276,7 +276,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('collectValues-disabled', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('collectValues-disabled', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-collect-off', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -330,7 +330,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('collectValues-default', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('collectValues-default', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-collect-on', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -391,7 +391,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('relation-precedence', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('relation-precedence', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-precedence', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -447,7 +447,7 @@ describe('DirectRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('result-shape-stable', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('result-shape-stable', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'direct-rule-shape', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/dsl-compiler.test.js b/tests/rigor/dsl-compiler.test.js index bdc3da3..b4470ad 100644 --- a/tests/rigor/dsl-compiler.test.js +++ b/tests/rigor/dsl-compiler.test.js @@ -91,7 +91,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { // signature — the DSL validator rejects undeclared predicates. [rigor.fn('check', check, rigor.args(rigor.gen.oneOf(['owns', 'canReadInner'])))], rigor.crucible([ - rigor.invariant('direct-emission', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('direct-emission', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-direct', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -126,7 +126,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('tus-emission', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('tus-emission', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-tus', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -158,7 +158,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('parent-emission', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('parent-emission', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-parent', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -195,7 +195,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('chain-emission', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('chain-emission', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-chain', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -244,7 +244,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('multi_hop-emission', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('multi_hop-emission', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-multi-hop', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -286,7 +286,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('logical-emission', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('logical-emission', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-logical', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -321,7 +321,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args(rigor.gen.enum(['>', '>=', '<', '<=', '==', '!='])))], rigor.crucible([ - rigor.invariant('relational-comparator-emission', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('relational-comparator-emission', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-relational-comparator', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -369,7 +369,7 @@ describe('DSLCompiler → engine rule mapping (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args(rigor.gen.int(0, catalog.length - 1)))], rigor.crucible([ - rigor.invariant('mapping-consistency', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('mapping-consistency', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'dsl-compiler-mapping-consistency', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/dsl-mutation-parity.test.js b/tests/rigor/dsl-mutation-parity.test.js index 0220efa..d47a5f5 100644 --- a/tests/rigor/dsl-mutation-parity.test.js +++ b/tests/rigor/dsl-mutation-parity.test.js @@ -129,7 +129,7 @@ describe('DSL-compiled vs hand-written parity under mutation (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('sequence-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('sequence-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'dsl-mutation-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -179,7 +179,7 @@ describe('DSL-compiled vs hand-written parity under mutation (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('grant-revoke-cycles', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('grant-revoke-cycles', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'dsl-grant-revoke-cycles' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/graph-indices.test.js b/tests/rigor/graph-indices.test.js index c23b804..739b0a9 100644 --- a/tests/rigor/graph-indices.test.js +++ b/tests/rigor/graph-indices.test.js @@ -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' }}); diff --git a/tests/rigor/logical-operators.test.js b/tests/rigor/logical-operators.test.js index 9caf84b..b9abc11 100644 --- a/tests/rigor/logical-operators.test.js +++ b/tests/rigor/logical-operators.test.js @@ -63,7 +63,7 @@ describe('LogicalOperators evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('union-max', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('union-max', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'logical-operators-union-max', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -102,7 +102,7 @@ describe('LogicalOperators evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('intersection-min', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('intersection-min', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'logical-operators-intersection-min', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -136,7 +136,7 @@ describe('LogicalOperators evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('union-mean', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('union-mean', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'logical-operators-union-mean', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -179,7 +179,7 @@ describe('LogicalOperators evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('exclusion', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('exclusion', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'logical-operators-exclusion', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -212,7 +212,7 @@ describe('LogicalOperators evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('possibility-bounded', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-bounded', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'logical-operators-possibility-bounded', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -249,7 +249,7 @@ describe('LogicalOperators evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('collected-values-concat', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('collected-values-concat', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'logical-operators-collected-values', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/manager-index-parity.test.js b/tests/rigor/manager-index-parity.test.js index ea39f6c..13c62a1 100644 --- a/tests/rigor/manager-index-parity.test.js +++ b/tests/rigor/manager-index-parity.test.js @@ -163,7 +163,7 @@ describe('Manager vs index lookup parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('lookup-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('lookup-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1200, seed: 'manager-index-parity' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/multi-hop-rule.test.js b/tests/rigor/multi-hop-rule.test.js index 9c12561..860ec0b 100644 --- a/tests/rigor/multi-hop-rule.test.js +++ b/tests/rigor/multi-hop-rule.test.js @@ -49,7 +49,7 @@ describe('MultiHopRule evaluation (rigor)', () => { rigor.args(rigor.gen.string(0, 20)) // may be empty )], rigor.crucible([ - rigor.invariant('missing-relation', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('missing-relation', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'multi-hop-missing-relation', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -86,7 +86,7 @@ describe('MultiHopRule evaluation (rigor)', () => { rigor.args(rigor.gen.float({ min: 0, max: 1 })) )], rigor.crucible([ - rigor.invariant('possibility-bounded', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-bounded', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'multi-hop-possibility-bounded', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -124,7 +124,7 @@ describe('MultiHopRule evaluation (rigor)', () => { rigor.args(rigor.gen.float({ min: 0.01, max: 1 })) )], rigor.crucible([ - rigor.invariant('single-path-strength', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('single-path-strength', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'multi-hop-single-path', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -159,7 +159,7 @@ describe('MultiHopRule evaluation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('no-path', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('no-path', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'multi-hop-no-path', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -206,7 +206,7 @@ describe('MultiHopRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('multi-hop-finds-path', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('multi-hop-finds-path', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'multi-hop-two-hop', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/multi-object-independence.test.js b/tests/rigor/multi-object-independence.test.js index 56c752e..3159e6c 100644 --- a/tests/rigor/multi-object-independence.test.js +++ b/tests/rigor/multi-object-independence.test.js @@ -152,7 +152,7 @@ describe('Multi-object independence (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('multi-object-isolation', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('multi-object-isolation', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1000, seed: 'multi-object-independence' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/node-lifecycle.test.js b/tests/rigor/node-lifecycle.test.js index 88f6bca..9e731df 100644 --- a/tests/rigor/node-lifecycle.test.js +++ b/tests/rigor/node-lifecycle.test.js @@ -199,7 +199,7 @@ describe('Node lifecycle semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('remove-cascade', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('remove-cascade', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1000, seed: 'node-lifecycle-remove' , artifacts: { dir: '', persist: 'never' }}); @@ -252,7 +252,7 @@ describe('Node lifecycle semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('node-readd', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('node-readd', ({ actual }) => actual !== undefined) ]) ).run({ effort: 600, seed: 'node-lifecycle-readd' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/node-manager.test.js b/tests/rigor/node-manager.test.js index f703800..70326b7 100644 --- a/tests/rigor/node-manager.test.js +++ b/tests/rigor/node-manager.test.js @@ -91,7 +91,7 @@ describe('NodeManager index invariants (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('inverse-maps', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('inverse-maps', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'node-manager-inverse-maps', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -131,7 +131,7 @@ describe('NodeManager index invariants (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('addNode-idempotent', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('addNode-idempotent', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'node-manager-add-idempotent', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -184,7 +184,7 @@ describe('NodeManager index invariants (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('size-invariant', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('size-invariant', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'node-manager-size-invariant', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -198,7 +198,7 @@ describe('NodeManager index invariants (rigor)', () => { it('nextNodeId advances monotonically across distinct addNode calls', async () => { async function check(keys, types) { const { manager, arbiter } = makeManager(); - if (keys.length !== types.length) return; // skip ill-formed + if (keys.length !== types.length) return { skipped: true }; // skip ill-formed const ids = []; for (let i = 0; i < keys.length; i++) { ids.push(manager.addNode(keys[i], types[i])); @@ -247,7 +247,7 @@ describe('NodeManager index invariants (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('monotonic-ids', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('monotonic-ids', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'node-manager-monotonic-ids', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -293,7 +293,7 @@ describe('NodeManager index invariants (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('removeNode-cleanup', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('removeNode-cleanup', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'node-manager-remove-cleanup', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -333,7 +333,7 @@ describe('NodeManager index invariants (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('clearNodes-resets', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('clearNodes-resets', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'node-manager-clear-resets', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -386,7 +386,7 @@ describe('NodeManager index invariants (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('updateNodeData-merges', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('updateNodeData-merges', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'node-manager-update-merge', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/overlay-precedence.test.js b/tests/rigor/overlay-precedence.test.js index bcdd170..7f94722 100644 --- a/tests/rigor/overlay-precedence.test.js +++ b/tests/rigor/overlay-precedence.test.js @@ -86,7 +86,7 @@ describe('Partial graph overlay precedence (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('persistent-precedence', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('persistent-precedence', ({ actual }) => actual !== undefined) ]) ).run({ effort: 500, seed: 'overlay-persistent-precedence' , artifacts: { dir: '', persist: 'never' }}); @@ -136,7 +136,7 @@ describe('Partial graph overlay precedence (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('layer-precedence', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('layer-precedence', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'overlay-layer-precedence' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/parent-rule.test.js b/tests/rigor/parent-rule.test.js index e2f6258..65261ed 100644 --- a/tests/rigor/parent-rule.test.js +++ b/tests/rigor/parent-rule.test.js @@ -85,7 +85,7 @@ describe('ParentRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('no-parents', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('no-parents', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'parent-rule-no-parents', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -129,7 +129,7 @@ describe('ParentRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('one-parent-strength', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('one-parent-strength', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'parent-rule-one-parent', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -173,7 +173,7 @@ describe('ParentRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('threshold-cutoff', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('threshold-cutoff', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'parent-rule-threshold', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -210,7 +210,7 @@ describe('ParentRule evaluation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('cycle-detection', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('cycle-detection', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'parent-rule-cycle', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -256,7 +256,7 @@ describe('ParentRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('multi-parent-max', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('multi-parent-max', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'parent-rule-multi-parent', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -295,7 +295,7 @@ describe('ParentRule evaluation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args(rigor.gen.boolean()))], rigor.crucible([ - rigor.invariant('parent-relation-default', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('parent-relation-default', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'parent-rule-default-relation', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -339,7 +339,7 @@ describe('ParentRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('possibility-bounded', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-bounded', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'parent-rule-possibility-bounded', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/pltc-reachability-parity.test.js b/tests/rigor/pltc-reachability-parity.test.js index 29349bf..f53f71b 100644 --- a/tests/rigor/pltc-reachability-parity.test.js +++ b/tests/rigor/pltc-reachability-parity.test.js @@ -126,7 +126,7 @@ describe('PLTC reachability parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('pltc-active-bypass-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('pltc-active-bypass-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1500, seed: 'pltc-parity-active-bypass' , artifacts: { dir: '', persist: 'never' }}); @@ -162,7 +162,7 @@ describe('PLTC reachability parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('pltc-fastfail-soundness', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('pltc-fastfail-soundness', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1000, seed: 'pltc-parity-fastfail' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/qualitative-rule-helpers.test.js b/tests/rigor/qualitative-rule-helpers.test.js index 68c6c50..d57706c 100644 --- a/tests/rigor/qualitative-rule-helpers.test.js +++ b/tests/rigor/qualitative-rule-helpers.test.js @@ -92,7 +92,7 @@ describe('QualitativeRelationalComparatorRule._getQualitativeScale (rigor)', () ) ], rigor.crucible([ - rigor.invariant('known-scales', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('known-scales', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-known-scales', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -123,7 +123,7 @@ describe('QualitativeRelationalComparatorRule._getQualitativeScale (rigor)', () ) ], rigor.crucible([ - rigor.invariant('fallback', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('fallback', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-fallback', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -160,7 +160,7 @@ describe('QualitativeRelationalComparatorRule._calculateDecayedPossibility (rigo ) ], rigor.crucible([ - rigor.invariant('stable-identity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('stable-identity', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-stable-identity', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -195,7 +195,7 @@ describe('QualitativeRelationalComparatorRule._calculateDecayedPossibility (rigo ) ], rigor.crucible([ - rigor.invariant('zero-periods', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('zero-periods', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-zero-periods', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -236,7 +236,7 @@ describe('QualitativeRelationalComparatorRule._calculateDecayedPossibility (rigo ) ], rigor.crucible([ - rigor.invariant('down-monotone', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('down-monotone', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-down-monotone', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -276,7 +276,7 @@ describe('QualitativeRelationalComparatorRule._calculateDecayedPossibility (rigo ) ], rigor.crucible([ - rigor.invariant('up-monotone', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('up-monotone', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-up-monotone', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -312,7 +312,7 @@ describe('QualitativeRelationalComparatorRule._calculateDecayedPossibility (rigo ) ], rigor.crucible([ - rigor.invariant('result-in-scale', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('result-in-scale', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-result-in-scale', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -349,7 +349,7 @@ describe('QualitativeRelationalComparatorRule._createQualitativeInterval (rigor) ) ], rigor.crucible([ - rigor.invariant('lower-le-upper', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('lower-le-upper', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-lower-le-upper', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -387,7 +387,7 @@ describe('QualitativeRelationalComparatorRule._createQualitativeInterval (rigor) ) ], rigor.crucible([ - rigor.invariant('point-contained', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('point-contained', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-point-contained', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -423,7 +423,7 @@ describe('QualitativeRelationalComparatorRule._createQualitativeInterval (rigor) ) ], rigor.crucible([ - rigor.invariant('bounds-in-scale', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('bounds-in-scale', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-bounds-in-scale', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -457,7 +457,7 @@ describe('QualitativeRelationalComparatorRule._createQualitativeInterval (rigor) ) ], rigor.crucible([ - rigor.invariant('zero-blur', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('zero-blur', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-zero-blur', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -488,7 +488,7 @@ describe('QualitativeRelationalComparatorRule._calculatePossibilityLossSteps (ri ) ], rigor.crucible([ - rigor.invariant('loss-is-zero', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('loss-is-zero', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-loss-is-zero', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -527,7 +527,7 @@ describe('QualitativeRelationalComparatorRule._calculatePossibilityLossSteps (ri ) ], rigor.crucible([ - rigor.invariant('loss-symmetric', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('loss-symmetric', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'qualitative-loss-symmetric', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/relation-manager.test.js b/tests/rigor/relation-manager.test.js index 2dd8aa9..5bb32c9 100644 --- a/tests/rigor/relation-manager.test.js +++ b/tests/rigor/relation-manager.test.js @@ -72,7 +72,7 @@ describe('RelationManager.addRelation/removeRelation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('add-and-get', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('add-and-get', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'relation-manager-add-get', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -125,7 +125,7 @@ describe('RelationManager.addRelation/removeRelation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('addRelation-idempotent', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('addRelation-idempotent', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'relation-manager-add-idempotent', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -193,7 +193,7 @@ describe('RelationManager.addRelation/removeRelation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('remove-clears-indexes', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('remove-clears-indexes', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'relation-manager-remove-cleanup', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -282,7 +282,7 @@ describe('RelationManager.addRelation/removeRelation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args(relGen))], rigor.crucible([ - rigor.invariant('index-coherence', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('index-coherence', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'relation-manager-index-coherence', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -362,7 +362,7 @@ describe('RelationManager.addRelation/removeRelation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args(opGen))], rigor.crucible([ - rigor.invariant('add-remove-roundtrip', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('add-remove-roundtrip', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'relation-manager-add-remove-roundtrip', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -394,7 +394,7 @@ describe('RelationManager.addRelation/removeRelation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('getDirectRelation-unknown', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('getDirectRelation-unknown', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'relation-manager-unknown-tuple', effort: 500 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/relational-comparator-router.test.js b/tests/rigor/relational-comparator-router.test.js index 6c7b1f3..ff9aebf 100644 --- a/tests/rigor/relational-comparator-router.test.js +++ b/tests/rigor/relational-comparator-router.test.js @@ -74,7 +74,7 @@ describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('qualitative-wins', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('qualitative-wins', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-router-qualitative-wins', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -114,7 +114,7 @@ describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('scaleName-triggers', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('scaleName-triggers', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-router-scale-name', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -154,7 +154,7 @@ describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('decay-blur-triggers', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('decay-blur-triggers', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-router-decay-blur', effort: 500 , artifacts: { dir: '', persist: 'never' }}); @@ -188,7 +188,7 @@ describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('marginSteps-correct', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('marginSteps-correct', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-router-margin-steps', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -229,7 +229,7 @@ describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('plain-numeric', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('plain-numeric', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-router-plain-numeric', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -274,7 +274,7 @@ describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('getImplType-consistent', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('getImplType-consistent', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-router-get-impl-type', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -320,7 +320,7 @@ describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => { ) ], rigor.crucible([ - rigor.invariant('hasValidProperty', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('hasValidProperty', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-router-has-valid-property', effort: 1000 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/relational-comparator-rule.test.js b/tests/rigor/relational-comparator-rule.test.js index 7c8ae14..d3d6b2a 100644 --- a/tests/rigor/relational-comparator-rule.test.js +++ b/tests/rigor/relational-comparator-rule.test.js @@ -70,7 +70,7 @@ describe('RelationalComparatorRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('left-gt-right', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('left-gt-right', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-rule-left-gt-right', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -109,7 +109,7 @@ describe('RelationalComparatorRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('left-lt-right', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('left-lt-right', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-rule-left-lt-right', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -144,7 +144,7 @@ describe('RelationalComparatorRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('possibility-bounded', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-bounded', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-rule-possibility-bounded', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -179,7 +179,7 @@ describe('RelationalComparatorRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('result-shape-stable', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('result-shape-stable', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-rule-shape-stable', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -218,7 +218,7 @@ describe('RelationalComparatorRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('determinism', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('determinism', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'rc-rule-determinism', effort: 800 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/snapshot-parity.test.js b/tests/rigor/snapshot-parity.test.js index 1905b29..72189c3 100644 --- a/tests/rigor/snapshot-parity.test.js +++ b/tests/rigor/snapshot-parity.test.js @@ -123,7 +123,7 @@ describe('Condensed snapshot round trip (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('snapshot-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('snapshot-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'snapshot-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -175,7 +175,7 @@ describe('Condensed snapshot round trip (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('readonly-enforced', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('readonly-enforced', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'snapshot-readonly' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/traversal-parity.test.js b/tests/rigor/traversal-parity.test.js index 0a5403b..4db98e3 100644 --- a/tests/rigor/traversal-parity.test.js +++ b/tests/rigor/traversal-parity.test.js @@ -186,7 +186,7 @@ describe('Traversal semantics parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('chain-direction-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('chain-direction-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1500, seed: 'traversal-chain-direction' , artifacts: { dir: '', persist: 'never' }}); @@ -262,7 +262,7 @@ describe('Traversal semantics parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('ttu-multi-tuple-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('ttu-multi-tuple-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 1200, seed: 'traversal-ttu-multituple' , artifacts: { dir: '', persist: 'never' }}); @@ -338,7 +338,7 @@ describe('Traversal semantics parity (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('update-path-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('update-path-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 600, seed: 'traversal-update-path' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/tuple-to-userset-rule.test.js b/tests/rigor/tuple-to-userset-rule.test.js index 4ed9216..e8b6bfc 100644 --- a/tests/rigor/tuple-to-userset-rule.test.js +++ b/tests/rigor/tuple-to-userset-rule.test.js @@ -90,7 +90,7 @@ describe('TupleToUsersetRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('no-tuples', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('no-tuples', ({ actual }) => actual !== undefined) ]) ).run({ effort: 500, seed: 'ttu-no-tuples', artifacts: { dir: '', persist: 'never' }}); @@ -139,7 +139,7 @@ describe('TupleToUsersetRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('min-fusion', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('min-fusion', ({ actual }) => actual !== undefined) ]) ).run({ effort: 800, seed: 'ttu-min-fusion', artifacts: { dir: '', persist: 'never' }}); @@ -192,7 +192,7 @@ describe('TupleToUsersetRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('multi-tuple-max', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('multi-tuple-max', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'ttu-multi-tuple-max', effort: 1500 , artifacts: { dir: '', persist: 'never' }}); @@ -230,7 +230,7 @@ describe('TupleToUsersetRule evaluation (rigor)', () => { ) )], rigor.crucible([ - rigor.invariant('possibility-bounded', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('possibility-bounded', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'ttu-possibility-bounded', effort: 800 , artifacts: { dir: '', persist: 'never' }}); @@ -290,7 +290,7 @@ describe('TupleToUsersetRule evaluation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('early-exit', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('early-exit', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'ttu-early-exit', effort: 200 , artifacts: { dir: '', persist: 'never' }}); @@ -340,7 +340,7 @@ describe('TupleToUsersetRule evaluation (rigor)', () => { const report = await rigor.campaign( [rigor.fn('check', check, rigor.args())], rigor.crucible([ - rigor.invariant('cycle-detection', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('cycle-detection', ({ actual }) => actual !== undefined) ]) ).run({ seed: 'ttu-cycle-detection', effort: 200 , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/validity-parity.test.js b/tests/rigor/validity-parity.test.js index 394bd9b..8ca21a7 100644 --- a/tests/rigor/validity-parity.test.js +++ b/tests/rigor/validity-parity.test.js @@ -138,7 +138,7 @@ describe('Possibilistic validity metadata (rigor)', () => { }; }, rigor.args(rigor.gen.array(rigor.gen.oneOf(['finite_sample', 'anytime', 'conformal', 'approximate', 'heuristic', 'unknown']), 1, 4)))], rigor.crucible([ - rigor.invariant('helper invariants', ({ error, errorMessage, actual }) => !error && !errorMessage && Object.values(actual).every(Boolean)) + rigor.invariant('helper invariants', ({ actual }) => !!actual && Object.values(actual).every(Boolean)) ]) ).run({ effort: 200, seed: 'validity-helpers-2026', artifacts: { dir: '', persist: 'never' } }); diff --git a/tests/rigor/zanzibar-consistency.test.js b/tests/rigor/zanzibar-consistency.test.js index e25023b..0f2d652 100644 --- a/tests/rigor/zanzibar-consistency.test.js +++ b/tests/rigor/zanzibar-consistency.test.js @@ -91,7 +91,7 @@ describe('Authorization state consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('ttu-mutation', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('ttu-mutation', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'consistency-ttu-mutation' , artifacts: { dir: '', persist: 'never' }}); @@ -152,7 +152,7 @@ describe('Authorization state consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('chain-mutation', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('chain-mutation', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'consistency-chain-mutation' , artifacts: { dir: '', persist: 'never' }}); @@ -220,7 +220,7 @@ describe('Authorization state consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('config-change', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('config-change', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'consistency-config-change' , artifacts: { dir: '', persist: 'never' }}); @@ -272,7 +272,7 @@ describe('Authorization state consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('threshold-excludes', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('threshold-excludes', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'consistency-threshold' , artifacts: { dir: '', persist: 'never' }}); @@ -338,7 +338,7 @@ describe('Authorization state consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('values-complete', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('values-complete', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'consistency-value-completeness' , artifacts: { dir: '', persist: 'never' }}); @@ -375,7 +375,7 @@ describe('Authorization state consistency (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('deterministic', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('deterministic', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'consistency-determinism' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/zanzibar-defeasible-dsl-comparator.test.js b/tests/rigor/zanzibar-defeasible-dsl-comparator.test.js index e1d227b..ae13b6e 100644 --- a/tests/rigor/zanzibar-defeasible-dsl-comparator.test.js +++ b/tests/rigor/zanzibar-defeasible-dsl-comparator.test.js @@ -98,7 +98,7 @@ describe('Defeasible logic, DSL parity, aggregation (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('defeasible-semantics', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('defeasible-semantics', ({ actual }) => actual !== undefined) ]) ).run({ effort: 600, seed: 'defeasible-semantics' , artifacts: { dir: '', persist: 'never' }}); @@ -172,7 +172,7 @@ describe('Defeasible logic, DSL parity, aggregation (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('dsl-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('dsl-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'dsl-runtime-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -263,7 +263,7 @@ describe('Defeasible logic, DSL parity, aggregation (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('aggregation-complete', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('aggregation-complete', ({ actual }) => actual !== undefined) ]) ).run({ effort: 500, seed: 'aggregation-completeness' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rigor/zanzibar-semantics.test.js b/tests/rigor/zanzibar-semantics.test.js index f7e2d34..cdc2091 100644 --- a/tests/rigor/zanzibar-semantics.test.js +++ b/tests/rigor/zanzibar-semantics.test.js @@ -310,7 +310,7 @@ describe('Zanzibar rewrite-rule semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('union-max', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('union-max', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'zanzibar-union' , artifacts: { dir: '', persist: 'never' }}); @@ -354,7 +354,7 @@ describe('Zanzibar rewrite-rule semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('intersection-min', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('intersection-min', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'zanzibar-intersection' , artifacts: { dir: '', persist: 'never' }}); @@ -397,7 +397,7 @@ describe('Zanzibar rewrite-rule semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('exclusion-blocks', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('exclusion-blocks', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'zanzibar-exclusion' , artifacts: { dir: '', persist: 'never' }}); @@ -432,7 +432,7 @@ describe('Zanzibar rewrite-rule semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('expand-parity', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('expand-parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 800, seed: 'zanzibar-expand-parity' , artifacts: { dir: '', persist: 'never' }}); @@ -488,7 +488,7 @@ describe('Zanzibar rewrite-rule semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('nested-weakest-link', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('nested-weakest-link', ({ actual }) => actual !== undefined) ]) ).run({ effort: 500, seed: 'zanzibar-nested-chain' , artifacts: { dir: '', persist: 'never' }}); @@ -544,7 +544,7 @@ describe('Zanzibar rewrite-rule semantics (rigor)', () => { )) ], rigor.crucible([ - rigor.invariant('cycle-safe', ({ error, errorMessage }) => !error && !errorMessage) + rigor.invariant('cycle-safe', ({ actual }) => actual !== undefined) ]) ).run({ effort: 400, seed: 'zanzibar-cycle-safety' , artifacts: { dir: '', persist: 'never' }}); diff --git a/tests/rules/chain-condition-step.test.js b/tests/rules/chain-condition-step.test.js index ceaf977..9d7a14c 100644 --- a/tests/rules/chain-condition-step.test.js +++ b/tests/rules/chain-condition-step.test.js @@ -81,14 +81,29 @@ describe('ChainRule condition step (rule-based final hop)', () => { assert.equal(res.reason, 'no_chain_path_found'); }); - it('rejects a condition step that is not the final step', () => { + it('expands an intermediate condition step (rule-based reachability)', () => { + arbiter.setRelationConfig('member_of', { type: 'direct' }); + arbiter.addNode('group:g', 'group'); + arbiter.addRelation('user:u', 'member_of', 'group:g', { possibility: 1.0 }); + arbiter.addRelation('group:g', 'can_view', 'doc:d', { possibility: 0.8 }); + // [condition(member_of unless banned), can_view] — the condition step is + // INTERMEDIATE and discovers its reachable nodes (its base relation's + // neighbors from the source, filtered by its defeater). + const intermediateConfig = { + type: 'logical', + when: { intersection: { rules: [{ type: 'direct', relation: 'member_of' }], aggregator: 'min' } }, + unless: { union: { rules: [{ type: 'direct', relation: 'banned', _subjectIsObject: true }], aggregator: 'max' } } + }; const rule = { type: 'chain', - steps: [{ rule: CONDITION_CONFIG, conditionStep: true }, 'can_view'] + steps: [{ rule: intermediateConfig, conditionStep: true }, 'can_view'] }; - const res = evalRule('user:u', 'doc:d', rule); - assert.equal(res.possibility, 0); - assert.equal(res.reason, 'condition_step_not_final'); + // g not banned → reachable via condition → can_view → doc + assert.ok(Math.abs(evalRule('user:u', 'doc:d', rule).possibility - 0.8) < 1e-9); + // banning g filters it out of the intermediate expansion → no path + arbiter.addRelation('group:g', 'banned', 'group:g', { possibility: 1.0 }); + const denied = evalRule('user:u', 'doc:d', rule); + assert.equal(denied.possibility, 0); }); it('combines across multiple parallel intermediates (max aggregation)', () => {