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 MailerError → accounts.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
VERSIONcontaining0.4.0. - Test first:
test_healthz_ok_on_migrated_empty_dbasserts{"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 atmp_path/index.html; GET/→ 200 + the html; GET/healthzand/api/auth/mestill answer JSON (API wins over the mount). Defaultstatic_dir=None→ resolvesrepo_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:
MailerErrorexists;build_mailer("smtp")returnsSmtpMailerwired from env (monkeypatched);SmtpMailer.senddrives a monkeypatchedsmtplib.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 onlysmtp 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
sendraisesMailerError→ service raisesaccounts.DeliveryFailed, noauth_coderow remains, and an immediate retry is not cooldown-blocked; endpoint test: 502{"error": {"code": "delivery_failed"}}. Run → FAIL. - Implement:
DeliveryFailed(AccountsError);request_codemovesconn.commit()aftermailer.send(...), wrapping send intry/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, tagv0.4.0on 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.