From b2e1f11a85de71e824e397be35238e3d4e0e29a5 Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Wed, 10 Jun 2026 07:08:24 -0700 Subject: [PATCH] =?UTF-8?q?spec(=C2=A76.6=E2=80=936.9):=20NFRs,=20key=20de?= =?UTF-8?q?cisions=20table,=20testing=20strategy,=20failure=20modes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- ...-0001-mvp-sign-up-and-single-storefront.md | 86 ++++++++++++++++++- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/specs/SD-0001-mvp-sign-up-and-single-storefront.md b/specs/SD-0001-mvp-sign-up-and-single-storefront.md index 1acd18d..7a3bb7b 100644 --- a/specs/SD-0001-mvp-sign-up-and-single-storefront.md +++ b/specs/SD-0001-mvp-sign-up-and-single-storefront.md @@ -808,19 +808,97 @@ sequenceDiagram ### 6.6 Non-functional requirements & cross-cutting concerns - +- **Security & privacy:** + - Authn: email + one-time code per INV-3; sessions in signed, HTTP-only, + `SameSite=Lax` cookies, `Secure` in deployed environments. + - Authz: MVP has one rule — you act only on your own storefront — enforced + in the domain layer (INV-6). + - No account enumeration: `request-code` answers uniformly for new and + known emails. + - Data classification: emails are personal data — the only personal data + held (§6.3); code hashes are short-lived secrets; both excluded from + logs in deployed environments. + - Secrets are references, never bytes (handbook §6.3): session secret and + SMTP credentials live in Secret Manager, named by deployment config + (INV-8). +- **Performance & scale:** MVP traffic is near-zero; the bar is honesty, not + throughput — interactive responses well under a second on the standard + single-VM deployment. SQLite/WAL with per-request connections is far above + this bar; revisit at real scale (§7.4 R-3). +- **Availability & resilience:** one VM, one process (standard flotilla + stack); deploys are fail-stop with `/healthz` gating; brief deploy-window + unavailability is accepted pre-v1. +- **Observability:** structured request logs (no emails/codes in deployed + logs); `/healthz` reporting migration currency; auth events (code issued / + verify ok / verify failed) logged with hashed identifiers — enough to see + the bootstrap rehearsal happen and debug delivery. +- **Accessibility:** the four screens are forms and text — semantic HTML, + labelled fields, keyboard-completable flows, visible focus; the code input + carries one-time-code semantics so password managers and OS autofill + cooperate. ### 6.7 Key decisions & alternatives considered - +| Decision | Chosen | Alternatives considered | Why chosen | +| --- | --- | --- | --- | +| Overall shape | Carry forward the prototype's 4-layer modular monolith + React SPA | Heavier re-architecture (Postgres, services, SSR); resume prototype code; new stack | Proven by R01–R07 against this exact domain; smallest bootstrap surface (§1.6 outcome 3); rebuild wants clean *code*, not unproven *shape* (§2) | +| Datastore | SQLite (WAL), no ORM, forward-only `.sql` migrations | Postgres (managed or on-VM) | Single-VM flotilla standard; zero standing service to provision per environment — bootstrap stays one gesture; prototype precedent. Revisit at scale (R-3) | +| Tenancy | One shared DB; tenant rows carry `storefront_id` (INV-5); ownership via membership relation | DB-per-storefront; hard 1-1 FK on account | Membership keeps the many-per-account door open (Feature #1 constraint) and is the staff-model seam (13.15.\*); DB-per-tenant multiplies the bootstrap story for no MVP gain | +| One-storefront rule | Service-layer guard + no UX affordance (INV-4) | UX-only; schema `UNIQUE(account_id)` | UX-only leaves the API dishonest (data could contradict the promise); schema-level closes the door Feature #1 says to keep open. The guard is one deletable check | +| Auth mechanism | Email + one-time code, passwordless | Passwords (+ reset/verification machinery); OAuth (Google/Apple) first | Corpus's primary path (14.01.0003–0004); prototype-proven; collapses the account-lifecycle question — no passwords to reset, verification inherent. OAuth deferred, addable under INV-2 | +| Admission | Open sign-up, no gate | Prototype's pending/granted invite gate | ecomm is a product, not an internal tool; a gate would hand-curate exactly what BUC-5 says must be flow-reachable. Explicit §1.7 non-goal | +| Bootstrap | No seed; empty DB is a working state (INV-1) | Prototype's seeded store/owner; seed scripts per environment | Seeding *is* hand-placed data — it's what Feature #1's acceptance forbids; INV-1 makes the bootstrap story a property of the app, not of tooling | +| API surface | REST BFF only; GraphQL deferred | Prototype's dual REST + GraphQL from day one | Four screens don't justify a second surface; INV-6's one-source-of-truth layering keeps GraphQL addable as a projection later | +| Email delivery | `mailer` port: `LogMailer` (dev/tests) / `SmtpMailer` (deployed); relay is deployment config | Provider SDK baked in; building codes-only without real mail | Port keeps INV-8 (no deployment shape in code) and makes the provider an operator choice (§9 Q-1); real mail is required by Feature #1's "no stubbed auth" in PPE/Prod | +| Sessions | Signed cookie, no session table | Server-side session rows | No revocation/multi-device requirement yet; cookie sessions are one less table and zero queries; revisit with staff (§6.2 note) | +| Vocabulary | **Storefront** is ecomm's canonical noun (corpus "store" maps to it) | Adopt corpus "store" | The charter, Feature #1, and repo names all say storefront; glossary (§10) carries the mapping so corpus scenario IDs still read cleanly | ### 6.8 Testing strategy - +The prototype's discipline, inherited: + +- **Scenario-bound end-to-end tests** drive the app through its HTTP surface + (FastAPI TestClient on a fresh temp DB). Where a corpus scenario covers the + behavior, the test name embeds its ID (`test_14_01_0003_email_code_sent`); + PUCs without a corpus ID bind to the PUC (`test_puc_05_...`). One test per + scenario/PUC, including every unhappy path in §4. +- **The bootstrap test is INV-1's enforcement:** from an empty database, one + test walks request-code → verify → create-storefront → `/me` → admin + answer, asserting no step needed seeded state. A second asserts + `migrate()` is idempotent (re-running applies nothing). +- **Invariant tests** for the sharp edges: code expiry/attempt + exhaustion/cooldown (INV-3), concurrent second-storefront refusal (INV-4), + enumeration uniformity (§6.6), `LogMailer` capture in tests. +- **Frontend:** typecheck + production build in CI; the entry-routing rule + (§6.5) unit-tested; component/E2E browser tests deferred until there is UI + beyond forms. +- **Gate:** `scripts/check.sh` = `lint-imports` (layer contract, INV-6) + + `pytest` + frontend typecheck/build — locally pre-merge and in CI. "Tested" + for a slice (§7.2) means its scenarios are green in this gate. +- **PPE tier:** the §7.2 SLICE-4 rehearsal *is* the deployed-tier test — the + product flows walked against PPE with real mail before Prod exists. ### 6.9 Failure modes, rollback & flags - +- **Mail relay down / refuses** → `request-code` returns `502 + delivery_failed`; the user is told the code didn't go out (INV-9, §5.2) — + never a fake 204. Operator sees it in logs; codes are re-requestable, no + state is corrupted. +- **Migration fails at startup** → process refuses to start (INV-7); + flotilla's fail-stop deploy keeps the prior version serving; operator fixes + forward (no down-migrations — MVP migrations are additive). +- **DB file lost/corrupted** → restore from VM disk snapshot (the standard + flotilla VM's recovery story); accepted pre-v1 bar — an explicit backup + cadence is deferred and logged (§9 Q-2). +- **Session-secret leak** → rotate the secret in Secret Manager + redeploy; + all sessions invalidate at once (accepted blast radius at MVP scale, + §6.2). +- **Code-guessing attempts** → INV-3's attempt cap and cooldown bound the + attack; events are logged. +- **Rollback** = flotilla redeploy of the previous version; additive-only + MVP migrations make old-code-on-new-schema safe. +- **Feature flags / kill switch:** none — pre-v1, nothing to protect that a + redeploy doesn't cover; first flags arrive when there are users to shield. ## 7. Delivery Plan