Skip to content

feat(python): claim envelope + contractSetCid, completes Side B (closes #222)#232

Merged
TSavo merged 1 commit into
mainfrom
worktree-agent-a52d44c58e01d6217
May 4, 2026
Merged

feat(python): claim envelope + contractSetCid, completes Side B (closes #222)#232
TSavo merged 1 commit into
mainfrom
worktree-agent-a52d44c58e01d6217

Conversation

@TSavo
Copy link
Copy Markdown
Owner

@TSavo TSavo commented May 4, 2026

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_decl and compute_contract_set_cid to walk contracts and emit a python.json attestation with a content-meaningful contractSetCid.

What landed

claim_envelope.py (new, 668 lines)

Mirrors implementations/rust/provekit-claim-envelope/src/lib.rs 1:1.

  • mint_contract / mint_bridge / mint_implication build the layered triple {envelope, header, metadata} per protocol/specs/2026-05-03-substrate-layers-envelope-header-body.md. Signature is Ed25519 over JCS({"header": header, "metadata": metadata}); attestation CID is BLAKE3-512(JCS(envelope)) AFTER the signature has been 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 §1: BLAKE3-512(JCS(sorted contractCids)). Order-independent.
  • ClaimEnvelope.from_contract_decl(decl, signer, *, produced_at, ...) lowers a python ContractDecl's Formula clauses via ir.formula_to_value and delegates to mint_contract.
  • Authoring tagged union (AuthoringKitAuthor / AuthoringLift / AuthoringLlm). Confidence is serialized as int(confidence * 1000), truncating toward zero to match rust's (confidence * 1000.0) as i64 cast.

signing.py (additive only)

  • Signer(seed, producer_id) minimal handle.
  • Signer.foundation_v0(producer_id) convenience.
  • Signer.sign_claim(decl, *, produced_at, ...) delegate to ClaimEnvelope.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:

cargo test -p provekit-claim-envelope --test cross_kit_pin -- --nocapture

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_bytes reference in PR #221's commit message.

tests/test_claim_envelope.py (new, 29 tests)

  • 6 cross-kit byte-equivalence tests (canonical bytes match rust, attestation CID matches, contract CID matches, compute_contract_set_cid matches, order-independence).
  • 5 layered-shape structural conformance tests (top-level triple, envelope keys, header required fields, attestation CID = BLAKE3-512(JCS(envelope)), signature verifies via libsodium against JCS({"header", "metadata"})).
  • 2 error-path tests, 5 authoring round-trip tests (incl. confidence truncation), 3 determinism/sensitivity tests, 2 bridge/implication smoke tests, 3 contractSetCid edge cases, 3 from_contract_decl tests.

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 kit suite) -- 142 passed, 1 skipped (pre-existing skip unchanged)
  • cargo test -p provekit-claim-envelope -- 24 passed, 0 failed (incl. the new cross_kit_pin tests)
  • Cross-kit bytes test: TestCrossKitByteEquivalence::test_fixture_bytes_match_rust -- PASS

Acceptance (from #222)

  • from provekit_lift_py_tests.claim_envelope import ClaimEnvelope works
  • ClaimEnvelope.from_contract_decl(decl, signer) produces a v1.2-layered envelope
  • compute_contract_set_cid(contracts) matches rust output for the same inputs (pinned)
  • Cross-kit byte-equivalence test against rust fixture
  • Updates signing.py API: adds Signer + Signer.sign_claim, no breaking change

Notes for review

  • confidence truncation: rust uses (confidence * 1000.0) as i64 (truncate toward zero). Python int(x) on a float matches this for all finite non-NaN inputs. Verified by test_llm_confidence_truncates_toward_zero (0.9009 -> 900, not 901).
  • The signing message is JCS of {"header": header, "metadata": metadata} (key name "metadata", not "body"), per substrate-layers spec §2 R2 and matching rust line 85-91.
  • Attestation CID is computed AFTER the signature is embedded into the envelope object, per spec §1 and matching rust assemble_layered.

🤖 Generated with Claude Code

#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>
Copilot AI review requested due to automatic review settings May 4, 2026 06:36
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Warning

Rate limit exceeded

@TSavo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 1 second before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0647c4c5-52db-4343-b47b-bd7068a2ff03

📥 Commits

Reviewing files that changed from the base of the PR and between fa955c0 and 3175ca9.

⛔ Files ignored due to path filters (1)
  • implementations/rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • implementations/python/provekit-lift-py-tests/src/provekit_lift_py_tests/__init__.py
  • implementations/python/provekit-lift-py-tests/src/provekit_lift_py_tests/claim_envelope.py
  • implementations/python/provekit-lift-py-tests/src/provekit_lift_py_tests/signing.py
  • implementations/python/provekit-lift-py-tests/tests/test_claim_envelope.py
  • implementations/rust/provekit-claim-envelope/Cargo.toml
  • implementations/rust/provekit-claim-envelope/tests/cross_kit_pin.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-agent-a52d44c58e01d6217

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.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 43 minutes and 1 second.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py in the Python kit, implementing layered {envelope, header, metadata} minting for contracts/bridges/implications plus contract_cid and compute_contract_set_cid.
  • Extends Python signing.py with a minimal Signer handle and Signer.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
Comment on lines +35 to +36
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
Comment on lines +153 to +174
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,
)
@TSavo TSavo merged commit 7f8c45f into main May 4, 2026
8 checks passed
TSavo added a commit that referenced this pull request May 4, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[kit:python][axis:B] Complete python Side B: claim envelope construction + contractSetCid (deferred from #206)

2 participants