fix(confidence-engine): unwrap focused deconstruction response
This commit is contained in:
@@ -52,11 +52,14 @@ export async function POST(request) {
|
||||
|
||||
const provider = getProvider();
|
||||
const startedAt = Date.now();
|
||||
const raw = await provider.generateReconstruction(prompt, process.env.OLLAMA_MODEL);
|
||||
const wrapper = await provider.generateReconstruction(prompt, process.env.OLLAMA_MODEL);
|
||||
const elapsedMs = Date.now() - startedAt;
|
||||
|
||||
// Unwrap the semantic deconstruction from the provider envelope.
|
||||
const deconstruction = wrapper.response;
|
||||
|
||||
// Validate schema (required fields present, no graph-mutation fields)
|
||||
const validationErrors = validateFocusedDeconstructSchema(raw);
|
||||
const validationErrors = validateFocusedDeconstructSchema(deconstruction);
|
||||
if (validationErrors.length > 0) {
|
||||
return Response.json(
|
||||
{
|
||||
@@ -73,11 +76,11 @@ export async function POST(request) {
|
||||
return Response.json({
|
||||
success: true,
|
||||
targetNodeId: body.targetNodeId,
|
||||
observations: raw.observations,
|
||||
uncertainties: raw.uncertainties,
|
||||
assumptions: raw.assumptions,
|
||||
relationships: raw.relationships,
|
||||
possibleFollowUpQuestions: raw.possibleFollowUpQuestions,
|
||||
observations: deconstruction.observations,
|
||||
uncertainties: deconstruction.uncertainties,
|
||||
assumptions: deconstruction.assumptions,
|
||||
relationships: deconstruction.relationships,
|
||||
possibleFollowUpQuestions: deconstruction.possibleFollowUpQuestions,
|
||||
elapsedMs,
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -1883,3 +1883,17 @@ The current handoff captures all operational facts needed to resume today. For h
|
||||
| Task routing by work type | `docs/task-context-packs.md` |
|
||||
| Broader architectural intent | `docs/architectural-principles.md` |
|
||||
| Experiment history (specific) | `docs/design-evolution/README.md` → relevant chapter |
|
||||
|
||||
## focused-deconstruction 502 root cause and fix
|
||||
|
||||
**Root cause:** `/api/focused-investigation/deconstruct/route.js` passed the full provider envelope `{ response, providerApiPath, providerExecution }` from `generateReconstruction()` directly into `validateFocusedDeconstructSchema()`. The validator expects semantic fields (`targetNodeId`, `observations`, etc.) at the top level — those fields live on `wrapper.response`, not on the wrapper. All six focused-deconstruction fields appeared absent, producing a structured-output 502 on every call.
|
||||
|
||||
**Fix:** Route now extracts `const deconstruction = wrapper.response` and passes that inner object to both validation and response serialization. Provider diagnostics (`providerApiPath`, `providerExecution`) are preserved but do not interfere with semantic fields.
|
||||
|
||||
**Tests:** `tests/focused-deconstruct-boundary.test.js` mocks now reflect the real provider wrapper contract (inner deconstruction nested under `.response`). A new assertion verifies that provider envelope fields never leak into the API response. Five tests pass on first run; 48 focused-investigation-boundary tests pass.
|
||||
|
||||
**Previous two focused-deconstruction semantic runs remain invalid as semantic evidence.** They produced all-zero semantic fields because the defect prevented the model output from ever reaching the validator or the client.
|
||||
|
||||
**Next boundary:** rerun exactly one substantive compound-question answer-deconstruction observation through the corrected production route.
|
||||
|
||||
**Zero live model calls occurred during the fix.**
|
||||
|
||||
@@ -13,18 +13,22 @@ import { describe, it, expect, vi } from "vitest";
|
||||
function makeMockProvider(inventedTargetNodeId) {
|
||||
return {
|
||||
generateReconstruction: vi.fn().mockResolvedValue({
|
||||
targetNodeId: inventedTargetNodeId,
|
||||
observations: ["doc is minimal", "processes in founder's head"],
|
||||
uncertainties: ["whether formal docs can capture tacit knowledge"],
|
||||
assumptions: ["documentation is primary mechanism for knowledge transfer"],
|
||||
relationships: [
|
||||
{ from: "founder", to: "processes", type: "holds", rationale: "tacit" },
|
||||
{ from: "ops-context", to: "docs-infra", type: "depends_on", rationale: "formal docs required" },
|
||||
],
|
||||
possibleFollowUpQuestions: [
|
||||
"What processes does the founder hold tacitly?",
|
||||
"How is knowledge transferred when founder is unavailable?",
|
||||
],
|
||||
response: {
|
||||
targetNodeId: inventedTargetNodeId,
|
||||
observations: ["doc is minimal", "processes in founder's head"],
|
||||
uncertainties: ["whether formal docs can capture tacit knowledge"],
|
||||
assumptions: ["documentation is primary mechanism for knowledge transfer"],
|
||||
relationships: [
|
||||
{ from: "founder", to: "processes", type: "holds", rationale: "tacit" },
|
||||
{ from: "ops-context", to: "docs-infra", type: "depends_on", rationale: "formal docs required" },
|
||||
],
|
||||
possibleFollowUpQuestions: [
|
||||
"What processes does the founder hold tacitly?",
|
||||
"How is knowledge transferred when founder is unavailable?",
|
||||
],
|
||||
},
|
||||
providerApiPath: "/api/chat",
|
||||
providerExecution: { chatRequestAttempted: true },
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -85,12 +89,16 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
vi.doMock("@/lib/llm/provider", () => ({
|
||||
getProvider: () => ({
|
||||
generateReconstruction: vi.fn().mockResolvedValue({
|
||||
targetNodeId: "some-invented-id",
|
||||
observations: mockObs,
|
||||
uncertainties: mockUnc,
|
||||
assumptions: mockAssm,
|
||||
relationships: mockRel,
|
||||
possibleFollowUpQuestions: mockFuq,
|
||||
response: {
|
||||
targetNodeId: "some-invented-id",
|
||||
observations: mockObs,
|
||||
uncertainties: mockUnc,
|
||||
assumptions: mockAssm,
|
||||
relationships: mockRel,
|
||||
possibleFollowUpQuestions: mockFuq,
|
||||
},
|
||||
providerApiPath: "/api/chat",
|
||||
providerExecution: { chatRequestAttempted: true },
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
@@ -158,17 +166,21 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
vi.doMock("@/lib/llm/provider", () => ({
|
||||
getProvider: () => ({
|
||||
generateReconstruction: vi.fn().mockResolvedValue({
|
||||
targetNodeId: modelInventedId,
|
||||
observations: ["Documentation is minimal."],
|
||||
uncertainties: [],
|
||||
assumptions: [
|
||||
"That formal documentation is the primary mechanism for capturing or transferring the founder's tacit knowledge of processes.",
|
||||
],
|
||||
relationships: [
|
||||
{ from: "Founder", to: "Processes", type: "holds" },
|
||||
{ from: "Operational Context", to: "Documentation Infrastructure", type: "affects" },
|
||||
],
|
||||
possibleFollowUpQuestions: [],
|
||||
response: {
|
||||
targetNodeId: modelInventedId,
|
||||
observations: ["Documentation is minimal."],
|
||||
uncertainties: [],
|
||||
assumptions: [
|
||||
"That formal documentation is the primary mechanism for capturing or transferring the founder's tacit knowledge of processes.",
|
||||
],
|
||||
relationships: [
|
||||
{ from: "Founder", to: "Processes", type: "holds" },
|
||||
{ from: "Operational Context", to: "Documentation Infrastructure", type: "affects" },
|
||||
],
|
||||
possibleFollowUpQuestions: [],
|
||||
},
|
||||
providerApiPath: "/api/chat",
|
||||
providerExecution: { chatRequestAttempted: true },
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
@@ -217,4 +229,55 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
const stored = { ...json };
|
||||
expect(stored.targetNodeId).toBe(originalNodeId);
|
||||
});
|
||||
|
||||
it("provider envelope fields do not leak into API response", async () => {
|
||||
vi.resetModules();
|
||||
|
||||
vi.doMock("@/lib/llm/provider", () => ({
|
||||
getProvider: () => ({
|
||||
generateReconstruction: vi.fn().mockResolvedValue({
|
||||
response: {
|
||||
targetNodeId: "nk04xvk",
|
||||
observations: ["obs"],
|
||||
uncertainties: ["unc"],
|
||||
assumptions: ["asm"],
|
||||
relationships: [],
|
||||
possibleFollowUpQuestions: ["fuq"],
|
||||
},
|
||||
providerApiPath: "/api/chat",
|
||||
providerExecution: { chatRequestAttempted: true, chatRequestSucceeded: true },
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js");
|
||||
|
||||
const response = await POST(
|
||||
new Request("http://localhost/api/focused-investigation/deconstruct", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
targetNodeId: "nk04xvk",
|
||||
targetLabel: "label",
|
||||
targetDescription: "desc",
|
||||
centralStatement: "central",
|
||||
question: "q?",
|
||||
answer: "a.",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
const json = await response.json();
|
||||
|
||||
// Semantic fields present
|
||||
expect(json.success).toBe(true);
|
||||
expect(json.targetNodeId).toBe("nk04xvk");
|
||||
expect(json.observations).toEqual(["obs"]);
|
||||
expect(json.possibleFollowUpQuestions).toEqual(["fuq"]);
|
||||
|
||||
// Provider envelope fields must NOT appear in the response
|
||||
expect(json.providerApiPath).toBeUndefined();
|
||||
expect(json.providerExecution).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user