Files
learnharness/tests/graph-view-filters.test.ts
amosblomqvist 91341a93da digest-aware serialized extraction + chain simplification replace the spine stage
Owner live-test verdict on the spine (2026-09-30): no perceptible gain —
the real problems (restatement nodes, shallow re-derivations, same
conclusion from many angles) are mint-time issues a post-hoc chain
minter cannot fix. This arc moves assembly INTO the extractor and adds
the deterministic dedupe the owner specified:

- Digest-aware extraction: each stretch's extractor prompt carries THE
  GRAPH SO FAR (its own source's merged propositions + chains, ids
  verbatim); build on existing ids instead of restating, lean on
  established conclusions as premises. Mixed-namespace chain endpoints
  (fresh p-ids OR existing n_-ids) validated at the submit seam.
- Serialized per source: a source's batches/chunks run strictly in
  order (each extract reads every earlier merge); cross-source runs
  stay parallel; source-wide abort stops the loop. The spine stage,
  role, phase, usage stage, cost row, and setup roster are gone.
- Core-ideas prompt: goal reframed as uncovering core ideas and showing
  how everything derives from the foundations; direction upward, prefer
  lean derivations, never re-derive what the graph already holds.
- Chain simplification (merger maintenance): simplifyChains drops
  provably redundant chains at every merge — prefer-fewest-premises +
  ancestry-cover ([A,B]⟹X with [X,C]⟹Y covers [A,B,C]⟹Y, transitive);
  same-key provenance lines drop together; rewriteLedgers under the
  audit lock; droppedChains counted separately in stats.

Chunk budget stays 8192 (owner: keep for now, maybe lower later).
2026-09-30 17:44:37 +03:00

70 lines
1.9 KiB
TypeScript

/**
* Legend/filters predicate tests — the pure half of the graph-view filters
* panel (isNodeVisible); the sigma reducers just consume its verdict.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { displayCostTotals, isNodeVisible } from "../components/graph-view.tsx";
const node = {
name: "Money multiplier",
statement: "The maximum ratio of broad money a bank can create from each unit of base money.",
};
describe("isNodeVisible (search predicate)", () => {
it("is visible with an empty query", () => {
assert.equal(isNodeVisible(node, ""), true);
});
it("search matches the statement case-insensitively", () => {
assert.equal(isNodeVisible(node, "MONEY multiplier"), true);
});
it("search matches the statement when the name does not", () => {
assert.equal(isNodeVisible(node, "MAXIMUM ratio"), true);
assert.equal(isNodeVisible(node, " velocity"), false);
});
it("search matches the name when the statement does not", () => {
assert.equal(isNodeVisible(node, "money MULTIPLIER"), true);
assert.equal(isNodeVisible(node, " banking"), false);
});
it("whitespace-only query is no filter", () => {
assert.equal(isNodeVisible(node, " "), true);
});
});
describe("displayCostTotals (naming-free cost fold)", () => {
const stage = (dollars: number, unpricedRuns = 0) => ({
runs: dollars > 0 ? 1 : 0,
dollars,
unpricedRuns,
tokens: {
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
cacheWriteTokens: 0,
reasoningTokens: 0,
},
});
const costs = {
stages: {
extraction: stage(1.25, 1),
audit: stage(0.75),
naming: stage(9.99, 2), // dead channel — must never reach the display
},
} as const;
it("sums the live stages only — the dead namer's spend is invisible", () => {
assert.equal(displayCostTotals(costs).totalDollars, 2);
});
it("unpriced counts fold the same two stages", () => {
assert.equal(displayCostTotals(costs).unpricedRuns, 1);
});
});