← Files OpsTruthARCHIVED FILE
scripts/smoke-production.mjs
5.27 KB · Oct 4, 2026 · 12:31 UTC
import { compareInternalAndExternalProbes, mcpRequest } from "./lib/production-smoke.mjs";
const endpoint = String(process.env.OPSTRUTH_PRODUCTION_URL || "https://mcp.opstruth.io").replace(/\/$/, "");
const expectedVersion = process.env.OPSTRUTH_EXPECTED_VERSION || "0.4.0";
const expectedCommit = process.env.OPSTRUTH_EXPECTED_COMMIT || null;
const expectedTools = 21;
const errors = [];
async function request(path, init = {}) {
try {
return await fetch(`${endpoint}${path}`, { ...init, signal: AbortSignal.timeout(30_000) });
} catch (error) {
errors.push(`${path}: ${error instanceof Error ? error.message : String(error)}`);
return null;
}
}
async function mcpCall(id, method, params = {}) {
const call = mcpRequest(id, method, params);
const response = await request(call.path, call.init);
if (!response) return {};
if (!response.ok) errors.push(`/mcp ${method}: HTTP ${response.status}`);
return response.json().catch(() => ({}));
}
const publicProbePaths = ["/health", "/privacy", "/terms", "/support"];
const externalStatuses = {};
for (const path of [...publicProbePaths, "/signing-key"]) {
const response = await request(path);
if (!response) continue;
if (publicProbePaths.includes(path)) externalStatuses[path] = response.status;
if (!response.ok) errors.push(`${path}: HTTP ${response.status}`);
if (path === "/health") {
const body = await response.json().catch(() => ({}));
if (body.version !== expectedVersion) errors.push(`/health: expected version ${expectedVersion}, got ${body.version || "missing"}`);
if (expectedCommit && body.commit !== expectedCommit) errors.push(`/health: expected commit ${expectedCommit}, got ${body.commit || "missing"}`);
if (body.tools !== expectedTools) errors.push(`/health: expected ${expectedTools} tools, got ${body.tools}`);
if (body.evidenceGraph !== "1.0.0") errors.push(`/health: expected Evidence Graph 1.0.0, got ${body.evidenceGraph || "missing"}`);
if (body.evidenceSigning !== "configured") errors.push(`/health: evidence signing is ${body.evidenceSigning || "missing"}`);
const githubVerification = body.githubVerification;
if (githubVerification?.mode !== "github_app_installation"
|| githubVerification?.configured !== true
|| githubVerification?.scope !== "selected_public_repository"
|| Object.keys(githubVerification || {}).sort().join(",") !== "configured,mode,scope") {
errors.push("/health: least-privilege GitHub App verification access is not configured");
}
}
if (path === "/signing-key") {
const body = await response.json().catch(() => ({}));
if (!body.configured || body.algorithm !== "Ed25519" || !/^sha256:[a-f0-9]{64}$/.test(body.signerFingerprint || "")) {
errors.push("/signing-key: stable Ed25519 evidence signing is not configured");
}
}
}
const initialise = await mcpCall(1, "initialize", { protocolVersion: "2025-06-18" });
if (initialise.result?.serverInfo?.version !== expectedVersion) errors.push("/mcp initialize: version mismatch");
const listed = await mcpCall(2, "tools/list");
const tools = listed.result?.tools || [];
if (tools.length !== expectedTools) errors.push(`/mcp tools/list: expected ${expectedTools} tools, got ${tools.length}`);
if (new Set(tools.map((tool) => tool.name)).size !== expectedTools) errors.push("/mcp tools/list: tool names are not unique");
if (tools.some((tool) => tool.annotations?.readOnlyHint !== true)) errors.push("/mcp tools/list: every tool must be explicitly read-only");
if (!tools.some((tool) => tool.name === "opstruth_snapshot_evidence")) errors.push("/mcp tools/list: Evidence Graph snapshot tool missing");
const internalProbe = await mcpCall(3, "tools/call", {
name: "opstruth_probe_deployment",
arguments: { deployment_url: endpoint, health_paths: publicProbePaths },
});
const internalProbeReport = internalProbe.result?.structuredContent;
if (internalProbe.result?.isError) errors.push("/mcp tools/call: internal deployment probe returned an error");
errors.push(...compareInternalAndExternalProbes({ endpoint, paths: publicProbePaths, externalStatuses, internalReport: internalProbeReport }));
const snapshot = await mcpCall(4, "tools/call", {
name: "opstruth_snapshot_evidence",
arguments: { repository_url: "AyobamiH/opstruth-chatgpt-plugin" },
});
const graph = snapshot.result?.structuredContent;
if (snapshot.result?.isError || graph?.schema !== "opstruth.evidence-graph" || graph?.schemaVersion !== "1.0.0") {
errors.push("/mcp tools/call: signed Evidence Graph snapshot failed");
} else if (!graph.proof || !/^sha256:[a-f0-9]{64}$/.test(graph.proof.signerFingerprint || "")) {
errors.push("/mcp tools/call: Evidence Graph proof missing or malformed");
}
const rejectedFeedback = await request("/feedback", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ reason: "release_smoke", surface: "mcp" }),
});
if (rejectedFeedback?.status !== 400) errors.push(`/feedback: invalid reason should be rejected with HTTP 400, got ${rejectedFeedback?.status || "no response"}`);
if (errors.length) {
console.error(errors.join("\n"));
process.exit(1);
}
console.log(`production smoke passed: ${endpoint} (${expectedVersion}${expectedCommit ? ` @ ${expectedCommit}` : ""}, ${expectedTools} read-only tools, signed Evidence Graph, selected-repository GitHub App reads)`);
SHA-256: c146c7e992a63a5a88fcb5926b77b2692098ce97d2327d8e5b6a5c58481099f2