Skip to content

Implementation Plan: Migrate to Rust-based api-simulator (PyO3 Rewrite)#651

Merged
Trecek merged 3 commits intointegrationfrom
migrate-to-rust-based-api-simulator-pyo3-rewrite/643
Apr 7, 2026
Merged

Implementation Plan: Migrate to Rust-based api-simulator (PyO3 Rewrite)#651
Trecek merged 3 commits intointegrationfrom
migrate-to-rust-based-api-simulator-pyo3-rewrite/643

Conversation

@Trecek
Copy link
Copy Markdown
Collaborator

@Trecek Trecek commented Apr 7, 2026

Summary

The api-simulator dependency has been rewritten in Rust with PyO3 bindings (TalonT-Org/api-simulator#74). The Python package now lives at crates/api-simulator-py/ within a Cargo workspace, is built via maturin, and exposes a new API surface (PyResponseSpec, PyFakeClaudeCLI, PySessionResult). This plan migrates AutoSkillit's CI infrastructure, dependency declaration, and test code to the new API without changing any production source code.

Requirements

CI Infrastructure (CI)

  • REQ-CI-001: All GitHub Actions workflows that resolve or install the api-simulator dev dependency (tests.yml, patch-bump-integration.yml, version-bump.yml) must include a Rust stable toolchain setup step before dependency installation.
  • REQ-CI-002: The Rust toolchain step must execute before uv sync --locked --extra dev and uv lock commands in each workflow.
  • REQ-CI-003: CI must pass on all matrix targets (ubuntu-latest, and macos-15 when targeting stable) with the Rust-based api-simulator.

Dependency Configuration (DEP)

Test Migration (TEST)

  • REQ-TEST-001: All imports from api_simulator must resolve against the Rust-based package.
  • REQ-TEST-002: test_quota_http.py must be rewritten to use PyResponseSpec for route registration, url() method calls, dict-based request inspection, and delay_ms integer milliseconds.
  • REQ-TEST-003: test_session_classification_e2e.py must be rewritten to use run(args, env) returning PySessionResult, session_log_dir() method, and delay_message(n, millis).
  • REQ-TEST-004: All 13 test methods in test_session_classification_e2e.py must pass against the Rust api-simulator.
  • REQ-TEST-005: All 7 test functions in test_quota_http.py must pass against the Rust api-simulator.

Architecture Impact

Development Diagram

%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 60, 'curve': 'basis'}}}%%
flowchart TB
    classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff;
    classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff;
    classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff;
    classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff;
    classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff;
    classDef terminal fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff;

    subgraph Deps ["DEPENDENCY LAYER"]
        PYPROJ["● pyproject.toml<br/>━━━━━━━━━━<br/>hatchling build<br/>api-simulator: git+subdir<br/>crates/api-simulator-py"]
        UVLOCK["● uv.lock<br/>━━━━━━━━━━<br/>Regenerated; pinned to<br/>post-#75 Rust commit"]
    end

    subgraph CIPreflight ["CI: preflight job (● tests.yml)"]
        RUST1["● dtolnay/rust-toolchain@stable<br/>━━━━━━━━━━<br/>Rust toolchain (new)<br/>Required for maturin build"]
        UVLOCK_CHK["uv lock --check<br/>━━━━━━━━━━<br/>Lockfile gate<br/>(fails fast on drift)"]
    end

    subgraph CITest ["CI: test job (● tests.yml)"]
        RUST2["● dtolnay/rust-toolchain@stable<br/>━━━━━━━━━━<br/>Rust toolchain (new)<br/>Compiles maturin wheel"]
        UV_SYNC["uv sync --locked --extra dev<br/>━━━━━━━━━━<br/>Installs all dev deps<br/>Builds api-simulator wheel"]
        TASK_ALL["task test-all<br/>━━━━━━━━━━<br/>lint-imports → pytest -n 4"]
    end

    subgraph CIPatchBump ["CI: patch-bump (● patch-bump-integration.yml)"]
        RUST_PB["● dtolnay/rust-toolchain@stable<br/>━━━━━━━━━━<br/>Required before uv lock"]
        UV_LOCK_PB["uv lock<br/>━━━━━━━━━━<br/>Regenerates lockfile<br/>with Rust dep resolution"]
    end

    subgraph CIVersionBump ["CI: version-bump (● version-bump.yml)"]
        RUST_VB["● dtolnay/rust-toolchain@stable<br/>━━━━━━━━━━<br/>Required before uv lock<br/>(main + integration)"]
        UV_LOCK_VB["uv lock × 2<br/>━━━━━━━━━━<br/>main branch + integration<br/>branch lockfile regeneration"]
    end

    subgraph Quality ["CODE QUALITY GATES"]
        RUFF["ruff format + check<br/>━━━━━━━━━━<br/>auto-fix on commit"]
        IMP_LINT["import-linter<br/>━━━━━━━━━━<br/>L0/L1/L2/L3 layering<br/>7 forbidden-import contracts"]
        MYPY["mypy<br/>━━━━━━━━━━<br/>type checking (py311)"]
    end

    subgraph TestFW ["TEST FRAMEWORK"]
        PYTEST["pytest -n 4 --dist worksteal<br/>━━━━━━━━━━<br/>pytest-asyncio · pytest-xdist<br/>timeout=60s (signal)"]
        MOCK_HTTP["● mock_http_server fixture<br/>━━━━━━━━━━<br/>PyResponseSpec API<br/>dict headers · url() method"]
        FAKE_CLD["● fake_claude fixture<br/>━━━━━━━━━━<br/>PyFakeClaudeCLI<br/>run([], None) · exit_code()"]
    end

    PYPROJ --> UVLOCK
    UVLOCK --> RUST1
    RUST1 --> UVLOCK_CHK
    UVLOCK --> RUST2
    RUST2 --> UV_SYNC
    UV_SYNC --> TASK_ALL
    TASK_ALL --> IMP_LINT
    IMP_LINT --> PYTEST
    PYTEST --> MOCK_HTTP
    PYTEST --> FAKE_CLD
    TASK_ALL --> RUFF
    RUFF --> MYPY

    PYPROJ --> RUST_PB
    RUST_PB --> UV_LOCK_PB

    PYPROJ --> RUST_VB
    RUST_VB --> UV_LOCK_VB

    class PYPROJ,UVLOCK stateNode;
    class RUST1,RUST2,RUST_PB,RUST_VB newComponent;
    class UV_SYNC,UV_LOCK_PB,UV_LOCK_VB phase;
    class UVLOCK_CHK,RUFF,IMP_LINT,MYPY detector;
    class TASK_ALL handler;
    class PYTEST handler;
    class MOCK_HTTP,FAKE_CLD output;
Loading

Module Dependency Diagram

%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 70, 'curve': 'basis'}}}%%
graph TB
    classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff;
    classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff;
    classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff;
    classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff;
    classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff;
    classDef integration fill:#c62828,stroke:#ef9a9a,stroke-width:2px,color:#fff;

    subgraph Config ["DEPENDENCY CONFIGURATION"]
        PYPROJ["● pyproject.toml<br/>━━━━━━━━━━<br/>api-simulator: git + subdirectory<br/>crates/api-simulator-py (Rust)"]
        UVLOCK["● uv.lock<br/>━━━━━━━━━━<br/>Pinned to post-#75 commit<br/>maturin wheel resolution"]
    end

    subgraph External ["EXTERNAL PACKAGE (Rust/PyO3)"]
        API_SIM["api-simulator<br/>━━━━━━━━━━<br/>TalonT-Org/api-simulator<br/>crates/api-simulator-py"]
        PY_RESP["api_simulator._api_simulator_py<br/>━━━━━━━━━━<br/>PyResponseSpec<br/>mock_http_server fixture"]
        FAKE_CLI["api_simulator.claude<br/>━━━━━━━━━━<br/>FakeClaudeCLI<br/>fake_claude fixture"]
    end

    subgraph TestL1 ["L1 TEST CONSUMERS (tests/execution/)"]
        QUOTA_HTTP["● test_quota_http.py<br/>━━━━━━━━━━<br/>imports PyResponseSpec<br/>7 test functions"]
        SESSION_E2E["● test_session_classification_e2e.py<br/>━━━━━━━━━━<br/>imports FakeClaudeCLI<br/>10 test methods"]
    end

    subgraph ProdL1 ["L1 PRODUCTION MODULE"]
        EXEC["execution/<br/>━━━━━━━━━━<br/>quota.py · session.py<br/>No api-simulator imports"]
    end

    PYPROJ --> UVLOCK
    UVLOCK --> API_SIM
    API_SIM --> PY_RESP
    API_SIM --> FAKE_CLI
    PY_RESP -->|"imports PyResponseSpec"| QUOTA_HTTP
    FAKE_CLI -->|"imports FakeClaudeCLI"| SESSION_E2E
    EXEC -.->|"tested by"| QUOTA_HTTP
    EXEC -.->|"tested by"| SESSION_E2E

    class PYPROJ,UVLOCK stateNode;
    class API_SIM integration;
    class PY_RESP,FAKE_CLI handler;
    class QUOTA_HTTP,SESSION_E2E output;
    class EXEC phase;
Loading

Closes #643

Implementation Plan

Plan file: /home/talon/projects/autoskillit-runs/impl-20260406-193330-615247/.autoskillit/temp/make-plan/migrate_rust_api_simulator_plan_2026-04-06_000000.md

🤖 Generated with Claude Code via AutoSkillit

Token Usage Summary

Step uncached output cache_read cache_write count time
plan 29 21.3k 724.2k 68.1k 1 7m 48s
verify 28 32.9k 1.2M 66.4k 1 9m 14s
implement 80 36.1k 5.2M 103.8k 1 16m 22s
open_pr 37 19.2k 1.3M 59.8k 1 7m 53s
Total 174 109.5k 8.4M 298.1k 41m 18s

Copy link
Copy Markdown
Collaborator Author

@Trecek Trecek left a comment

Choose a reason for hiding this comment

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

AutoSkillit PR Review — Verdict: changes_requested. 2 blocking issues found (1 critical, 1 warning). See inline comments.

Comment thread tests/execution/test_quota_http.py Outdated
returncode=proc.returncode,
stdout=proc.stdout,
stderr=proc.stderr,
stderr="",
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[critical] tests: Indentation error: stderr="" at L314 is indented with only 8 spaces inside the SubprocessResult(...) call, but other keyword arguments use 12 spaces. This will cause a SyntaxError/IndentationError at parse time — the test file will fail to import.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Investigated — this is intentional. Direct byte inspection of L314 shows 12 spaces of indentation (matching L312 returncode=, L313 stdout=, L315 termination=, L316 pid=0 — all 12 spaces). 'python3 -m py_compile' exits cleanly with no output, confirming zero syntax/indentation errors. The reviewer misread the diff hunk's relative indentation (8 spaces in unified diff context = 12 absolute spaces in the file). No code change needed.

Copy link
Copy Markdown
Collaborator Author

@Trecek Trecek left a comment

Choose a reason for hiding this comment

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

AutoSkillit review found 2 blocking issues. See inline comments for details.

Copy link
Copy Markdown
Collaborator Author

@Trecek Trecek left a comment

Choose a reason for hiding this comment

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

AutoSkillit review found 2 blocking issues (verdict: changes_requested). See inline comments on the diff for details.

@Trecek Trecek enabled auto-merge April 7, 2026 03:38
Trecek added 3 commits April 6, 2026 20:39
…rkflows

dtolnay/rust-toolchain@stable is required before uv sync/uv lock so
maturin can build the Rust-based api-simulator wheel from source.
- pyproject.toml: add subdirectory = 'crates/api-simulator-py' to resolve
  maturin-built wheel from Cargo workspace
- uv.lock: regenerated; pinned to post-Rust commit e12c6140
- test_quota_http.py: migrate register/register_sequence calls to
  PyResponseSpec; update header access to dict-style requests[0]['headers']
- test_session_classification_e2e.py: update import to FakeClaudeCLI,
  change fake_claude.run() to run([], None), drop proc.stderr -> ''
…tead of private _api_simulator_py import

Addresses reviewer warning: direct import from api_simulator._api_simulator_py
accesses a private Rust extension module. The public surface re-exports PyResponseSpec
as MockResponseSpec via api_simulator.http, so tests depend on the stable interface.
@Trecek Trecek force-pushed the migrate-to-rust-based-api-simulator-pyo3-rewrite/643 branch from c4e22cc to 6063f77 Compare April 7, 2026 03:44
@Trecek Trecek added this pull request to the merge queue Apr 7, 2026
Merged via the queue into integration with commit 99e92b6 Apr 7, 2026
2 checks passed
@Trecek Trecek deleted the migrate-to-rust-based-api-simulator-pyo3-rewrite/643 branch April 7, 2026 03:51
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