The remedy for undeletable nodes (preserve-graph orphans from session
deletion; notes-import nodes with no chat behind them): DELETE
/api/graph/nodes/[id], full-death semantics — the user pointing at the
node IS the intent; references from other sessions do NOT preserve it.
- core/graph-deletion.ts deleteNodeTraces: the node's every line, every
edge touching it, every quiz/observation event naming it, its mapping
lines, and its resting position — one synchronous atomic rewriteLedgers
call; only the ledgers that changed rewrite (mtimes stay honest). Blast
radius is exactly the rows naming the node id; notes source records stay
(deleting a proposition is not deleting the file). Derived data
self-heals on read: the birth cluster shrinks below MIN_CLUSTER_SIZE and
stops rendering.
- nodeDeletionImpact (GET /api/graph/nodes/[id]): the confirm dialog's
pre-flight facts — edge count, quiz/observation history, referencing
sessions. The snapshot's edges + nodeSessions cover part of this, but
the raw event history was nowhere on the client — hence the read.
- Guards (server/graph.ts deleteGraphNode), decided + documented: NO
turn/jobs gates (they never write graph ledgers; the orchestration is
synchronous end to end, nothing interleaves). THE EXTRACTION GATE: 409
while ANY pipeline run is registered — a mid-flight run holds
pre-deletion findings and could re-reference the dying node. The run is
never aborted (its spend belongs to another session — outside this
deletion's blast radius; the UI retries once the dock drains. A run
started after the deletion is legitimate re-learning: fresh ledgers,
fresh node id, and mergeFindings' existingIds guard ignores linkTo ids
that no longer exist — no dangling edges, no resurrection.
Live-verified on dev data: impact read, full-death stats, honest 404 on
repeat, and the file cluster fell 3→2 and unrendered itself.
859/859 tests, typecheck/lint/build green.
EOF
)
Part 1 — the ledger + remnants interlude (master-dispatched, owner ruling
2026-09-23: nothing configures the graph beyond chat models):
- The embedding ledger surface is gone: readEmbeddings/writeEmbeddings
ports, the EmbeddingsLedger contract, EMBEDDINGS_FILE (config, store
impls, graphRevision source list), and graph-deletion's vector-pruning
step. On-disk data files stay untouched (inert history).
- The extraction embedder seam dies wholesale: ExtractionPipelineDeps
drops the embedder dep (chat-handler + backfill/import-notes stubs go
with it), the 'embedding' phase leaves ExtractionPhase, the usage
capture/persist helpers die, and stages.embedding leaves
ExtractionRunUsage (historical records keep their shape — reads are
lenient). Extraction is two stages again, like the local-embedder era.
- Consumer-less remnants: latestQuizOutcomes + QUIZ_EVENT_KINDS,
clusterContentId, matchClusterLabelings + Community + ClusterLabeling,
the readClusterLabels/appendClusterLabel port seams + store impls +
LEDGER_FILES.clusterLabels. NamingRunUsage/readNamingRuns STAY
(standing ruling — the cost pill renders historical naming spend);
clusterHue + MIN_CLUSTER_SIZE stay (live).
- The 'labeler' role dies: BACKGROUND_MODEL_ROLES, ROLE_SLOT, ModelRole,
the catalog pin projections, and the UI roster strings. RolePins keys
shrink with the union.
The notes birth fallback (owner-approved 2026-09-23, surfaced by the
interlude's live smoke: production notes imports create provenance-EMPTY
nodes (importNotes passes messages: null — the file's anchor is the
mapping line), so the provenance-only birth rule left every imported
file unclustered and invisible to the prober's session list, against the
locked file-birth ruling. A provenance-empty node now births under its
FIRST mapping line's chatId (ledger order); only notes-born nodes ever
hit the fallback (chat nodes always carry provenance; a re-encountered
node's link-append re-opens the provenance rule). Deletion-safe: mapping
lines of deleted chats are stripped alongside their provenance.
Live-verified on port 3004: a fresh notes import clusters under its
src_ id with the file path as the label, end to end with no embedder.
845/845 tests, typecheck/lint/build green.
EOF
)
DELETE /api/chats/[id]?mode=preserve-graph|delete-nodes (mode required):
- preserve-graph: only extraction bookkeeping dies (coverage rows,
legacy checkpoints) — graph ledgers byte-untouched
- delete-nodes: the chat's ledger traces die. Node survival = surviving
provenance point / mapping line (notes imports carry empty
provenance) / surviving event. The creator's aliases + provenance are
stripped from a surviving node's create line (owner ruling
2026-09-23: name/statement/createdAt are the shared identity);
orphan nodes, their edges, events, mappings, positions, embedding
vectors and cluster members die; cluster labels stay (immutable)
Gates: 404 unknown, 409 turn_in_flight / jobs_running (with jobIds),
503 extraction_winding_down after a 10s abort-and-poll; the purge is
one sync block (ledgers → jobs → chat record last). rewriteLedgers is
atomic (tmp + rename). graphRevision (max mtime over graph data files)
rides the activity feed + the response as the change stamp.
Anti-resurrection: the chat pipeline re-checks chat liveness at each
merge — a run that slips past the wind-down aborts with nothing merged
or checkpointed; its partial spend dies with the chat.