mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 06:15:59 +00:00
fix(mcp): authenticate inventory facts before prompt completion
This commit is contained in:
1 parent
591c87c4b6
commit
170bfc7b3f
2 files changed
+109
-40
No files matched your search
@@ -7,8 +7,9 @@ import type { AnalysisOperationPort } from "../application/AnalysisProvider.js";
|
||||
import type { BinarySessionPort } from "../application/binary/BinarySessionPort.js";
|
||||
import type { PromptCompletionKind } from "../contracts/promptContracts.js";
|
||||
import { currentMcpRequestSignal } from "./EvidenceMcpServer.js";
|
||||
import { artifactInventoryResultSchema } from "../domain/artifactGraph.js";
|
||||
import { parseArtifactInventoryEvidence } from "../domain/artifactInventoryEvidence.js";
|
||||
import { artifactInspectionResultSchema } from "../domain/artifactInspection.js";
|
||||
import { EvidenceIntegrityError } from "../domain/evidenceErrors.js";
|
||||
import { parseProcessCapture } from "../domain/process/processCaptureParsing.js";
|
||||
|
||||
const documentListSchema = z.array(z.string().min(1));
|
||||
@@ -180,29 +181,38 @@ const evidenceValues = (
|
||||
if (kind === "evidence") return [evidence.evidence_id];
|
||||
if (kind === "capture")
|
||||
return isProcessCaptureEvidence(evidence) ? [evidence.evidence_id] : [];
|
||||
let inventoryResults: readonly unknown[];
|
||||
if (evidence.operation === "inventory_artifact")
|
||||
inventoryResults = [evidence.normalized_result];
|
||||
else if (evidence.operation === "inspect_artifact") {
|
||||
return artifactInventoryValues(kind, evidence);
|
||||
if (evidence.operation === "inspect_artifact") {
|
||||
const inspection = artifactInspectionResultSchema.safeParse(
|
||||
evidence.normalized_result,
|
||||
);
|
||||
if (!inspection.success) return [];
|
||||
inventoryResults = inspection.data.substeps.map(
|
||||
({ evidence: inventoryEvidence }) => inventoryEvidence.normalized_result,
|
||||
return inspection.data.substeps.flatMap(({ evidence: inventoryEvidence }) =>
|
||||
artifactInventoryValues(kind, inventoryEvidence),
|
||||
);
|
||||
} else return [];
|
||||
const values: string[] = [];
|
||||
for (const inventoryResult of inventoryResults) {
|
||||
const inventory = artifactInventoryResultSchema.safeParse(inventoryResult);
|
||||
if (!inventory.success) continue;
|
||||
if (kind === "manifest") values.push(inventory.data.manifest.manifest_id);
|
||||
else
|
||||
values.push(
|
||||
...inventory.data.occurrences.map(({ occurrence_id: id }) => id),
|
||||
);
|
||||
}
|
||||
return values;
|
||||
return [];
|
||||
};
|
||||
|
||||
const artifactInventoryValues = (
|
||||
kind: "manifest" | "occurrence",
|
||||
evidence: unknown,
|
||||
): readonly string[] => {
|
||||
try {
|
||||
const { inventory } = parseArtifactInventoryEvidence(evidence);
|
||||
return kind === "manifest"
|
||||
? [inventory.manifest.manifest_id]
|
||||
: inventory.occurrences.map(({ occurrence_id: id }) => id);
|
||||
} catch (cause: unknown) {
|
||||
if (
|
||||
cause instanceof TypeError ||
|
||||
cause instanceof z.ZodError ||
|
||||
cause instanceof EvidenceIntegrityError
|
||||
)
|
||||
return [];
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
|
||||
const isProcessCaptureEvidence = (evidence: LedgerEvidence): boolean => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { ARTIFACT_COMPARISON_EXAMPLE } from "../../../src/contracts/artifactComparisonExample.js";
|
||||
import { PROCESS_CAPTURE_REFERENCE } from "../../../src/contracts/investigationExamples.js";
|
||||
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import {
|
||||
artifactInspectionResultSchema,
|
||||
createArtifactInspection,
|
||||
@@ -212,28 +212,7 @@ describe("guided prompt completion from investigation records", () => {
|
||||
describe("guided prompt completion from retained records", () => {
|
||||
it("offers identifiers from every retained inspection inventory substep", async () => {
|
||||
const session = createTestBinarySession(() => client([]));
|
||||
const leftInspection = createArtifactInspection(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.left,
|
||||
);
|
||||
const rightInspection = createArtifactInspection(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.right,
|
||||
);
|
||||
const inspection = artifactInspectionResultSchema.parse({
|
||||
...leftInspection,
|
||||
substeps: [...leftInspection.substeps, ...rightInspection.substeps],
|
||||
evidence_links: [
|
||||
...leftInspection.evidence_links,
|
||||
...rightInspection.evidence_links,
|
||||
],
|
||||
});
|
||||
const inspectionEvidence = createEvidence(undefined, fixtureProvider, {
|
||||
operation: "inspect_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inspection),
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
evidenceLinks: inspection.evidence_links,
|
||||
});
|
||||
const inspectionEvidence = createCompositeInspectionEvidence();
|
||||
expect(session.recordEvidence(inspectionEvidence).ok).toBe(true);
|
||||
const completion = createPromptCompletionSource(session, session);
|
||||
const expectedManifests = [
|
||||
@@ -267,6 +246,38 @@ describe("guided prompt completion from retained records", () => {
|
||||
await session.close();
|
||||
});
|
||||
|
||||
it("skips an inner inventory whose payload no longer matches its Evidence ID", async () => {
|
||||
const session = createTestBinarySession(() => client([]));
|
||||
const inspectionEvidence = createCompositeInspectionEvidence(true);
|
||||
const corruptedInner = artifactInspectionResultSchema.parse(
|
||||
inspectionEvidence.normalized_result,
|
||||
).substeps[1]?.evidence;
|
||||
if (corruptedInner === undefined)
|
||||
throw new Error("composite fixture must contain a second inventory");
|
||||
expect(corruptedInner.evidence_id).toBe(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.right.evidence_id,
|
||||
);
|
||||
expect(
|
||||
artifactInventoryResultSchema.safeParse(corruptedInner.normalized_result)
|
||||
.success,
|
||||
).toBe(true);
|
||||
expect(() => parseEvidence(corruptedInner)).toThrow();
|
||||
expect(session.recordEvidence(inspectionEvidence).ok).toBe(true);
|
||||
const completion = createPromptCompletionSource(session, session);
|
||||
const firstInventory = artifactInventoryResultSchema.parse(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.left.normalized_result,
|
||||
);
|
||||
|
||||
expect(await completion.complete("manifest", "agm_")).toEqual([
|
||||
firstInventory.manifest.manifest_id,
|
||||
]);
|
||||
expect(await completion.complete("occurrence", "occ_")).toEqual(
|
||||
firstInventory.occurrences.map(({ occurrence_id }) => occurrence_id),
|
||||
);
|
||||
|
||||
await session.close();
|
||||
});
|
||||
|
||||
it("deduplicates, case-folds, and returns deterministic live evidence IDs", async () => {
|
||||
const session = createTestBinarySession(() => client([]));
|
||||
for (let index = 149; index >= 0; index -= 1) {
|
||||
@@ -340,6 +351,54 @@ const fixtureEnvironment = {
|
||||
isolation: "process" as const,
|
||||
};
|
||||
|
||||
const createCompositeInspectionEvidence = (corruptSecondInventory = false) => {
|
||||
const leftInspection = createArtifactInspection(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.left,
|
||||
);
|
||||
const rightInspection = createArtifactInspection(
|
||||
ARTIFACT_COMPARISON_EXAMPLE.right,
|
||||
);
|
||||
const substeps = [...leftInspection.substeps, ...rightInspection.substeps];
|
||||
if (corruptSecondInventory) {
|
||||
const secondSubstep = substeps[1];
|
||||
if (secondSubstep === undefined)
|
||||
throw new Error("composite fixture must contain a second inventory");
|
||||
const inventory = artifactInventoryResultSchema.parse(
|
||||
secondSubstep.evidence.normalized_result,
|
||||
);
|
||||
substeps[1] = {
|
||||
...secondSubstep,
|
||||
evidence: {
|
||||
...secondSubstep.evidence,
|
||||
normalized_result: {
|
||||
...inventory,
|
||||
coverage: { ...inventory.coverage, status: "unknown" },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const inspection = artifactInspectionResultSchema.parse({
|
||||
...leftInspection,
|
||||
substeps,
|
||||
evidence_links: [
|
||||
...leftInspection.evidence_links,
|
||||
...rightInspection.evidence_links,
|
||||
],
|
||||
coverage: {
|
||||
...leftInspection.coverage,
|
||||
substeps_completed: substeps.length,
|
||||
},
|
||||
});
|
||||
return createEvidence(undefined, fixtureProvider, {
|
||||
operation: "inspect_artifact",
|
||||
parameters: {},
|
||||
result: jsonValueSchema.parse(inspection),
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
evidenceLinks: inspection.evidence_links,
|
||||
});
|
||||
};
|
||||
|
||||
const client = (
|
||||
requests: Array<Readonly<Record<string, unknown>>>,
|
||||
analysisProfile?: AnalysisProfileCommitment,
|
||||
|
||||
Reference in new issue
Block a user