Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions scripts/kpi-gate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { hasUnsafeSourceId } from "./lib/source-id.mjs";
import { createKpiChildEnvironment } from "./lib/kpi-child-environment.mjs";
import { hasDuplicateJsonObjectKeys } from "./normalize-commercial-readiness-evidence.mjs";

const fatalUtf8Decoder = new TextDecoder("utf-8", { fatal: true });
const parsedArgs = parseArgs(process.argv.slice(2));
const logPath = parsedArgs.positionals[0] ?? process.env.NOEMA_KPI_LOG_PATH ?? "exchange-30d.ndjson";
const failThreshold = parsedArgs.positionals[1] ?? process.env.NOEMA_KPI_FAILURE_THRESHOLD ?? "0.02";
Expand Down Expand Up @@ -233,9 +235,25 @@ async function loadProductionProvenance(path, expectedLogPath) {
};
}

let provenanceText;
try {
provenanceText = fatalUtf8Decoder.decode(await readFile(path));
} catch {
return {
pass: false,
reason: `KPI provenance file is not valid UTF-8: ${path}.`,
};
}

let parsed;
try {
parsed = JSON.parse(await readFile(path, "utf8"));
if (hasDuplicateJsonObjectKeys(provenanceText)) {
return {
pass: false,
reason: `KPI provenance file contains duplicate decoded JSON object keys: ${path}.`,
};
}
parsed = JSON.parse(provenanceText);
} catch {
return {
pass: false,
Expand Down Expand Up @@ -461,4 +479,4 @@ function parseArgs(args) {
result.positionals.push(arg);
}
return result;
}
}
109 changes: 109 additions & 0 deletions test/kpi-provenance-json-integrity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { createHash } from "node:crypto";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { describe, expect, it } from "vitest";

function writeThirtyDayExchangeLog(path: string) {
const records = [
{
event: "http_request",
route: "/exchange",
status_code: 200,
latency_ms: 120,
timestamp: "2026-06-01T00:00:00.000Z",
},
{
event: "http_request",
route: "/exchange",
status_code: 200,
latency_ms: 150,
timestamp: "2026-07-01T03:00:00.000Z",
},
];
writeFileSync(path, `${records.map((record) => JSON.stringify(record)).join("\n")}\n`);
}

function logIdentity(path: string) {
const bytes = readFileSync(path);
return {
logSha256: createHash("sha256").update(bytes).digest("hex"),
logBytes: bytes.byteLength,
};
}

function runStrictGate(logPath: string, provenancePath: string, evidencePath: string) {
return spawnSync(process.execPath, ["scripts/kpi-gate.mjs", logPath], {
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
NOEMA_KPI_STRICT: "1",
NOEMA_KPI_REQUIRE_WINDOW_DAYS: "30",
NOEMA_KPI_PROVENANCE_PATH: provenancePath,
NOEMA_KPI_EVIDENCE_PATH: evidencePath,
},
});
}

function validProvenanceJson(logPath: string) {
const identity = logIdentity(logPath);
return JSON.stringify({
sourceKind: "production",
sourceId: "cloudflare-logpush:noema-production",
sourceMethod: "log-url",
logPath,
records: 2,
collectedAt: "2026-07-02T00:00:00.000Z",
...identity,
});
}

describe("strict KPI provenance JSON integrity", () => {
it("rejects duplicate decoded provenance keys before last-key-wins parsing", () => {
const dir = mkdtempSync(join(tmpdir(), "noema-kpi-provenance-integrity-"));
try {
const logPath = join(dir, "exchange-30d.ndjson");
const provenancePath = join(dir, "exchange-30d.ndjson.provenance.json");
const evidencePath = join(dir, "evidence.json");
writeThirtyDayExchangeLog(logPath);
const { logSha256, logBytes } = logIdentity(logPath);
writeFileSync(
provenancePath,
`{"sourceKind":"staging","sourceK\\u0069nd":"production","sourceId":"cloudflare-logpush:noema-production","sourceMethod":"log-url","logPath":${JSON.stringify(logPath)},"records":2,"collectedAt":"2026-07-02T00:00:00.000Z","logSha256":"${logSha256}","logBytes":${logBytes}}`,
);

const result = runStrictGate(logPath, provenancePath, evidencePath);

expect(result.status).toBe(1);
expect(result.stdout).toContain("KPI provenance file");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

it("rejects malformed UTF-8 provenance bytes instead of replacement-decoding them", () => {
const dir = mkdtempSync(join(tmpdir(), "noema-kpi-provenance-integrity-"));
try {
const logPath = join(dir, "exchange-30d.ndjson");
const provenancePath = join(dir, "exchange-30d.ndjson.provenance.json");
const evidencePath = join(dir, "evidence.json");
writeThirtyDayExchangeLog(logPath);
const text = validProvenanceJson(logPath);
const bytes = Buffer.from(text, "utf8");
const marker = Buffer.from("log-url", "utf8");
const markerOffset = bytes.indexOf(marker);
expect(markerOffset).toBeGreaterThanOrEqual(0);
bytes[markerOffset] = 0x80;
writeFileSync(provenancePath, bytes);

const result = runStrictGate(logPath, provenancePath, evidencePath);

expect(result.status).toBe(1);
expect(result.stdout).toContain("KPI provenance file");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
});
Loading