Skip to content
134 changes: 134 additions & 0 deletions verification-reports/F112-AAASM-1202.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# F112 — Python SDK platform wheel distribution

> Story: [AAASM-1202](https://lightning-dust-mite.atlassian.net/browse/AAASM-1202)
> Epic: [AAASM-1199](https://lightning-dust-mite.atlassian.net/browse/AAASM-1199)
> Verification sub-ticket: [AAASM-1219](https://lightning-dust-mite.atlassian.net/browse/AAASM-1219)
> Verified on: 2026-05-23

## Sub-ticket map → PRs

| Sub-ticket | Scope | PR |
| --- | --- | --- |
| [AAASM-1215](https://lightning-dust-mite.atlassian.net/browse/AAASM-1215) | Configure maturin build backend | #53 |
| [AAASM-1216](https://lightning-dust-mite.atlassian.net/browse/AAASM-1216) | Redesign pyproject.toml extras + dependency-groups | #54 |
| [AAASM-1217](https://lightning-dust-mite.atlassian.net/browse/AAASM-1217) | Matrix wheel build + PyPI Trusted Publisher workflow | #55 |
| [AAASM-1218](https://lightning-dust-mite.atlassian.net/browse/AAASM-1218) | `ensure_runtime()` fallback in `_install.py` | #56 |

Per-AC verification below.

## AC1 — maturin configured in python-sdk with correct build targets (AAASM-1215, PR #53)

**Status: ✅ Pass**

| Check | Result |
| --- | --- |
| `[build-system].build-backend == "maturin"` | ✅ Verified — `python -c "import tomllib; print(tomllib.loads(open('pyproject.toml').read())['build-system']['build-backend'])"` returns `maturin` |
| `[build-system].requires` contains `maturin>=1.7,<2` | ✅ |
| `[tool.maturin].manifest-path == "rust/aa-ffi-python/Cargo.toml"` | ✅ Points at the existing PyO3 binding crate |
| `[tool.maturin].module-name == "agent_assembly._core"` | ✅ Matches `#[pyclass(module = "agent_assembly._core")]` in `rust/aa-ffi-python/src/lib.rs` |
| `[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 |
| `[tool.hatch.build.targets.*]` removed | ✅ — no orphaned hatchling config |

**Caveats**

* 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`.
* 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.

## AC2 — pyproject extras + dependency-groups split (AAASM-1216, PR #54)

**Status: ✅ Pass**

| Check | Result |
| --- | --- |
| `[project.optional-dependencies].runtime == []` | ✅ Empty marker extra — selection happens by platform wheel tag, not deps |
| `[project.optional-dependencies].all == ["agent-assembly[runtime]"]` | ✅ Self-reference aggregator |
| `[dependency-groups].lint` contains ruff + mypy | ✅ |
| `[dependency-groups].test` contains pytest + pytest-cov + pytest-rerunfailures + pytest-asyncio + pytest-benchmark | ✅ |
| `[dependency-groups].dev` slimmed via `{include-group = "lint"}` + `{include-group = "test"}` | ✅ No duplication of literals |
| `[dependency-groups].pre-commit-ci` sources mypy via `{include-group = "lint"}` | ✅ |
| `uv lock --check` after restructure | ✅ 84 packages resolved, lock in sync |
| `uv sync --group dev --dry-run` | ✅ resolves cleanly |
| `uv sync --group lint --dry-run` | ✅ resolves cleanly |
| `uv sync --group test --dry-run` | ✅ resolves cleanly |

**Caveats**

* Project name is `agent-assembly` (not `agent-assembly-python` as the Story description references); the `all` extra correctly uses the actual package name.

## AC3 — release-python.yml: matrix build + PyPI Trusted Publisher (AAASM-1217, PR #55)

**Status: ✅ Pass (structure) / ⏳ Deferred to CI (E2E execution)**

| Check | Result |
| --- | --- |
| Workflow triggers on `v*.*.*` tag push | ✅ |
| Workflow has `workflow_dispatch` for dry-run | ✅ with `dry-run: true` default input |
| Top-level `permissions: id-token: write` set | ✅ Required for PyPI Trusted Publisher OIDC |
| 5 build jobs present | ✅ `build-sdist`, `build-linux-x86_64`, `build-linux-aarch64`, `build-macos-arm64`, `build-macos-x86_64` |
| Per-job: download `aasm` binary before maturin build | ✅ Each platform job uses `gh release download` against `AI-agent-assembly/agent-assembly` |
| Each platform job uses `PyO3/maturin-action@v1` with platform target | ✅ |
| `publish` job has `needs:` listing all 5 build jobs | ✅ Verified via `assert set(d['jobs']['publish']['needs']) == {...}` |
| `publish` job is gated `if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')` | ✅ |
| `publish` step uses `pypa/gh-action-pypi-publish@release/v1` with no `password:` | ✅ Trusted Publisher OIDC, no stored token |
| `publish.environment.name == 'pypi'` | ✅ For Trusted Publisher registration matching |
| Untrusted-input safety: no `${{ github.event.* }}` interpolated into any `run:` shell | ✅ Repo name flows only via workflow-level `env:` |

**One-time setup required outside the PR** (documented in PR #55's body):

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`.
2. Create GitHub Environment `pypi` in the repo settings.

**CI-only verifications** (cannot run locally — owned by the CI matrix runners):

* Actual `maturin build --release` per platform with `aasm` binary staged.
* Wheel platform tags inspected post-build (`manylinux_*`, `macosx_*`, not `none-any` except for sdist).
* Trusted Publisher OIDC handshake on dry-run via `workflow_dispatch` once the PyPI registration is in place.

## AC4 — `ensure_runtime()` install-time fallback (AAASM-1218, PR #56)

**Status: ✅ Pass**

| Check | Result |
| --- | --- |
| Module `agent_assembly/_install.py` exists | ✅ |
| `BINARY_NAME == "aasm"` | ✅ |
| `WHEEL_BUNDLED_BIN` resolves to `agent_assembly/bin/aasm` | ✅ — `Path(__file__).resolve().parent / "bin" / BINARY_NAME` |
| `INSTALL_HINT` lists pip [runtime], Homebrew tap, curl installer | ✅ |
| `ensure_runtime()` returns `shutil.which` match when on PATH | ✅ Test `test_ensure_runtime_returns_path_match_first` |
| `ensure_runtime()` returns `WHEEL_BUNDLED_BIN` when not on PATH but file present | ✅ Test `test_ensure_runtime_falls_back_to_wheel_bundled` |
| `ensure_runtime()` raises `RuntimeError(INSTALL_HINT)` when neither found | ✅ Test `test_ensure_runtime_raises_with_install_hint` |
| 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` |
| Full SDK pytest suite | ✅ 380 passed, 11 skipped (pre-existing env-conditional skips) |

**Caveats**

* `_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.
* 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`).

## CI-only smoke-test plan (post-merge)

These exercises run only after PRs #53..#56 land on master and the PyPI Trusted Publisher / GitHub Environment registration is in place.

1. **Dry-run `workflow_dispatch` on master** of `release-python.yml` with `dry-run: true`.
- Confirm all 5 build jobs produce wheels (sdist `.tar.gz` + 4 platform `.whl`).
- Confirm `publish` job is skipped (because `github.event_name == 'workflow_dispatch'`, not `push`).
2. **Cut a pre-release tag** `v0.0.1-rc1` and push.
- Confirm the `publish` job runs and the OIDC token exchange with PyPI succeeds.
- Confirm wheels appear on PyPI (or TestPyPI if the workflow is wired to test index first).
3. **Sample installations** on each target platform:
- `pip install agent-assembly==0.0.1rc1` — SDK-only, no `agent_assembly/bin/aasm` in site-packages.
- `pip install 'agent-assembly[runtime]'==0.0.1rc1` on Linux x86_64 — wheel includes Linux binary.
- `pip install 'agent-assembly[runtime]'==0.0.1rc1` on macOS arm64 — wheel includes mac arm64 binary.
4. **`ensure_runtime()` end-to-end** inside each install:
- `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.

## Final F112 sign-off

| Sub-ticket | Static structural verification | E2E / smoke (deferred to CI) |
| --- | --- | --- |
| AAASM-1215 | ✅ | Covered by step 1 above |
| AAASM-1216 | ✅ | Covered by steps 3–4 above |
| AAASM-1217 | ✅ | Covered by steps 1–2 above |
| AAASM-1218 | ✅ (unit-tested) | Covered by step 4 above |

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.