houserules/packages/rete/tests/golden/GOLDEN-MAP.md
Joey Yakimowich-Payne 0401295bbc
test(rete): port pararules golden tests; tag Phase 1 parity (P1.14)
10 integration tests in packages/rete/tests/golden/ manually wire
AlphaNode → BetaMemory → JoinNode → ProductionNode chains and drive
them via Session.insert/retract. Each test maps to a pararules Nim
reference test (documented in GOLDEN-MAP.md).

Coverage: packages/rete/src at 96.8% statements / 95.4% branch /
97.8% functions — all well above the 90% Phase 1 gate.

Tests: 227 total (166 pre-existing + 61 new golden), all green.
2026-04-16 14:16:19 -06:00

5.5 KiB

Golden Test Map

Maps each golden integration test to its pararules origin in the Nim reference implementation. Source: https://github.com/paranim/pararules

Overview

These tests manually wire Rete II network components (AlphaNode → BetaMemory → JoinNode → ProductionNode) via the Session's internal accessors (_getAlpha(), _getWM()), then insert/ retract facts via Session.insert/retract to drive the alpha dispatch pipeline. This proves component interoperability at the integration level while the full Session.add() auto-wiring is pending (Phase 2).


Test Map

File Test Suite Core Assertion Pararules Origin Nim File / Line
g1-single-condition.test.ts G1 — single-condition match Insert (id, Health, 42) → 1 match with hp=42 tests/test2.nim "queries" getPerson rule (single what-condition) test2.nim ~1-30
g2-two-condition-join.test.ts G2 — two-condition join (same entity) (?id, X, ?x) ∧ (?id, Y, ?y) → 1 match per complete entity tests/test2.nim "joins and advanced queries" getCharacter rule test2.nim ~60-90
g3-derived-facts.test.ts G3 — derived facts (truth maintenance) Insert X=5 → derived Double=10; Retract → Double disappears tests/test2.nim "derived facts" thenFinally block test2.nim ~95-130
g4-multiple-entities.test.ts G4 — multiple entities wildcard N entities with Health → N matches tests/test1.nim "number of conditions != number of facts" (Xavier/Thomas/George Height entities) test1.nim ~1-40
g5-filter-predicate.test.ts G5 — filter predicate (cond) Health=100 passes hp > 50; Health=10 filtered out tests/test2.nim "complex types" stopPlayer with cond: x >= windowWidth test2.nim ~10-50
g6-conflict-resolution.test.ts G6 — conflict resolution ordering orderActivations sorts by salience↓ → specificity↓ → addedAt↑ tests/test2.nim "recursion limit" (rule cascade order) + SPEC §Conflict Resolution test2.nim ~130-160
g7-serialization.test.ts G7 — serialization round-trip defineRule → serialize → JSON → deserialize → equivalent RuleDefinition tests/test3.nim staticRuleset (compile-time rule serialization analogue) test3.nim ~1-40
g8-variable-binding-chain.test.ts G8 — variable binding 3-condition chain (?eid, Type, ?t) ∧ (?eid, Health, ?hp) ∧ (?eid, Position, ?pos) → idEquality through chain tests/test1.nim "adding facts out of order" (multi-condition, deep binding) test1.nim ~42-80
g9-retraction.test.ts G9 — retraction removes match Insert → match; Retract → no match; Re-insert → match again tests/test1.nim "removing facts" (retract clears queryAll) test1.nim ~82-110
g10-cycle-detection.test.ts G10 — cycle detection / recursion limit fireRules() at depth ≥ limit throws RecursionLimitExceededError tests/test2.nim "recursion limit" (rule1→rule2→rule3 cascade loop) test2.nim ~130-160

Pararules API → TypeScript Engine Mapping

Pararules construct TypeScript equivalent
initSession(Fact) new Session({ autoFire: false })
session.add(rule) Manual network wiring via _getAlpha().buildNode()
session.insert(id, attr, val) session.insert(id as EntityId, attr, val)
session.retract(id, attr) session.retract(id as EntityId, attr)
session.queryAll(rule).len prod.matches.length
session.query(rule).field query(prod)["field"]
what: (id, Attr, var) alphaNode.buildNode({id: null, attr: "Attr"}) + JoinNode(…, "var", "id")
cond: expr FilterNode([{predicate: "name", args: [...]}], predicateRegistry)
thenFinally: session.insert(…) DerivedFactProduction(ruleName, wm, handler)
salience on rules defineRule({salience: N, …}) + orderActivations([…])
recursion limit error RecursionLimitExceededError from session.fireRules()
JSON rule schema serialize(rule) / deserialize(json, registry)

Network Wiring Pattern

All golden tests follow this manual wiring pattern (since Session.add() auto-wiring is Phase 2):

// 1. Create Session (WM↔AlphaNetwork already wired internally)
const session = new Session({ autoFire: false });
const alpha = session._getAlpha();

// 2. Root BetaMemory with seed token (drives first join)
const rootMem = new BetaMemory();
rootMem.leftActivate(new Token(null, ROOT_FACT, {}));

// 3. Alpha node for each condition
const alphaNode = alpha.buildNode({ id: null, attr: "Health" });

// 4. JoinNode connecting left memory and alpha memory
const join = new JoinNode(rootMem, alphaNode.memory, [], "hp", "eid");

// 5. Wire alpha → join right side
alphaNode.onActivate((id, attr, value) => join.rightActivate(id, attr, value));
alphaNode.onDeactivate((id, attr, value) => join.rightDeactivate(id, attr, value));

// 6. Wire join → ProductionNode
const prod = new ProductionNode("ruleName");
join.addDownstreamActivate(prod.leftActivate.bind(prod));
join.addDownstreamDeactivate(prod.leftDeactivate.bind(prod));

// 7. Insert facts via Session — flows WM → Alpha → JoinNode → ProductionNode
session.insert(e1, "Health", 100);

// 8. Assert
expect(prod.matches).toHaveLength(1);

For multi-condition rules, intermediate BetaMemory nodes connect joins in a chain, with each downstream BetaMemory forwarding new tokens to the next JoinNode via addDownstreamActivate(nextJoin.leftActivate.bind(nextJoin)).