Graph nodes gain a name; nodes are objective facts (no learner/session references)

Schema: GraphNode + ExtractedProposition carry 'name' — a short 2-6 word
header, the rendering handle. Node theory made explicit: a node is an
objective world-facing fact; the learner's relationship to it lives
entirely in the event ledger (state labels are derived, never stored).
Extractor + auditor instructions enforce it — misconceptions state the
wrong idea itself as a claim, never 'the learner believes Y'. Legacy
ledger lines without a name derive one from the statement on read.
graph-view: labels use the name; the detail panel shows name as header
and statement as body.
This commit is contained in:
Amos
2026-09-01 03:34:47 +03:00
parent 49a6a6d9a0
commit 2e4e43b8c4
9 changed files with 84 additions and 14 deletions
+2
View File
@@ -135,6 +135,7 @@ const FINDINGS: SessionFindings = {
propositions: [
{
id: "p1",
name: "Chain rule",
statement: "Chain rule intuition",
aliases: [],
kind: "truth",
@@ -143,6 +144,7 @@ const FINDINGS: SessionFindings = {
},
{
id: "p2",
name: "Applying chain rule",
statement: "Applying the chain rule",
aliases: [],
kind: "truth",
+3 -1
View File
@@ -43,7 +43,7 @@ import { PATHS } from "../config.ts";
// ─── fixtures ────────────────────────────────────────────────────────────────
function node(id: string, statement: string, over: Partial<GraphNode> = {}): GraphNode {
return { id, statement, aliases: [], kind: "truth", createdAt: "2026-08-22T00:00:00.000Z", ...over };
return { id, name: statement.split(";")[0]!, statement, aliases: [], kind: "truth", createdAt: "2026-08-22T00:00:00.000Z", ...over };
}
function edge(from: string, to: string): GraphEdge {
@@ -329,6 +329,7 @@ function treeFindings(tree: ReturnType<typeof createTree>): SessionFindings {
return {
propositions: tree.nodes.map((n, i) => ({
id: `p${i + 1}`,
name: n.title,
statement: n.title,
aliases: [],
kind: "truth" as const,
@@ -380,6 +381,7 @@ describe("exact-match merge (merger identity resolution)", () => {
const ledgers = memoryLedgers();
ledgers.appendNode({
id: "n_alias",
name: "Differentiation",
statement: "Differentiation basics",
aliases: ["the derivative"],
kind: "truth",
+15 -1
View File
@@ -136,6 +136,7 @@ function treeFindings(tree: PlanTree): SessionFindings {
return {
propositions: tree.nodes.map((n, i) => ({
id: `p${i + 1}`,
name: n.title,
statement: n.title,
aliases: [],
kind: "truth" as const,
@@ -428,7 +429,7 @@ describe("the trigger helper (pendingQuizAnswers)", () => {
describe("graph ledger store", () => {
it("round-trips lines and skips corrupt / foreign-shaped lines (strict reads)", () => {
const node = { id: "n_storetest", statement: "Store test", aliases: [], kind: "truth" as const, createdAt: "2026-08-22T00:00:00.000Z" };
const node = { id: "n_storetest", name: "Store test", statement: "Store test", aliases: [], kind: "truth" as const, createdAt: "2026-08-22T00:00:00.000Z" };
graphStore.appendNode(node);
// A torn append and a legacy V0-shaped node (different schema) — both skipped.
fs.appendFileSync(
@@ -439,5 +440,18 @@ describe("graph ledger store", () => {
const nodes = graphStore.readNodes().filter((n) => n.id === "n_storetest" || n.id === "legacy");
assert.equal(nodes.length, 1);
assert.equal(nodes[0]!.statement, "Store test");
assert.equal(nodes[0]!.name, "Store test");
});
it("derives a name for pre-name ledger lines (legacy reads)", () => {
// A full, valid node line written before `name` existed.
fs.appendFileSync(
LEDGER_FILES.nodes,
`${JSON.stringify({ id: "n_prename", statement: "Derivatives measure instantaneous change; they are limits.", aliases: [], kind: "truth", createdAt: "2026-08-22T00:00:00.000Z" })}\n`,
"utf-8",
);
const node = graphStore.readNodes().find((n) => n.id === "n_prename");
assert.ok(node);
assert.equal(node.name, "Derivatives measure instantaneous change");
});
});