Files
learnharness/tests/record-adapter.test.ts
T
amosblomqvist 6b13583e96 wave 7: the prober reads its map — graph-context injection dies whole
The owner's zero-configuration ruling (2026-09-23): nothing configures
the graph beyond chat models. The prior-knowledge injection — its budget
dial, its runner, its marker part, its dock/dispatch entries — is
removed across all bands; the prober now reads the learner's persistent
knowledge map on demand through two gated tools instead:

- list_sessions: every birth session, names + kinds + counts + dates
  (graphSessionIndex, Platform's core seam)
- list_propositions(session): what was born there + the session's own
  latest understanding level + that evaluation's date
  (graphSessionPropositions) — names/levels/dating ONLY, no statement
  text (owner ruling: the map's organization is the prober's business,
  its prose is not)

Registered via createProberTools when chat-handler passes graphStore
(the graphToolsEnabled gate decided server-side, per-turn fresh); the
prober doctrine gains the map-review nudge at session start.

Also: runner's JobKind special-casing collapses (KIND_ROLE/KIND_RUNNERS
are plain records again), semantic-clusters.ts dies with its test, the
embedder dep's doc tells the wave-7 stub story, and ports.ts's
readClusterLabels/readNamingRuns surface stays untouched for Platform's
ledger-port interlude.

848/848 tests, typecheck/lint/build green.
2026-09-23 15:24:48 +03:00

89 lines
3.7 KiB
TypeScript

/**
* Unit tests — the record→pi adapter's SYSTEM MESSAGE flattening (the
* custom-message scheme, user-decided 2026-09-12).
*
* Wake and verdict messages are user-role messages carrying ONLY a marker
* data part — no human text exists. The adapter owns their model-facing
* wording (flattens the marker to the canonical text from core contracts):
* the transcript never persists fake human speech, and the wording can't
* drift between the seam, the tests, and reality.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import type { UIMessage } from "ai";
import { recordToPiMessages } from "../src/server/pi/record-adapter.ts";
import { HANDOFF_TEXT, verdictText } from "../src/core/contracts/chat.ts";
import { buildAbortSteer, buildProbeHandoffSteer } from "../src/core/contracts/jobs.ts";
function userText(text: string): UIMessage {
return { id: `u_${text.length}`, role: "user", parts: [{ type: "text", text }] };
}
function userParts(parts: unknown[]): UIMessage {
return { id: "u_sys", role: "user", parts: parts as UIMessage["parts"] };
}
const texts = (msgs: ReturnType<typeof recordToPiMessages>): string[] => {
const out: string[] = [];
for (const m of msgs) {
if (m.role === "user") {
for (const c of m.content) {
if ((c as { type?: string }).type === "text") out.push((c as { text: string }).text);
}
}
}
return out;
};
describe("record-adapter: system message flattening", () => {
it("an ACCEPT verdict flattens to the canonical model-facing text (no human speech persisted)", () => {
const out = recordToPiMessages([
userText("teach me"),
userParts([{ type: "data-verdict", id: "vd_1", data: { accepted: true, at: "…" } }]),
]);
assert.deepEqual(texts(out), ["teach me", verdictText(true)]);
assert.equal(texts(out)[1], "The learner accepted the plan — begin teaching.");
});
it("a DECLINE verdict flattens to the canonical revision text", () => {
const out = recordToPiMessages([
userParts([{ type: "data-verdict", id: "vd_2", data: { accepted: false, at: "…" } }]),
]);
assert.deepEqual(texts(out), [verdictText(false)]);
assert.match(texts(out)[0]!, /revised/);
});
it("the probe handoff steer flattens to the canonical plan-authoring instruction", () => {
const handoff = buildProbeHandoffSteer("chat_x");
assert.equal(handoff.parts.length, 1, "the handoff is marker-ONLY — no text part");
assert.equal(handoff.parts[0]!.type, "data-probe-handoff");
const out = recordToPiMessages([handoff]);
assert.deepEqual(texts(out), [HANDOFF_TEXT]);
assert.match(texts(out)[0]!, /plan_init/);
});
it("note + verdict (the two-message accept) reach the model as two user texts, in order", () => {
const out = recordToPiMessages([
userText("Start with the basics, skip the history."),
userParts([{ type: "data-verdict", id: "vd_3", data: { accepted: true, at: "…" } }]),
]);
assert.deepEqual(texts(out), ["Start with the basics, skip the history.", verdictText(true)]);
});
it("sub-agent steers keep their text (payload, not boilerplate): the artifact part adds nothing", () => {
const abort = buildAbortSteer({ id: "j1", kind: "research", toolCallId: "c1" });
const out = recordToPiMessages([abort]);
const got = texts(out);
assert.equal(got.length, 1, "one user text — the marker part is UI-only");
assert.match(got[0]!, /aborted/i);
});
it("a pure UI data part with no system meaning still never reaches the model", () => {
const out = recordToPiMessages([
userParts([{ type: "data-plan-block", id: "pb", data: { kind: "full", at: "…", current: null, nodes: [], edges: [] } }]),
]);
assert.equal(out.length, 0, "no model face at all — skipped");
});
});