Node schema pivot: short name (label) + full-precision statement; description dropped
With real extracted data, full-sentence statements as canvas labels proved unworkable: 10-line label blocks, illegible overprint clusters, bottom-edge clipping. Per the user's 2026-09-08 decision: - GraphNode/ExtractedProposition gain `name` — a very short, glanceable label (glossary-headword style: "Will to power"), presentational only: not unique, not identity, never precise. Set at creation; link-appends never change it (first line defines; labels stay stable across sessions). - `statement` keeps its full precision and becomes the identity anchor; the detail card shows it under the name. `description` is dropped. - Extractor coins the name (freshest session context); auditor carries it through on create, rewording only clearly unglanceable names; linked propositions keep the existing node's name. - Renderer: canvas label = name; search matches name + statement; detail card = name (bold) + statement (muted). - Store guard requires name (pre-pivot node lines are skipped as legacy); vision.md node-schema bullet + amendments table updated (2026-09-08).
This commit is contained in:
@@ -99,6 +99,13 @@ const messagesField = z
|
||||
|
||||
const propositionSchema = z.object({
|
||||
id: z.string().describe("Local id: \"p1\", \"p2\", … — referenced by edges and attribution."),
|
||||
name: z
|
||||
.string()
|
||||
.describe(
|
||||
"A very short label for the proposition — a few words, a compact handle like a glossary headword " +
|
||||
'(\'Will to power\', \'Divine command theory\'). Not a sentence, needn\'t be unique or ' +
|
||||
"self-contained: the statement carries the precision. This becomes the node's label in the graph.",
|
||||
),
|
||||
statement: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -107,11 +114,6 @@ const propositionSchema = z.object({
|
||||
'"X". A misconception is stated as the TRUE correction ("Nietzsche did not endorse ' +
|
||||
'might-makes-right social Darwinism") — the wrongness lives in the quiz outcomes, never here.',
|
||||
),
|
||||
description: z
|
||||
.string()
|
||||
.describe(
|
||||
"The elaborated version of the same proposition — more words: scope, precision, what exactly it means. Still true, still world-facing.",
|
||||
),
|
||||
aliases: z.array(z.string()).describe("Verbatim phrasings this session used for the same proposition, if any."),
|
||||
messages: messagesField,
|
||||
evidence,
|
||||
@@ -168,8 +170,12 @@ misconception is NOT a node of its own wrong claim — state the TRUE correction
|
||||
quiz outcomes carry that the learner believed the wrong version.
|
||||
|
||||
1. PROPOSITIONS — every distinct reasoning-step-sized proposition the session touched:
|
||||
- Each is a self-contained TRUE statement ("statement"), plus its elaboration ("description" — more
|
||||
words, same proposition: scope, precision, what exactly it means).
|
||||
- Each has a "name" — a VERY short label, a few words, a compact handle like a glossary headword
|
||||
("Will to power", "Divine command theory"). Not a sentence; needn't be unique or self-contained.
|
||||
It becomes the node's label in the graph, so keep it glanceable: names are what one reads on
|
||||
the graph, statements are what one reads on the node.
|
||||
- And a "statement" — the self-contained TRUE proposition at full precision (the elaboration lives
|
||||
in the graph only as this one statement).
|
||||
- Include corrections of misconceptions the teacher articulated, and true facts explained in prose.
|
||||
- One reasoning step per proposition: never a topic blob, never a sub-step.
|
||||
- "messages": the [id] tags of every message where the proposition came up — provenance, report it.
|
||||
@@ -254,10 +260,12 @@ FAITHFULNESS — verify every candidate against the transcript:
|
||||
scale or drop.
|
||||
|
||||
IDENTITY — link-or-create, per proposition:
|
||||
- You see the current graph (every node: id, statement, description, aliases). For each verified proposition,
|
||||
- You see the current graph (every node: id, name, statement, aliases). For each verified proposition,
|
||||
decide: does an EXISTING node already state this same proposition?
|
||||
- If yes: set "linkTo" to that node's id. The session's phrasings will join its aliases and its provenance.
|
||||
- If no: omit "linkTo" and a new node is created.
|
||||
- If yes: set "linkTo" to that node's id. The session's phrasings will join its aliases and its provenance;
|
||||
the node keeps its existing name (labels stay stable across sessions — ignore your name for linked nodes).
|
||||
- If no: omit "linkTo" and a new node is created, with the extractor's name as its label. Reword only a
|
||||
clearly unglanceable name (an unwieldy sentence of a name); keep it short.
|
||||
- When unsure, omit — duplicates are recoverable later; wrong merges are not.
|
||||
|
||||
Also drop propositions not referenced by anything else ONLY if they have no other backing — an unreferenced but
|
||||
@@ -271,7 +279,7 @@ function graphView(nodes: GraphNode[]): string {
|
||||
if (nodes.length === 0) return "(the graph is currently empty — every proposition is a new node)";
|
||||
const lines = nodes.map(
|
||||
(n) =>
|
||||
`- ${n.id} :: ${n.statement}\n description: ${n.description}` +
|
||||
`- ${n.id} :: ${n.name}\n statement: ${n.statement}` +
|
||||
(n.aliases.length > 0 ? `\n aliases: ${n.aliases.join(" | ")}` : ""),
|
||||
);
|
||||
return lines.join("\n");
|
||||
|
||||
+22
-13
@@ -33,13 +33,18 @@ export interface ProvenancePoint {
|
||||
export interface GraphNode {
|
||||
/** Opaque stable id, store-assigned: "n_" + nanoid-like. */
|
||||
id: string;
|
||||
/** The minimal proposition — one true, self-contained statement. This is
|
||||
* BOTH the rendering label AND the identity key: the auditor recognizes
|
||||
* a re-encounter by judgment (link-or-create), not by string matching. */
|
||||
/** A very short label for the node — a compact handle like a glossary
|
||||
* headword ("Will to power", "Divine command theory"): what the graph
|
||||
* shows at a glance. Presentational only — not unique, not an identity
|
||||
* key, never precise: the statement carries the precision. Set at
|
||||
* creation (first line defines); a link-append never changes it. */
|
||||
name: string;
|
||||
/** The minimal proposition — one true, self-contained statement, at
|
||||
* full precision. The IDENTITY ANCHOR: the auditor recognizes a
|
||||
* re-encounter by judgment against this (link-or-create), not by
|
||||
* string matching. Shown in the detail card; the canvas label is the
|
||||
* name. */
|
||||
statement: string;
|
||||
/** The elaborated version — required. Same proposition, more words:
|
||||
* scope, precision, the "what exactly does it mean". */
|
||||
description: string;
|
||||
/** Verbatim phrasings sessions used for this proposition (the identity
|
||||
* audit trail — how the world said what the node says). */
|
||||
aliases: string[];
|
||||
@@ -108,10 +113,11 @@ export interface LedgerEvent {
|
||||
* (nodes/edges/events/mappings/sessions), all append-only.
|
||||
*
|
||||
* nodes.jsonl one line per node — the FIRST line for an id defines
|
||||
* statement/description/createdAt; later lines for the
|
||||
* name/statement/createdAt; later lines for the
|
||||
* same id are link-appends (a session's re-encounter):
|
||||
* their statements/aliases join `aliases`, their
|
||||
* provenance unions. getGraph merges by id.
|
||||
* provenance unions; the name stays the first line's.
|
||||
* getGraph merges by id.
|
||||
* edges.jsonl one line per edge ASSERTION; readers merge by
|
||||
* (from,to,type), unioning provenance.
|
||||
*
|
||||
@@ -136,13 +142,16 @@ export interface LedgerEvent {
|
||||
export interface ExtractedProposition {
|
||||
/** Extractor-local id ("p1", …) referenced by edges + attribution. */
|
||||
id: string;
|
||||
/** A very short label — a few words, a compact handle like a glossary
|
||||
* headword. Not a sentence, needn't be unique or self-contained: the
|
||||
* statement carries the precision. Becomes the node's name on create;
|
||||
* on link, the existing node keeps its name. */
|
||||
name: string;
|
||||
/** The minimal proposition — a true, self-contained, world-facing
|
||||
* statement. No learner / teacher / session references: misconceptions
|
||||
* are stated as the true correction, and wrongness lives in the event
|
||||
* ledger, never in the node. */
|
||||
* statement, at full precision. No learner / teacher / session
|
||||
* references: misconceptions are stated as the true correction, and
|
||||
* wrongness lives in the event ledger, never in the node. */
|
||||
statement: string;
|
||||
/** The elaborated version — required. */
|
||||
description: string;
|
||||
/** Verbatim phrasings the session used. */
|
||||
aliases: string[];
|
||||
/** The message ids where this proposition came up — provenance. */
|
||||
|
||||
+7
-4
@@ -166,10 +166,12 @@ export function mergeFindings(chatId: string, audit: AuditResult, deps: GraphDep
|
||||
// line — the session's phrasings (statement + aliases) join the
|
||||
// node's aliases, the provenance unions. Reads merge by id.
|
||||
graphNodeId = p.linkTo;
|
||||
// Link-append: phrasings + provenance join at read time (the
|
||||
// first line's name/statement/createdAt stand).
|
||||
ledgers.appendNode({
|
||||
id: graphNodeId,
|
||||
name: p.name,
|
||||
statement: p.statement,
|
||||
description: p.description,
|
||||
aliases: [...p.aliases],
|
||||
createdAt: now,
|
||||
provenance: points,
|
||||
@@ -177,8 +179,8 @@ export function mergeFindings(chatId: string, audit: AuditResult, deps: GraphDep
|
||||
} else {
|
||||
const node: GraphNode = {
|
||||
id: newGraphNodeId(),
|
||||
name: p.name,
|
||||
statement: p.statement,
|
||||
description: p.description,
|
||||
aliases: [...p.aliases],
|
||||
createdAt: now,
|
||||
provenance: points,
|
||||
@@ -329,8 +331,9 @@ export interface DerivedGraph {
|
||||
|
||||
/**
|
||||
* The whole graph from the ledgers. Node LINES may repeat an id (link-
|
||||
* appends): the FIRST line defines statement/description/createdAt; later
|
||||
* lines' statements and aliases join `aliases`, provenance unions. Edge
|
||||
* appends): the FIRST line defines name/statement/createdAt; later
|
||||
* lines' statements and aliases join `aliases`, provenance unions — the
|
||||
* name never changes after creation. Edge
|
||||
* LINES are assertions, merged by (from,to,type) with unioned provenance,
|
||||
* first-seen order. `store events, derive state` — this is the only reader
|
||||
* that matters.
|
||||
|
||||
+1
-1
@@ -83,8 +83,8 @@ const isProvenancePoint = (v: unknown): v is ProvenancePoint =>
|
||||
const isGraphNode = (v: unknown): v is GraphNode =>
|
||||
isRecord(v) &&
|
||||
isString(v.id) &&
|
||||
isString(v.name) &&
|
||||
isString(v.statement) &&
|
||||
isString(v.description) &&
|
||||
Array.isArray(v.aliases) &&
|
||||
v.aliases.every(isString) &&
|
||||
isString(v.createdAt) &&
|
||||
|
||||
Reference in New Issue
Block a user