Auditor submits links ONLY — the edges argument is gone (owner, 2026-09-28 evening)
Hypothesis under test by the owner: coherent extractions turn to mush after merging because the auditor minted too many bridging edges of its own — inference, not identity, and out of the auditor's scope. The graph now connects across sources solely through identified equivalences; relations between propositions come from the extractor alone. - auditSchema drops 'edges' (submit_review = links only); the submit-seam required-field doctrine reworded per tool - AUDITOR_INSTRUCTIONS: the BRIDGING EDGES section dies; deliverable is the identity verdicts only - AuditVerdicts drops edges; ExtractedEdge endpoints are extractor-local ids only; mergeFindings stops consuming verdict edges and its resolve() drops the raw-node-id branch (dead — the extractor never sees graph ids) - extractor untouched: submit_findings keeps propositions + follows/ supports edges, required as before - tests: 17 submit fixtures + 9 typed verdict literals slimmed to links - docs: architecture.md division-of-trust + pipeline diagram updated
This commit is contained in:
+17
-20
@@ -367,7 +367,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
it("stringified-payload heal (owner, 2026-09-28): propositions arriving as ONE JSON string still validate and merge", async () => {
|
||||
const { chatId } = fixtureChat();
|
||||
const ledgers = memoryLedgers();
|
||||
const verdict: AuditVerdicts = { links: [], edges: [] };
|
||||
const verdict: AuditVerdicts = { links: [] };
|
||||
|
||||
// The weak-model failure from the OpenRouter log: the whole array
|
||||
// emitted as a JSON-STRING ("propositions.0: must be object").
|
||||
@@ -400,7 +400,6 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
});
|
||||
const verdict: AuditVerdicts = {
|
||||
links: [{ id: FINDINGS.propositions[0]!.id, linkTo: existingId }],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
const pipeline = createExtractionPipeline({
|
||||
@@ -515,7 +514,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
const { chatId } = fixtureChat();
|
||||
const ledgers = memoryLedgers();
|
||||
// Empty verdicts: everything creates (verdicts-only, 2026-09-28).
|
||||
const verdict: AuditVerdicts = { links: [], edges: [] };
|
||||
const verdict: AuditVerdicts = { links: [] };
|
||||
const pipeline = createExtractionPipeline({
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
@@ -534,7 +533,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
it("engineFor fills the stages (model slots wave): the chain replaces config pins", async () => {
|
||||
const { chatId } = fixtureChat();
|
||||
const ledgers = memoryLedgers();
|
||||
const verdict: AuditVerdicts = { links: [], edges: [] };
|
||||
const verdict: AuditVerdicts = { links: [] };
|
||||
const consulted: string[] = [];
|
||||
const pipeline = createExtractionPipeline({
|
||||
chatStore,
|
||||
@@ -561,7 +560,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
it("direct test values still WIN over engineFor (the priority pin)", async () => {
|
||||
const { chatId } = fixtureChat();
|
||||
const ledgers = memoryLedgers();
|
||||
const verdict: AuditVerdicts = { links: [], edges: [] };
|
||||
const verdict: AuditVerdicts = { links: [] };
|
||||
const pipeline = createExtractionPipeline({
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
@@ -597,7 +596,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
extractor: silentModel(),
|
||||
auditor: submitModel("submit_review", { links: [], edges: [] }),
|
||||
auditor: submitModel("submit_review", { links: [] }),
|
||||
});
|
||||
assert.deepEqual(await failingExtractor.runExtraction(chatId), { nodes: 0, edges: 0 });
|
||||
assert.equal(ledgers.readCheckpoints().length, 0);
|
||||
@@ -619,7 +618,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
engineFor: (role) =>
|
||||
role === "extractor" ? fauxEngine([countingStep]) : submitModel("submit_review", { links: [], edges: [] }),
|
||||
role === "extractor" ? fauxEngine([countingStep]) : submitModel("submit_review", { links: [] }),
|
||||
});
|
||||
assert.deepEqual(await mixed.runExtraction(chunkyId), { nodes: 2, edges: 1 }, "batch 1's merge survives batch 2's failure");
|
||||
assert.equal(invocations, 2);
|
||||
@@ -647,7 +646,6 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
const phaseOf = () => extractionStatuses().find((r) => r.chatId === chatId)?.phase;
|
||||
const verdict: AuditVerdicts = {
|
||||
links: [{ id: FINDINGS.propositions[0]!.id, linkTo: existingId }],
|
||||
edges: [],
|
||||
};
|
||||
const pipeline = createExtractionPipeline({
|
||||
chatStore,
|
||||
@@ -681,7 +679,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
extractor: submitModel("submit_findings", findingsA),
|
||||
auditor: gatedSubmitModel("submit_review", { links: [], edges: [] }, gate, () => {
|
||||
auditor: gatedSubmitModel("submit_review", { links: [] }, gate, () => {
|
||||
aAuditing = true;
|
||||
}),
|
||||
});
|
||||
@@ -698,7 +696,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
// THE pin: when B's auditor is finally invoked, A's merge must
|
||||
// already be in the ledgers — B explores the post-merge graph,
|
||||
// which is the entire point of the gate.
|
||||
auditor: submitModel("submit_review", { links: [], edges: [] }, () => {
|
||||
auditor: submitModel("submit_review", { links: [] }, () => {
|
||||
bAuditorCalled = true;
|
||||
bSawAlphasMerge = ledgers.readNodes().some((n) => n.name === "Alpha claim");
|
||||
}),
|
||||
@@ -735,7 +733,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
extractor: submitModel("submit_findings", single("pa", "Alpha claim", "Alpha statement")),
|
||||
auditor: gatedSubmitModel("submit_review", { links: [], edges: [] }, gate),
|
||||
auditor: gatedSubmitModel("submit_review", { links: [] }, gate),
|
||||
});
|
||||
const runA = pipelineA.runExtraction(a.chatId);
|
||||
await until(() => extractionStatuses().some((r) => r.chatId === a.chatId && r.phase === "auditing"));
|
||||
@@ -745,7 +743,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
extractor: submitModel("submit_findings", single("pb", "Beta claim", "Beta statement")),
|
||||
auditor: submitModel("submit_review", { links: [], edges: [] }, () => {
|
||||
auditor: submitModel("submit_review", { links: [] }, () => {
|
||||
bAuditorCalled = true;
|
||||
}),
|
||||
});
|
||||
@@ -767,7 +765,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
extractor: submitModel("submit_findings", single("pc", "Gamma claim", "Gamma statement")),
|
||||
auditor: submitModel("submit_review", { links: [], edges: [] }),
|
||||
auditor: submitModel("submit_review", { links: [] }),
|
||||
});
|
||||
assert.deepEqual(await pipelineC.runExtraction(c.chatId), { nodes: 1, edges: 0 });
|
||||
});
|
||||
@@ -787,7 +785,6 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
const usageSeen: (number | undefined)[] = [];
|
||||
const verdict: AuditVerdicts = {
|
||||
links: [{ id: FINDINGS.propositions[0]!.id, linkTo: existingId }],
|
||||
edges: [],
|
||||
};
|
||||
const pipeline = createExtractionPipeline({
|
||||
chatStore,
|
||||
@@ -833,7 +830,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
extractor: hangingModel(), // hangs until aborted — no step finishes
|
||||
auditor: submitModel("submit_review", { links: [], edges: [] }),
|
||||
auditor: submitModel("submit_review", { links: [] }),
|
||||
});
|
||||
const runPromise = pipeline.runExtraction(chatId);
|
||||
|
||||
@@ -1057,7 +1054,7 @@ describe("the notes front door (importNotes — format-agnostic)", () => {
|
||||
engineFor: (role) =>
|
||||
role === "extractor"
|
||||
? extractorStep()
|
||||
: submitModel("submit_review", { links: [], edges: [] }),
|
||||
: submitModel("submit_review", { links: [] }),
|
||||
getChunkTokens: () => 20, // 80 chars/line-groups → several chunks
|
||||
});
|
||||
const stats = await pipeline.importNotes({ filePath: "/vault/big.md", text });
|
||||
@@ -1092,7 +1089,7 @@ describe("the notes front door (importNotes — format-agnostic)", () => {
|
||||
const pipeline = createExtractionPipeline({
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
engineFor: (role) => (role === "extractor" ? noEdges() : submitModel("submit_review", { links: [], edges: [] })),
|
||||
engineFor: (role) => (role === "extractor" ? noEdges() : submitModel("submit_review", { links: [] })),
|
||||
});
|
||||
// Isolated job failure (per-chunk jobs, owner 2026-09-28): the run
|
||||
// resolves zeros — no throw — and still nothing merged.
|
||||
@@ -1398,7 +1395,7 @@ describe("per-chunk parallel jobs (owner, 2026-09-28)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
engineFor: (role) =>
|
||||
role === "extractor" ? fauxEngine([abortingStep]) : submitModel("submit_review", { links: [], edges: [] }),
|
||||
role === "extractor" ? fauxEngine([abortingStep]) : submitModel("submit_review", { links: [] }),
|
||||
getChunkTokens: () => 20,
|
||||
});
|
||||
const runPromise = pipeline.importNotes({ filePath: "/vault/parallel.md", text: wideText() });
|
||||
@@ -1440,7 +1437,7 @@ describe("per-chunk parallel jobs (owner, 2026-09-28)", () => {
|
||||
{ stopReason: "toolUse" },
|
||||
);
|
||||
const reviewMessage = fauxAssistantMessage(
|
||||
[fauxToolCall("submit_review", { links: [], edges: [] } as unknown as ToolCall["arguments"], { id: "review-call" })],
|
||||
[fauxToolCall("submit_review", { links: [] } as unknown as ToolCall["arguments"], { id: "review-call" })],
|
||||
{ stopReason: "toolUse" },
|
||||
);
|
||||
const slowExtract: FauxResponseStep = async () => {
|
||||
@@ -1485,7 +1482,7 @@ describe("the session-deletion guard (chat deleted mid-run)", () => {
|
||||
chatStore,
|
||||
graphStore: ledgers,
|
||||
extractor: submitModel("submit_findings", FINDINGS),
|
||||
auditor: submitModel("submit_review", { links: [], edges: [] }, () => chatStore.deleteChat(chatId)),
|
||||
auditor: submitModel("submit_review", { links: [] }, () => chatStore.deleteChat(chatId)),
|
||||
});
|
||||
const stats = await pipeline.runExtraction(chatId);
|
||||
|
||||
|
||||
+8
-8
@@ -256,7 +256,7 @@ function fixtureFindings(): SessionFindings {
|
||||
/** The auditor's verdicts when nothing links: an empty verdict set is a
|
||||
* valid full review (verdicts-only, owner 2026-09-28). */
|
||||
function fixtureVerdicts(): AuditVerdicts {
|
||||
return { links: [], edges: [] };
|
||||
return { links: [] };
|
||||
}
|
||||
|
||||
/** The chat every merger test folds (shared, so ordering is intentional). */
|
||||
@@ -359,7 +359,7 @@ describe("the merger (mergeFindings)", () => {
|
||||
},
|
||||
// VERDICTS-ONLY: the identity call is a link entry, not a field
|
||||
// on the proposition (owner, 2026-09-28).
|
||||
{ links: [{ id: "q1", linkTo: alphaId }], edges: [] },
|
||||
{ links: [{ id: "q1", linkTo: alphaId }] },
|
||||
graphDeps,
|
||||
sessionSource(chat2.id),
|
||||
);
|
||||
@@ -397,7 +397,7 @@ describe("the merger (mergeFindings)", () => {
|
||||
],
|
||||
edges: [],
|
||||
},
|
||||
{ links: [], edges: [] },
|
||||
{ links: [] },
|
||||
graphDeps,
|
||||
{ foldedAt: "2026-09-13T00:00:00.000Z" },
|
||||
);
|
||||
@@ -430,7 +430,7 @@ describe("the merger (mergeFindings)", () => {
|
||||
{ from: "p1", to: "p3", type: "follows", },
|
||||
],
|
||||
},
|
||||
{ links: [], edges: [] },
|
||||
{ links: [] },
|
||||
graphDeps,
|
||||
{ foldedAt: "2026-09-13T00:00:00.000Z" },
|
||||
);
|
||||
@@ -482,7 +482,7 @@ describe("the merger (mergeFindings)", () => {
|
||||
edges: [],
|
||||
},
|
||||
// empty verdicts — identity is the auditor's call alone
|
||||
{ links: [], edges: [] },
|
||||
{ links: [] },
|
||||
graphDeps,
|
||||
sessionSource(chat3.id),
|
||||
);
|
||||
@@ -506,7 +506,7 @@ describe("the merger (mergeFindings)", () => {
|
||||
],
|
||||
edges: [],
|
||||
},
|
||||
{ links: [], edges: [] },
|
||||
{ links: [] },
|
||||
graphDeps,
|
||||
{ foldedAt: "2026-09-13T00:00:00.000Z" },
|
||||
);
|
||||
@@ -610,7 +610,7 @@ describe("session deletion (deleteChatTraces + deleteChatSession, wave 6)", () =
|
||||
],
|
||||
edges: [{ from: "p1", to: "p2", type: "follows", }],
|
||||
};
|
||||
mergeFindings(dA.id, findingsA, { links: [], edges: [] }, graphDeps, sessionSource(dA.id));
|
||||
mergeFindings(dA.id, findingsA, { links: [] }, graphDeps, sessionSource(dA.id));
|
||||
// Resolve through THIS session's mappings — the shared ledger holds
|
||||
// lookalike statements from earlier tests, so statement-lookup would
|
||||
// link dB to the wrong node.
|
||||
@@ -636,7 +636,7 @@ describe("session deletion (deleteChatTraces + deleteChatSession, wave 6)", () =
|
||||
],
|
||||
edges: [{ from: "q1", to: "q2", type: "follows", }],
|
||||
};
|
||||
mergeFindings(dB.id, findingsB, { links: [{ id: "q1", linkTo: xId }], edges: [] }, graphDeps, sessionSource(dB.id));
|
||||
mergeFindings(dB.id, findingsB, { links: [{ id: "q1", linkTo: xId }] }, graphDeps, sessionSource(dB.id));
|
||||
const zId = graphStore.readMappings().find((m) => m.chatId === dB.id && m.sourceId === "q2")!.graphNodeId;
|
||||
|
||||
// Coverage rows (both chats) + positions garnish — the deletion
|
||||
|
||||
Reference in New Issue
Block a user