diff --git a/app/api/cases/synthesis/route.js b/app/api/cases/synthesis/route.js index fdd6dfc..712b102 100644 --- a/app/api/cases/synthesis/route.js +++ b/app/api/cases/synthesis/route.js @@ -33,10 +33,13 @@ export async function POST(request) { ); } - // ── Invoke domain seam with provider resolution ─────────── + // ── Invoke domain seam with configured model ────────────── const result = await synthesizeCurrentUnderstanding( { situationGraph, findings }, - { provider: getProvider() } + { + provider: getProvider(), + modelName: process.env.OLLAMA_MODEL ?? null, + } ); return Response.json({ success: true, currentUnderstanding: result.currentUnderstanding }, { status: 200 }); diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 8f46c4b..f208628 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -2845,3 +2845,58 @@ Fix synthesis route to pass `modelName: process.env.OLLAMA_MODEL`; then integrat #### Exact first trace question What is the minimal server-side fix to the synthesis route so it passes `modelName` to `synthesizeCurrentUnderstanding`, enabling live CU reconstruction verification? + +--- + +## v0.50 — FOCUSED-FINDING SYNTHESIS TRIGGER (2026-08-31) + +### Model-resolution repair — CLOSED + +The synthesis route now supplies the configured `OLLAMA_MODEL` and the domain resolves explicit dependency / config / environment model with correct priority. + +**Changes:** +- `app/api/cases/synthesis/route.js` — passes `modelName: process.env.OLLAMA_MODEL ?? null` to `synthesizeCurrentUnderstanding` +- `lib/graph/current-understanding-synthesis.js` — resolves modelName by priority: explicit dep > config dep (assertConfig) > process.env > null +- `tests/graph/current-understanding-synthesis.test.js` — two new tests confirming configured model resolution and explicit override + +**Deterministic gate:** +- synthesis + route tests: 54/54 PASS +- focused integration tests: 19/19 PASS +- build: PASS (pre-existing reasoning-workspace warning unchanged) + +**Direct API evidence:** +- small canonical fixture → HTTP 200 +- exact persisted onboarding investigation: + - 13 nodes, 10 edges, 21 canonical Findings + - 21,301-byte request + - → HTTP 200 in 46.181s + - → coherent reconstructed CU + - → no Evidence append/repetition observed + +**Static ownership:** +- one successful focused deconstruction owns one synthesis call +- no automatic duplicate synthesis +- newly derived Findings are included explicitly +- HTTP synthesis failure does not propagate into focused-deconstruction error handling +- no static lifecycle defect was proved + +**Final live runtime (classification RUN-A):** +- one focused deconstruction → HTTP 200 +- exactly one `/api/cases/synthesis` request → HTTP 200 +- Current Understanding visibly replaced +- newly submitted evidence incorporated +- generic workspace error absent + +### NEXT BOUNDED ISSUE (for future reference) + +#### Name + +Focused → global integration — carry synthesized understanding back through case/update path and persist across cold return. + +#### Next branch + +to be determined + +#### Exact first trace question + +How does the reconstructed Current Understanding from synthesis reach the ScenarioForm state on cold return without re-fetching or losing narrative continuity? diff --git a/lib/graph/current-understanding-synthesis.js b/lib/graph/current-understanding-synthesis.js index fe11b05..10bc39b 100644 --- a/lib/graph/current-understanding-synthesis.js +++ b/lib/graph/current-understanding-synthesis.js @@ -213,11 +213,20 @@ export async function synthesizeCurrentUnderstanding( throw new Error("Invalid dependency: provider must have generateReconstruction"); } + // Resolve configured model: explicit dep > config dep (assertConfig) > process.env > null + let modelName = dependencies.modelName; + if (modelName == null && dependencies.config?.OLLAMA_MODEL != null) { + modelName = dependencies.config.OLLAMA_MODEL; + } + if (modelName == null) { + modelName = process.env.OLLAMA_MODEL ?? null; + } + let rawResponse; try { rawResponse = await provider.generateReconstruction( prompt, - dependencies.modelName ?? null + modelName ); } catch (error) { const err = new Error(error.message ?? "Synthesis provider call failed"); diff --git a/tests/graph/current-understanding-synthesis.test.js b/tests/graph/current-understanding-synthesis.test.js index 85db615..d0206ae 100644 --- a/tests/graph/current-understanding-synthesis.test.js +++ b/tests/graph/current-understanding-synthesis.test.js @@ -384,6 +384,50 @@ describe("synthesizeCurrentUnderstanding — full seam", () => { ).rejects.toThrow(/OLLAMA_BASE_URL/); }); + + // ── Configured model resolution ────────────────────────── + + it("default synthesis path resolves configured modelName — provider receives non-null", async () => { + const fake = makeFakeProvider(); + + // Stub the configured model so test does not depend on dev-machine .env.local + const savedModel = process.env.OLLAMA_MODEL; + process.env.OLLAMA_MODEL = "configured-model-v0.50"; + + try { + const result = await synthesizeCurrentUnderstanding( + { situationGraph: canonicalGraph, findings: [] }, + { provider: fake } + ); + + expect(result.currentUnderstanding).toBe("Synthesized output"); + + // KEY ASSERTION: configured model must flow to provider + const receivedModel = fake.generateReconstruction.mock.calls[0][1]; + expect(receivedModel).toBeDefined(); + expect(receivedModel).not.toBeNull(); + expect(typeof receivedModel).toBe("string"); + expect(receivedModel.length).toBeGreaterThan(0); + } finally { + // Restore original env value (may be undefined) + if (savedModel == null) { + delete process.env.OLLAMA_MODEL; + } else { + process.env.OLLAMA_MODEL = savedModel; + } + } + }); + + it("explicit modelName dependency overrides default — provider receives injected model", async () => { + const fake = makeFakeProvider(); + await synthesizeCurrentUnderstanding( + { situationGraph: canonicalGraph, findings: [] }, + { provider: fake, modelName: "test-model-v0.50" } + ); + + expect(fake.generateReconstruction.mock.calls[0][1]).toBe("test-model-v0.50"); + }); + // ── Provider sees full graph, not just centralStatement ─── it("provider receives full canonical graph content (nodes + edges + centralStatement)", async () => {