From 9723f429c0c4f36304f09efaa33f7510dfbb21d9 Mon Sep 17 00:00:00 2001 From: Greg Soucy Date: Sat, 21 Mar 2026 23:39:59 -0400 Subject: [PATCH] Align SDK commons contract to v1.1.0 --- CHANGELOG.md | 2 +- DEVELOPER_EXPERIENCE.md | 9 ++++---- EXAMPLES.md | 12 ++++------- QUICKSTART.md | 4 +--- README.md | 9 +------- python-sdk/README.md | 3 +-- python-sdk/commandlayer/client.py | 9 +++----- python-sdk/commandlayer/verify.py | 2 +- python-sdk/docs/client.md | 2 ++ python-sdk/tests/test_client.py | 24 ++++++++++++++++++++-- python-sdk/tests/test_public_api.py | 2 -- python-sdk/tests/test_verify.py | 5 ----- test_vectors/README.md | 2 +- test_vectors/expected_hash.txt | 2 +- test_vectors/public_key_base64.txt | 2 +- test_vectors/receipt_invalid_sig.json | 9 ++------ test_vectors/receipt_malformed_pubkey.json | 11 +++------- test_vectors/receipt_valid.json | 11 +++------- test_vectors/receipt_valid_v1.json | 11 +++------- test_vectors/receipt_wrong_kid.json | 11 +++------- typescript-sdk/README.md | 8 +------- typescript-sdk/scripts/unit-tests.mjs | 22 +++++++++++++++++--- typescript-sdk/src/index.ts | 19 +++++++++++++---- 23 files changed, 93 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ff655..c02f6b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ CommandLayer SDKs now align on the Commons-first Protocol-Commons v1.1.0 surface ### Breaking changes - Consumers should treat the v1.1.0 wrapper and receipt shape as the only canonical public contract documented by this repository. -- Verification guidance now assumes the canonical receipt payload and current signer-discovery flow; integrations built around older mixed envelopes should update. +- Verification guidance now assumes the canonical receipt payload and current signer-discovery flow; integrations built around older mixed envelopes and payment-blended Commons payloads should update. ### Non-breaking improvements - Improved cross-SDK parity coverage for hashing, signature verification, and signer resolution behavior. diff --git a/DEVELOPER_EXPERIENCE.md b/DEVELOPER_EXPERIENCE.md index a9dfe5a..a31c6a9 100644 --- a/DEVELOPER_EXPERIENCE.md +++ b/DEVELOPER_EXPERIENCE.md @@ -23,11 +23,11 @@ This document is for maintainers and advanced integrators. Start with `README.md The signed payload includes: - `status`, -- the protocol metadata block at `x402` (Commons verb/version metadata; not a commercial SDK claim), - `result` or `error`, -- `metadata.receipt_id`, and - `metadata.proof` with `alg`, `canonical`, `signer_id`, `hash_sha256`, and `signature_b64`. +Commons receipts should not add payment metadata. A retained `x402` block is legacy / commercial-only compatibility and is not part of the Commons happy path. + ### Runtime metadata Unsigned context can include: @@ -61,12 +61,13 @@ If one SDK intentionally diverges, document it in that package README and in rel ## Verification rules Both SDKs use the same verification contract: -1. strip `receipt_id` and the signed hash/signature fields from the receipt, +1. strip `receipt_id` if present and remove the signed hash/signature fields from the receipt, 2. canonicalize with `cl-stable-json-v1`, 3. recompute `sha256`, 4. compare against `metadata.proof.hash_sha256`, 5. verify the Ed25519 signature over the UTF-8 hash string, -6. optionally discover the signing key via ENS. +6. treat `metadata.receipt_id` as optional / opaque, +7. optionally discover the signing key via ENS. ## CLI rules diff --git a/EXAMPLES.md b/EXAMPLES.md index f2d47e2..3334a85 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,6 +1,6 @@ # CommandLayer SDK Examples -Canonical examples for the CommandLayer SDK repo. These examples keep the Commons v1.1.0 story receipt-first: `receipt` is signed, `runtime_metadata` is optional and unsigned, and the `x402` object is protocol metadata rather than a commercial SDK surface. +Canonical examples for the CommandLayer SDK repo. These examples keep the Commons v1.1.0 story receipt-first: `receipt` is signed, `runtime_metadata` is optional and unsigned, and Commons examples do not include payment metadata. All examples in this file target: - Protocol-Commons v1.1.0, @@ -13,15 +13,10 @@ All examples in this file target: { "receipt": { "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0", - }, "result": { "summary": "..." }, "metadata": { - "receipt_id": "...", "proof": { "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", @@ -134,7 +129,8 @@ const response = await client.format({ const response = await client.parse({ content: '{ "a": 1 }', contentType: "json", - mode: "strict" + mode: "strict", + schema: "invoice.summary.v1" }); ``` @@ -162,7 +158,7 @@ converted = client.convert(content='{"a":1}', from_format="json", to_format="csv description = client.describe(subject="receipt verification") explanation = client.explain(subject="receipt verification", style="step-by-step") formatted = client.format(content="a: 1\nb: 2", to="table") -parsed = client.parse(content='{ "a": 1 }', content_type="json", mode="strict") +parsed = client.parse(content='{ "a": 1 }', content_type="json", mode="strict", schema="invoice.summary.v1") fetched = client.fetch(source="https://example.com", include_metadata=True) ``` diff --git a/QUICKSTART.md b/QUICKSTART.md index b1829ee..7cb3994 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -63,10 +63,8 @@ Both SDKs return the same shape: { "receipt": { "status": "success", - "x402": { "verb": "summarize", "version": "1.1.0" }, "result": { "summary": "..." }, "metadata": { - "receipt_id": "...", "proof": { "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", @@ -83,7 +81,7 @@ Both SDKs return the same shape: } ``` -Use `response.receipt` as the durable protocol artifact. `runtime_metadata` is optional execution context. The retained `x402` object carries Commons verb metadata and is not a commercial feature signal. +Use `response.receipt` as the durable protocol artifact. `runtime_metadata` is optional execution context. Commons responses should be treated as receipt-first and non-payment-aware; `x402` only appears in legacy or explicitly commercial payloads. ## 4. Verify the receipt diff --git a/README.md b/README.md index d940319..db07b59 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This repo is aligned to the current CommandLayer v1.1.0 surface: - canonical signed receipts as the verification contract payload, and - optional `runtime_metadata` as unsigned execution context. -Protocol-Commercial / x402 payment flows are not a first-class SDK surface in this repo today. The retained `receipt.x402` metadata block is part of the Commons protocol schema here; it should not be read as commercial feature coverage. +Protocol-Commercial / x402 payment flows are intentionally separate from the Commons SDK surface in this repo. Commons examples and helpers below avoid payment metadata entirely; any retained `receipt.x402` handling is legacy / commercial-only compatibility, not the Commons happy path. ## Install @@ -60,7 +60,6 @@ const response = await client.summarize({ }); console.log(response.receipt.result?.summary); -console.log(response.receipt.metadata?.receipt_id); console.log(response.runtime_metadata?.duration_ms); const verification = await verifyReceipt(response.receipt, { @@ -82,7 +81,6 @@ response = client.summarize( ) print(response["receipt"]["result"]["summary"]) -print(response["receipt"]["metadata"]["receipt_id"]) print(response.get("runtime_metadata", {}).get("duration_ms")) verification = verify_receipt( @@ -100,15 +98,10 @@ Client methods now return a command response envelope: { "receipt": { "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0", - }, "result": { "summary": "..." }, "metadata": { - "receipt_id": "...", "proof": { "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", diff --git a/python-sdk/README.md b/python-sdk/README.md index 7e08641..042e0b8 100644 --- a/python-sdk/README.md +++ b/python-sdk/README.md @@ -8,7 +8,7 @@ The Python package mirrors the TypeScript SDK's protocol model: - `runtime_metadata` is optional execution context, and - verification can use an explicit Ed25519 key or ENS discovery. -The retained `response["receipt"]["x402"]` block is Commons protocol metadata, not a commercial SDK surface in this repository. +Any `response["receipt"]["x402"]` block should be treated as legacy / commercial-only metadata rather than part of the Commons happy path in this repository. ## Install @@ -30,7 +30,6 @@ response = client.summarize( ) print(response["receipt"]["result"]["summary"]) -print(response["receipt"]["metadata"]["receipt_id"]) print(response.get("runtime_metadata", {}).get("duration_ms")) verification = verify_receipt( diff --git a/python-sdk/commandlayer/client.py b/python-sdk/commandlayer/client.py index 032cf04..8ba67be 100644 --- a/python-sdk/commandlayer/client.py +++ b/python-sdk/commandlayer/client.py @@ -240,6 +240,7 @@ def parse( content: str, content_type: str = "text", mode: str = "best_effort", + schema: str | None = None, target_schema: str | None = None, max_tokens: int = 1000, ) -> CommandResponse: @@ -251,8 +252,8 @@ def parse( }, "limits": {"max_output_tokens": max_tokens}, } - if target_schema: - payload["input"]["target_schema"] = target_schema + if schema or target_schema: + payload["input"]["schema"] = schema or target_schema return self.call("parse", payload) def fetch( @@ -275,10 +276,6 @@ def fetch( def _build_payload(self, verb: str, body: dict[str, Any]) -> dict[str, Any]: return { - "x402": { - "verb": verb, - "version": COMMONS_VERSION, - }, "actor": body.get("actor", self.actor), **body, } diff --git a/python-sdk/commandlayer/verify.py b/python-sdk/commandlayer/verify.py index 80d69d7..7219b01 100644 --- a/python-sdk/commandlayer/verify.py +++ b/python-sdk/commandlayer/verify.py @@ -215,7 +215,7 @@ def verify_receipt( metadata = target.get("metadata") if isinstance(target, dict) else None receipt_id_value = metadata.get("receipt_id") if isinstance(metadata, dict) else None receipt_id = receipt_id_value if isinstance(receipt_id_value, str) else None - receipt_id_matches = bool(claimed_hash and receipt_id == claimed_hash) + receipt_id_matches = not receipt_id or not claimed_hash or receipt_id == claimed_hash pubkey: bytes | None = None pubkey_source: str | None = None diff --git a/python-sdk/docs/client.md b/python-sdk/docs/client.md index 811d7f3..ce9f5da 100644 --- a/python-sdk/docs/client.md +++ b/python-sdk/docs/client.md @@ -38,6 +38,8 @@ Every verb method returns: - `parse` - `fetch` +`parse()` accepts the current-line `schema` field for schema-guided parsing. The legacy `target_schema` argument is still accepted as a compatibility alias, but new integrations should prefer `schema`. + ## Generic invoke Use `client.call(verb, payload)` for full control. diff --git a/python-sdk/tests/test_client.py b/python-sdk/tests/test_client.py index a894b8b..aed1c8b 100644 --- a/python-sdk/tests/test_client.py +++ b/python-sdk/tests/test_client.py @@ -34,7 +34,6 @@ def handler(request: httpx.Request) -> httpx.Response: json={ "receipt": { "status": "success", - "x402": {"verb": "summarize"}, "metadata": { "proof": {"alg": "ed25519-sha256", "canonical": "cl-stable-json-v1"} }, @@ -54,7 +53,7 @@ def handler(request: httpx.Request) -> httpx.Response: sent = captured["json"] assert isinstance(sent, dict) assert sent["actor"] == "tester" - assert sent["x402"]["verb"] == "summarize" + assert "x402" not in sent assert response["receipt"]["status"] == "success" assert response["runtime_metadata"]["duration_ms"] == 12 @@ -71,6 +70,27 @@ def handler(_: httpx.Request) -> httpx.Response: assert exc.value.status_code == 422 +def test_parse_uses_current_schema_field() -> None: + captured: dict[str, object] = {} + + def handler(request: httpx.Request) -> httpx.Response: + captured["json"] = json.loads(request.content.decode("utf-8")) + return httpx.Response(200, json={"receipt": {"status": "success", "metadata": {"proof": {}}}}) + + client = CommandLayerClient( + runtime="https://runtime.commandlayer.org", + actor="tester", + http_client=httpx.Client(transport=httpx.MockTransport(handler)), + ) + + client.parse(content='{"a":1}', content_type="json", mode="strict", schema="invoice.summary.v1") + + sent = captured["json"] + assert isinstance(sent, dict) + assert sent["input"]["schema"] == "invoice.summary.v1" + assert "x402" not in sent + + def test_client_verify_receipts_failure(monkeypatch: pytest.MonkeyPatch) -> None: def handler(_: httpx.Request) -> httpx.Response: return httpx.Response( diff --git a/python-sdk/tests/test_public_api.py b/python-sdk/tests/test_public_api.py index 2658f6f..6e1fa75 100644 --- a/python-sdk/tests/test_public_api.py +++ b/python-sdk/tests/test_public_api.py @@ -89,7 +89,6 @@ def handler(_: httpx.Request) -> httpx.Response: json={ "receipt": { "status": "success", - "x402": {"verb": "summarize", "version": "1.1.0"}, "result": {"summary": "done"}, "metadata": { "proof": { @@ -113,7 +112,6 @@ def handler(_: httpx.Request) -> httpx.Response: client.close() assert set(response.keys()) == {"receipt", "runtime_metadata"} - assert response["receipt"]["x402"]["verb"] == "summarize" assert response["receipt"]["result"]["summary"] == "done" assert response["runtime_metadata"]["duration_ms"] == 7 diff --git a/python-sdk/tests/test_verify.py b/python-sdk/tests/test_verify.py index 3f486b8..d898d53 100644 --- a/python-sdk/tests/test_verify.py +++ b/python-sdk/tests/test_verify.py @@ -24,10 +24,6 @@ def get_text(self, name: str, key: str) -> str | None: def _signed_receipt() -> tuple[dict[str, object], str]: receipt: dict[str, object] = { "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0", - }, "metadata": { "proof": { "alg": "ed25519-sha256", @@ -48,7 +44,6 @@ def _signed_receipt() -> tuple[dict[str, object], str]: assert isinstance(proof, dict) proof["hash_sha256"] = h proof["signature_b64"] = base64.b64encode(sig).decode("utf-8") - metadata["receipt_id"] = h return receipt, pub_b64 diff --git a/test_vectors/README.md b/test_vectors/README.md index 91cd403..2a11812 100644 --- a/test_vectors/README.md +++ b/test_vectors/README.md @@ -1,6 +1,6 @@ # Test vectors -This directory contains shared receipt fixtures used by both SDKs and the parity check. The fixtures model the current Commons v1.1.0 receipt contract: `receipt` is canonical, `runtime_metadata` is unsigned, and the `x402` object is retained only as protocol metadata. +This directory contains shared receipt fixtures used by both SDKs and the parity check. The fixtures model the current Commons v1.1.0 receipt contract: `receipt` is canonical, `runtime_metadata` is unsigned, and Commons fixtures do not carry payment metadata. ## Files diff --git a/test_vectors/expected_hash.txt b/test_vectors/expected_hash.txt index 34034be..b670582 100644 --- a/test_vectors/expected_hash.txt +++ b/test_vectors/expected_hash.txt @@ -1 +1 @@ -509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196 +e4a5caf79b2caff783539414d44fbac79ad7f6fa95e54a9f7529ffc7ccd663cb diff --git a/test_vectors/public_key_base64.txt b/test_vectors/public_key_base64.txt index ef2dcde..bd73e86 100644 --- a/test_vectors/public_key_base64.txt +++ b/test_vectors/public_key_base64.txt @@ -1 +1 @@ -A6EHv/POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg= +QcCc+o5cZhWY/qRvNCJz2mFaekBB7NGJlaD+61ew+o0= diff --git a/test_vectors/receipt_invalid_sig.json b/test_vectors/receipt_invalid_sig.json index c7ced1e..adb91c9 100644 --- a/test_vectors/receipt_invalid_sig.json +++ b/test_vectors/receipt_invalid_sig.json @@ -1,10 +1,6 @@ { "kid": "v1", "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0" - }, "result": { "summary": "fixture" }, @@ -13,9 +9,8 @@ "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", "signer_id": "runtime.commandlayer.eth", - "hash_sha256": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196", + "hash_sha256": "e4a5caf79b2caff783539414d44fbac79ad7f6fa95e54a9f7529ffc7ccd663cb", "signature_b64": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" - }, - "receipt_id": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196" + } } } diff --git a/test_vectors/receipt_malformed_pubkey.json b/test_vectors/receipt_malformed_pubkey.json index bc6464d..65d6636 100644 --- a/test_vectors/receipt_malformed_pubkey.json +++ b/test_vectors/receipt_malformed_pubkey.json @@ -1,10 +1,6 @@ { "kid": "v1", "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0" - }, "result": { "summary": "fixture" }, @@ -13,9 +9,8 @@ "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", "signer_id": "runtime.commandlayer.eth", - "hash_sha256": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196", - "signature_b64": "RXtxKNIiU4uRSQoTK2wjByZnj3wJsA8g4+Ofuv+y3pUf/d57p1V9dL0vSoYUXLLMgPa7CpxKMsOl50AFhojcCw==" - }, - "receipt_id": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196" + "hash_sha256": "e4a5caf79b2caff783539414d44fbac79ad7f6fa95e54a9f7529ffc7ccd663cb", + "signature_b64": "QrS9hVm8IVuLSPdrun9WiMtW49mphrW4OeMPJOfxDbsAByHR0v64IyLQ5PaxsanPfvHKzZqYFJT8g5lR1yGvBA==" + } } } diff --git a/test_vectors/receipt_valid.json b/test_vectors/receipt_valid.json index bc6464d..65d6636 100644 --- a/test_vectors/receipt_valid.json +++ b/test_vectors/receipt_valid.json @@ -1,10 +1,6 @@ { "kid": "v1", "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0" - }, "result": { "summary": "fixture" }, @@ -13,9 +9,8 @@ "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", "signer_id": "runtime.commandlayer.eth", - "hash_sha256": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196", - "signature_b64": "RXtxKNIiU4uRSQoTK2wjByZnj3wJsA8g4+Ofuv+y3pUf/d57p1V9dL0vSoYUXLLMgPa7CpxKMsOl50AFhojcCw==" - }, - "receipt_id": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196" + "hash_sha256": "e4a5caf79b2caff783539414d44fbac79ad7f6fa95e54a9f7529ffc7ccd663cb", + "signature_b64": "QrS9hVm8IVuLSPdrun9WiMtW49mphrW4OeMPJOfxDbsAByHR0v64IyLQ5PaxsanPfvHKzZqYFJT8g5lR1yGvBA==" + } } } diff --git a/test_vectors/receipt_valid_v1.json b/test_vectors/receipt_valid_v1.json index bc6464d..65d6636 100644 --- a/test_vectors/receipt_valid_v1.json +++ b/test_vectors/receipt_valid_v1.json @@ -1,10 +1,6 @@ { "kid": "v1", "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0" - }, "result": { "summary": "fixture" }, @@ -13,9 +9,8 @@ "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", "signer_id": "runtime.commandlayer.eth", - "hash_sha256": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196", - "signature_b64": "RXtxKNIiU4uRSQoTK2wjByZnj3wJsA8g4+Ofuv+y3pUf/d57p1V9dL0vSoYUXLLMgPa7CpxKMsOl50AFhojcCw==" - }, - "receipt_id": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196" + "hash_sha256": "e4a5caf79b2caff783539414d44fbac79ad7f6fa95e54a9f7529ffc7ccd663cb", + "signature_b64": "QrS9hVm8IVuLSPdrun9WiMtW49mphrW4OeMPJOfxDbsAByHR0v64IyLQ5PaxsanPfvHKzZqYFJT8g5lR1yGvBA==" + } } } diff --git a/test_vectors/receipt_wrong_kid.json b/test_vectors/receipt_wrong_kid.json index 282c795..838f215 100644 --- a/test_vectors/receipt_wrong_kid.json +++ b/test_vectors/receipt_wrong_kid.json @@ -1,10 +1,6 @@ { "kid": "v2", "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0" - }, "result": { "summary": "fixture" }, @@ -13,9 +9,8 @@ "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", "signer_id": "runtime.commandlayer.eth", - "hash_sha256": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196", - "signature_b64": "RXtxKNIiU4uRSQoTK2wjByZnj3wJsA8g4+Ofuv+y3pUf/d57p1V9dL0vSoYUXLLMgPa7CpxKMsOl50AFhojcCw==" - }, - "receipt_id": "509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196" + "hash_sha256": "e4a5caf79b2caff783539414d44fbac79ad7f6fa95e54a9f7529ffc7ccd663cb", + "signature_b64": "QrS9hVm8IVuLSPdrun9WiMtW49mphrW4OeMPJOfxDbsAByHR0v64IyLQ5PaxsanPfvHKzZqYFJT8g5lR1yGvBA==" + } } } diff --git a/typescript-sdk/README.md b/typescript-sdk/README.md index 1005e99..59e1532 100644 --- a/typescript-sdk/README.md +++ b/typescript-sdk/README.md @@ -30,7 +30,6 @@ const response = await client.summarize({ }); console.log(response.receipt.result?.summary); -console.log(response.receipt.metadata?.receipt_id); console.log(response.runtime_metadata?.duration_ms); const verification = await verifyReceipt(response.receipt, { @@ -48,13 +47,8 @@ Client methods return: { "receipt": { "status": "success", - "x402": { - "verb": "summarize", - "version": "1.1.0" - }, "result": {}, "metadata": { - "receipt_id": "...", "proof": { "alg": "ed25519-sha256", "canonical": "cl-stable-json-v1", @@ -72,7 +66,7 @@ Client methods return: } ``` -`verifyReceipt()` accepts the canonical `receipt` object. The retained `receipt.x402` block is Commons protocol metadata, not a commercial SDK surface. The SDK also accepts a whole response envelope for legacy compatibility, but new integrations should pass `response.receipt` explicitly. +`verifyReceipt()` accepts the canonical `receipt` object. The SDK also accepts a whole response envelope for legacy compatibility, but new integrations should pass `response.receipt` explicitly. Any `receipt.x402` block should be treated as legacy / commercial-only metadata rather than part of the Commons contract. ## Verification modes diff --git a/typescript-sdk/scripts/unit-tests.mjs b/typescript-sdk/scripts/unit-tests.mjs index c1e349d..646808d 100644 --- a/typescript-sdk/scripts/unit-tests.mjs +++ b/typescript-sdk/scripts/unit-tests.mjs @@ -196,7 +196,6 @@ await assertRejects( const receipt = { status: "success", - x402: { verb: "summarize", version: "1.1.0" }, result: { summary: "test" }, metadata: { proof: { @@ -213,13 +212,12 @@ const receiptSig = nacl.sign.detached(new Uint8Array(receiptMsg), kp.secretKey); receipt.metadata.proof.hash_sha256 = hash_sha256; receipt.metadata.proof.signature_b64 = Buffer.from(receiptSig).toString("base64"); -receipt.metadata.receipt_id = hash_sha256; const vr = await verifyReceipt(receipt, { publicKey: `ed25519:${b64Key}` }); assert(vr.ok === true, "verifyReceipt ok for valid receipt (explicit key)"); assert(vr.checks.hash_matches === true, "verifyReceipt hash matches"); assert(vr.checks.signature_valid === true, "verifyReceipt signature valid"); -assert(vr.checks.receipt_id_matches === true, "verifyReceipt receipt_id matches"); +assert(vr.checks.receipt_id_matches === true, "verifyReceipt tolerates absent receipt_id"); const vrEns = await verifyReceipt(receipt, { ens: { @@ -248,6 +246,24 @@ try { assert(err instanceof CommandLayerError, "client.call rejects unknown verb with CommandLayerError"); } +const captured = []; +const mockClient = new CommandLayerClient({ + fetchImpl: async (_url, init) => { + captured.push(JSON.parse(init.body)); + return { + ok: true, + status: 200, + async json() { + return { receipt: { status: "success", metadata: { proof: {} } } }; + } + }; + } +}); +await mockClient.parse({ content: '{"ok":true}', contentType: "json", schema: "invoice.summary.v1" }); +assert(captured[0].actor === "sdk-user", "client payload includes actor"); +assert(!("x402" in captured[0]), "commons request payload omits x402 metadata"); +assert(captured[0].input.schema === "invoice.summary.v1", "parse uses current-line schema field"); + // ---- Summary ---- console.log(`\n${passed} passed, ${failed} failed`); diff --git a/typescript-sdk/src/index.ts b/typescript-sdk/src/index.ts index 31249f8..0ec4795 100644 --- a/typescript-sdk/src/index.ts +++ b/typescript-sdk/src/index.ts @@ -26,6 +26,10 @@ export type ReceiptMetadata = { export type CanonicalReceipt = { status: "success" | "error" | string; + /** + * Legacy / commercial-only metadata. + * Commons v1.1.0 receipts should not rely on or emit this block. + */ x402?: { verb?: string; version?: string; @@ -324,7 +328,7 @@ export async function verifyReceipt( const { hash_sha256: recomputedHash } = recomputeReceiptHashSha256(receipt); const hashMatches = claimedHash ? recomputedHash === claimedHash : false; const receiptId = typeof receipt.metadata?.receipt_id === "string" ? receipt.metadata.receipt_id : null; - const receiptIdMatches = claimedHash ? receiptId === claimedHash : false; + const receiptIdMatches = !receiptId || !claimedHash ? true : receiptId === claimedHash; let pubkey: Uint8Array | null = null; let pubkey_source: "explicit" | "ens" | null = null; @@ -509,13 +513,21 @@ export class CommandLayerClient { }); } - async parse(opts: { content: string; contentType?: "json" | "yaml" | "text"; mode?: "best_effort" | "strict"; targetSchema?: string; maxTokens?: number }) { + async parse(opts: { + content: string; + contentType?: "json" | "yaml" | "text"; + mode?: "best_effort" | "strict"; + schema?: string; + /** @deprecated Use schema. */ + targetSchema?: string; + maxTokens?: number; + }) { return this.call("parse", { input: { content: opts.content, content_type: opts.contentType ?? "text", mode: opts.mode ?? "best_effort", - ...(opts.targetSchema ? { target_schema: opts.targetSchema } : {}) + ...(opts.schema || opts.targetSchema ? { schema: opts.schema ?? opts.targetSchema } : {}) }, limits: { max_output_tokens: opts.maxTokens ?? 1000 } }); @@ -537,7 +549,6 @@ export class CommandLayerClient { this.ensureVerifyConfigIfEnabled(); const payload = { - x402: { verb, version: commonsVersion }, ...(body.actor ? { actor: body.actor } : { actor: this.actor }), ...body };