mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor(verifiers): consolidate capture result admission and parity
This commit is contained in:
1 parent
99062feaa7
commit
a84e0cfcfe
7 files changed
+134
-68
No files matched your search
@@ -38,7 +38,15 @@ export const requireMcpOutput = (
|
|||||||
throw new Error(`${operation} failed: ${mcpTextValue(result)}`);
|
throw new Error(`${operation} failed: ${mcpTextValue(result)}`);
|
||||||
if (contract.name !== operation)
|
if (contract.name !== operation)
|
||||||
throw new Error(`${operation} received the ${contract.name} contract`);
|
throw new Error(`${operation} received the ${contract.name} contract`);
|
||||||
return contract.outputSchema.parse(jsonValue(result));
|
const text = jsonValue(result);
|
||||||
|
const output = contract.outputSchema.parse(text);
|
||||||
|
if (result.structuredContent !== undefined) {
|
||||||
|
// The producer emits both representations from the same JSON value.
|
||||||
|
// Compare before schema projection can discard disagreeing source fields.
|
||||||
|
if (!isDeepStrictEqual(text, result.structuredContent))
|
||||||
|
throw new Error(`${operation} text and structured results disagree`);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
const operationResultKey = (contract) => {
|
const operationResultKey = (contract) => {
|
||||||
@@ -124,15 +132,6 @@ export const requireMcpEvidenceBundle = (result) => {
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
"get_evidence_bundle returned summary metadata instead of complete Evidence",
|
"get_evidence_bundle returned summary metadata instead of complete Evidence",
|
||||||
);
|
);
|
||||||
if (result.structuredContent !== undefined) {
|
|
||||||
const structured = contract.outputSchema.parse(
|
|
||||||
result.structuredContent,
|
|
||||||
).result;
|
|
||||||
if (!isDeepStrictEqual(bundle, structured))
|
|
||||||
throw new Error(
|
|
||||||
"get_evidence_bundle text and structured results disagree",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return authenticateParsedEvidenceBundle(bundle);
|
return authenticateParsedEvidenceBundle(bundle);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
} from "./lib/http3-verifier-lifecycle.mjs";
|
} from "./lib/http3-verifier-lifecycle.mjs";
|
||||||
import {
|
import {
|
||||||
requireMcpEvidenceBundle,
|
requireMcpEvidenceBundle,
|
||||||
mcpTextValue,
|
requireMcpOutput,
|
||||||
} from "./lib/mcp-verifier-results.mjs";
|
} from "./lib/mcp-verifier-results.mjs";
|
||||||
|
|
||||||
const root = process.argv[2];
|
const root = process.argv[2];
|
||||||
@@ -137,14 +137,11 @@ async function mcp(input, name) {
|
|||||||
join(directory, `${name}.mcp.reply.json`),
|
join(directory, `${name}.mcp.reply.json`),
|
||||||
JSON.stringify(reply, null, 2),
|
JSON.stringify(reply, null, 2),
|
||||||
);
|
);
|
||||||
const value = JSON.parse(mcpTextValue(reply));
|
const evidence = requireMcpOutput(reply, contract.name, contract);
|
||||||
assert.notEqual(reply.isError, true, mcpTextValue(reply));
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
validateOutput(reply.structuredContent),
|
validateOutput(reply.structuredContent),
|
||||||
JSON.stringify(validateOutput.errors),
|
JSON.stringify(validateOutput.errors),
|
||||||
);
|
);
|
||||||
const evidence = contract.outputSchema.parse(value);
|
|
||||||
assert.deepEqual(reply.structuredContent, value);
|
|
||||||
retainedEvidence.push(evidence);
|
retainedEvidence.push(evidence);
|
||||||
return evidence;
|
return evidence;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { requireMcpEvidenceBundle } from "./lib/mcp-verifier-results.mjs";
|
import {
|
||||||
|
requireMcpEvidenceBundle,
|
||||||
|
requireMcpOutput,
|
||||||
|
requireMcpToolError,
|
||||||
|
} from "./lib/mcp-verifier-results.mjs";
|
||||||
import { estimateTokenCount } from "tokenx";
|
import { estimateTokenCount } from "tokenx";
|
||||||
import { execFile } from "node:child_process";
|
import { execFile } from "node:child_process";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
@@ -124,12 +128,13 @@ await withVerifierCleanup(async () => {
|
|||||||
name: tool.name,
|
name: tool.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
ajv.validate(tool.outputSchema, response.structuredContent),
|
ajv.validate(tool.outputSchema, response.structuredContent),
|
||||||
JSON.stringify(ajv.errors),
|
JSON.stringify(ajv.errors),
|
||||||
);
|
);
|
||||||
const evidence = parseEvidence(response.structuredContent);
|
const evidence = parseEvidence(
|
||||||
|
requireMcpOutput(response, "inspect_packet_capture"),
|
||||||
|
);
|
||||||
const report = evidence.normalized_result;
|
const report = evidence.normalized_result;
|
||||||
assert.equal(
|
assert.equal(
|
||||||
report.artifact.sha256,
|
report.artifact.sha256,
|
||||||
@@ -305,12 +310,13 @@ await withVerifierCleanup(async () => {
|
|||||||
name: pcapTool.name,
|
name: pcapTool.name,
|
||||||
arguments: { path: capturePath, detail: "summary" },
|
arguments: { path: capturePath, detail: "summary" },
|
||||||
});
|
});
|
||||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
pcapAjv.validate(pcapTool.outputSchema, response.structuredContent),
|
pcapAjv.validate(pcapTool.outputSchema, response.structuredContent),
|
||||||
pcapAjv.errorsText(),
|
pcapAjv.errorsText(),
|
||||||
);
|
);
|
||||||
const summary = parseEvidence(response.structuredContent).normalized_result;
|
const summary = parseEvidence(
|
||||||
|
requireMcpOutput(response, "inspect_pcap_capture"),
|
||||||
|
).normalized_result;
|
||||||
assert.equal(summary.summary.capture_start, expectedCaptureStart);
|
assert.equal(summary.summary.capture_start, expectedCaptureStart);
|
||||||
assert.equal(summary.summary.capture_end, expectedCaptureEnd);
|
assert.equal(summary.summary.capture_end, expectedCaptureEnd);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
@@ -343,10 +349,9 @@ await withVerifierCleanup(async () => {
|
|||||||
view: { kind: "pcap", facet: "protocols", limit: 10 },
|
view: { kind: "pcap", facet: "protocols", limit: 10 },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
assert.notEqual(page.isError, true, JSON.stringify(page));
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
parseEvidence(page.structuredContent).normalized_result
|
parseEvidence(requireMcpOutput(page, "inspect_analysis_view"))
|
||||||
.parent_evidence_id,
|
.normalized_result.parent_evidence_id,
|
||||||
source.evidence_id,
|
source.evidence_id,
|
||||||
);
|
);
|
||||||
cases++;
|
cases++;
|
||||||
@@ -368,19 +373,16 @@ await withVerifierCleanup(async () => {
|
|||||||
name: tool.name,
|
name: tool.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.equal(response.isError, true);
|
requireMcpToolError(response);
|
||||||
await client.ping();
|
await client.ping();
|
||||||
cases++;
|
cases++;
|
||||||
}
|
}
|
||||||
await writeFile(path, "not a capture");
|
await writeFile(path, "not a capture");
|
||||||
assert.equal(
|
requireMcpToolError(
|
||||||
(
|
await client.callTool({
|
||||||
await client.callTool({
|
name: tool.name,
|
||||||
name: tool.name,
|
arguments: { path, fields: ["frame.number"] },
|
||||||
arguments: { path, fields: ["frame.number"] },
|
}),
|
||||||
})
|
|
||||||
).isError,
|
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
cases++;
|
cases++;
|
||||||
const discovery = (await client.listTools()).tools.find(
|
const discovery = (await client.listTools()).tools.find(
|
||||||
@@ -414,12 +416,13 @@ await withVerifierCleanup(async () => {
|
|||||||
name: discovery.name,
|
name: discovery.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
ajv.validate(discovery.outputSchema, response.structuredContent),
|
ajv.validate(discovery.outputSchema, response.structuredContent),
|
||||||
JSON.stringify(ajv.errors),
|
JSON.stringify(ajv.errors),
|
||||||
);
|
);
|
||||||
const evidence = parseEvidence(response.structuredContent);
|
const evidence = parseEvidence(
|
||||||
|
requireMcpOutput(response, "inspect_packet_fields"),
|
||||||
|
);
|
||||||
const report = evidence.normalized_result;
|
const report = evidence.normalized_result;
|
||||||
const kind = input.kind ?? "protocols";
|
const kind = input.kind ?? "protocols";
|
||||||
const query = (input.query ?? "").toLowerCase();
|
const query = (input.query ?? "").toLowerCase();
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import { Client } from "@modelcontextprotocol/client";
|
|||||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||||
|
import { toolContract } from "../dist/contracts/toolContracts.js";
|
||||||
|
import {
|
||||||
|
requireMcpOutput,
|
||||||
|
requireMcpToolError,
|
||||||
|
} from "./lib/mcp-verifier-results.mjs";
|
||||||
|
|
||||||
// Caller-supplied producer evidence stays local; no fixture capture or secrets are bundled.
|
// Caller-supplied producer evidence stays local; no fixture capture or secrets are bundled.
|
||||||
const [controlPath, lossPath, reportPath] = process.argv.slice(2);
|
const [controlPath, lossPath, reportPath] = process.argv.slice(2);
|
||||||
@@ -40,6 +45,7 @@ transport.stderr?.on("data", (bytes) => {
|
|||||||
});
|
});
|
||||||
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
||||||
const measurements = [];
|
const measurements = [];
|
||||||
|
const contract = toolContract("inspect_web_network_capture");
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
await client.connect(transport);
|
await client.connect(transport);
|
||||||
@@ -68,12 +74,13 @@ try {
|
|||||||
name: tool.name,
|
name: tool.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.notEqual(response.isError, true);
|
const mcp = parseEvidence(
|
||||||
|
requireMcpOutput(response, contract.name, contract),
|
||||||
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
validateOutput(response.structuredContent),
|
validateOutput(response.structuredContent),
|
||||||
JSON.stringify(validateOutput.errors),
|
JSON.stringify(validateOutput.errors),
|
||||||
);
|
);
|
||||||
const mcp = parseEvidence(response.structuredContent);
|
|
||||||
const completed = await promisify(execFile)(
|
const completed = await promisify(execFile)(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
[entrypoint, "inspect-web-network-capture", path, "qlog", "--json"],
|
[entrypoint, "inspect-web-network-capture", path, "qlog", "--json"],
|
||||||
@@ -153,17 +160,14 @@ try {
|
|||||||
view: { kind: "api-surface" },
|
view: { kind: "api-surface" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
assert.equal(retained.isError, true);
|
assert.equal(requireMcpToolError(retained).code, "unsupported_target");
|
||||||
assert.equal(
|
|
||||||
JSON.parse(retained.content.find((item) => item.type === "text").text)
|
|
||||||
.error.code,
|
|
||||||
"unsupported_target",
|
|
||||||
);
|
|
||||||
const selected = await client.callTool({
|
const selected = await client.callTool({
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
arguments: { ...input, record_ordinals: [capture.total_records - 1, 0] },
|
arguments: { ...input, record_ordinals: [capture.total_records - 1, 0] },
|
||||||
});
|
});
|
||||||
const subset = parseEvidence(selected.structuredContent).normalized_result;
|
const subset = parseEvidence(
|
||||||
|
requireMcpOutput(selected, contract.name, contract),
|
||||||
|
).normalized_result;
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
subset.records.map((record) => record.ordinal),
|
subset.records.map((record) => record.ordinal),
|
||||||
[capture.total_records - 1, 0],
|
[capture.total_records - 1, 0],
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { requireMcpEvidenceBundle } from "./lib/mcp-verifier-results.mjs";
|
import {
|
||||||
|
requireMcpEvidenceBundle,
|
||||||
|
requireMcpOutput,
|
||||||
|
requireMcpToolError,
|
||||||
|
} from "./lib/mcp-verifier-results.mjs";
|
||||||
import { estimateTokenCount } from "tokenx";
|
import { estimateTokenCount } from "tokenx";
|
||||||
import { execFile } from "node:child_process";
|
import { execFile } from "node:child_process";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
@@ -47,9 +51,7 @@ const assertRejectedSelector = (
|
|||||||
field,
|
field,
|
||||||
expectedExitCode = 1,
|
expectedExitCode = 1,
|
||||||
) => {
|
) => {
|
||||||
assert.equal(response.isError, true);
|
const error = requireMcpToolError(response);
|
||||||
const text = response.content.find((item) => item.type === "text")?.text;
|
|
||||||
const { error } = JSON.parse(text ?? "{}");
|
|
||||||
assert.equal(error.code, "invalid_request");
|
assert.equal(error.code, "invalid_request");
|
||||||
assert.equal(error.details.operation, operation);
|
assert.equal(error.details.operation, operation);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
@@ -123,12 +125,13 @@ await withVerifierCleanup(async () => {
|
|||||||
name: advertised.name,
|
name: advertised.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
ajv.validate(advertised.outputSchema, response.structuredContent),
|
ajv.validate(advertised.outputSchema, response.structuredContent),
|
||||||
JSON.stringify(ajv.errors),
|
JSON.stringify(ajv.errors),
|
||||||
);
|
);
|
||||||
const evidence = parseEvidence(response.structuredContent);
|
const evidence = parseEvidence(
|
||||||
|
requireMcpOutput(response, "decode_signal_recording"),
|
||||||
|
);
|
||||||
const report = evidence.normalized_result;
|
const report = evidence.normalized_result;
|
||||||
for (const byte of ["52", "45", "41"])
|
for (const byte of ["52", "45", "41"])
|
||||||
assert.match(
|
assert.match(
|
||||||
@@ -238,12 +241,13 @@ await withVerifierCleanup(async () => {
|
|||||||
name: advertised.name,
|
name: advertised.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
ajv.validate(advertised.outputSchema, response.structuredContent),
|
ajv.validate(advertised.outputSchema, response.structuredContent),
|
||||||
JSON.stringify(ajv.errors),
|
JSON.stringify(ajv.errors),
|
||||||
);
|
);
|
||||||
const evidence = parseEvidence(response.structuredContent);
|
const evidence = parseEvidence(
|
||||||
|
requireMcpOutput(response, "decode_signal_recording"),
|
||||||
|
);
|
||||||
const report = evidence.normalized_result;
|
const report = evidence.normalized_result;
|
||||||
assert.deepEqual(report.output_window, {
|
assert.deepEqual(report.output_window, {
|
||||||
total_lines: 120,
|
total_lines: 120,
|
||||||
@@ -298,9 +302,9 @@ await withVerifierCleanup(async () => {
|
|||||||
name: advertised.name,
|
name: advertised.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.equal(response.isError, true);
|
|
||||||
if (rejectedField !== undefined)
|
if (rejectedField !== undefined)
|
||||||
assertRejectedSelector(response, advertised.name, rejectedField);
|
assertRejectedSelector(response, advertised.name, rejectedField);
|
||||||
|
else requireMcpToolError(response);
|
||||||
await client.ping();
|
await client.ping();
|
||||||
cases++;
|
cases++;
|
||||||
}
|
}
|
||||||
@@ -309,7 +313,7 @@ await withVerifierCleanup(async () => {
|
|||||||
name: advertised.name,
|
name: advertised.name,
|
||||||
arguments: { path, decoders: "uart" },
|
arguments: { path, decoders: "uart" },
|
||||||
});
|
});
|
||||||
assert.equal(invalid.isError, true);
|
requireMcpToolError(invalid);
|
||||||
cases++;
|
cases++;
|
||||||
const discoveryTool = (await client.listTools()).tools.find(
|
const discoveryTool = (await client.listTools()).tools.find(
|
||||||
({ name }) => name === "inspect_signal_decoders",
|
({ name }) => name === "inspect_signal_decoders",
|
||||||
@@ -367,12 +371,13 @@ await withVerifierCleanup(async () => {
|
|||||||
name: discoveryTool.name,
|
name: discoveryTool.name,
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
assert.notEqual(response.isError, true, JSON.stringify(response));
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
ajv.validate(discoveryTool.outputSchema, response.structuredContent),
|
ajv.validate(discoveryTool.outputSchema, response.structuredContent),
|
||||||
JSON.stringify(ajv.errors),
|
JSON.stringify(ajv.errors),
|
||||||
);
|
);
|
||||||
const evidence = parseEvidence(response.structuredContent);
|
const evidence = parseEvidence(
|
||||||
|
requireMcpOutput(response, "inspect_signal_decoders"),
|
||||||
|
);
|
||||||
const report = evidence.normalized_result;
|
const report = evidence.normalized_result;
|
||||||
discoveryMeasurements.push({
|
discoveryMeasurements.push({
|
||||||
input,
|
input,
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|||||||
import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js";
|
import { PrivateRuntimeRoot } from "../dist/process/PrivateRuntimeRoot.js";
|
||||||
import { runOwnedCommand } from "../dist/process/OwnedCommand.js";
|
import { runOwnedCommand } from "../dist/process/OwnedCommand.js";
|
||||||
import { WEB_NETWORK_CAPTURE_LIMITS } from "../dist/domain/webNetworkCapture.js";
|
import { WEB_NETWORK_CAPTURE_LIMITS } from "../dist/domain/webNetworkCapture.js";
|
||||||
import { mcpTextValue } from "./lib/mcp-verifier-results.mjs";
|
import {
|
||||||
|
mcpTextValue,
|
||||||
|
requireMcpOutput,
|
||||||
|
requireMcpToolError,
|
||||||
|
} from "./lib/mcp-verifier-results.mjs";
|
||||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||||
|
|
||||||
const executable = process.env.REA_MITMDUMP_COMMAND;
|
const executable = process.env.REA_MITMDUMP_COMMAND;
|
||||||
@@ -903,15 +907,15 @@ async function inspect(mode, input, errorCategory) {
|
|||||||
name: "inspect_web_network_capture",
|
name: "inspect_web_network_capture",
|
||||||
arguments: input,
|
arguments: input,
|
||||||
});
|
});
|
||||||
const value = JSON.parse(mcpTextValue(result));
|
|
||||||
if (errorCategory !== undefined) {
|
if (errorCategory !== undefined) {
|
||||||
assert.equal(result.isError, true);
|
const error = requireMcpToolError(result);
|
||||||
assert.equal(value.error.category, errorCategory);
|
assert.equal(error.category, errorCategory);
|
||||||
|
const rawError = JSON.parse(mcpTextValue(result)).error;
|
||||||
for (const literal of input.sensitive_values ?? [])
|
for (const literal of input.sensitive_values ?? [])
|
||||||
assert.ok(!JSON.stringify(value.error).includes(literal));
|
assert.ok(!JSON.stringify(rawError).includes(literal));
|
||||||
return value.error;
|
return error;
|
||||||
}
|
}
|
||||||
assert.notEqual(result.isError, true, mcpTextValue(result));
|
const value = requireMcpOutput(result, "inspect_web_network_capture");
|
||||||
assertSensitiveLimitations(value, input.sensitive_values ?? []);
|
assertSensitiveLimitations(value, input.sensitive_values ?? []);
|
||||||
if (input.format === "har") {
|
if (input.format === "har") {
|
||||||
const surface = await client.callTool({
|
const surface = await client.callTool({
|
||||||
@@ -923,13 +927,12 @@ async function inspect(mode, input, errorCategory) {
|
|||||||
});
|
});
|
||||||
if ((input.sensitive_values ?? []).length > 0) {
|
if ((input.sensitive_values ?? []).length > 0) {
|
||||||
// Derived view text cannot be checked against undisclosed declarations.
|
// Derived view text cannot be checked against undisclosed declarations.
|
||||||
assert.equal(surface.isError, true);
|
requireMcpToolError(surface);
|
||||||
const refused = JSON.parse(mcpTextValue(surface));
|
const refused = JSON.parse(mcpTextValue(surface));
|
||||||
for (const literal of input.sensitive_values)
|
for (const literal of input.sensitive_values)
|
||||||
assert.ok(!JSON.stringify(refused).includes(literal));
|
assert.ok(!JSON.stringify(refused).includes(literal));
|
||||||
} else {
|
} else {
|
||||||
assert.notEqual(surface.isError, true, mcpTextValue(surface));
|
const projected = requireMcpOutput(surface, "inspect_analysis_view");
|
||||||
const projected = JSON.parse(mcpTextValue(surface));
|
|
||||||
assert.equal(projected.normalized_result.kind, "api-surface");
|
assert.equal(projected.normalized_result.kind, "api-surface");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
projected.normalized_result.records.examined,
|
projected.normalized_result.records.examined,
|
||||||
@@ -944,13 +947,12 @@ async function inspect(mode, input, errorCategory) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if ((input.sensitive_values ?? []).length > 0) {
|
if ((input.sensitive_values ?? []).length > 0) {
|
||||||
assert.equal(manifest.isError, true);
|
requireMcpToolError(manifest);
|
||||||
const refusedManifest = JSON.parse(mcpTextValue(manifest));
|
const refusedManifest = JSON.parse(mcpTextValue(manifest));
|
||||||
for (const literal of input.sensitive_values)
|
for (const literal of input.sensitive_values)
|
||||||
assert.ok(!JSON.stringify(refusedManifest).includes(literal));
|
assert.ok(!JSON.stringify(refusedManifest).includes(literal));
|
||||||
} else {
|
} else {
|
||||||
assert.notEqual(manifest.isError, true, mcpTextValue(manifest));
|
const projected = requireMcpOutput(manifest, "inspect_analysis_view");
|
||||||
const projected = JSON.parse(mcpTextValue(manifest));
|
|
||||||
assert.equal(projected.normalized_result.kind, "environment-manifest");
|
assert.equal(projected.normalized_result.kind, "environment-manifest");
|
||||||
assert.equal(projected.normalized_result.unknowns.length, 3);
|
assert.equal(projected.normalized_result.unknowns.length, 3);
|
||||||
for (const record of projected.normalized_result.records)
|
for (const record of projected.normalized_result.records)
|
||||||
|
|||||||
@@ -219,6 +219,62 @@ it("returns complete named Evidence with its subject, provider and analysis prov
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("admits matching complete text and structured Evidence and rejects either malformed shape", async () => {
|
||||||
|
const { requireMcpOutput } = await verifierResults();
|
||||||
|
const evidence = namedEvidence();
|
||||||
|
expect(
|
||||||
|
requireMcpOutput(
|
||||||
|
{ ...textReply(evidence), structuredContent: evidence },
|
||||||
|
"address_name",
|
||||||
|
),
|
||||||
|
).toEqual(evidence);
|
||||||
|
const malformed = { ...evidence, normalized_result: { name: "entry" } };
|
||||||
|
for (const [text, structuredContent] of [
|
||||||
|
[evidence, malformed],
|
||||||
|
[malformed, evidence],
|
||||||
|
])
|
||||||
|
expect(() =>
|
||||||
|
requireMcpOutput(
|
||||||
|
{ ...textReply(text), structuredContent },
|
||||||
|
"address_name",
|
||||||
|
),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects disagreement in lifecycle envelope fields that the named schema would otherwise discard", async () => {
|
||||||
|
const { requireMcpOutput } = await verifierResults();
|
||||||
|
const result = retainedBundle();
|
||||||
|
const text = { result, vendor_note: "text observation" };
|
||||||
|
const structuredContent = { result, vendor_note: "different observation" };
|
||||||
|
const contract = toolContract("get_evidence_bundle");
|
||||||
|
expect(contract.outputSchema.parse(text)).toEqual(
|
||||||
|
contract.outputSchema.parse(structuredContent),
|
||||||
|
);
|
||||||
|
expect(() =>
|
||||||
|
requireMcpOutput(
|
||||||
|
{ ...textReply(text), structuredContent },
|
||||||
|
"get_evidence_bundle",
|
||||||
|
),
|
||||||
|
).toThrow("text and structured results disagree");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects disagreement in complete Evidence provenance before downstream extraction", async () => {
|
||||||
|
const { requireMcpOutput } = await verifierResults();
|
||||||
|
const evidence = namedEvidence();
|
||||||
|
expect(() =>
|
||||||
|
requireMcpOutput(
|
||||||
|
{
|
||||||
|
...textReply(evidence),
|
||||||
|
structuredContent: {
|
||||||
|
...evidence,
|
||||||
|
raw_result: { engine_name: "different" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"address_name",
|
||||||
|
),
|
||||||
|
).toThrow("text and structured results disagree");
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects mismatched named contracts, error-marked success and malformed normalized results", async () => {
|
it("rejects mismatched named contracts, error-marked success and malformed normalized results", async () => {
|
||||||
const { requireMcpOutput } = await verifierResults();
|
const { requireMcpOutput } = await verifierResults();
|
||||||
const evidence = namedEvidence();
|
const evidence = namedEvidence();
|
||||||
|
|||||||
Reference in new issue
Block a user