feat(sdk): Carapace v0.5.0 — catalog sync, gate check, signed receipts#11
Merged
Conversation
Python (carapace/catalog.py): - CatalogEntry, CatalogState, GateResult dataclasses - fetch_catalog() — ETag-based ARIA catalog fetch with 304 support - run_gate_check() — five-gate pre-call verification: catalog_membership, active_status, revocation_clear, clawmark_gate, delegation_valid - Fail-open: catalog=None → all gates pass with mode=fail_open - Observe-only: trust_gates_enabled=False → all gates pass Python (carapace/receipt.py): - create_receipt() — SHA-256 hashes only, optional Ed25519 signing - verify_receipt() — Ed25519 signature verification (returns bool, never raises) - post_receipt() / post_receipt_async() — fire-and-forget, NEVER raises TypeScript (typescript/src/catalog.ts + receipt.ts): - fetchCatalog(), runGateCheck(), catalogGet(), catalogIsActive() - createReceipt(), verifyReceipt(), postReceipt() via WebCrypto - Full TypeScript types exported from index.ts Bumped version to 0.5.0 (pyproject.toml + typescript/package.json). 40 new tests, 259 total passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR aims to release Carapace SDK v0.5.0 by adding ARIA catalog sync + local gate checks and introducing signed tool-call receipts (with best-effort posting), alongside TypeScript equivalents and version bumps.
Changes:
- Added catalog sync helpers (ETag/304 fetch + five-gate pre-call verifier) in Python and TypeScript.
- Added receipt creation/verification + best-effort receipt posting helpers in Python and TypeScript.
- Bumped SDK versions to 0.5.0 and added a new v0.5 Phase B test suite (Python).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
typescript/src/receipt.ts |
Adds WebCrypto-based receipt hashing/signing/verification and receipt posting helpers. |
typescript/src/catalog.ts |
Adds catalog types, ETag-based fetch, and five-gate runGateCheck helper. |
typescript/src/index.ts |
Re-exports new v0.5.0 catalog + receipt APIs from the package entrypoint. |
typescript/package.json |
Bumps npm package version to 0.5.0 and reformats keywords. |
carapace/catalog.py |
Adds Python catalog dataclasses, fetch via ETag/304, and a five-gate verifier. |
carapace/receipt.py |
Adds Python receipt hashing/signing/verification plus best-effort HTTP posting. |
carapace/__init__.py |
Bumps version to 0.5.0 and exports new catalog/receipt APIs. |
pyproject.toml |
Bumps Python project version to 0.5.0 and updates description. |
tests/test_v05_phase_b.py |
Adds v0.5.0 tests for catalog sync + receipts (repo-root test suite). |
tests/test_v04_features.py |
Removes the previous v0.4 feature test contents from the repo-root suite. |
Comments suppressed due to low confidence (1)
pyproject.toml:15
- New v0.5.0 features (catalog sync and receipt posting) depend on httpx, but httpx is not listed in this package's dependencies. As a result, fetch_catalog() will raise ImportError at runtime unless users manually install httpx. Consider adding httpx to dependencies (or an explicit extra like carapace-sdk[net]) and documenting it accordingly.
name = "carapace-sdk"
version = "0.5.0"
description = "Portable trust envelope for AI agents — enforcement, expiry, versioning, delegation, epistemic tracking, compliance profiles, escalation workflows, catalog sync, and signed receipts"
readme = "README.md"
requires-python = ">=3.10"
license = "Apache-2.0"
dependencies = [
"pydantic>=2.0",
"cryptography>=41.0",
]
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+37
to
+41
| async function sha256Json(obj: unknown): Promise<string> { | ||
| const canonical = JSON.stringify( | ||
| obj, | ||
| Object.keys(obj as Record<string, unknown>).sort(), | ||
| ); |
Comment on lines
+82
to
+89
| if (privateKeyBytes) { | ||
| const keyPair = await crypto.subtle.importKey( | ||
| 'raw', | ||
| privateKeyBytes, | ||
| { name: 'Ed25519' }, | ||
| false, | ||
| ['sign'], | ||
| ); |
Comment on lines
+98
to
+114
| // Note: WebCrypto Ed25519 importKey('raw') gives a private key only. | ||
| // To get the public key hex, the caller should pass it separately or | ||
| // derive it from a CryptoKeyPair (generateKey). This is a limitation | ||
| // of WebCrypto's Ed25519 API for raw private key import. | ||
| publicKeyHex = null; | ||
| } | ||
|
|
||
| return { | ||
| tool_id: toolId, | ||
| agent_id: agentId, | ||
| call_hash: callHash, | ||
| args_hash: argsHash, | ||
| result_hash: resultHash, | ||
| signature, | ||
| public_key: publicKeyHex, | ||
| status, | ||
| }; |
Comment on lines
+58
to
+63
| export async function createReceipt( | ||
| toolId: string, | ||
| args: unknown, | ||
| result: unknown = null, | ||
| options: CreateReceiptOptions = {}, | ||
| ): Promise<ReceiptPayload> { |
Comment on lines
+61
to
+69
| export async function fetchCatalog( | ||
| ariaUrl: string, | ||
| options: FetchCatalogOptions = {}, | ||
| ): Promise<{ state: CatalogState | null; etag: string | null }> { | ||
| const { etag, timeout = 5000, apiKey } = options; | ||
| const headers: Record<string, string> = {}; | ||
| if (etag) headers['If-None-Match'] = etag; | ||
| if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`; | ||
|
|
Comment on lines
+31
to
+32
| clawmark_breakdown: dict = field(default_factory=dict) | ||
| scope_requirements: dict = field(default_factory=dict) |
Comment on lines
+24
to
+32
| def create_receipt( | ||
| tool_id: str, | ||
| args: Any, | ||
| result: Any = None, | ||
| *, | ||
| agent_id: Optional[str] = None, | ||
| status: str = "ok", | ||
| private_key_bytes: Optional[bytes] = None, | ||
| ) -> dict[str, Any]: |
Comment on lines
+112
to
+123
| async def post_receipt_async( | ||
| aria_url: str, | ||
| receipt: dict[str, Any], | ||
| *, | ||
| api_key: Optional[str] = None, | ||
| timeout: float = 3.0, | ||
| ) -> bool: | ||
| """ | ||
| Fire-and-forget async receipt post. Returns True on success, False on any failure. | ||
| NEVER raises — a failed post MUST NOT block the tool call. | ||
| """ | ||
| try: |
Comment on lines
+100
to
+114
| from .catalog import ( | ||
| CatalogEntry, | ||
| CatalogState, | ||
| GateResult, | ||
| fetch_catalog, | ||
| run_gate_check, | ||
| ) | ||
| from .receipt import ( | ||
| create_receipt, | ||
| post_receipt, | ||
| post_receipt_async, | ||
| verify_receipt, | ||
| ) | ||
|
|
||
| __version__ = "0.5.0" |
Comment on lines
+10
to
+18
| # ─── Catalog tests ──────────────────────────────────────────────────────────── | ||
|
|
||
| from carapace.catalog import ( | ||
| CatalogEntry, | ||
| CatalogState, | ||
| GateResult, | ||
| fetch_catalog, | ||
| run_gate_check, | ||
| ) |
…eipts
- TypeScript: fix sha256Json to sort keys recursively (array-replacer dropped nested keys)
- TypeScript: replace privateKeyBytes with keyPair CryptoKeyPair so public_key is populated
- TypeScript: fix ETag normalization to strip W/ weak prefix in catalog.ts
- TypeScript: add DOM lib to tsconfig for CryptoKey/CryptoKeyPair types
- Python: remove unused hashlib/json imports from catalog.py
- Python: add dict[str, Any] types to clawmark_breakdown/scope_requirements fields
- Python: replace 'Lobsters' with 'Agents' in catalog module docstring
- Python: add status validation ('ok'|'error') in create_receipt()
- Python: fix post_receipt/post_receipt_async docstrings to 'best-effort (never raises)'
- Mirror carapace/catalog.py and receipt.py into python/carapace/ for CI/publishing
- Add python/tests/test_v05_phase_b.py (40 tests) mirrored from root tests/
- Bump python package version to 0.5.0
- Add typescript/test/v05_receipts.test.js (17 new tests, 26 total passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ryan10sa-star
added a commit
that referenced
this pull request
May 23, 2026
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
carapace/catalog.py— CatalogEntry, CatalogState, GateResult dataclasses; fetch_catalog() with ETag/304 support; run_gate_check() five-gate pre-call verifiercarapace/receipt.py— create_receipt() (hashes only, optional Ed25519 signing), verify_receipt(), post_receipt() / post_receipt_async() (fire-and-forget, never raises)typescript/src/catalog.ts— same catalog types and functions in TypeScripttypescript/src/receipt.ts— receipt helpers via WebCrypto (Ed25519, Chrome 113+/Node 20+)Gate check design
Five gates run in order, short-circuit on first failure: catalog_membership → active_status → revocation_clear → clawmark_gate → delegation_valid
Fail-open rules preserved: catalog=None passes all gates; trust_gates_enabled=False passes all gates.
Depends on
Phase A: relayforge-ai/aria-registry#7
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need.