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.
This commit is contained in:
parent
0259268e34
commit
0401295bbc
12 changed files with 1510 additions and 2 deletions
127
packages/rete/tests/golden/g3-derived-facts.test.ts
Normal file
127
packages/rete/tests/golden/g3-derived-facts.test.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* G3 — Derived facts (truth maintenance)
|
||||
*
|
||||
* Pararules origin: tests/test2.nim "derived facts" (thenFinally block):
|
||||
* rule getCharacter(Fact):
|
||||
* what:
|
||||
* (id, X, x)
|
||||
* (id, Y, y)
|
||||
* thenFinally:
|
||||
* let chars = session.queryAll(this)
|
||||
* session.insert(Derived, AllCharacters, chars)
|
||||
*
|
||||
* Our equivalent uses DerivedFactProduction, which inserts derived facts into
|
||||
* working memory when a match activates and retracts them when it withdraws.
|
||||
* Derived entity ids are negative (per SPEC §ID Authority).
|
||||
*
|
||||
* Network:
|
||||
* root → join(alphaX) → DerivedFactProduction (inserts "Double" = x*2 into WM)
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Session } from "../../src/session.js";
|
||||
import { BetaMemory } from "../../src/beta.js";
|
||||
import { Token } from "../../src/beta.js";
|
||||
import { JoinNode } from "../../src/join.js";
|
||||
import { DerivedFactProduction } from "../../src/derived.js";
|
||||
import type { EntityId } from "../../src/schema.js";
|
||||
import type { Bindings, TokenFact } from "../../src/beta.js";
|
||||
import type { DerivedFact } from "../../src/derived.js";
|
||||
|
||||
const ROOT_FACT: TokenFact = { id: 0 as EntityId, attr: "__root", value: null };
|
||||
|
||||
function buildDerivedNetwork(handler: (bindings: Bindings) => readonly DerivedFact[]) {
|
||||
const session = new Session({ autoFire: false });
|
||||
const alpha = session._getAlpha();
|
||||
const wm = session._getWM();
|
||||
|
||||
const rootMem = new BetaMemory();
|
||||
rootMem.leftActivate(new Token(null, ROOT_FACT, {}));
|
||||
|
||||
const alphaX = alpha.buildNode({ id: null, attr: "X" });
|
||||
const join = new JoinNode(rootMem, alphaX.memory, [], "x", "eid");
|
||||
alphaX.onActivate((id, attr, value) => join.rightActivate(id, attr, value));
|
||||
alphaX.onDeactivate((id, attr, value) => join.rightDeactivate(id, attr, value));
|
||||
|
||||
const derivedProd = new DerivedFactProduction("deriveDouble", wm, handler);
|
||||
join.addDownstreamActivate(derivedProd.leftActivate.bind(derivedProd));
|
||||
join.addDownstreamDeactivate(derivedProd.leftDeactivate.bind(derivedProd));
|
||||
|
||||
return { session, wm };
|
||||
}
|
||||
|
||||
describe("G3 — derived facts (truth maintenance)", () => {
|
||||
it("inserting a fact activates derived production and asserts Double=x*2 in WM", () => {
|
||||
const { session, wm } = buildDerivedNetwork((b) => [
|
||||
{ attr: "Double", value: (b["x"] as number) * 2 },
|
||||
]);
|
||||
|
||||
const e1 = session.nextId();
|
||||
session.insert(e1, "X", 5);
|
||||
|
||||
const allFacts = wm.allFacts();
|
||||
const doubleFact = allFacts.find((f) => f.attr === "Double");
|
||||
expect(doubleFact).toBeDefined();
|
||||
expect(doubleFact?.value).toBe(10);
|
||||
});
|
||||
|
||||
it("derived entity id is negative (per SPEC §ID Authority)", () => {
|
||||
const { session, wm } = buildDerivedNetwork((b) => [
|
||||
{ attr: "Score", value: b["x"] },
|
||||
]);
|
||||
|
||||
const e1 = session.nextId();
|
||||
session.insert(e1, "X", 7);
|
||||
|
||||
const allFacts = wm.allFacts();
|
||||
const scoreFact = allFacts.find((f) => f.attr === "Score");
|
||||
expect(scoreFact).toBeDefined();
|
||||
expect(scoreFact?.id as number).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it("retracting the source fact retracts the derived fact (truth maintenance)", () => {
|
||||
const { session, wm } = buildDerivedNetwork((b) => [
|
||||
{ attr: "Double", value: (b["x"] as number) * 2 },
|
||||
]);
|
||||
|
||||
const e1 = session.nextId();
|
||||
session.insert(e1, "X", 5);
|
||||
expect(wm.allFacts().find((f) => f.attr === "Double")).toBeDefined();
|
||||
|
||||
session.retract(e1, "X");
|
||||
expect(wm.allFacts().find((f) => f.attr === "Double")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("updating a fact retracts old derived and asserts new derived", () => {
|
||||
const { session, wm } = buildDerivedNetwork((b) => [
|
||||
{ attr: "Double", value: (b["x"] as number) * 2 },
|
||||
]);
|
||||
|
||||
const e1 = session.nextId();
|
||||
session.insert(e1, "X", 3); // Double = 6
|
||||
expect(wm.allFacts().find((f) => f.attr === "Double")?.value).toBe(6);
|
||||
|
||||
session.insert(e1, "X", 10); // Update → Double = 20
|
||||
const doubleFacts = wm.allFacts().filter((f) => f.attr === "Double");
|
||||
expect(doubleFacts).toHaveLength(1);
|
||||
expect(doubleFacts[0]?.value).toBe(20);
|
||||
});
|
||||
|
||||
it("two entities produce two independent derived facts with distinct negative ids", () => {
|
||||
const { session, wm } = buildDerivedNetwork((b) => [
|
||||
{ attr: "Double", value: (b["x"] as number) * 2 },
|
||||
]);
|
||||
|
||||
const e1 = session.nextId();
|
||||
const e2 = session.nextId();
|
||||
session.insert(e1, "X", 4);
|
||||
session.insert(e2, "X", 8);
|
||||
|
||||
const doubles = wm.allFacts().filter((f) => f.attr === "Double");
|
||||
expect(doubles).toHaveLength(2);
|
||||
expect(doubles.map((f) => f.value).sort((a, b) => (a as number) - (b as number))).toEqual([8, 16]);
|
||||
// All derived ids are negative and distinct
|
||||
const ids = doubles.map((f) => f.id as number);
|
||||
expect(ids.every((id) => id < 0)).toBe(true);
|
||||
expect(ids[0]).not.toBe(ids[1]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue