Skip to content
Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 5 additions & 4 deletions DEVELOPER_EXPERIENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
12 changes: 4 additions & 8 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -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"
});
```

Expand Down Expand Up @@ -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)
```

Expand Down
4 changes: 1 addition & 3 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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

Expand Down
9 changes: 1 addition & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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, {
Expand All @@ -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(
Expand All @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions python-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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(
Expand Down
9 changes: 3 additions & 6 deletions python-sdk/commandlayer/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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(
Expand All @@ -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,
}
Expand Down
2 changes: 1 addition & 1 deletion python-sdk/commandlayer/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions python-sdk/docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
24 changes: 22 additions & 2 deletions python-sdk/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
},
Expand All @@ -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

Expand All @@ -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(
Expand Down
2 changes: 0 additions & 2 deletions python-sdk/tests/test_public_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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

Expand Down
5 changes: 0 additions & 5 deletions python-sdk/tests/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion test_vectors/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion test_vectors/expected_hash.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
509b71764d0158fab6bb073ed0bf28182bc1fa0227c00dc4ef79a0067e968196
e4a5caf79b2caff783539414d44fbac79ad7f6fa95e54a9f7529ffc7ccd663cb
2 changes: 1 addition & 1 deletion test_vectors/public_key_base64.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
A6EHv/POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg=
QcCc+o5cZhWY/qRvNCJz2mFaekBB7NGJlaD+61ew+o0=
9 changes: 2 additions & 7 deletions test_vectors/receipt_invalid_sig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"kid": "v1",
"status": "success",
"x402": {
"verb": "summarize",
"version": "1.1.0"
},
"result": {
"summary": "fixture"
},
Expand All @@ -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"
}
}
}
11 changes: 3 additions & 8 deletions test_vectors/receipt_malformed_pubkey.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"kid": "v1",
"status": "success",
"x402": {
"verb": "summarize",
"version": "1.1.0"
},
"result": {
"summary": "fixture"
},
Expand All @@ -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=="
}
}
}
11 changes: 3 additions & 8 deletions test_vectors/receipt_valid.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"kid": "v1",
"status": "success",
"x402": {
"verb": "summarize",
"version": "1.1.0"
},
"result": {
"summary": "fixture"
},
Expand All @@ -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=="
}
}
}
11 changes: 3 additions & 8 deletions test_vectors/receipt_valid_v1.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"kid": "v1",
"status": "success",
"x402": {
"verb": "summarize",
"version": "1.1.0"
},
"result": {
"summary": "fixture"
},
Expand All @@ -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=="
}
}
}
Loading
Loading