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.
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)).