SLICE-4 (part 1): deploy contract — versioned health, backend-served SPA, SmtpMailer + honest 502 (SD-0001 §7.2) #10

Merged
ben.stull merged 5 commits from slice-4-deploy-contract into main 2026-06-11 06:36:27 +00:00
4 changed files with 21 additions and 4 deletions
Showing only changes of commit a1c5544694 - Show all commits
+1
View File
@@ -0,0 +1 @@
0.4.0
+12 -2
View File
@@ -11,6 +11,7 @@ from __future__ import annotations
import logging import logging
import sys import sys
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any from typing import Any
import psycopg import psycopg
@@ -26,6 +27,15 @@ from app.platform.mailer import Mailer
from app.platform import session as session_mod from app.platform import session as session_mod
# The repo-root VERSION file is the single version source: the deploy pin checks out its
# tag, and /healthz must report it back (flotilla-core's verify gate compares them).
_REPO_ROOT = Path(__file__).resolve().parents[2]
try:
_APP_VERSION = (_REPO_ROOT / "VERSION").read_text().strip()
except OSError:
_APP_VERSION = "0.0.0"
class RequestCodeBody(BaseModel): class RequestCodeBody(BaseModel):
email: str email: str
@@ -94,7 +104,7 @@ def create_app(database_url: str | None = None) -> FastAPI:
finally: finally:
app.state.pool.close() app.state.pool.close()
app = FastAPI(title="ecomm", version="0.3", lifespan=lifespan) app = FastAPI(title="ecomm", version=_APP_VERSION, lifespan=lifespan)
@app.get("/healthz") @app.get("/healthz")
def healthz(response: Response, conn: psycopg.Connection = Depends(get_conn)): def healthz(response: Response, conn: psycopg.Connection = Depends(get_conn)):
@@ -108,7 +118,7 @@ def create_app(database_url: str | None = None) -> FastAPI:
if pending: if pending:
response.status_code = 503 response.status_code = 503
return {"status": "unavailable", "reason": "migrations_pending"} return {"status": "unavailable", "reason": "migrations_pending"}
return {"status": "ok"} return {"status": "ok", "version": _APP_VERSION}
@app.post("/api/auth/request-code") @app.post("/api/auth/request-code")
def request_code( def request_code(
+1 -1
View File
@@ -12,7 +12,7 @@ def test_inv_1_bootstrap_whole_flow_from_empty(fresh_db_url):
# fresh_db_url is a brand-new empty database; create_app() self-migrates (INV-7). # fresh_db_url is a brand-new empty database; create_app() self-migrates (INV-7).
with TestClient(create_app(database_url=fresh_db_url)) as client: with TestClient(create_app(database_url=fresh_db_url)) as client:
# a fresh deployment serves healthz green before any row exists # a fresh deployment serves healthz green before any row exists
assert client.get("/healthz").json() == {"status": "ok"} assert client.get("/healthz").json()["status"] == "ok"
# PUC-2: first visitor requests a code; it reaches them via the dev channel # PUC-2: first visitor requests a code; it reaches them via the dev channel
assert client.post( assert client.post(
+7 -1
View File
@@ -1,14 +1,20 @@
from pathlib import Path
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from app.main import create_app from app.main import create_app
# /healthz reports the VERSION file's value — the flotilla deploy gate compares
# body.version against the pinned target (flotilla-core SPEC §8.1 phase 8).
_VERSION = (Path(__file__).resolve().parents[2] / "VERSION").read_text().strip()
def test_healthz_ok_on_migrated_empty_db(fresh_db_url): def test_healthz_ok_on_migrated_empty_db(fresh_db_url):
app = create_app(database_url=fresh_db_url) app = create_app(database_url=fresh_db_url)
with TestClient(app) as client: with TestClient(app) as client:
resp = client.get("/healthz") resp = client.get("/healthz")
assert resp.status_code == 200 assert resp.status_code == 200
assert resp.json() == {"status": "ok"} assert resp.json() == {"status": "ok", "version": _VERSION}
def test_startup_migrates_from_empty(fresh_db_url): def test_startup_migrates_from_empty(fresh_db_url):