Skip to content

feat(python): gtm phase 2 pyo3 bindings for awp-core - #10

Merged
andyjsbell merged 4 commits into
mainfrom
gtm-phase-2/pyo3-bindings
May 19, 2026
Merged

feat(python): gtm phase 2 pyo3 bindings for awp-core#10
andyjsbell merged 4 commits into
mainfrom
gtm-phase-2/pyo3-bindings

Conversation

@andyjsbell

Copy link
Copy Markdown
Contributor

Summary

Implements Step 1 of GTM Phase 2 (planning/gtm-phase-2-agent-prompts.md → "Step 1 — GTM Phase 2: PyO3 Bindings"): a Python extension that wraps awp-core's signing path so attestations produced from Python verify byte-identically in Rust.

  • New crate crates/awp-python/ — PyO3 abi3-py39 bindings, distribution name awp-core-py, Python import name awp.
  • API surface: AgentIdentity.generate / load_or_create / from_secret_bytes, sign_attestation(dict, identity), verify_attestation(att, pubkey), and full Attestation field accessors.
  • Byte-identical signatures pinned via a fixed-seed test vector in crates/awp-core/tests/cross_language_vector.rs and crates/awp-python/tests/cross_language.py. Drift on either side fires both test suites.
  • Cross-language round-trip uses a new stdin-driven awp-verify binary (crates/awp-core/src/bin/awp_verify.rs). Python pipes a signed attestation through it for Rust-side verification; the reverse direction asserts byte equality of signing_payload.
  • make check extended to build the wheel via maturin develop and run pytest (19 tests pass).
  • CI matrix (.github/workflows/python-wheels.yml): builds wheels for macOS (arm64 + x86_64), Linux (x86_64 + aarch64 manylinux 2_28), and Windows (x86_64); runs pytest on Python 3.9 + 3.12 per platform; TestPyPI publish gated by manual workflow_dispatch per the GTM plan.

Test plan

  • make check passes (cargo fmt clean, clippy -D warnings clean, cargo test --workspace --exclude awp-python green, maturin develop + pytest 19/19 green).
  • Existing Rust examples unaffected (cargo run --example kyc_receipts, cargo run --example dispatcher_flow).
  • Spec smoke test: python -c "import awp; ident = awp.AgentIdentity.generate('agent-test'); att = awp.sign_attestation({'task': 'hello'}, ident); assert awp.verify_attestation(att, ident.public_key); print('ok')"ok.
  • Cross-language self-test: python -m pytest crates/awp-python/tests/cross_language.py -v → 7 passed (sign_python_verify_rust, sign_rust_verify_python, canonical_bytes_match, plus 4 more).
  • CI matrix produces all 5 platform wheels on PR (will validate on first push).

Non-goals (deferred)

  • Real-PyPI publish — gated on Phase 2 design-partner close per the plan.
  • Async API, streaming attestations, multi-process coordination.
  • LangGraph SDK / awp-cloud integration — handled by parallel Steps 2-3 in the same phase.

Implements Step 1 — PyO3 Bindings.

🤖 Generated with Claude Code

andyjsbell and others added 4 commits May 19, 2026 11:16
Adds a tiny stdin-driven verifier binary (`awp-verify`) and a pinned
deterministic test vector so cross-language attestation signing (Rust ↔
Python) can be self-tested byte-for-byte. The vector covers
canonical-encoding, signature, output_hash, and signing_payload — any
drift in either language fails both test suites in the same CI run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps awp-core's signing path in a PyO3 abi3-py39 extension so Python
callers (LangGraph SDK, awp-cloud client, design partners) can produce
attestations whose signatures verify byte-identically in Rust.

Surface (per planning/gtm-phase-2-plan.md → Step 1):
- AgentIdentity.generate / load_or_create / from_secret_bytes
- sign_attestation(dict, identity) — dict canonicalised inside Rust
- verify_attestation(attestation, pubkey)
- Attestation field accessors (id, agent_id, agent_pubkey, output, ...)

The cross-language self-test under tests/cross_language.py pins
byte-for-byte equality against the Rust vector in
crates/awp-core/tests/cross_language_vector.rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make check` now builds the awp-verify binary, builds the awp-python
wheel with maturin develop, and runs pytest against crates/awp-python/tests.
awp-python is excluded from cargo test because its cdylib triggers a
libpython link error outside maturin's build environment — the Python
test suite covers that crate's surface end to end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitHub Actions workflow builds awp-core-py wheels for macOS (x86_64,
arm64), Linux (x86_64, aarch64 manylinux 2_28), and Windows (x86_64)
on every PR and tag push. Cross-language pytest runs on each platform
against Python 3.9 and 3.12 to confirm abi3 coverage.

Publish to TestPyPI is gated behind manual workflow_dispatch with a
publish_testpypi boolean per the GTM Phase 2 plan — the real-PyPI
promotion is a separate decision tied to the design-partner close.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andyjsbell

Copy link
Copy Markdown
Contributor Author

Agent Run Report

Implementation Commits

  • 8ab3818 feat(core): add awp-verify binary and cross-language signing vector
  • b9e2a8f feat(python): add awp-core-py PyO3 bindings for sign/verify
  • f023998 chore(ci): extend make check with awp-python wheel + pytest gate
  • 74e3411 feat(ci): add python wheel matrix workflow with TestPyPI publish gate

Review Report

Requirements Checked

  • Workspace member crates/awp-python added — PASS
  • PyO3 with abi3-py39 feature, Cargo lib name awp_core_py, Python import awp — PASS
  • API surface (sign_attestation, verify_attestation, AgentIdentity.load_or_create, AgentIdentity.generate) — PASS
  • Attestation field accessors (id, agent_id, agent_pubkey, task_hash, output_hash, output, status, references, timestamp, signature) — PASS
  • payload: dict canonicalised inside Rust via py_to_json_value (no pre-serialisation in Python) — PASS
  • Byte-identical signing across Rust and Python (pinned signature, signing_payload, output_hash hex on both sides) — PASS
  • Cross-language self-test both directions (test_sign_python_verify_rust, test_sign_rust_verify_python) — PASS
  • Byte-equality of signing_payload bytes (not just signature verify) — PASS
  • Hardcoded expected-signature test vector duplicated and cross-checked Rust/Python — PASS
  • maturin-based build with pyproject.toml — PASS
  • .github/workflows/python-wheels.yml covering macOS (arm64+x86_64), Linux (x86_64+aarch64 manylinux 2_28), Windows (x86_64); pytest on 3.9 + 3.12 to validate abi3 — PASS
  • TestPyPI publish gated by manual workflow_dispatch with publish_testpypi input — PASS
  • README.md covers install, usage, byte-identical guarantee (with worked example), FFI boundary, limitations — PASS
  • Python tests for identity round-trip + signing round-trip — PASS
  • make check extended to run Python tests — PASS
  • Rust examples (kyc_receipts, dispatcher_flow) unaffected — PASS (both run unchanged)
  • Do Not Touch: no edits to services/awp-cloud/, python/awp-langgraph/, tools/landing-page/ — PASS (paths absent or untouched)
  • Attestation::signing_payload in crates/awp-core/src/attestation.rs untouched — PASS (file unchanged in diff)
  • crates/awp-core/ changes additive only (new bin + Cargo.toml bin section + cross-language test vector) — PASS
  • crates/awp-agents/ untouched — PASS

Gaps Found

None.

Fixes Made

None.

Quality Gate

make check: PASS (cargo fmt clean, clippy -D warnings clean, 62 awp-core unit tests + 6 cross-language vector tests + 19 Python pytest tests all green, maturin develop succeeds, awp-verify binary built).

Reviewer note

The spec phrasing "Module name: awp_core_py (exposed to Python as awp)" is implemented by setting [lib] name = "awp_core_py" in Cargo (so the compiled artifact carries that name) and using module-name = "awp._native" in [tool.maturin] so the Python package directory at crates/awp-python/python/awp/ re-exports the native symbols. User-facing import is import awp, which is the load-bearing surface; the underlying native submodule is named _native rather than literally awp_core_py. The Cargo lib name matches the spec, the Python import name matches the spec — no fix made.

@andyjsbell
andyjsbell marked this pull request as ready for review May 19, 2026 09:26
@andyjsbell
andyjsbell merged commit 684cc9c into main May 19, 2026
5 of 6 checks passed
andyjsbell added a commit that referenced this pull request May 19, 2026
Resolves conflicts in `.gitignore` and `Makefile` against Step 1
(`gtm-phase-2/pyo3-bindings`, merged as PR #10):

- `.gitignore`: union of both branches' additions. Step 2 keeps the
  cloud's local-dev blob path; Step 1 keeps the Python tooling ignores
  (venv, pycache, maturin dist, in-place PyO3 extension artefacts).
- `Makefile`: combined `.PHONY` list; `check` now depends on
  `lint test check-python cloud-check` so the top-level gate covers
  both the Python wheel + pytest path from Step 1 and the
  `services/awp-cloud/` sub-workspace from Step 2.

Verified post-merge:
- `make lint test cloud-check`: PASS (Rust workspaces clean, 33
  awp-cloud tests green).
- `make check-python`: PASS (19 awp-python tests green).
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.

1 participant