# 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): ```typescript // 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))`.