Files
agents-marketplace/tests/unit/surplus-vector-proof.test.ts

341 lines
12 KiB
TypeScript

import { createHash } from "node:crypto";
import {
mkdtemp,
readFile,
rm,
stat,
symlink,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
buildSurplusMerkleBatch,
canonicalizeSurplusSale,
canonicalSurplusSaleJson,
FileSurplusVectorProofLedger,
hashSurplusSale,
SURPLUS_SALE_PROOF_PROTOCOL,
verifySurplusMerkleProof,
type SurplusMerkleProof,
type SurplusMerkleBatch,
type SurplusSaleProofInput,
} from "../../supplier/src/surplus/vectorProof.js";
const directories: string[] = [];
async function temporaryDirectory(): Promise<string> {
const directory = await mkdtemp(join(tmpdir(), "surplus-vector-proof-"));
directories.push(directory);
return directory;
}
afterEach(async () => {
await Promise.all(directories.splice(0).map((path) =>
rm(path, { recursive: true, force: true })
));
});
function sale(
id: string,
overrides: Partial<SurplusSaleProofInput> = {},
): SurplusSaleProofInput {
return {
id,
offerId: "offer-1",
model: "deepseek-v4-flash",
createdAt: "2026-08-28T11:00:00.123Z",
inputTokens: 10,
outputTokens: 2,
cacheReadTokens: 3,
sellerCostMicroUsd: 83,
effectiveInputUsdPer1m: 0.003972,
effectiveOutputUsdPer1m: 0.007944,
...overrides,
};
}
function proofFor(
batch: SurplusMerkleBatch,
saleId: string,
): SurplusMerkleProof {
const leaf = batch.leaves.find((candidate) => candidate.sale.saleId === saleId);
if (leaf === undefined) throw new Error(`Missing test leaf ${saleId}`);
return {
saleHash: leaf.saleHash,
batchRoot: leaf.batchRoot,
leafIndex: leaf.leafIndex,
leafCount: leaf.leafCount,
siblings: [...leaf.siblings],
};
}
function changedHash(hash: string): string {
return `${hash[0] === "0" ? "1" : "0"}${hash.slice(1)}`;
}
describe("Surplus Vector sale projection", () => {
it("encodes the versioned leaf in the required field order and hashes its UTF-8 JSON", () => {
const input = sale("sale-1");
const canonical = canonicalizeSurplusSale(input);
const json = canonicalSurplusSaleJson(canonical);
expect(canonical).toEqual({
protocol: SURPLUS_SALE_PROOF_PROTOCOL,
saleId: "sale-1",
offerId: "offer-1",
model: "deepseek-v4-flash",
createdAtEpochMs: Date.parse("2026-08-28T11:00:00.123Z"),
inputTokens: 10,
outputTokens: 2,
cacheReadTokens: 3,
sellerCostMicroUsd: 83,
effectiveInputUsdPer1m: "0.003972",
effectiveOutputUsdPer1m: "0.007944",
});
expect(json).toBe(
'{"protocol":"surplus-sale-proof-v1","saleId":"sale-1","offerId":"offer-1","model":"deepseek-v4-flash","createdAtEpochMs":1787914800123,"inputTokens":10,"outputTokens":2,"cacheReadTokens":3,"sellerCostMicroUsd":83,"effectiveInputUsdPer1m":"0.003972","effectiveOutputUsdPer1m":"0.007944"}',
);
expect(hashSurplusSale(input)).toBe(
createHash("sha256").update(json, "utf8").digest("hex"),
);
});
it("rejects sales that cannot produce an immutable canonical leaf", () => {
expect(() => canonicalizeSurplusSale(sale("sale-1", { offerId: null })))
.toThrow("offer id");
expect(() => canonicalizeSurplusSale(sale("sale-1", { createdAt: null })))
.toThrow("no creation time");
expect(() => canonicalizeSurplusSale(sale("sale-1", { inputTokens: -1 })))
.toThrow("non-negative safe integer");
expect(() => canonicalizeSurplusSale(sale("bad\nid")))
.toThrow("printable ASCII");
});
});
describe("Surplus sale Merkle batches", () => {
it("sorts by sale id and duplicates the final node at odd tree levels", () => {
const batch = buildSurplusMerkleBatch([
canonicalizeSurplusSale(sale("sale-c")),
canonicalizeSurplusSale(sale("sale-a")),
canonicalizeSurplusSale(sale("sale-b")),
]);
expect(batch.count).toBe(3);
expect(batch.firstSaleId).toBe("sale-a");
expect(batch.lastSaleId).toBe("sale-c");
expect(batch.leaves.map((leaf) => leaf.sale.saleId)).toEqual([
"sale-a",
"sale-b",
"sale-c",
]);
const last = batch.leaves[2];
expect(last.siblings[0]).toBe(last.saleHash);
for (const leaf of batch.leaves) {
expect(verifySurplusMerkleProof(leaf.sale, leaf)).toBe(true);
}
const reordered = buildSurplusMerkleBatch([
canonicalizeSurplusSale(sale("sale-b")),
canonicalizeSurplusSale(sale("sale-c")),
canonicalizeSurplusSale(sale("sale-a")),
]);
expect(reordered.root).toBe(batch.root);
expect(reordered.leaves).toEqual(batch.leaves);
});
it("rejects every material proof mutation", () => {
const originalSale = sale("sale-a");
const batch = buildSurplusMerkleBatch([
canonicalizeSurplusSale(originalSale),
canonicalizeSurplusSale(sale("sale-b")),
canonicalizeSurplusSale(sale("sale-c")),
]);
const proof = proofFor(batch, "sale-a");
expect(verifySurplusMerkleProof(originalSale, proof)).toBe(true);
const changedFields: SurplusSaleProofInput[] = [
sale("sale-x"),
sale("sale-a", { offerId: "offer-2" }),
sale("sale-a", { model: "another-model" }),
sale("sale-a", { createdAt: "2026-08-28T11:00:00.124Z" }),
sale("sale-a", { inputTokens: 11 }),
sale("sale-a", { outputTokens: 3 }),
sale("sale-a", { cacheReadTokens: 4 }),
sale("sale-a", { sellerCostMicroUsd: 84 }),
sale("sale-a", { effectiveInputUsdPer1m: 0.003973 }),
sale("sale-a", { effectiveOutputUsdPer1m: 0.007945 }),
];
for (const changed of changedFields) {
expect(verifySurplusMerkleProof(changed, proof)).toBe(false);
}
expect(verifySurplusMerkleProof(originalSale, {
...proof,
siblings: [changedHash(proof.siblings[0]), ...proof.siblings.slice(1)],
})).toBe(false);
expect(verifySurplusMerkleProof(originalSale, {
...proof,
leafIndex: 1,
})).toBe(false);
expect(verifySurplusMerkleProof(originalSale, {
...proof,
batchRoot: changedHash(proof.batchRoot),
})).toBe(false);
expect(verifySurplusMerkleProof(originalSale, {
...proof,
saleHash: changedHash(proof.saleHash),
})).toBe(false);
});
});
describe("FileSurplusVectorProofLedger", () => {
it("persists intent before confirmation and never selects confirmed sales again", async () => {
const directory = await temporaryDirectory();
const path = join(directory, "proofs", "ledger.json");
let now = Date.parse("2026-08-28T12:00:00.000Z");
const ledger = await FileSurplusVectorProofLedger.open(path, { now: () => now });
expect(await ledger.ingestSales([
sale("sale-c"),
sale("sale-a"),
sale("sale-b"),
sale("sale-a"),
])).toEqual({ inserted: 3, duplicates: 1 });
expect(ledger.selectUnprovedSales(2).map((item) => item.saleId)).toEqual([
"sale-a",
"sale-b",
]);
const intent = await ledger.persistPendingBatch(["sale-b", "sale-a"]);
expect(intent.leaves.map((leaf) => leaf.saleId)).toEqual(["sale-a", "sale-b"]);
expect((await stat(path)).mode & 0o777).toBe(0o600);
const restarted = await FileSurplusVectorProofLedger.open(path, { now: () => now });
expect(restarted.pendingBatchIntent()).toEqual(intent);
expect(() => restarted.selectUnprovedSales()).toThrow(
"pending Surplus proof batch must be resolved first",
);
await expect(restarted.createPendingBatch()).rejects.toThrow(
"pending Surplus proof batch must be resolved first",
);
now += 1_000;
const txHash = "AB".repeat(32);
const confirmed = await restarted.confirmPendingBatch(intent.batchRoot, txHash);
expect(confirmed.txHash).toBe(txHash.toLowerCase());
for (const id of ["sale-a", "sale-b"]) {
const proof = restarted.dashboardProof(id);
expect(proof).toMatchObject({
status: "confirmed",
batchRoot: intent.batchRoot,
txHash: txHash.toLowerCase(),
});
expect(verifySurplusMerkleProof(sale(id), {
saleHash: proof!.saleHash,
batchRoot: proof!.batchRoot!,
leafIndex: proof!.leafIndex!,
leafCount: intent.count,
siblings: proof!.siblings,
})).toBe(true);
}
const confirmedRestart = await FileSurplusVectorProofLedger.open(path);
expect(confirmedRestart.selectUnprovedSales().map((item) => item.saleId))
.toEqual(["sale-c"]);
expect(confirmedRestart.confirmedBatchHistory()).toEqual([confirmed]);
});
it("deduplicates exact sales and rejects an immutable id conflict", async () => {
const directory = await temporaryDirectory();
const path = join(directory, "ledger.json");
const ledger = await FileSurplusVectorProofLedger.open(path);
expect(await ledger.ingestSales([sale("sale-a")]))
.toEqual({ inserted: 1, duplicates: 0 });
expect(await ledger.ingestSales([sale("sale-a")]))
.toEqual({ inserted: 0, duplicates: 1 });
await expect(ledger.ingestSales([
sale("sale-a", { sellerCostMicroUsd: 84 }),
])).rejects.toThrow("Conflicting Surplus sale id");
expect(ledger.selectUnprovedSales()).toHaveLength(1);
});
it("records a safe failed attempt and makes its sales eligible for a later batch", async () => {
const directory = await temporaryDirectory();
const path = join(directory, "ledger.json");
let now = Date.parse("2026-08-28T12:00:00.000Z");
const ledger = await FileSurplusVectorProofLedger.open(path, { now: () => now });
await ledger.ingestSales([sale("sale-a"), sale("sale-b")]);
const intent = await ledger.createPendingBatch();
if (intent === null) throw new Error("Expected a pending test batch");
now += 1_000;
const failure = await ledger.failPendingBatch(intent.batchRoot, "submit");
expect(failure).toEqual({
batchRoot: intent.batchRoot,
count: 2,
firstSaleId: "sale-a",
lastSaleId: "sale-b",
stage: "submit",
failedAt: "2026-08-28T12:00:01.000Z",
});
expect(ledger.dashboardProof("sale-a")).toMatchObject({
status: "failed",
batchRoot: intent.batchRoot,
txHash: null,
leafIndex: 0,
});
const restarted = await FileSurplusVectorProofLedger.open(path);
expect(restarted.failedAttemptHistory()).toEqual([failure]);
expect(restarted.selectUnprovedSales().map((item) => item.saleId)).toEqual([
"sale-a",
"sale-b",
]);
const retry = await restarted.createPendingBatch();
expect(retry?.batchRoot).toBe(intent.batchRoot);
expect(restarted.dashboardProof("sale-a")?.status).toBe("pending");
});
it("stores only canonical sale fields and proof data", async () => {
const directory = await temporaryDirectory();
const path = join(directory, "ledger.json");
const ledger = await FileSurplusVectorProofLedger.open(path);
const input = {
...sale("sale-a"),
apiKey: "must-not-be-written",
buyerAddress: "must-not-be-written",
};
await ledger.ingestSales([input]);
const text = await readFile(path, "utf8");
expect(text).not.toContain("must-not-be-written");
expect(text).not.toContain("apiKey");
expect(text).not.toContain("buyerAddress");
});
it("rejects ledger symlinks on load and before an atomic write", async () => {
const directory = await temporaryDirectory();
const target = join(directory, "target.json");
const loadPath = join(directory, "load-ledger.json");
await writeFile(
target,
'{"version":1,"sales":[],"pendingBatch":null,"confirmedBatches":[],"failedAttempts":[]}\n',
{ mode: 0o600 },
);
await symlink(target, loadPath);
await expect(FileSurplusVectorProofLedger.open(loadPath)).rejects.toThrow();
const writePath = join(directory, "write-ledger.json");
const ledger = await FileSurplusVectorProofLedger.open(writePath);
await symlink(target, writePath);
await expect(ledger.ingestSales([sale("sale-a")])).rejects.toThrow(
"wrong file type",
);
expect(await readFile(target, "utf8")).not.toContain("sale-a");
});
});