mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
refactor(coach): split the chat turn into a pipeline under lib/ai/coach/turn
The route keeps the front door: auth, the coach capability, the body read and schema, the rate limit, the locale and the inbound refusal screen. Everything after it moves into one module per step: conversation, context, chain, budget, model, reply-guards, cards, provenance, persist, sse, errors and pipeline. The capability re-check at the egress site stays owned by the route and is handed to the pipeline, so the capability inventory still reads both calls there. The stream producer now writes through a TurnEmitter created inside createSseStream and passed to the model step, which is where live progress frames will come from. Nothing writes to it early yet. No behaviour change: the golden transcripts pass byte-identical, the existing route tests pass unmodified, and every annotate, audit and error-code name appears as often as before. Guards follow the code. The fence call-site check reads turn/context.ts, the German-default locale ban covers every file in turn/, and the fenced module-graph test now asserts that turn/sse.ts and turn/errors.ts reach neither the tool registry nor the snapshot builder. That test named lib/ai/coach/tools.ts, which stopped existing when the registry became a directory, so its negative check could not fail; it points at tools/index.ts now, and a new sanity check fails if the tool route can no longer reach the path it names.
This commit is contained in:
1 parent
17abd37dd7
commit
fe53804c4c
17 files changed
+2191
-1532
No files matched your search
@@ -69,7 +69,7 @@ const TOOL_ROUTE = resolve(SRC, "app/api/insights/chat/route.ts");
|
||||
// Modules that constitute the tool loop / health snapshot — forbidden in the
|
||||
// fenced graph.
|
||||
const TOOL_MODULES = [
|
||||
resolve(SRC, "lib/ai/coach/tools.ts"),
|
||||
resolve(SRC, "lib/ai/coach/tools/index.ts"),
|
||||
resolve(SRC, "lib/ai/coach/snapshot.ts"),
|
||||
];
|
||||
|
||||
@@ -101,6 +101,39 @@ describe("fenced-chat module graph — the fence is structural", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("the Coach turn's wire modules reach neither the tool registry nor the snapshot builder", () => {
|
||||
// `turn/sse.ts` (framing, refusal and error streams) and `turn/errors.ts`
|
||||
// (provider-error classification) are the parts of the Coach turn a fenced
|
||||
// pipeline could share. They stay usable there only while they are
|
||||
// tool-free.
|
||||
const reachable = reachableFrom([
|
||||
resolve(SRC, "lib/ai/coach/turn/sse.ts"),
|
||||
resolve(SRC, "lib/ai/coach/turn/errors.ts"),
|
||||
]);
|
||||
for (const forbidden of TOOL_MODULES) {
|
||||
expect(
|
||||
reachable.has(forbidden),
|
||||
`Coach turn wire modules must NOT import ${forbidden}`,
|
||||
).toBe(false);
|
||||
}
|
||||
// Sanity: the walk actually left the entry files.
|
||||
expect(reachable.has(resolve(SRC, "lib/ai/coach/turn/types.ts"))).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("sanity: the tool route DOES reach the tool registry through the turn pipeline", () => {
|
||||
const reachable = reachableFrom([TOOL_ROUTE]);
|
||||
// Without this the negative checks above would pass against a path that
|
||||
// no longer exists — which is how `tools.ts` (now `tools/index.ts`) sat
|
||||
// here unmatched after the tool registry became a directory.
|
||||
for (const target of TOOL_MODULES) {
|
||||
expect(reachable.has(target), `Tool route must reach ${target}`).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("sanity: the fenced pipeline DOES reach its document loader + prompt builder", () => {
|
||||
const reachable = reachableFrom(FENCED_ENTRIES);
|
||||
expect(reachable.has(resolve(SRC, "lib/documents/content-index.ts"))).toBe(
|
||||
|
||||
Reference in new issue
Block a user