diff --git a/README.md b/README.md index 5548aa3..651b2a7 100644 --- a/README.md +++ b/README.md @@ -170,13 +170,11 @@ This repository's GitHub Actions workflows currently run: `npm test` runs Node unit tests plus `tests/smoke.mjs`. -The Python SDK test file present in this repo is a placeholder and is not part of the npm-based CI path. - ## Legacy verification code This repository still contains `runtime/src/receipt-verification.js` and related tests under `runtime/tests/` and `sdk/typescript-sdk/tests/`. -That code exercises an older verification model and compatibility helpers for test material in this repository. It is not the production verification path used by `server.mjs`, which imports signing and verification from `@commandlayer/runtime-core`. +That file is explicitly legacy compatibility code for older fixture-based verification coverage in this repository. It is not the production verification path used by `server.mjs`, which imports signing and verification from `@commandlayer/runtime-core`. ## Configuration and operations diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index 66cc4df..2a8c43d 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -169,4 +169,4 @@ Blocked localhost, private-network, or disallowed-host requests fail before the The checked-in GitHub workflows currently run Node-based checks and a scheduled/manual ENS smoke script. -This repo also contains `sdk/python-sdk/tests/test_verification.py`, but the file is a placeholder and is not part of the npm-based CI workflow defined in `.github/workflows/ci.yml`. +There is no repo-local Python verification test in the npm-based CI workflow defined in `.github/workflows/ci.yml`. diff --git a/runtime/src/receipt-verification.js b/runtime/src/receipt-verification.js index 15ab904..ce285c1 100644 --- a/runtime/src/receipt-verification.js +++ b/runtime/src/receipt-verification.js @@ -1,5 +1,13 @@ import crypto from "node:crypto"; +/** + * Legacy compatibility verifier used only by repo-local fixtures and SDK parity tests. + * + * Production runtime verification flows through @commandlayer/runtime-core via server.mjs. + * Keep this file only for older compatibility material in this repository; do not treat it + * as the primary verification path for the runtime service. + */ + const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex"); export function stableStringify(value) { @@ -64,6 +72,7 @@ function verifySignature(receiptHash, signatureB64, pubkeyBytes) { return crypto.verify(null, Buffer.from(receiptHash, "utf8"), key, Buffer.from(signatureB64, "base64")); } +/** @deprecated Legacy compatibility helper; production verification uses @commandlayer/runtime-core. */ export async function verifyReceipt(receipt, { resolver, expectedIssuer } = {}) { if (!resolver) throw new Error("Resolver required"); if (expectedIssuer && receipt.issuer !== expectedIssuer) throw new Error("Issuer mismatch"); diff --git a/runtime/tests/ens-resolution.test.mjs b/runtime/tests/ens-resolution.test.mjs index c292da7..db9f2a4 100644 --- a/runtime/tests/ens-resolution.test.mjs +++ b/runtime/tests/ens-resolution.test.mjs @@ -3,11 +3,11 @@ import assert from "node:assert/strict"; import { resolveSigner } from "../src/receipt-verification.js"; import { buildResolver } from "./helpers.mjs"; -test("ENS Resolution: resolves cl.receipt.signer correctly", async () => { +test("Legacy verification helper: resolves cl.receipt.signer correctly", async () => { const signer = await resolveSigner("parseagent.eth", buildResolver()); assert.equal(signer, "runtime.commandlayer.eth"); }); -test("ENS Resolution: fails if cl.receipt.signer missing", async () => { +test("Legacy verification helper: fails if cl.receipt.signer missing", async () => { await assert.rejects(() => resolveSigner("invalidagent.eth", buildResolver()), /Missing cl\.receipt\.signer/); }); diff --git a/runtime/tests/key-resolution.test.mjs b/runtime/tests/key-resolution.test.mjs index aa60a6c..e11128b 100644 --- a/runtime/tests/key-resolution.test.mjs +++ b/runtime/tests/key-resolution.test.mjs @@ -3,16 +3,16 @@ import assert from "node:assert/strict"; import { resolveSignatureKey } from "../src/receipt-verification.js"; import { buildResolver } from "./helpers.mjs"; -test("Signer Key Resolution: resolves cl.sig.pub and cl.sig.kid", async () => { +test("Legacy verification helper: resolves cl.sig.pub and cl.sig.kid", async () => { const key = await resolveSignatureKey("runtime.commandlayer.eth", buildResolver()); assert.equal(key.kid, "v1"); assert.equal(key.pubkeyBytes.length, 32); }); -test("Signer Key Resolution: fails if cl.sig.pub missing", async () => { +test("Legacy verification helper: fails if cl.sig.pub missing", async () => { await assert.rejects(() => resolveSignatureKey("bad-signer.eth", buildResolver()), /Missing cl\.sig\.pub/); }); -test("Signer Key Resolution: fails if pubkey malformed", async () => { +test("Legacy verification helper: fails if pubkey malformed", async () => { await assert.rejects(() => resolveSignatureKey("malformed.eth", buildResolver()), /Invalid ed25519 format/); }); diff --git a/runtime/tests/key-rotation.test.mjs b/runtime/tests/key-rotation.test.mjs index ed67ac6..6458e2b 100644 --- a/runtime/tests/key-rotation.test.mjs +++ b/runtime/tests/key-rotation.test.mjs @@ -3,7 +3,7 @@ import assert from "node:assert/strict"; import { verifyReceipt } from "../src/receipt-verification.js"; import { buildResolver, loadFixture } from "./helpers.mjs"; -test("Key Rotation: v1 receipt still verifies after v2 key added", async () => { +test("Legacy receipt verification compatibility: v1 receipt still verifies after v2 key added", async () => { const baseResolver = buildResolver(); const v1Pub = await baseResolver.getText("runtime.commandlayer.eth", "cl.sig.pub"); const resolver = buildResolver({ diff --git a/runtime/tests/receipt-verification.test.mjs b/runtime/tests/receipt-verification.test.mjs index e3b9bdc..5d310e1 100644 --- a/runtime/tests/receipt-verification.test.mjs +++ b/runtime/tests/receipt-verification.test.mjs @@ -3,18 +3,18 @@ import assert from "node:assert/strict"; import { verifyReceipt } from "../src/receipt-verification.js"; import { buildResolver, loadFixture } from "./helpers.mjs"; -test("Receipt Verification: valid receipt verifies", async () => { +test("Legacy receipt verification compatibility: valid receipt verifies", async () => { const result = await verifyReceipt(loadFixture("receipt_valid.json"), { resolver: buildResolver() }); assert.equal(result.valid, true); }); -test("Receipt Verification: invalid signature fails", async () => { +test("Legacy receipt verification compatibility: invalid signature fails", async () => { const result = await verifyReceipt(loadFixture("receipt_invalid_sig.json"), { resolver: buildResolver() }); assert.equal(result.valid, false); assert.match(result.error, /Signature verification failed/); }); -test("Receipt Verification: wrong kid fails", async () => { +test("Legacy receipt verification compatibility: wrong kid fails", async () => { const result = await verifyReceipt(loadFixture("receipt_wrong_kid.json"), { resolver: buildResolver() }); assert.equal(result.valid, false); assert.match(result.error, /Unknown key id/); diff --git a/sdk/python-sdk/tests/test_verification.py b/sdk/python-sdk/tests/test_verification.py deleted file mode 100644 index cfad456..0000000 --- a/sdk/python-sdk/tests/test_verification.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest - - -def test_valid_receipt_verifies(): - # Template placeholder for Python SDK parity suite. - assert True - - -def test_invalid_signature_fails(): - # Template placeholder for Python SDK parity suite. - assert True - - -def test_missing_signer_fails(): - with pytest.raises(Exception): - raise Exception("Missing signer") diff --git a/sdk/typescript-sdk/tests/canonicalization.test.mjs b/sdk/typescript-sdk/tests/canonicalization.test.mjs index 14ba5aa..9f34f55 100644 --- a/sdk/typescript-sdk/tests/canonicalization.test.mjs +++ b/sdk/typescript-sdk/tests/canonicalization.test.mjs @@ -3,7 +3,7 @@ import assert from "node:assert/strict"; import { computeReceiptHash } from "../../../runtime/src/receipt-verification.js"; import { loadFixture } from "../../../runtime/tests/helpers.mjs"; -test("Canonicalization: stable JSON produces deterministic hash", () => { +test("Legacy verification compatibility: stable JSON produces deterministic hash", () => { const receipt = loadFixture("receipt_valid.json"); const hash1 = computeReceiptHash(receipt); const hash2 = computeReceiptHash(JSON.parse(JSON.stringify(receipt))); diff --git a/sdk/typescript-sdk/tests/ens-delegation.test.mjs b/sdk/typescript-sdk/tests/ens-delegation.test.mjs index bdb6f8c..e0e7c3f 100644 --- a/sdk/typescript-sdk/tests/ens-delegation.test.mjs +++ b/sdk/typescript-sdk/tests/ens-delegation.test.mjs @@ -3,7 +3,7 @@ import assert from "node:assert/strict"; import { resolveSignerKey } from "../../../runtime/src/receipt-verification.js"; import { buildResolver } from "../../../runtime/tests/helpers.mjs"; -test("ENS Delegation Flow: agent delegates to runtime signer", async () => { +test("Legacy verification compatibility: agent delegates to runtime signer", async () => { const { algorithm, kid, rawPublicKeyBytes } = await resolveSignerKey("parseagent.eth", buildResolver()); assert.equal(algorithm, "ed25519"); assert.equal(kid, "v1"); diff --git a/sdk/typescript-sdk/tests/security-cases.test.mjs b/sdk/typescript-sdk/tests/security-cases.test.mjs index 4855781..01d0e36 100644 --- a/sdk/typescript-sdk/tests/security-cases.test.mjs +++ b/sdk/typescript-sdk/tests/security-cases.test.mjs @@ -3,13 +3,13 @@ import assert from "node:assert/strict"; import { verifyReceipt } from "../../../runtime/src/receipt-verification.js"; import { buildResolver, loadFixture } from "../../../runtime/tests/helpers.mjs"; -test("Security Edge Cases: fails if receipt.issuer mismatches ENS name", async () => { +test("Legacy verification compatibility: fails if receipt.issuer mismatches ENS name", async () => { const receipt = loadFixture("receipt_valid.json"); receipt.issuer = "evil.eth"; await assert.rejects(() => verifyReceipt(receipt, { resolver: buildResolver(), expectedIssuer: "parseagent.eth" }), /Issuer mismatch/); }); -test("Security Edge Cases: fails on tampered payload_hash", async () => { +test("Legacy verification compatibility: fails on tampered payload_hash", async () => { const receipt = loadFixture("receipt_valid.json"); receipt.payload_hash = "fakehash"; const result = await verifyReceipt(receipt, { resolver: buildResolver() }); diff --git a/server.mjs b/server.mjs index 9c0496c..700fff9 100644 --- a/server.mjs +++ b/server.mjs @@ -1423,39 +1423,37 @@ app.get("/", (req, res) => { ); }); -app.get("/health", (req, res) => { +function buildHealthPayload() { + return { + ok: true, + service: SERVICE_NAME, + version: SERVICE_VERSION, + api_version: API_VERSION, + base: CANONICAL_BASE, + node: process.version, + host: HOST, + port: PORT, + enabled_verbs: ENABLED_VERBS, + signer_id: runtimeConfig.signerId, + signer_ok: signerBootState.ok, + verifier_ok: !!getActivePublicPem() || hasRpc(), + signer_source: activeSigner.source, + kid: runtimeConfig.kid, + canonical_id: runtimeConfig.canonicalId, + public_key_fingerprint: activeSigner.publicKeyFingerprint, + signer_errors: signerBootState.errors, + time: nowIso(), + ...instancePayload(), + }; +} + +function sendHealth(res) { res.setHeader("Content-Type", "application/json; charset=utf-8"); - return res.status(200).end( - JSON.stringify({ - ok: true, - service: SERVICE_NAME, - version: SERVICE_VERSION, - api_version: API_VERSION, - base: CANONICAL_BASE, - node: process.version, - host: HOST, - port: PORT, - enabled_verbs: ENABLED_VERBS, - signer_id: runtimeConfig.signerId, - signer_ok: signerBootState.ok, - verifier_ok: !!getActivePublicPem() || hasRpc(), - signer_source: activeSigner.source, - kid: runtimeConfig.kid, - canonical_id: runtimeConfig.canonicalId, - public_key_fingerprint: activeSigner.publicKeyFingerprint, - signer_errors: signerBootState.errors, - time: nowIso(), - ...instancePayload(), - }) - ); -}); + return res.status(200).end(JSON.stringify(buildHealthPayload())); +} -app.get("/healthz", (req, res) => { - // keep behavior: alias to /health - req.url = "/health"; - req.originalUrl = "/health"; - return app._router.handle(req, res, () => {}); -}); +app.get("/health", (req, res) => sendHealth(res)); +app.get("/healthz", (req, res) => sendHealth(res)); // ----------------------- // debug (gated)