|
| 1 | +# F112 — Python SDK platform wheel distribution |
| 2 | + |
| 3 | +> Story: [AAASM-1202](https://lightning-dust-mite.atlassian.net/browse/AAASM-1202) |
| 4 | +> Epic: [AAASM-1199](https://lightning-dust-mite.atlassian.net/browse/AAASM-1199) |
| 5 | +> Verification sub-ticket: [AAASM-1219](https://lightning-dust-mite.atlassian.net/browse/AAASM-1219) |
| 6 | +> Verified on: 2026-05-23 |
| 7 | +
|
| 8 | +## Sub-ticket map → PRs |
| 9 | + |
| 10 | +| Sub-ticket | Scope | PR | |
| 11 | +| --- | --- | --- | |
| 12 | +| [AAASM-1215](https://lightning-dust-mite.atlassian.net/browse/AAASM-1215) | Configure maturin build backend | #53 | |
| 13 | +| [AAASM-1216](https://lightning-dust-mite.atlassian.net/browse/AAASM-1216) | Redesign pyproject.toml extras + dependency-groups | #54 | |
| 14 | +| [AAASM-1217](https://lightning-dust-mite.atlassian.net/browse/AAASM-1217) | Matrix wheel build + PyPI Trusted Publisher workflow | #55 | |
| 15 | +| [AAASM-1218](https://lightning-dust-mite.atlassian.net/browse/AAASM-1218) | `ensure_runtime()` fallback in `_install.py` | #56 | |
| 16 | + |
| 17 | +Per-AC verification below. |
| 18 | + |
| 19 | +## AC1 — maturin configured in python-sdk with correct build targets (AAASM-1215, PR #53) |
| 20 | + |
| 21 | +**Status: ✅ Pass** |
| 22 | + |
| 23 | +| Check | Result | |
| 24 | +| --- | --- | |
| 25 | +| `[build-system].build-backend == "maturin"` | ✅ Verified — `python -c "import tomllib; print(tomllib.loads(open('pyproject.toml').read())['build-system']['build-backend'])"` returns `maturin` | |
| 26 | +| `[build-system].requires` contains `maturin>=1.7,<2` | ✅ | |
| 27 | +| `[tool.maturin].manifest-path == "rust/aa-ffi-python/Cargo.toml"` | ✅ Points at the existing PyO3 binding crate | |
| 28 | +| `[tool.maturin].module-name == "agent_assembly._core"` | ✅ Matches `#[pyclass(module = "agent_assembly._core")]` in `rust/aa-ffi-python/src/lib.rs` | |
| 29 | +| `[tool.maturin].include` bundles `agent_assembly/bin/aasm` into wheel | ✅ With `format = "wheel"` so the file appears only in the platform wheel, not in sdist | |
| 30 | +| `[tool.hatch.build.targets.*]` removed | ✅ — no orphaned hatchling config | |
| 31 | + |
| 32 | +**Caveats** |
| 33 | + |
| 34 | +* End-to-end `maturin build --release` execution is deferred to CI (AAASM-1217) — local execution requires the upstream `aa-core` / `aa-proto` crates which the rust binding fetches from `AI-agent-assembly/agent-assembly`. |
| 35 | +* Per memory: pyo3 0.20 on a Python 3.13 venv needs `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`; the CI matrix pins `${{ env.PYTHON_VERSION }} = '3.12'` to sidestep this. |
| 36 | + |
| 37 | +## AC2 — pyproject extras + dependency-groups split (AAASM-1216, PR #54) |
| 38 | + |
| 39 | +**Status: ✅ Pass** |
| 40 | + |
| 41 | +| Check | Result | |
| 42 | +| --- | --- | |
| 43 | +| `[project.optional-dependencies].runtime == []` | ✅ Empty marker extra — selection happens by platform wheel tag, not deps | |
| 44 | +| `[project.optional-dependencies].all == ["agent-assembly[runtime]"]` | ✅ Self-reference aggregator | |
| 45 | +| `[dependency-groups].lint` contains ruff + mypy | ✅ | |
| 46 | +| `[dependency-groups].test` contains pytest + pytest-cov + pytest-rerunfailures + pytest-asyncio + pytest-benchmark | ✅ | |
| 47 | +| `[dependency-groups].dev` slimmed via `{include-group = "lint"}` + `{include-group = "test"}` | ✅ No duplication of literals | |
| 48 | +| `[dependency-groups].pre-commit-ci` sources mypy via `{include-group = "lint"}` | ✅ | |
| 49 | +| `uv lock --check` after restructure | ✅ 84 packages resolved, lock in sync | |
| 50 | +| `uv sync --group dev --dry-run` | ✅ resolves cleanly | |
| 51 | +| `uv sync --group lint --dry-run` | ✅ resolves cleanly | |
| 52 | +| `uv sync --group test --dry-run` | ✅ resolves cleanly | |
| 53 | + |
| 54 | +**Caveats** |
| 55 | + |
| 56 | +* Project name is `agent-assembly` (not `agent-assembly-python` as the Story description references); the `all` extra correctly uses the actual package name. |
| 57 | + |
| 58 | +## AC3 — release-python.yml: matrix build + PyPI Trusted Publisher (AAASM-1217, PR #55) |
| 59 | + |
| 60 | +**Status: ✅ Pass (structure) / ⏳ Deferred to CI (E2E execution)** |
| 61 | + |
| 62 | +| Check | Result | |
| 63 | +| --- | --- | |
| 64 | +| Workflow triggers on `v*.*.*` tag push | ✅ | |
| 65 | +| Workflow has `workflow_dispatch` for dry-run | ✅ with `dry-run: true` default input | |
| 66 | +| Top-level `permissions: id-token: write` set | ✅ Required for PyPI Trusted Publisher OIDC | |
| 67 | +| 5 build jobs present | ✅ `build-sdist`, `build-linux-x86_64`, `build-linux-aarch64`, `build-macos-arm64`, `build-macos-x86_64` | |
| 68 | +| Per-job: download `aasm` binary before maturin build | ✅ Each platform job uses `gh release download` against `AI-agent-assembly/agent-assembly` | |
| 69 | +| Each platform job uses `PyO3/maturin-action@v1` with platform target | ✅ | |
| 70 | +| `publish` job has `needs:` listing all 5 build jobs | ✅ Verified via `assert set(d['jobs']['publish']['needs']) == {...}` | |
| 71 | +| `publish` job is gated `if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')` | ✅ | |
| 72 | +| `publish` step uses `pypa/gh-action-pypi-publish@release/v1` with no `password:` | ✅ Trusted Publisher OIDC, no stored token | |
| 73 | +| `publish.environment.name == 'pypi'` | ✅ For Trusted Publisher registration matching | |
| 74 | +| Untrusted-input safety: no `${{ github.event.* }}` interpolated into any `run:` shell | ✅ Repo name flows only via workflow-level `env:` | |
| 75 | + |
| 76 | +**One-time setup required outside the PR** (documented in PR #55's body): |
| 77 | + |
| 78 | +1. Register Trusted Publisher at https://pypi.org/manage/account/publishing/ for project `agent-assembly` referencing `AI-agent-assembly/python-sdk`, workflow `release-python.yml`, environment `pypi`. |
| 79 | +2. Create GitHub Environment `pypi` in the repo settings. |
| 80 | + |
| 81 | +**CI-only verifications** (cannot run locally — owned by the CI matrix runners): |
| 82 | + |
| 83 | +* Actual `maturin build --release` per platform with `aasm` binary staged. |
| 84 | +* Wheel platform tags inspected post-build (`manylinux_*`, `macosx_*`, not `none-any` except for sdist). |
| 85 | +* Trusted Publisher OIDC handshake on dry-run via `workflow_dispatch` once the PyPI registration is in place. |
| 86 | + |
| 87 | +## AC4 — `ensure_runtime()` install-time fallback (AAASM-1218, PR #56) |
| 88 | + |
| 89 | +**Status: ✅ Pass** |
| 90 | + |
| 91 | +| Check | Result | |
| 92 | +| --- | --- | |
| 93 | +| Module `agent_assembly/_install.py` exists | ✅ | |
| 94 | +| `BINARY_NAME == "aasm"` | ✅ | |
| 95 | +| `WHEEL_BUNDLED_BIN` resolves to `agent_assembly/bin/aasm` | ✅ — `Path(__file__).resolve().parent / "bin" / BINARY_NAME` | |
| 96 | +| `INSTALL_HINT` lists pip [runtime], Homebrew tap, curl installer | ✅ | |
| 97 | +| `ensure_runtime()` returns `shutil.which` match when on PATH | ✅ Test `test_ensure_runtime_returns_path_match_first` | |
| 98 | +| `ensure_runtime()` returns `WHEEL_BUNDLED_BIN` when not on PATH but file present | ✅ Test `test_ensure_runtime_falls_back_to_wheel_bundled` | |
| 99 | +| `ensure_runtime()` raises `RuntimeError(INSTALL_HINT)` when neither found | ✅ Test `test_ensure_runtime_raises_with_install_hint` | |
| 100 | +| Lazy-imports compatibility (PEP 562 in `__init__.py`) preserved | ✅ Module is not added to `_LAZY_EXPORTS` — kept as direct-import `from agent_assembly._install import ensure_runtime` | |
| 101 | +| Full SDK pytest suite | ✅ 380 passed, 11 skipped (pre-existing env-conditional skips) | |
| 102 | + |
| 103 | +**Caveats** |
| 104 | + |
| 105 | +* `_install.ensure_runtime()` is distinct from `runtime.find_aasm_binary()` — the former is a synchronous presence check, the latter manages full lifecycle (port probe + subprocess spawn). Both observe the same `agent_assembly/bin/aasm` wheel-bundled location. |
| 106 | +* Known collision risk: `[project.scripts] aasm = "agent_assembly.cli.main:main"` creates a Python entry-point at `.venv/bin/aasm` that would shadow the Rust sidecar on PATH. Tracked as out-of-scope for F112; needs a follow-up to rename one of them (likely the Python CLI → `aasm-py`). |
| 107 | + |
| 108 | +## CI-only smoke-test plan (post-merge) |
| 109 | + |
| 110 | +These exercises run only after PRs #53..#56 land on master and the PyPI Trusted Publisher / GitHub Environment registration is in place. |
| 111 | + |
| 112 | +1. **Dry-run `workflow_dispatch` on master** of `release-python.yml` with `dry-run: true`. |
| 113 | + - Confirm all 5 build jobs produce wheels (sdist `.tar.gz` + 4 platform `.whl`). |
| 114 | + - Confirm `publish` job is skipped (because `github.event_name == 'workflow_dispatch'`, not `push`). |
| 115 | +2. **Cut a pre-release tag** `v0.0.1-rc1` and push. |
| 116 | + - Confirm the `publish` job runs and the OIDC token exchange with PyPI succeeds. |
| 117 | + - Confirm wheels appear on PyPI (or TestPyPI if the workflow is wired to test index first). |
| 118 | +3. **Sample installations** on each target platform: |
| 119 | + - `pip install agent-assembly==0.0.1rc1` — SDK-only, no `agent_assembly/bin/aasm` in site-packages. |
| 120 | + - `pip install 'agent-assembly[runtime]'==0.0.1rc1` on Linux x86_64 — wheel includes Linux binary. |
| 121 | + - `pip install 'agent-assembly[runtime]'==0.0.1rc1` on macOS arm64 — wheel includes mac arm64 binary. |
| 122 | +4. **`ensure_runtime()` end-to-end** inside each install: |
| 123 | + - `python -c "from agent_assembly._install import ensure_runtime; print(ensure_runtime())"` returns the bundled binary path with the `[runtime]` install; raises `RuntimeError` with the install hint without it. |
| 124 | + |
| 125 | +## Final F112 sign-off |
| 126 | + |
| 127 | +| Sub-ticket | Static structural verification | E2E / smoke (deferred to CI) | |
| 128 | +| --- | --- | --- | |
| 129 | +| AAASM-1215 | ✅ | Covered by step 1 above | |
| 130 | +| AAASM-1216 | ✅ | Covered by steps 3–4 above | |
| 131 | +| AAASM-1217 | ✅ | Covered by steps 1–2 above | |
| 132 | +| AAASM-1218 | ✅ (unit-tested) | Covered by step 4 above | |
| 133 | + |
| 134 | +All acceptance criteria pass for the parts that can be verified statically and via local unit tests. The remaining smoke-test items are scoped to CI under the post-merge plan above. |
0 commit comments