Repository navigation
v2.0.0 — All-Rust rewrite
BSS-CLI is now all-Rust. The complete Python → Rust rewrite is done and mainline. This was a behaviour-frozen port (strangler-fig): the motto, scope, write-policy, TMF surfaces, domain events, DB schema, and the LLM tool surface are unchanged — only the implementation stack changed. Existing deployments upgrade in place.
Highlights
- Complete rewrite, same behaviour. Wire contracts, event routing keys, payloads, the Postgres schema, and every LLM tool are byte-compatible with v1.8. Your scenarios, integrations, and data keep working.
- Dramatically lighter. The whole 11-service app plane now idles under 100 MiB — a headline motto-#6 win.
- Faster cold start and lower tail latency (numbers below).
- Alembic retired in favour of the
sqlxmigrator — no Python/greenlet in the runtime story. - Beyond the port: a new Open Order / MSISDN-reservation signup funnel, a signup confirmation screen (real eSIM QR), and async-plane self-healing — see "Beyond the rewrite" below.
Performance (motto #6 — re-measured, idle-vs-idle)
| Budget | v1.8 (Python) | v2.0 (Rust) | Δ |
|---|---|---|---|
| Full-stack RAM (11 services) < 4 GB | 1204 MiB | ~91 MiB idle (≤~160 under load) | ~13× smaller — 2.2% of budget |
| Cold start (all 11) < 30 s | 6.36 s | 3.08 s | ~2.1× faster |
p99 /health < 50 ms |
12.8 ms | 6.16 ms | ~2× lower tail |
| p99 real DB read < 50 ms | — | 8.47 ms | under budget |
Beyond the rewrite
v2.0 isn't only a port — it also shipped a new signup feature and hardened the async plane.
Open Order & MSISDN reservation (new feature)
Numbers no longer collide during a live signup, and an interrupted signup becomes a first-class, resumable record. (Roadmap feature — new behaviour, not in the prior Python line.)
- Reserve-at-pick. Picking a number places a soft hold (distinct from the hard provisioning reserve) via a single atomic, self-healing
UPDATE— concurrent pickers can't both win the same MSISDN. - Persisted, resumable order. One open order per customer (keyed on the verified email). Starting a second is blocked (
open_order.already_open); returning resumes the same plan + held number. - "My open order" account screen. Customers view / resume / cancel their own pending order — ownership-bound (the id comes from the session, never the request) — with a resume link on the dashboard. Clearing a stuck order no longer needs API access.
- 24h expiry sweep. A worker (
FOR UPDATE SKIP LOCKED, multi-replica safe,bss_clock-driven — same discipline as renewal) auto-releases abandoned holds, so a stuck order never permanently strands a number.
Signup confirmation
- A post-signup confirmation screen with a real PNG eSIM QR code + activation poll, plus the MSISDN picker.
- An explicit "Review and confirm" step before the card is charged — previously a returning customer adding a line was charged + activated instantly with no pause. Signup block errors (taken number / existing open order) that used to render silently now show a banner with a "View your open order" link.
Async-plane self-healing & idempotency
The message plane now recovers from a broker blip on its own (lapin, unlike Python's aio-pika robust connection, doesn't auto-recover) — the reliability the soak gated on:
- Publish path self-heals: the channel/connection sit behind a mutex and are recreated on demand (re-declaring the durable exchange), so a transient broker blip no longer wedges the outbox relay.
- Consume path auto-resubscribes after a drop with a shared exponential backoff (1 s → 30 s) — before, a dropped consumer silently stopped consuming until a restart.
- SOM
task.completedis idempotent (already-activated → ack-and-skip), breaking a duplicate-delivery "park storm" that could starve fresh orders from ever completing.
Also fixed during the cutover
Stripe add-card Checkout flow (blank page → working), several Jinja2 → MiniJinja template-flip artifacts, cockpit transcript rehydration (a stale-turn bug in long sessions), and the REPL .env auto-load + branding/banner polish.
Stack (Python → Rust)
- FastAPI → axum; SQLAlchemy/asyncpg → sqlx; Alembic → sqlx migrator (
bss admin migrate) - Pydantic → serde; LangGraph → a hand-rolled ReAct loop (
bss-orchestrator) - Typer/Rich → clap + reedline + ASCII renderers; aio-pika → lapin
- FastAPI portals → axum + MiniJinja + HTMX (same templates, no npm/bundler)
- OpenTelemetry hand-stitched at 4 seams (no auto-instrumentors);
tracingJSON logs - Multi-stage
cargo-chefDockerfiles, non-root distroless final stage
Upgrading
- Fresh install:
bss admin migratethenmake seed. - Existing (Alembic-created) DB:
bss admin migrate --baselineonce to stamp the baseline without re-running it, then you're on the sqlx migrator going forward. Runbook:docs/runbooks/rust-schema-baseline.md. - Build the CLI:
cargo build --release -p bss-cli(orcargo install --path cli).make up/make up-allbring up the all-Rust stack.
Removals / changes
- The original Python implementation was archived, not deleted — recoverable via git tag
python-oracle-final+ tarball. Seedocs/PYTHON-ORACLE.md. - Retired Python-only dev tooling: the
py-*/python-check/check-clock/lint-typesmake targets and the Python Playwrightmake e2e*suite (the Rust ship gate ismake scenarios-hero). - The demo seed was ported to
bss admin seed-demo(+--reset) — 3 demo customers + 2 promotions + VIP assignment, BSS + loyalty in lockstep. Its Python-onlyloyalty-wipemode (and thedemo-restoreone-button) did not carry over — loyalty-cli owns its own DB reset.
Quality gate at release
fmt · clippy -D warnings · cargo test (120 suites, 0 failed) · 14 doctrine guards · hero suite 19/19.
The nominal 14-day soak was cut early by operator decision; all gates were green at cut. See
DECISIONS.md(2026-07-21) andphases/2.0/07-SOAK.md. If a latent regression surfaces, the golden-diff oracle is onegit worktree add … python-oracle-finalaway.