From 3dc68abb9ed1a5d54caaa97a797a02dce829a229 Mon Sep 17 00:00:00 2001 From: emeraldleaf Date: Wed, 27 May 2026 19:39:48 -0600 Subject: [PATCH 1/4] plan(deploy): add full-saga demo deployment plan + STATUS pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-session tracking artifact for the multi-PR, multi-week effort to stand up NextAurora as a portfolio-grade demo deployment running the full Order → Payment → Shipping → Notification saga over real cloud infrastructure with the Stripe gateway stubbed. Structure: - Three phases, each independently shippable: 1. Order saga visible (Catalog + Order + minimal Storefront). Order persists, OrderPlacedEvent stages, saga stalls because PaymentService isn't deployed yet — itself a teaching demo. 2. Full saga (Payment + Shipping + Notification). End-to-end flow over real infrastructure with stubbed Stripe. 3. Polish (observability + ops + minimal UX) — demoable to humans. - Four open decisions (D1-D4) called out explicitly: SQL Server hosting, identity provider, messaging transport, cost ceiling. All block Phase 1. - Out-of-scope explicitly listed: real Stripe SDK (stub retirement), PaymentRecoveryJob retry-with-key (gated on stub retirement), production-grade DR/SLAs. Honest framing of what this is and isn't. - Cost ledger structure ready for first entries once provisioning begins. STATUS.md "Next" section now points at the plan so future sessions can pick up coherently. Following the IDSD/intent-driven discipline encoded in the recent articles: explicit intent + expectations + context as a tracked artifact BEFORE implementation, not after. The plan is the "ICE" artifact for this multi-PR effort. Co-Authored-By: Claude Opus 4.7 --- docs/STATUS.md | 2 + docs/full-saga-deployment-plan.md | 241 ++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 docs/full-saga-deployment-plan.md diff --git a/docs/STATUS.md b/docs/STATUS.md index cc33d122..3659d197 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -100,6 +100,8 @@ These two commits collectively add: ## Next +**Active multi-PR effort:** [Full-saga demo deployment](full-saga-deployment-plan.md) — three-phase plan to stand up NextAurora as a portfolio-grade demo running the full Order → Payment → Shipping → Notification saga over real cloud infrastructure (Stripe stubbed). Currently planning; four open decisions (D1–D4) block Phase 1. + **Active item:** smoke-run the system end-to-end. ### How to run diff --git a/docs/full-saga-deployment-plan.md b/docs/full-saga-deployment-plan.md new file mode 100644 index 00000000..192587d9 --- /dev/null +++ b/docs/full-saga-deployment-plan.md @@ -0,0 +1,241 @@ +# Full-saga demo deployment — plan + tracker + +> **Cross-session tracking artifact.** Multi-PR, multi-week effort to stand up +> NextAurora as a portfolio-grade demo deployment running the full +> Order → Payment → Shipping → Notification saga over real cloud infrastructure +> with the Stripe gateway stubbed. Pick up here when resuming the work. + +**Last updated:** 2026-05-27 (plan created) + +**Current state:** planning. No new infrastructure provisioned yet beyond the +existing CatalogService Fly.io demo. + +--- + +## Why this doc exists + +The CatalogService Fly.io demo proves "I can deploy a .NET service to the cloud." +A full-saga demo would prove "I can architect, build, deploy, and operate a +distributed system." Big difference for a portfolio piece. This is the plan that +turns the second story from architecture-on-disk into infrastructure-running. + +The work is several weekends across 5–8 PRs and an ongoing ~$15–40/mo +infrastructure spend. Big enough to need a tracking artifact so each session +can pick up coherently instead of re-deriving the plan. + +--- + +## Scope + +**In:** +- All 5 services deployed to Fly.io +- Real databases (split TBD — see decisions D1) +- Real messaging (transport TBD — see decision D3) +- Real Redis (Upstash or Fly Redis) +- Hosted identity (TBD — see decision D2) +- Real telemetry (App Insights or OpenTelemetry ingestion) +- Storefront UI with a working checkout flow (minimum viable, not polished) +- Stripe gateway stubbed; UI banner: *"Payments are stubbed for demo safety"* + +**Out:** +- Real Stripe SDK integration (stub retirement — separate, gated on project-purpose change) +- PaymentRecoveryJob retry-with-key (gated on stub retirement; tracked in STATUS.md) +- Production-grade DR / backups / runbooks — this is a demo, not a business +- SLAs, on-call, alerting beyond basic uptime + +**Why "production-shaped, payment-stubbed":** +- Demonstrates the architecture working end-to-end +- Removes PCI scope entirely +- Honest in the demo copy +- Cheaper than real-payments infra by an order of magnitude + +--- + +## Phases + +Three phases, each independently shippable. If life happens between phases, +each phase ends with something demoable. + +### Phase 1 — Order saga visible (Catalog + Order + minimal Storefront) + +**Goal.** Land the deployment pattern with the smallest possible footprint. +Show an Order being placed, persisted, and **stalling at payment because +PaymentService isn't deployed yet** — itself a teaching demo of "what does the +saga look like when downstream isn't available?" + +**Why this is the right starting point.** +- First-deployment gotchas (Dockerfile cold start, EF migration on boot, JWT + config, gRPC over TLS, secrets binding) get caught once, not three times. +- Visible milestone: a deployed Order saga that stalls is itself a portfolio + piece. If something interrupts the work, you've landed something coherent. +- Cost validation: real bills for a week before committing to Phase 2's larger + footprint. + +**Deliverables.** +- [ ] `OrderService` Dockerfile + `fly.toml` + GitHub Actions deploy workflow + (mirrors CatalogService's pattern at [Dockerfile.catalog](../Dockerfile.catalog)) +- [ ] `OrderService` Fly app + database hosting per decision D1 +- [ ] Minimal Storefront deployed — single "place an order" flow + (the existing Blazor WASM scaffold or a simpler Razor Pages slice — decide + during phase, not now) +- [ ] JWT validation working against deployed identity provider (decision D2) +- [ ] gRPC Catalog ↔ Order working over TLS in the deployed environment +- [ ] `DemoMode` flag applied to `OrderService` (mirrors CatalogService's + pattern: gates Scalar + OpenAPI + skip-HTTPS-redirect + migrate-on-startup) +- [ ] Cost ledger first entry; verify ≤ $15/mo target + +**Risk callouts.** +- **D1 (SQL Server hosting) is the biggest cost lever.** Azure SQL serverless + with auto-pause is ~$15-30/mo for two DBs; Postgres-only-for-demo is free + with Fly Postgres but breaks the "two engines on purpose" architectural + story in the deployed shape (would need a footnote in the README + Wolverine + outbox provider swap). +- **D2 (identity)** — deploying Keycloak is operationally heavy. Hosted + alternatives (Auth0, Azure AD B2C) are simpler but introduce vendor lock-in. +- **First-deployment Dockerfile gotchas** — cold start can be 10+ seconds for + a fresh Fly Machine; EF migration on boot adds to that. May need a warmup + endpoint or accept it in demo copy. + +**Definition of done.** Place an Order via the Storefront, see it persist in +the Order DB, see `OrderPlacedEvent` staged in `wolverine.outgoing_envelopes`, +see the saga stall because PaymentService isn't deployed yet. All over real +cloud infrastructure with real JWT auth. Reachable via public URL. + +### Phase 2 — Full saga (Payment + Shipping + Notification) + +**Goal.** Light up the remaining three services so the saga completes +end-to-end with stubbed Stripe. + +**Deliverables.** +- [ ] Same deployment pattern (Dockerfile + fly.toml + GitHub Actions) for + PaymentService, ShippingService, NotificationService +- [ ] Real Azure Service Bus namespace OR alternative transport (decision D3) +- [ ] `DemoMode` flag sweep — apply the existing CatalogService pattern to + PaymentService, ShippingService, NotificationService +- [ ] Stripe stub remains; optionally make it slightly more interesting + (latency variation, decline-by-amount) for a richer demo +- [ ] Banner in Storefront UI: *"Payments are stubbed for demo safety"* + +**Risk callouts.** +- Messaging cost: real ASB Basic ~$10/mo + per-message. Alternative: NATS or + RabbitMQ on Fly (cheaper but more ops surface), or AWS SQS+SNS (free tier + generous, but Wolverine reconfig). +- Cold-start latency compounds across 5 services. May need to keep one or + more services warm (small extra cost) or document the first-request delay. + +**Definition of done.** Place an Order, watch it progress through Payment +(stubbed) → Shipping → Notification end-to-end. View it in observability +tooling. Demoable in ≤ 60 seconds after warmup, or document the cold-start +delay honestly in the demo copy. + +### Phase 3 — Polish (observability + ops + UX) + +**Goal.** Make it actually demoable to other humans. + +**Deliverables.** +- [ ] Real telemetry endpoint wired (App Insights or OpenTelemetry collector) +- [ ] Dashboards for the saga flow (one timeline per Order, CorrelationId-keyed) +- [ ] Storefront UX polished enough to live-demo (minimal, not feature-rich) +- [ ] `README` "Try the live demo" section with auth credentials, expected + flow, known cold-start gotchas +- [ ] Cost dashboard + monthly review cadence + +**Risk callouts.** +- Storefront UX scope creep — keep it minimal, the demo is the architecture + not the UI. +- Internet-exposed = real security surface. Every IDOR / JWT / CSRF rule + encoded in CLAUDE.md is now live-fire, not theoretical. Worth an explicit + security pass before sharing the URL. + +**Definition of done.** Send the live URL to someone who's never seen the +codebase; they can place an order and watch the saga complete. + +--- + +## Open decisions + +These block Phase 1. Resolve before infrastructure provisioning starts. + +### D1 — SQL Server hosting + +OrderService + PaymentService both use SQL Server (RowVersion shadow column + +Wolverine outbox via `PersistMessagesWithSqlServer`). Options: + +- **(a) Azure SQL Database serverless with auto-pause.** ~$15-30/mo for two + databases. Auto-pause after 1h idle → cold start on first request after + pause (~30s spin-up). +- **(b) Postgres-only-for-demo with provider swap.** Free with Fly Postgres. + Requires swapping Order + Payment to `xmin` concurrency + `PersistMessagesWithPostgresql` + outbox. README "two engines on purpose" framing gets a "demo deployment + exception" footnote. +- **(c) Defer the decision** — deploy Phase 1 (Order only) on Postgres + temporarily, re-evaluate after seeing real cold-start + cost data. + +### D2 — Identity provider + +Local dev uses Keycloak in an Aspire container. For deployed: options: + +- **(a) Keycloak self-hosted on Fly.** Free-ish but operationally heavy + (DB, persistence, realm import on boot). +- **(b) Auth0 free tier.** 7k MAU free. Simpler, but vendor lock-in. JWT + validation config in ServiceDefaults stays the same. +- **(c) Azure AD B2C free tier.** 50k MAU free. Deeper Azure integration, + same JWT story. +- **(d) Static JWKS with hardcoded test users.** Zero cost. Fake auth — only + acceptable if banner is explicit ("this is a demo, anyone can log in as + the test buyer"). + +### D3 — Messaging transport + +Currently uses Azure Service Bus via Aspire emulator (dev only). Options: + +- **(a) Real Azure Service Bus Basic tier.** ~$10/mo + per-message. Smallest + code change (existing routing config works). +- **(b) NATS or RabbitMQ on Fly.** Cheaper, more ops surface. Wolverine + supports both — but the existing `UseAzureServiceBus` config swaps. +- **(c) AWS SQS+SNS.** Free tier covers our volume (1M req/mo free). Wolverine + has an AWS provider. Bigger code change but no ongoing cost at demo scale. + +### D4 — Cost ceiling + +Hard cap before provisioning anything. Suggested: + +- Phase 1 target: ≤ $15/mo, hard ceiling $20/mo +- Phase 2 target: ≤ $30/mo, hard ceiling $50/mo +- Phase 3 target: unchanged (no new persistent infrastructure) + +If costs exceed ceiling: stop, audit, decide before resuming. + +--- + +## Cost ledger + +| Date | Phase | Component | Plan | Monthly cost | Cumulative | +|---|---|---|---|---|---| +| (none yet) | | | | | | + +Existing CatalogService demo (separate ledger): ~$0–$5/mo on Fly.io (scale-to-zero, $25 prepaid cap). + +--- + +## Prerequisites before any phase starts + +- [ ] All four open decisions (D1–D4) resolved and captured in this doc +- [ ] Fly.io account billing set up with a hard spend cap matching D4 +- [ ] Identity provider account created per D2 +- [ ] Messaging provider account created per D3 (if D3 ≠ Fly-native) +- [ ] Branch convention agreed: `deploy/phase-1-order-saga-visible`, etc. + +--- + +## Related docs + +- [docs/demo-deployment.md](demo-deployment.md) — Recipe for the existing + single-service (CatalogService) Fly.io deployment. Phase 1 builds on this. +- [docs/demo-deployment-story.md](demo-deployment-story.md) — Narrative of the + single-service deployment, gotchas, decisions. Useful context for what to + expect in Phase 1. +- [docs/STATUS.md](STATUS.md) — Cross-session entry point. Has a one-line + pointer to this doc under "Open issues." +- [README.md](../README.md) — Demo URL + scope callout will need updating + after Phase 2 lands. From fa4e1db7b0f16a08dca47ec57abe8a54a989b4f5 Mon Sep 17 00:00:00 2001 From: emeraldleaf Date: Wed, 27 May 2026 19:45:58 -0600 Subject: [PATCH 2/4] plan(deploy): resolve D1-D4 decisions + split Phase 1 into 1A + 1B MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-resolved decisions captured in the plan doc: - D1 (SQL Server hosting): Postgres-only-for-demo with provider swap. Dev environment keeps two-engine split (the architectural story is still genuine for dev/learning). Deployed environment uses Postgres for all four services with state. README footnote will explain the demo-deployment exception. - D2 (Identity): Auth0 free tier. Hosted, simplest setup. Keycloak stays for local dev. ServiceDefaults JWT config is already config-driven — points at Auth0 issuer in deployed environment. - D3 (Messaging): AWS SQS+SNS free tier. Wolverine reconfig from UseAzureServiceBus to UseAmazonSqs. 1M req/mo free covers demo volume comfortably. - D4 (Cost ceiling): $30/mo target, $50/mo hard ceiling. Phase 1 ≤$15, Phase 2 ≤$30. Fly.io spend cap to be set at $50 before any provisioning. Phase 1 now splits naturally into two sub-PRs given D1: - Phase 1A: Postgres provider swap (code only). Add `DatabaseProvider` config setting, branch Order + Payment Program.cs on it, regenerate Postgres migrations, branch concurrency-token config (RowVersion vs xmin). Integration tests cover both paths. README footnote. - Phase 1B: Deploy Order + minimal Storefront. Original Phase 1 goal, now sitting on top of 1A's plumbing. Co-Authored-By: Claude Opus 4.7 --- docs/full-saga-deployment-plan.md | 151 +++++++++++++++++++----------- 1 file changed, 98 insertions(+), 53 deletions(-) diff --git a/docs/full-saga-deployment-plan.md b/docs/full-saga-deployment-plan.md index 192587d9..dc64e5a7 100644 --- a/docs/full-saga-deployment-plan.md +++ b/docs/full-saga-deployment-plan.md @@ -152,59 +152,104 @@ codebase; they can place an order and watch the saga complete. --- -## Open decisions - -These block Phase 1. Resolve before infrastructure provisioning starts. - -### D1 — SQL Server hosting - -OrderService + PaymentService both use SQL Server (RowVersion shadow column + -Wolverine outbox via `PersistMessagesWithSqlServer`). Options: - -- **(a) Azure SQL Database serverless with auto-pause.** ~$15-30/mo for two - databases. Auto-pause after 1h idle → cold start on first request after - pause (~30s spin-up). -- **(b) Postgres-only-for-demo with provider swap.** Free with Fly Postgres. - Requires swapping Order + Payment to `xmin` concurrency + `PersistMessagesWithPostgresql` - outbox. README "two engines on purpose" framing gets a "demo deployment - exception" footnote. -- **(c) Defer the decision** — deploy Phase 1 (Order only) on Postgres - temporarily, re-evaluate after seeing real cold-start + cost data. - -### D2 — Identity provider - -Local dev uses Keycloak in an Aspire container. For deployed: options: - -- **(a) Keycloak self-hosted on Fly.** Free-ish but operationally heavy - (DB, persistence, realm import on boot). -- **(b) Auth0 free tier.** 7k MAU free. Simpler, but vendor lock-in. JWT - validation config in ServiceDefaults stays the same. -- **(c) Azure AD B2C free tier.** 50k MAU free. Deeper Azure integration, - same JWT story. -- **(d) Static JWKS with hardcoded test users.** Zero cost. Fake auth — only - acceptable if banner is explicit ("this is a demo, anyone can log in as - the test buyer"). - -### D3 — Messaging transport - -Currently uses Azure Service Bus via Aspire emulator (dev only). Options: - -- **(a) Real Azure Service Bus Basic tier.** ~$10/mo + per-message. Smallest - code change (existing routing config works). -- **(b) NATS or RabbitMQ on Fly.** Cheaper, more ops surface. Wolverine - supports both — but the existing `UseAzureServiceBus` config swaps. -- **(c) AWS SQS+SNS.** Free tier covers our volume (1M req/mo free). Wolverine - has an AWS provider. Bigger code change but no ongoing cost at demo scale. - -### D4 — Cost ceiling - -Hard cap before provisioning anything. Suggested: - -- Phase 1 target: ≤ $15/mo, hard ceiling $20/mo -- Phase 2 target: ≤ $30/mo, hard ceiling $50/mo -- Phase 3 target: unchanged (no new persistent infrastructure) - -If costs exceed ceiling: stop, audit, decide before resuming. +## Resolved decisions (2026-05-27) + +### D1 — SQL Server hosting → **Postgres-only-for-demo (provider swap)** + +Deployed shape uses Postgres for all four services with state. Dev environment +keeps the two-engine split (SQL Server for Order + Payment, Postgres for +Catalog + Shipping) — the "two engines on purpose" architectural story is +still genuine in the dev/learning context; the deployed shape gets a README +footnote explaining the demo exception. + +**Implications:** +- **Provider swap on Order + Payment.** Both services need + `PersistMessagesWithPostgresql` (currently `PersistMessagesWithSqlServer`) + and `xmin` concurrency tokens (currently `RowVersion` shadow column). + Config-driven via a `DatabaseProvider` setting so dev keeps SQL Server. +- **Postgres-flavored migrations** for Order + Payment. Either regenerate + from scratch against the Postgres provider, or maintain parallel migration + histories per provider (EF Core supports `ContextType` partitioning). +- **README footnote.** "Two database engines on purpose" gets a "demo + deployment exception" callout linking here. CLAUDE.md unchanged — the + rule is still genuine for dev/learning. +- Free with Fly Postgres → keeps Phase 1 cost ≤ $15/mo target. + +### D2 — Identity provider → **Auth0 free tier** + +7k MAU free, simplest setup (paste OIDC config). Local dev keeps Keycloak in +the Aspire container; deployed environment points at the Auth0 tenant. + +**Implications:** +- Auth0 tenant setup (one-time). +- `Authentication:Authority` config swaps to the Auth0 issuer URL in deployed + environments. ServiceDefaults already reads from config — no code change. +- Test buyer + seller users in Auth0 (matching the test users currently in + the local Keycloak realm). +- JWT claim mapping (`sub`, `preferred_username`, `realm_access.roles`) needs + verification — Auth0's claim names may differ from Keycloak's. + +### D3 — Messaging transport → **AWS SQS+SNS free tier** + +Free tier covers 1M req/mo, comfortably more than demo volume. Wolverine has +an AWS provider package. + +**Implications:** +- AWS account setup + IAM user for the demo with SQS/SNS-only permissions. +- `Wolverine.AmazonSqs` (and SNS support if needed) added to + `Directory.Packages.props`. +- `Program.cs` in each event-publishing service: `UseAzureServiceBus(...)` → + `UseAmazonSqs(...)`. Config-driven so local dev keeps the ASB emulator. +- Topic/subscription topology recreated as SQS queues + SNS topics. The + existing per-service subscription names (e.g. `notify-orders-sub`) map to + SQS queue names; topics (`order-events`, `payment-events`) map to SNS + topics. +- AWS credentials wiring: env vars in Fly Machine secrets + dev secrets via + `dotnet user-secrets` locally if cross-stack work is needed in dev. + +### D4 — Cost ceiling → **$30/mo target, $50/mo hard ceiling** + +- Phase 1 target: ≤ $15/mo (Postgres free with Fly + Auth0 free tier + SQS + free tier + small Fly Machines) +- Phase 2 target: ≤ $30/mo (additional Fly Machines for Payment + Shipping + + Notification + Storefront) +- Phase 3 target: unchanged +- **Hard ceiling: $50/mo.** If actual costs exceed this: stop, audit, decide + before resuming. Set Fly.io spend cap to $50 before provisioning anything. + +## Implementation order + +Given the resolved decisions, Phase 1 splits naturally into two sub-PRs: + +### Phase 1A — Postgres provider swap (code only, no deployment) + +**Goal.** Land the dual-provider config plumbing in `main` so Phase 1B's +deployment can simply set `DatabaseProvider=Postgres` and pick up the right +EF + Wolverine + concurrency-token combo. + +**Deliverables:** +- [ ] `DatabaseProvider` config setting (defaults `SqlServer` in dev, override + to `Postgres` in deployed `appsettings.Production.json` or via env var) +- [ ] Order + Payment `Program.cs`: branch on `DatabaseProvider` for `AddDbContext` + + Wolverine outbox provider +- [ ] Order + Payment EF migrations re-generated against the Postgres provider + (parallel migration history or `ContextType` partitioning) +- [ ] Order + Payment concurrency-token config branches on provider + (`RowVersion` for SqlServer, `xmin` for Postgres) +- [ ] Integration tests verify both code paths build + run (the existing + OrderService.Tests.Integration uses SQL Server Testcontainer; add a + Postgres Testcontainer slice for the new path) +- [ ] README footnote: "demo deployment uses Postgres-only as an exception" + +### Phase 1B — Deploy Order + minimal Storefront + +**Goal.** Phase 1's original goal: deployed Order saga visible. + +**Deliverables:** as already listed in Phase 1 above, plus: +- [ ] Auth0 tenant set up with test buyer/seller users +- [ ] Fly Postgres provisioned for OrderService +- [ ] OrderService deployed with `DatabaseProvider=Postgres` +- [ ] Minimal Storefront deployed --- From 9106b8a5c3e74078d4f53b3e1048ca69ede62f2e Mon Sep 17 00:00:00 2001 From: emeraldleaf Date: Wed, 27 May 2026 19:51:00 -0600 Subject: [PATCH 3/4] plan(deploy): switch D2 to Keycloak self-hosted + restructure Phase 1 + Phase 3 rate-limit audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User reconsidered D2 after seeing an article on Keycloak's value as an identity provider. Switched from Auth0 to Keycloak self-hosted on Fly. Rationale: - Same IdP in dev and prod. NextAurora already runs Keycloak in local Aspire dev with the realm at realms/nextaurora-realm.json. Deployed Keycloak imports the same realm export on boot — one IdP, one realm, two environments. Removes "works in dev, breaks in prod" risk around realm shape, claim names, test users. - Stronger portfolio story than vendor-managed Auth0. - Cost is roughly comparable (~$5-10/mo Fly Machine vs Auth0 free vendor-locked). Trade-off: real ops surface (Postgres DB for Keycloak state, persistent volume, realm-import-on-boot, two-stage readiness probe, ~30-60s cold start for Java + realm import). Phase 1 now splits into three sub-PRs: - 1A: Postgres provider swap (code only) — unchanged - 1B: Deploy Keycloak self-hosted (NEW infrastructure-only sub-phase) - 1C: Deploy Order + minimal Storefront against deployed Keycloak Phase 3 deliverables grown by one item: rate-limit audit + Redis-backed swap if any service runs 2+ Fly Machines. The in-memory ASP.NET Core limiter on Catalog search + Payment process is correct for single- instance Phase 2 but silently weakens under scale-out. The rule encoding lands in a separate PR (CLAUDE.md / .coderabbit.yaml / architecture-reviewer.md). Co-Authored-By: Claude Opus 4.7 --- docs/full-saga-deployment-plan.md | 83 +++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/docs/full-saga-deployment-plan.md b/docs/full-saga-deployment-plan.md index dc64e5a7..cd1fbb26 100644 --- a/docs/full-saga-deployment-plan.md +++ b/docs/full-saga-deployment-plan.md @@ -139,6 +139,13 @@ delay honestly in the demo copy. - [ ] `README` "Try the live demo" section with auth credentials, expected flow, known cold-start gotchas - [ ] Cost dashboard + monthly review cadence +- [ ] **Rate-limit audit + Redis-backed swap if multi-instance.** Catalog + search + Payment process both use ASP.NET Core's built-in in-memory + limiter. If any service runs 2+ Fly Machines for resilience after + Phase 2, the in-memory limit silently multiplies by N — bypassable. + Swap those endpoints to a Redis-backed limiter using the Redis + instance already present for HybridCache. See CLAUDE.md + "Security Requirements → Rate Limiting" for the rule details. **Risk callouts.** - Storefront UX scope creep — keep it minimal, the demo is the architecture @@ -175,19 +182,32 @@ footnote explaining the demo exception. rule is still genuine for dev/learning. - Free with Fly Postgres → keeps Phase 1 cost ≤ $15/mo target. -### D2 — Identity provider → **Auth0 free tier** +### D2 — Identity provider → **Keycloak self-hosted on Fly** -7k MAU free, simplest setup (paste OIDC config). Local dev keeps Keycloak in -the Aspire container; deployed environment points at the Auth0 tenant. +Same IdP in dev and prod. The local Aspire-managed Keycloak container already +imports `realms/nextaurora-realm.json` on boot — the deployed Keycloak does +the same with the same realm export. One IdP, one realm, two environments. +Removes a class of "works in dev, breaks in prod" bugs around realm shape, +claim names, and test users. **Implications:** -- Auth0 tenant setup (one-time). -- `Authentication:Authority` config swaps to the Auth0 issuer URL in deployed - environments. ServiceDefaults already reads from config — no code change. -- Test buyer + seller users in Auth0 (matching the test users currently in - the local Keycloak realm). -- JWT claim mapping (`sub`, `preferred_username`, `realm_access.roles`) needs - verification — Auth0's claim names may differ from Keycloak's. +- **Keycloak Fly app + Postgres database** for Keycloak's own state + (~$5-10/mo for the Fly Machine; Postgres free with Fly's shared instance). +- **Realm import on boot** via `KC_DB_*` + `KEYCLOAK_ADMIN*` env vars + + `--import-realm` startup flag pointing at the volume-mounted realm export. +- **Persistent volume** for Keycloak's data dir so realm changes (new users, + password resets) survive Machine restarts. +- **Two-stage readiness probe** — Keycloak serves HTTP before the realm + import completes; health check has to know about both. Documented gotcha + in the article. +- **Boot cost** — Keycloak is a Java app, ~512MB RAM minimum, ~30-60s cold + start. Scale-to-zero is unrealistic; keep one Machine always-on or accept + the first-request wait. +- **Realm export workflow** — `kc.sh export` from local Keycloak → commit + the JSON → deploy reads it. Document this in the deployment recipe. +- ServiceDefaults JWT config is already config-driven; deployed + `Authentication:Authority` points at the Fly Keycloak URL. Zero code + change in any service. ### D3 — Messaging transport → **AWS SQS+SNS free tier** @@ -219,11 +239,12 @@ an AWS provider package. ## Implementation order -Given the resolved decisions, Phase 1 splits naturally into two sub-PRs: +Given the resolved decisions, Phase 1 splits naturally into three sub-PRs. +Each is independently shippable. ### Phase 1A — Postgres provider swap (code only, no deployment) -**Goal.** Land the dual-provider config plumbing in `main` so Phase 1B's +**Goal.** Land the dual-provider config plumbing in `main` so Phase 1C's deployment can simply set `DatabaseProvider=Postgres` and pick up the right EF + Wolverine + concurrency-token combo. @@ -241,15 +262,45 @@ EF + Wolverine + concurrency-token combo. Postgres Testcontainer slice for the new path) - [ ] README footnote: "demo deployment uses Postgres-only as an exception" -### Phase 1B — Deploy Order + minimal Storefront +### Phase 1B — Deploy Keycloak self-hosted on Fly (infrastructure, no service code change) -**Goal.** Phase 1's original goal: deployed Order saga visible. +**Goal.** Stand up the deployed identity provider before any application +service tries to validate JWTs against it. ServiceDefaults already handles +JWT validation config-driven, so this phase is pure infrastructure. + +**Deliverables:** +- [ ] Fly Postgres database provisioned for Keycloak's state +- [ ] `Dockerfile.keycloak` + `fly.keycloak.toml` + GitHub Actions deploy + workflow for Keycloak +- [ ] Persistent volume for Keycloak data dir +- [ ] Realm export workflow documented in [demo-deployment.md](demo-deployment.md): + `kc.sh export --realm nextaurora` from local dev → commit the JSON → + deploy reads it on boot +- [ ] Two-stage readiness probe (Keycloak serves HTTP before realm import + completes; both have to be green before traffic flows) +- [ ] Test users from local realm (buyer, seller, admin) imported in deployed + Keycloak +- [ ] Documented public Keycloak URL for future services to point at via + `Authentication:Authority` env var +- [ ] Cost verification — Fly Machine for Keycloak + free Postgres ≤ $10/mo + +**Risk callouts.** +- Keycloak cold start is real (~30-60s for Java + realm import). Either keep + one Machine always-on (cost) or accept the first-request wait in demo copy. +- Realm export format can change across Keycloak versions; pin the version + in `Dockerfile.keycloak` and document the export-format-version dependency. + +### Phase 1C — Deploy Order + minimal Storefront + +**Goal.** Phase 1's original visible-saga goal: deployed Order with auth. **Deliverables:** as already listed in Phase 1 above, plus: -- [ ] Auth0 tenant set up with test buyer/seller users - [ ] Fly Postgres provisioned for OrderService -- [ ] OrderService deployed with `DatabaseProvider=Postgres` +- [ ] OrderService deployed with `DatabaseProvider=Postgres` and + `Authentication:Authority` pointing at deployed Keycloak (1B) - [ ] Minimal Storefront deployed +- [ ] End-to-end smoke test: log into Storefront with Keycloak test user, + place an order, watch it stall at payment --- From 942e909d5107efac17f5f41306669d1913d2a524 Mon Sep 17 00:00:00 2001 From: emeraldleaf Date: Wed, 27 May 2026 20:17:50 -0600 Subject: [PATCH 4/4] plan(deploy): CodeRabbit review fixes + Seq for Phase 3 telemetry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small edits: 1. CodeRabbit #1 (full-saga-deployment-plan.md:335): the "Related docs" pointer said STATUS.md references this doc "under 'Open issues'" but the entry actually lives under "Next." Fixed to match. 2. CodeRabbit #2 (STATUS.md:103): the "Next" entry still said "four open decisions (D1-D4) block Phase 1" but the plan doc now lists D1-D4 as resolved 2026-05-27. Updated to reflect resolved state + name the chosen options + point at Phase 1A as the next concrete work. 3. Seq named as the Phase 3 telemetry choice. Sourced from an article on structured logging + distributed tracing for microservices with Seq. Seq fits this deployment shape better than App Insights — unified logs + traces in one UI, self-hostable on Fly with persistent volume (matches D2 Keycloak pattern), free tier covers demo scope, OTLP-native (slots into existing OpenTelemetry export with one config-line change). Gotcha captured: pin OpenTelemetry.Instrumentation.* packages explicitly in Directory.Packages.props, since non-stable RC versions for instrumentations like StackExchangeRedis differ across major bumps. No new rule encoding — the article reinforces existing CLAUDE.md rules (structured logging with message templates, OpenTelemetry instrumentation patterns) without adding net-new lessons worth encoding. Co-Authored-By: Claude Opus 4.7 --- docs/STATUS.md | 2 +- docs/full-saga-deployment-plan.md | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/STATUS.md b/docs/STATUS.md index 05b96bc3..5b809885 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -100,7 +100,7 @@ These two commits collectively add: ## Next -**Active multi-PR effort:** [Full-saga demo deployment](full-saga-deployment-plan.md) — three-phase plan to stand up NextAurora as a portfolio-grade demo running the full Order → Payment → Shipping → Notification saga over real cloud infrastructure (Stripe stubbed). Currently planning; four open decisions (D1–D4) block Phase 1. +**Active multi-PR effort:** [Full-saga demo deployment](full-saga-deployment-plan.md) — three-phase plan to stand up NextAurora as a portfolio-grade demo running the full Order → Payment → Shipping → Notification saga over real cloud infrastructure (Stripe stubbed). Decisions D1–D4 resolved 2026-05-27 (Postgres-only-for-demo, Keycloak self-hosted on Fly, AWS SQS+SNS free tier, $50/mo hard ceiling). Next concrete work: Phase 1A (Postgres provider swap on Order + Payment, code only — no deployment yet). **Active item:** smoke-run the system end-to-end. diff --git a/docs/full-saga-deployment-plan.md b/docs/full-saga-deployment-plan.md index cd1fbb26..f5c1e828 100644 --- a/docs/full-saga-deployment-plan.md +++ b/docs/full-saga-deployment-plan.md @@ -33,7 +33,7 @@ can pick up coherently instead of re-deriving the plan. - Real messaging (transport TBD — see decision D3) - Real Redis (Upstash or Fly Redis) - Hosted identity (TBD — see decision D2) -- Real telemetry (App Insights or OpenTelemetry ingestion) +- Real telemetry (Seq self-hosted on Fly, OTLP-ingested — see Phase 3) - Storefront UI with a working checkout flow (minimum viable, not polished) - Stripe gateway stubbed; UI banner: *"Payments are stubbed for demo safety"* @@ -133,7 +133,17 @@ delay honestly in the demo copy. **Goal.** Make it actually demoable to other humans. **Deliverables.** -- [ ] Real telemetry endpoint wired (App Insights or OpenTelemetry collector) +- [ ] **Real telemetry endpoint wired — Seq self-hosted on Fly.** Seq is the + strongest fit for this deployment shape: unified logs + traces in one + UI (no "App Insights for traces, somewhere-else for logs" split), + self-hostable on Fly with a persistent volume (matches the Keycloak + pattern from D2), free tier covers demo scope, OTLP-native so it + slots into the existing OpenTelemetry export with one config-line + change (`http://seq:5341/ingest/otlp/v1/traces`). Gotcha to pin + around: `OpenTelemetry.Instrumentation.*` packages — non-stable RC + versions for instrumentations like StackExchangeRedis differ across + major bumps, so pin versions explicitly in `Directory.Packages.props` + rather than relying on floating ranges. - [ ] Dashboards for the saga flow (one timeline per Order, CorrelationId-keyed) - [ ] Storefront UX polished enough to live-demo (minimal, not feature-rich) - [ ] `README` "Try the live demo" section with auth credentials, expected @@ -332,6 +342,6 @@ Existing CatalogService demo (separate ledger): ~$0–$5/mo on Fly.io (scale-to- single-service deployment, gotchas, decisions. Useful context for what to expect in Phase 1. - [docs/STATUS.md](STATUS.md) — Cross-session entry point. Has a one-line - pointer to this doc under "Open issues." + pointer to this doc under "Next" (currently the active multi-PR effort). - [README.md](../README.md) — Demo URL + scope callout will need updating after Phase 2 lands.