feat(python): claim envelope + contractSetCid, completes Side B (closes #222)#232
Conversation
#222) Adds the v1.2 layered claim-envelope subsystem to the python kit. PR #221 shipped only the proof-side crypto primitives and explicitly deferred the claim-envelope construction (tasks 6-7 of #206) as "separate subsystem". This change closes that gap. What landed: - src/provekit_lift_py_tests/claim_envelope.py (new): mirrors implementations/rust/provekit-claim-envelope/src/lib.rs. - mint_contract / mint_bridge / mint_implication build the layered triple {envelope, header, metadata} per substrate-layers spec (2026-05-03-substrate-layers-envelope-header-body.md). Signature is Ed25519 over JCS({header, metadata}); attestation CID is BLAKE3-512(JCS(envelope)) AFTER signature is embedded. - contract_cid (signer-independent content CID, per 2026-05-03-contract-cid-vs-attestation-cid.md). - compute_contract_set_cid per 2026-05-03-contract-set-extension.md: BLAKE3-512(JCS(sorted contractCids)). - ClaimEnvelope.from_contract_decl(decl, signer, ...) lowers a python ContractDecl's Formula clauses via ir.formula_to_value and delegates to mint_contract. - Authoring tagged union (KitAuthor / Lift / Llm) with Llm.confidence serialized as int(confidence * 1000) (truncate toward zero, matching rust's `as i64` cast, NOT round). - src/provekit_lift_py_tests/signing.py: adds Signer (seed + producer_id) and Signer.sign_claim convenience delegate. PR #221's exported surface is unchanged. - tests/test_claim_envelope.py (new): 29 tests covering layered-shape conformance, error paths, authoring round-trip, determinism, bridge/implication smoke, contractSetCid edge cases, and cross-kit byte-equivalence against the rust reference (pinned bytes, attestation CID, contract CID, contractSetCid). - implementations/rust/provekit-claim-envelope/tests/cross_kit_pin.rs (new): reproducible pin generator for cross-kit conformance. Other kits (cpp, csharp, go, ts) can import the same fixture inputs. Avoids the dangling `cargo run --example proof_envelope_bytes` reference in PR #221. Cross-kit result: PASS. Python output is byte-identical to rust for the canonical fixture (1615-byte layered envelope; 100% match). - attestation CID: blake3-512:b5cd82094dd4d7da...3523639c - contract CID: blake3-512:bca0bb9144b3b35e...8715db1 - contractSetCid: blake3-512:e42f67a1f9947237...75962506 Test plan: - pytest tests/test_claim_envelope.py -v: 29 passed - pytest -v (full python suite): 142 passed, 1 skipped (pre-existing) - cargo test -p provekit-claim-envelope: 24 passed, 0 failed Acceptance (from #222): - [x] from provekit_lift_py_tests.claim_envelope import ClaimEnvelope works - [x] ClaimEnvelope.from_contract_decl(decl, signer) produces v1.2-layered - [x] compute_contract_set_cid matches rust output (pinned) - [x] Cross-kit byte-equivalence test against rust fixture - [x] signing.py: adds Signer + Signer.sign_claim, no breaking change Unblocks #205 (python Side A orchestrator). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 43 minutes and 1 second.Comment |
There was a problem hiding this comment.
Pull request overview
Adds the v1.2 layered claim-envelope construction and contractSetCid computation to the Python kit (closing #222 / completing Side B), with cross-kit byte-equivalence pinned against the Rust reference implementation.
Changes:
- Introduces
claim_envelope.pyin the Python kit, implementing layered{envelope, header, metadata}minting for contracts/bridges/implications pluscontract_cidandcompute_contract_set_cid. - Extends Python
signing.pywith a minimalSignerhandle andSigner.sign_claim()convenience wrapper. - Adds cross-kit pin generator test in Rust and a comprehensive Python test suite asserting byte-identical output vs Rust.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| implementations/rust/provekit-claim-envelope/tests/cross_kit_pin.rs | New Rust test that prints canonical fixture bytes/CIDs for other kits to pin against. |
| implementations/rust/provekit-claim-envelope/Cargo.toml | Adds hex dev-dependency for fixture byte printing. |
| implementations/rust/Cargo.lock | Locks the new hex dependency for the Rust workspace. |
| implementations/python/provekit-lift-py-tests/src/provekit_lift_py_tests/claim_envelope.py | New Python claim-envelope subsystem mirroring Rust (layered shape + hashing/signing + contractSetCid). |
| implementations/python/provekit-lift-py-tests/src/provekit_lift_py_tests/signing.py | Adds Signer abstraction and sign_claim() helper (additive API). |
| implementations/python/provekit-lift-py-tests/tests/test_claim_envelope.py | New Python tests for byte-equivalence vs Rust + structural conformance + edge cases. |
| implementations/python/provekit-lift-py-tests/src/provekit_lift_py_tests/init.py | Exposes the new claim-envelope API and Signer at the package top-level. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass, field |
| from provekit_lift_py_tests.ir import ContractDecl, atomic, forall, gt, num, make_var, Int | ||
| from provekit_lift_py_tests.signing import FOUNDATION_V0_SEED, Signer, ed25519_pubkey_string |
| def sign_claim( | ||
| self, | ||
| decl, | ||
| *, | ||
| produced_at: str, | ||
| authoring=None, | ||
| input_cids=None, | ||
| ): | ||
| """Build a v1.2-layered ClaimEnvelope from a `ContractDecl`. | ||
|
|
||
| Convenience delegate to ``ClaimEnvelope.from_contract_decl``. | ||
| Imported lazily to avoid the ``signing -> claim_envelope -> | ||
| signing`` import cycle (claim_envelope itself imports `Signer`). | ||
| """ | ||
| from .claim_envelope import ClaimEnvelope | ||
| return ClaimEnvelope.from_contract_decl( | ||
| decl, | ||
| self, | ||
| produced_at=produced_at, | ||
| authoring=authoring, | ||
| input_cids=input_cids, | ||
| ) |
…ing (closes #205) Side A bootstrap for the Python kit per issue #176. The orchestrator walks the canonical Python slab (5 slabs of 3 contracts each, 15 total: blake3 / jcs / cbor / signing / proof_envelope), mints each contract as a v1.2 layered signed memento under the foundation key, and bundles them into a `.proof` envelope whose filename IS its catalog CID. The KIT_TABLE flip routes `--kit=python` from the placeholder `python` surface to the new `python-self-contracts` surface, mirroring the rust / go / cpp / ts / ruby wiring pattern (PRs #180, #183, #217, #220, #234). The orchestrator imports the python claim-envelope substrate that landed in PR #232 (`ClaimEnvelope.from_contract_decl`, `compute_contract_set_cid`) plus the proof-envelope crypto primitives from PR #221 (`build_proof_envelope`, `Signer.foundation_v0`, `ed25519_pubkey_string`, `blake3_512_of`); no reimplementation. Unlike the ruby PR #234, no inline `mint_contract` port was needed because the python lib already exposes the full layered-mint API. The `--rpc` mode follows the daemon-lifecycle pattern PR #220 established: persistent NDJSON stdio loop, EOF on stdin = graceful shutdown, explicit `shutdown` method acks then exits, JSON-RPC error objects on parse / method-not-found / lift-failed without crashing. - [x] Orchestrator (`provekit-self-contracts.py` + bin shim `bin/mint-python-self-contracts`) walks the python slab, calls `ClaimEnvelope.from_contract_decl`, builds proof envelope via existing primitives. - [x] Speaks the canonical `--rpc` lift-protocol, emits proof-envelope to stdout with `kind`, `filename_cid`, `contract_set_cid`, `bytes_base64`, `diagnostics`. - [x] Add `python-self-contracts` lift surface manifest at `implementations/python/.provekit/lift/python-self-contracts/manifest.toml`. - [x] Update `KIT_TABLE` in `implementations/rust/provekit-cli/src/cmd_mint.rs` python entry surface from `"python"` to `"python-self-contracts"`. - [x] Add a pinned-CID test (`python_kit_pins_expected_contract_set_cid`) in `mint_kit_integration.rs`. Move `python` from `KITS_WITHOUT_LIFTERS` to `KITS_WITH_LIFTERS` AND `KITS_WITH_REAL_CONTRACTS`. - [x] `make mint-python` produces a content-meaningful contractSetCid (`blake3-512:b1de9417...`), not the empty-set sentinel. - [x] `provekit prove implementations/python` exits 0. - [x] Pinned-CID test passes. - [x] LSP daemon lifecycle is explicit (no orphan processes after the run): EOF on stdin or explicit `shutdown` returns 0. ```rust // before ("python", "python", "python", "python"), // after ("python", "python", "python-self-contracts", "python"), ``` ``` >> minting python self-contracts cid: blake3-512:64c8ddf3a7ef02c6d12665866e1c2483b59009830a5f27cee869b123d5ece63cccb7dc8d62002383f348b15cd866857de0c6a053dd2fc02e3d9356bd3295b0a4 contractSetCid: blake3-512:b1de941756d0a3b352ca79ebed8b75644b7c782c3afe4163273220384125ec100457d5e969a921b7ceb277e329a24c5a4ea21ffd54963b51c1756befdb1793dc OK .provekit/self-contracts-attestations/python.json (contractSetCid blake3-512:b1de941756d0a3b352ca79ebed8b75644b7c782c3afe4163273220384125ec100457d5e969a921b7ceb277e329a24c5a4ea21ffd54963b51c1756befdb1793dc) ``` The catalog CID and contractSetCid are byte-deterministic across two consecutive mint runs (orchestrator's built-in determinism check, plus the rust integration test). - Persistent NDJSON stdio loop (one process serves multiple `lift` calls). - `initialize` returns the protocol version, plugin name, and capabilities. - `lift` returns the `proof-envelope` shape with base64-encoded bytes. - `shutdown` writes the ack response and exits 0. - EOF on stdin = graceful shutdown (loop exit returns 0). - Errors emit JSON-RPC error objects (`-32700` parse, `-32601` method-not-found, `1005` LIFT_FAILED) without crashing the process. - [x] Direct CLI smoke (`python3 implementations/python/bin/mint-python-self-contracts /tmp/...`): passes; deterministic across runs. - [x] RPC smoke (initialize / lift / shutdown over stdin pipe): all three responses well-formed. - [x] `provekit mint --kit=python --quiet` (via the dispatcher): passes; emits the same `cid` / `contractSetCid`. - [x] `cargo test --release -p provekit-cli --test mint_kit_integration python_kit_pins_expected_contract_set_cid`: passes. - [x] `cargo test --release -p provekit-cli --test mint_kit_integration all_kits_mint_produces_valid_attestation_structure`: passes (python now in `KITS_WITH_LIFTERS` + `KITS_WITH_REAL_CONTRACTS`). - [x] `provekit prove implementations/python`: exit 0. - [x] All 143 pre-existing python pytest suites in `implementations/python/provekit-lift-py-tests/`: pass. - **Import path**: the bin shim prepends `provekit-lift-py-tests/src` to `sys.path` so the orchestrator works without `pip install -e .` (the `mint-python` Makefile target has no `build-python` dependency, unlike `test-python`). The orchestrator self-injects too as belt-and-suspenders. - **Python wheels**: `blake3`, `pynacl`, `cbor2` are declared dependencies of `provekit-lift-py-tests` and are installed in CI via `make test-python`'s `pip install -e .`. The pinned-CID test skips on toolchain failure (mirrors ruby/cpp/ts), so a missing wheel surfaces as test-skip rather than test-fail. - **No em-dashes** per CLAUDE.md: the docstrings and commit message use commas and parentheses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes #222. Adds the v1.2 layered claim-envelope subsystem to the python kit. PR #221 shipped only the proof-side crypto primitives (Ed25519, CBOR, BLAKE3, JCS, proof envelope) and explicitly deferred the claim-envelope construction (tasks 6-7 of #206) as a "separate subsystem". This change closes that gap so Side B is fully closed.
Unblocks #205 (python Side A orchestrator), which needs both
ClaimEnvelope.from_contract_declandcompute_contract_set_cidto walk contracts and emit apython.jsonattestation with a content-meaningful contractSetCid.What landed
claim_envelope.py(new, 668 lines)Mirrors
implementations/rust/provekit-claim-envelope/src/lib.rs1:1.mint_contract/mint_bridge/mint_implicationbuild the layered triple{envelope, header, metadata}perprotocol/specs/2026-05-03-substrate-layers-envelope-header-body.md. Signature is Ed25519 overJCS({"header": header, "metadata": metadata}); attestation CID isBLAKE3-512(JCS(envelope))AFTER the signature has been embedded.contract_cid(signer-independent content CID per2026-05-03-contract-cid-vs-attestation-cid.md).compute_contract_set_cidper2026-05-03-contract-set-extension.md§1:BLAKE3-512(JCS(sorted contractCids)). Order-independent.ClaimEnvelope.from_contract_decl(decl, signer, *, produced_at, ...)lowers a pythonContractDecl'sFormulaclauses viair.formula_to_valueand delegates tomint_contract.Authoringtagged union (AuthoringKitAuthor/AuthoringLift/AuthoringLlm). Confidence is serialized asint(confidence * 1000), truncating toward zero to match rust's(confidence * 1000.0) as i64cast.signing.py(additive only)Signer(seed, producer_id)minimal handle.Signer.foundation_v0(producer_id)convenience.Signer.sign_claim(decl, *, produced_at, ...)delegate toClaimEnvelope.from_contract_decl.PR #221's exported surface (
FOUNDATION_V0_SEED,ed25519_sign_with_seed,ed25519_sign_string,ed25519_pubkey_string,ed25519_verify_string) is unchanged.tests/cross_kit_pin.rs(new in rust crate)Reproducible cross-kit pin generator. Run with:
Other kits (cpp, csharp, go, ts) can import the same fixture inputs and pin the same outputs. This avoids the dangling
cargo run --example proof_envelope_bytesreference in PR #221's commit message.tests/test_claim_envelope.py(new, 29 tests)compute_contract_set_cidmatches, order-independence).JCS({"header", "metadata"})).Cross-kit result
PASS. Python output is byte-identical to rust for the canonical fixture (1615-byte layered envelope; 100% match).
blake3-512:b5cd82094dd4d7da...3523639cblake3-512:bca0bb9144b3b35e...8715db1blake3-512:e42f67a1f9947237...75962506Test plan
pytest tests/test_claim_envelope.py -v-- 29 passedpytest -v(full python kit suite) -- 142 passed, 1 skipped (pre-existing skip unchanged)cargo test -p provekit-claim-envelope-- 24 passed, 0 failed (incl. the newcross_kit_pintests)TestCrossKitByteEquivalence::test_fixture_bytes_match_rust-- PASSAcceptance (from #222)
from provekit_lift_py_tests.claim_envelope import ClaimEnvelopeworksClaimEnvelope.from_contract_decl(decl, signer)produces a v1.2-layered envelopecompute_contract_set_cid(contracts)matches rust output for the same inputs (pinned)signing.pyAPI: addsSigner+Signer.sign_claim, no breaking changeNotes for review
confidencetruncation: rust uses(confidence * 1000.0) as i64(truncate toward zero). Pythonint(x)on a float matches this for all finite non-NaN inputs. Verified bytest_llm_confidence_truncates_toward_zero(0.9009 -> 900, not 901).{"header": header, "metadata": metadata}(key name "metadata", not "body"), per substrate-layers spec §2 R2 and matching rust line 85-91.assemble_layered.🤖 Generated with Claude Code