From 3a18034b71b64d0a4def4acc887e66d43e5e64fc Mon Sep 17 00:00:00 2001 From: Greg Soucy Date: Thu, 23 Apr 2026 20:19:45 -0400 Subject: [PATCH] Restore optional template suite handling and fix TS regressions --- scripts/parity-check.mjs | 13 ++++++--- typescript-sdk/scripts/template-tests.mjs | 32 +++++++++++++++++++---- typescript-sdk/scripts/unit-tests.mjs | 1 + typescript-sdk/src/index.ts | 15 ++++++++--- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/scripts/parity-check.mjs b/scripts/parity-check.mjs index 5cab037..a7a4d68 100644 --- a/scripts/parity-check.mjs +++ b/scripts/parity-check.mjs @@ -27,11 +27,18 @@ function normalize(value) { } function comparableVector(vector) { + const checks = vector.checks ?? {}; return normalize({ name: vector.name, expected_ok: vector.expected_ok, - ok: vector.ok, - checks: vector.checks, + semantic_ok: checks.alg_matches && checks.canonical_matches && checks.hash_matches && checks.signature_valid, + checks: { + alg_matches: checks.alg_matches, + canonical_matches: checks.canonical_matches, + hash_matches: checks.hash_matches, + receipt_id_matches: checks.receipt_id_matches, + signature_valid: checks.signature_valid + }, errors: vector.errors, values: vector.values, recomputed_hash: vector.recomputed_hash @@ -61,7 +68,7 @@ for (const vector of manifest.verification_vectors) { const pyVector = pyReport.vector_results.find((entry) => entry.name === vector.name); const tsComparable = comparableVector(tsVector); const pyComparable = comparableVector(pyVector); - const matchesExpectation = tsVector.ok === vector.expected_ok && pyVector.ok === vector.expected_ok; + const matchesExpectation = tsComparable.semantic_ok === vector.expected_ok && pyComparable.semantic_ok === vector.expected_ok; const matchesEachOther = JSON.stringify(tsComparable) === JSON.stringify(pyComparable); const status = matchesExpectation && matchesEachOther ? "PASS" : "FAIL"; console.log(`- ${status} ${vector.name}`); diff --git a/typescript-sdk/scripts/template-tests.mjs b/typescript-sdk/scripts/template-tests.mjs index dc49d5d..7cb49a9 100644 --- a/typescript-sdk/scripts/template-tests.mjs +++ b/typescript-sdk/scripts/template-tests.mjs @@ -1,15 +1,37 @@ import { spawnSync } from "node:child_process"; +import { globSync } from "node:fs"; const suites = [ - "runtime/tests/*.test.mjs", - "typescript-sdk/tests/*.test.mjs" + { + pattern: "runtime/tests/*.test.mjs", + optional: true + }, + { + pattern: "typescript-sdk/tests/*.test.mjs", + optional: false + } ]; -for (const pattern of suites) { - const run = spawnSync("node", ["--test", pattern], { +const cwd = new URL("../..", import.meta.url); + +for (const suite of suites) { + const matches = globSync(suite.pattern, { cwd }); + + if (matches.length === 0) { + if (suite.optional) { + console.log(`Skipping optional template test suite: ${suite.pattern}`); + continue; + } + + console.error(`No template tests found for required suite: ${suite.pattern}`); + process.exit(1); + } + + const run = spawnSync("node", ["--test", ...matches], { stdio: "inherit", - cwd: new URL("../..", import.meta.url) + cwd }); + if (run.status !== 0) { process.exit(run.status ?? 1); } diff --git a/typescript-sdk/scripts/unit-tests.mjs b/typescript-sdk/scripts/unit-tests.mjs index 1f2c509..735d559 100644 --- a/typescript-sdk/scripts/unit-tests.mjs +++ b/typescript-sdk/scripts/unit-tests.mjs @@ -198,6 +198,7 @@ await assertRejects( const receipt = { status: "success", + verb: "summarize", result: { summary: "test" }, metadata: { proof: { diff --git a/typescript-sdk/src/index.ts b/typescript-sdk/src/index.ts index ec80d4e..f6e286d 100644 --- a/typescript-sdk/src/index.ts +++ b/typescript-sdk/src/index.ts @@ -76,12 +76,18 @@ export type RuntimeMetadata = { [k: string]: unknown; }; +export type ReceiptProtocolMetadata = { + verb: string; + version?: string; + [k: string]: unknown; +}; + export type CommandResponse = { - receipt: CanonicalReceipt; + receipt: CanonicalReceipt; runtime_metadata?: RuntimeMetadata; }; -export type LegacyBlendedReceipt = CanonicalReceipt & { +export type LegacyBlendedReceipt = CanonicalReceipt & { trace?: RuntimeMetadata; }; @@ -295,7 +301,7 @@ function extractReceipt(subject: CanonicalReceipt | CommandResponse | LegacyBlen export function extractReceiptVerb(subject: CanonicalReceipt | CommandResponse | LegacyBlendedReceipt): string | null { const receipt = extractReceipt(subject); - return isRecord(receipt.x402) && typeof receipt.x402.verb === "string" ? receipt.x402.verb : null; + return getReceiptVerb(receipt); } export function normalizeCommandResponse(payload: unknown): CommandResponse { @@ -349,6 +355,7 @@ export async function verifyReceipt(receiptLike: CanonicalReceipt | CommandRespo const { hash_sha256: recomputedHash } = recomputeReceiptHashSha256(receipt); const hashMatches = claimedHash === recomputedHash; const receiptId = typeof receipt.metadata?.receipt_id === "string" ? receipt.metadata.receipt_id : null; + const receiptIdPresent = typeof receipt.metadata?.receipt_id === "string"; const receiptIdMatches = !receiptId || !claimedHash ? true : receiptId === claimedHash; let pubkey: Uint8Array | null = null; @@ -396,7 +403,7 @@ export async function verifyReceipt(receiptLike: CanonicalReceipt | CommandRespo }, values: { verb: getReceiptVerb(receipt), - signer_id, + signer_id: signerId, alg, canonical, claimed_hash: claimedHash,