Files
wiggleverse-ecomm/docs/superpowers/plans/2026-06-10-slice-4-deploy-contract.md
T
ben.stull acff7ac588
ci / check (pull_request) Has been cancelled
ci / check (push) Has been cancelled
docs(slice-4): BOOTSTRAP.md PPE runbook, version 0.4.0, README status
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:36:24 -07:00

6.5 KiB

SLICE-4 (part 1) — Deploy-Contract Code Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make ecomm deployable by flotilla-core's 9-phase gesture and rehearsable on PPE: versioned health, SPA served by the backend, real SmtpMailer with honest delivery failure (closes ecomm#7), per SD-0001 §7.2 (SLICE-4) — the code half. Provisioning (Cloud SQL via the new launch-app provision-datastore, VM, deployment.toml) and the PPE rehearsal follow as operator gestures in the same session.

Anchor: SD-0001 §7.2 SLICE-4 (R2a, checked this session). flotilla contract: flotilla-core SPEC §8.1 (checkout v<VERSION> tag → pip → npm ci && npm run build → write backend/.env → restart → verify body.version == target && body.status == "ok"); provision-vm: nginx proxies ALL routes to uvicorn app.main:app (WorkingDirectory backend/), so the backend must serve frontend/dist.

Architecture: VERSION at repo root is the single version source (healthz body + FastAPI version + the deploy pin). create_app() mounts frontend/dist (when present) after all API routes — SPA fallback via StaticFiles(html=True). SmtpMailer is the second adapter of the existing mailer port (STARTTLS smtplib, config from ECOMM_SMTP_*, INV-8); delivery failure raises MailerErroraccounts.request_code sends before commit (rollback on failure — no orphan code, no tripped cooldown; ecomm#7) → BFF surfaces 502 delivery_failed (INV-9).

Tech Stack: stdlib smtplib/email.message; FastAPI StaticFiles; no new dependencies.


Task 1: VERSION + versioned /healthz

Files: Create VERSION (root). Modify backend/app/main.py, backend/tests/test_healthz.py.

  • Write VERSION containing 0.4.0.
  • Test first: test_healthz_ok_on_migrated_empty_db asserts {"status": "ok", "version": "0.4.0"} read from the VERSION file (compare against (repo_root/"VERSION").read_text().strip(), not a literal). Run → FAIL.
  • main.py: add _APP_VERSION = (Path(__file__).resolve().parents[2] / "VERSION").read_text().strip() (fallback "0.0.0" when missing); healthz returns {"status": "ok", "version": _APP_VERSION}; FastAPI(version=_APP_VERSION). Run → PASS. Commit.

Task 2: backend serves the SPA (deploy phase-8 contract)

Files: Modify backend/app/main.py. Test backend/tests/test_static_spa.py.

  • Test first: create_app(database_url=..., static_dir=tmp_path) with a tmp_path/index.html; GET / → 200 + the html; GET /healthz and /api/auth/me still answer JSON (API wins over the mount). Default static_dir=None → resolves repo_root/frontend/dist, skipped silently when absent (dev: Vite serves). Run → FAIL.
  • create_app(database_url=None, static_dir: str | Path | None = None); after the last route: resolve dir, if dir/index.html exists: app.mount("/", StaticFiles(directory=dir, html=True), name="spa"). Run → PASS (whole suite). Commit.

Task 3: SmtpMailer + config surface (INV-8)

Files: Modify backend/app/platform/{mailer,config}.py. Test backend/tests/test_mailer.py (extend).

  • Config additions: smtp_host() (ECOMM_SMTP_HOST), smtp_port() (ECOMM_SMTP_PORT, 587), smtp_user(), smtp_password(), smtp_from() (default = user), smtp_starttls() (default on).
  • Tests first: MailerError exists; build_mailer("smtp") returns SmtpMailer wired from env (monkeypatched); SmtpMailer.send drives a monkeypatched smtplib.SMTP (starttls → login → send_message with To/Subject/From + body) and never logs the body; SMTP exception → MailerError. Run → FAIL.
  • Implement: MailerError(Exception); SmtpMailer (EmailMessage; smtplib.SMTP(host, port, timeout=10), STARTTLS per config, login when user set, send_message; except Exception → raise MailerError; logs only smtp sent to=<sha256[:8] of recipient> — §6.6 log hygiene); build_mailer("smtp") builds it from config. Run → PASS. Commit.

Task 4: honest delivery failure — send-before-commit + 502 (closes ecomm#7)

Files: Modify backend/app/domains/accounts/{errors,service,__init__}.py, backend/app/main.py. Test backend/tests/test_accounts_request_code.py + test_auth_endpoints.py (extend).

  • Tests first: a mailer whose send raises MailerError → service raises accounts.DeliveryFailed, no auth_code row remains, and an immediate retry is not cooldown-blocked; endpoint test: 502 {"error": {"code": "delivery_failed"}}. Run → FAIL.
  • Implement: DeliveryFailed(AccountsError); request_code moves conn.commit() after mailer.send(...), wrapping send in try/except MailerError → conn.rollback(); raise DeliveryFailed; BFF maps it to _error(502, "delivery_failed", "We couldn't send the code — try again."). Run → PASS (whole backend suite). Commit.

Task 5: BOOTSTRAP.md PPE section + housekeeping + gate

Files: Modify docs/BOOTSTRAP.md, README.md, frontend/package.json (0.4.0).

  • BOOTSTRAP.md: replace the "land with SLICE-4" sentence; add Pre-production (PPE) section — prerequisites (suite-run provisioning: scaffold-gcp-project → provision-datastore (Cloud SQL, engineering#46) → provision-vm → define-deployment/import; secrets as references), the one deploy gesture (CLOUDSDK_ACTIVE_CONFIG_NAME=<config> flotilla-core deploy <name>), how to watch /healthz, the rehearsal walk (PUC-11), reset-to-empty note; Prod section: placeholder "lands with the prod stand-up" honestly.
  • README status: deploy contract in place; frontend package 0.4.0.
  • ./scripts/check.sh → all green. Commit. Push, PR citing SD-0001 §7.2 SLICE-4 + ecomm#7, merge, tag v0.4.0 on the merge commit and push the tag (the deploy pin).

Self-review

Spec coverage: SmtpMailer + config wiring INV-8 ✓ (T3), §6.6 hardening — Secure cookies already config-driven, log hygiene ✓ (T3 no-body logging; LogMailer is dev-only by config), deployment.toml + provisioning deliberately deferred to the Phase-C suite gestures (needs the GCP project id that scaffold-gcp-project mints), BOOTSTRAP.md PPE ✓ (T5), versioned health for the §8.1 verify ✓ (T1), SPA serving for the §2 topology ✓ (T2), ecomm#7 ✓ (T4). E2E browser tests still deferred per §6.8. Type consistency: MailerError lives in platform/mailer; DeliveryFailed in accounts errors; both exported via package surfaces.