Implementation Phases
Timeline: 8+ weeks (comprehensive) Start: Phase 0 begins immediately
Phase status (concise)
| Phase | Plan | Status |
|---|---|---|
| 0 Foundation + accounts | Week 1 | 🟡 Partial — repo/dev/schema/prices done; Forgejo remote + some accounts missing |
| 0.5 Garage (k3s) | Week 1 | ⬜ Not started (cross-repo nixos-redbeanco) |
| 1 Zola frontend + i18n | Week 2 | ✅ Done (minus Lighthouse verify, deploy) |
| 2 D1 + Workers core | Week 3 | ✅ Done |
| 3 Stripe integration | Week 4 | ✅ Done (minus prod webhook registration) |
| 4 Notifications | Week 5 | 🟡 Partial — core done; delivery-status/fallback/inbound pending |
| 5 Staff PWA | Week 5–6 | 🟡 Partial — wiring done; CF Access + ntfy push pending |
| 6 Surcharge/refund cron | Week 6 | 🟡 Partial — core done; ntfy alert + dead-letter pending |
| 7 Rust async service | Week 7 | 🟡 Partial — code done; cf_metrics stub; deploy pending |
| 8 Monitoring & alerts | Week 7–8 | 🟡 Partial — dashboards done; alert rules pending |
| 9 Security hardening | Week 8 | 🟡 Partial — headers done; rate-limit/CSP/JWT/CI-audit pending |
| 10 Testing & QA | Week 9 | 🟡 Partial — unit done; E2E/integration/load-run/security pending |
| 11 Launch prep | Week 10 | ⬜ Not started |
| 12 Post-launch | Ongoing | ⬜ Not started |
Legend: ✅ Done · 🟡 Partial · ⬜ Not started.
Phase 0: Foundation & Account Setup (Week 1)
Accounts to Create
- Cloudflare account
- Register domain via Cloudflare Registrar (e.g.,
redbeanco.com.auor dedicated domain) - Stripe account (Australia) — start with test mode, get production keys after ABN verification
- ClickSend account — register Sender ID with ACMA
- Resend account — verify domain for email sending
- Forgejo repo:
redbeanco-detailing(on existing Forgejo instance)
Software Repo Setup
-
git initin~/Projects/redbeanco-detailing/ -
flake.nixwith flake-parts, crane, fenix, worker-build inputs -
flake-modules/packages.nix— Zola build, Worker WASM builds (viaworker-build), Rust service (via crane) -
flake-modules/dev.nix— devShell with rust, wrangler, zola, cargo-watch -
flake-modules/ci.nix— om ci config (build + test + lint + audit) -
.forgejo/workflows/ci.yaml— build + test + lint + audit -
.forgejo/workflows/deploy-cf.yaml— deploy to CF Pages + Workers -
.forgejo/workflows/build-image.yaml— build + push Rust service container image -
prices.nix— vehicle × service pricing matrix (with bilingual labels) -
schema/001_initial.sql— D1 bookings table with idempotency constraints -
docs/architecture.md— document all decisions (already done)
Infrastructure Repo
- Add
redbeanco-detailingas flake input innixos-redbeanco/flake.nix - Create
cluster/applications/detailing-service.nix(empty placeholder) - Create
cluster/applications/garage.nix(empty placeholder)
Exit Criteria
-
nix developworks (devShell functional) -
nix flake checkpasses - CI pipeline runs (build + test passes, even if no code yet)
- All external accounts created and accessible
Phase 0.5: Deploy Garage (Week 1)
-
npins add container dxflrs/garage --tag v2.2.0in nixos-redbeanco - Write
cluster/applications/garage.nix(followingminio.nixpattern) - Add import to
cluster/env/prod.nix - Create OpenBao secret:
kvv2/redbeanco/garage -
nixidy build .#prod→ commit → push → ArgoCD deploys - Initialize Garage:
garage status,garage layout assign - Create bucket:
garage bucket create detailing-backup - Create access key:
garage key create detailing-service - Configure S3 lifecycle: 30-day expiry on
detailing-backupbucket - Verify S3 access:
aws s3 ls --endpoint http://100.64.0.3:30060
Exit Criteria
- Garage pod running on k3s
-
detailing-backupbucket created - S3 lifecycle configured
- Access key stored in OpenBao
Phase 1: Zola Frontend + i18n (Week 2)
- Zola
config.toml:default_language = "en",[languages.zh]with translations - Content files:
_index.md+_index.zh.md,faq.md+faq.zh.md,privacy.md+privacy.zh.md,terms.md+terms.zh.md -
prices.nix→ Zola template injection (Nix generates a config file consumed by Zola'sload_data) - Booking form: vehicle type + service type selectors (prices update live)
- Mobile-first responsive SASS (no framework, <500KB page weight)
- Accessibility: WCAG 2.1 AA (form labels, ARIA, contrast, keyboard nav)
- Staff PWA pages: dashboard, detail, surcharge, complete, photo upload
- PWA manifest + service worker (offline cache for staff pages)
- QR code SVG (static, print-ready, error correction level H)
- CI:
zola build→wrangler pages deploy - CF Pages project setup (dev + prod environments)
Exit Criteria
- Zola site builds and deploys to CF Pages (builds ✅, deploy ⬜)
- Booking form renders with live price calculation
- Both EN and ZH pages render correctly
- Staff PWA pages render (no backend yet, mock data)
- Lighthouse score > 90 (performance, accessibility, best practices)
Phase 2: D1 + Workers Core (Week 3)
-
wrangler d1 create redbeanco-detailing(prod + dev databases) - Migration pipeline:
wrangler d1 migrations apply - Apply
schema/001_initial.sqlto both databases - Booking Worker (Rust → WASM):
Cargo.toml:worker = { features = ["d1"] }#[event(fetch)]Router withPOST /api/booking- Input validation with
validatorcrate (serde + validator derive) - D1 prepared statements (
d1.prepare("...").bind(&[...])) - Read prices from build-time injected module (generated from
prices.nix) - Create Stripe Checkout Session via
reqwestto Stripe API - Return Checkout URL
- Photo Worker:
Cargo.toml:worker = { features = ["r2"] }POST /api/photos/presign(staff only, CF Access)- R2 presigned PUT URL generation
- Security headers middleware (CSP, HSTS, X-Frame-Options, etc.)
-
wrangler devlocal development with D1 local - Unit tests:
cargo testfor validation + pricing logic - CI:
wrangler deployfor Workers
Exit Criteria
- Booking Worker creates Stripe Checkout Sessions (test mode)
- D1 writes work (booking records created)
- Photo Worker generates presigned R2 URLs
- All D1 queries use prepared statements
- Unit tests pass in CI (tests pass, CI not running)
Phase 3: Stripe Integration (Week 4)
- Stripe Checkout Session creation (line items from
prices.nix) - Apple/Google Pay support (automatic with Checkout)
- GST-inclusive pricing in Stripe line items
- Webhook Worker:
#[event(fetch)]— verify Stripe signature → enqueue to CF QueuesCargo.toml:worker = { features = ["queue"] }- HMAC-SHA256 signature verification
- Enqueue to CF Queues
- Return 200 to Stripe immediately
- Queue Consumer:
#[event(queue)]— dequeue → idempotent D1 write → send confirmationstripe_event_idunique constraint for idempotency- Write booking record: status = 'confirmed'
- Write audit_log entry
- Stripe test mode for dev/CI
- Restricted Stripe keys:
Checkout Sessions:write,Refunds:write,Charges:read - Webhook endpoint registration in Stripe dashboard
- Integration tests with Stripe test mode
Exit Criteria
- Full payment flow works in dev (form → Stripe test payment → D1 record created)
- Webhook signature verification works
- Idempotency verified (duplicate webhooks don't create duplicate records)
- Audit log entries created
Phase 4: Notifications (Week 5)
- ClickSend integration: SMS booking confirmation
- ClickSend integration: MMS photo delivery (after service completion)
- ClickSend integration: SMS surcharge payment links
- Resend integration: email booking confirmation
- Bilingual templates (EN/ZH based on booking language preference)
- Template system: parameterized, no injection risk
- ACMA Sender ID registration verification
- Delivery status tracking (webhook from ClickSend/Resend → D1 audit log)
- Inbound SMS support (customer support replies)
- Fallback logic: ClickSend fails → Resend; Resend fails → ClickSend
Exit Criteria
- SMS confirmation sent after payment (bilingual)
- Email confirmation sent after payment (bilingual)
- SMS delivery status tracked in D1
- Fallback logic tested (simulate provider failure)
Phase 5: Staff PWA (Week 5-6)
- Staff dashboard: booking list with status filters (pending, confirmed, surcharge_pending, completed, cancelled)
- Booking detail view: customer info, vehicle, parking, flight, status timeline
- Surcharge workflow: create Stripe Checkout → send SMS → D1 records
surcharge_expires_at - Photo upload: presigned R2 URL → direct phone→R2 upload → store URL in D1
- Complete workflow: update D1 → generate R2 read URLs → send MMS
- Offline capability: service worker caches, IndexedDB action queue, background sync
- Photo compression (canvas API before upload, max 1920x1080, JPEG 85%)
- CF Access protection on all staff routes
- Push notifications for new bookings (ntfy
/redbeanco-detailingtopic)
Exit Criteria
- Staff can log in via CF Access
- Staff can view and filter bookings
- Staff can upload photos (direct to R2)
- Staff can complete service (sends MMS)
- PWA works offline (cached booking list, queued actions)
Phase 6: Surcharge & Refund Cron (Week 6)
- Cron Worker:
#[event(scheduled)]withcrons = ["*/5 * * * *"]Cargo.toml:worker = { features = ["d1"] }- Query D1 for expired pending surcharges
- Call Stripe Refunds API (partial refund retaining $60 call-out fee)
- Update D1:
surcharge_pending→cancelled - Customer notification (SMS: refund processed, $60 retained)
- Alert on refund failures (ntfy urgent)
- Dead letter handling for failed refunds
- Verify: this runs on CF Cron ONLY (not self-hosted Rust service)
- Test: create surcharge, fast-forward expiry, verify cron processes refund
Exit Criteria
- Cron executes every 5 minutes
- Expired surcharges are refunded correctly (payment - $60 call-out fee)
- Customer receives SMS notification of refund
- Failed refunds trigger ntfy urgent alert
- Alert fires if cron hasn't run in 10 min
Phase 7: Rust Async Service (Week 7)
-
Cargo.toml: axum, aws-sdk-s3 (Garage compatible), reqwest, prometheus, tracing, tokio - D1 mirror: poll D1 REST API → write JSON to Garage S3 bucket
- R2 mirror: list R2 objects → copy to Garage S3
- Prometheus metrics: booking count, revenue, status distribution, mirror lag, sync errors
- Config from env vars (12-factor #3):
D1_API_URL,D1_API_TOKEN,S3_ENDPOINT,S3_ACCESS_KEY,S3_SECRET_KEY, etc. - Health probes:
/health(k8s readiness),/metrics(Prometheus scrape) - Graceful shutdown on SIGTERM
- Crane build in flake → container image → push to Forgejo container registry
-
npins add container forgejo.redbeanco.org/redbeanco/detailing-servicein nixos-redbeanco - Write
cluster/applications/detailing-service.nix— nixidy module - ExternalSecret for OpenBao secrets
- ArgoCD deployment via existing GitOps pipeline
- Daily business summary (ntfy at 8am AEST)
Exit Criteria
- Rust service running on k3s
- D1 data mirrored to Garage S3 every 15 min
- R2 objects mirrored to Garage S3
- Prometheus scraping
/metrics(visible in Grafana) - Daily summary arrives via ntfy
Phase 8: Monitoring & Alerts (Week 7-8)
- Add Prometheus scrape target for
detailing-service(extendmonitoring.nix) - Add Garage scrape target
- Alert rules (in
redbeanco-detailing/monitoring/alerts/, imported into monitoring configMap):DetailingServiceDown,GarageS3Down,MirrorLagHigh,RefundCronFailureDetailingSyncErrors,DetailingSurchargeStuck,DetailingHighMemory
- Grafana dashboards: booking overview, payment metrics, service health
- ntfy routing: critical → urgent, warnings → high
- CF Workers structured logging (JSON)
- Rust
tracingwith JSON formatter → Promtail → Loki - Uptime monitoring (UptimeRobot free tier: customer site + Worker health)
- Configure CF Dashboard alerts (Worker errors, D1 latency, Queue depth, Cron failures)
Exit Criteria
- All alerts fire correctly (tested by stopping services)
- Grafana dashboards show real data
- ntfy notifications arrive on phone
- Uptime monitoring active
Phase 9: Security Hardening (Week 8)
- OWASP Top 10 checklist review against implementation (see security.md)
- CF Workers security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- Rate limiting: CF rules on
/api/booking(10/min/IP),/api/photos/presign(30/min/user) - Input validation audit (every field, every endpoint)
- D1 query audit (all parameterized)
- R2 bucket policy: public read for photos only, private write, 15-min presigned URL expiry
- Booking ID: nanoid (21 chars, unguessable) — verify no sequential IDs anywhere
- Stripe key permission verification (restricted keys, not secret keys)
-
cargo auditin CI (blocking on fail) -
cargo clippy -- -D warningsin CI (blocking) - OWASP ZAP automated scan in CI
-
gitleakssecret scanning in CI - Manual penetration testing checklist
Exit Criteria
- OWASP Top 10 checklist all green
-
cargo auditclean -
cargo clippyclean - OWASP ZAP scan passes
- No secrets in git (gitleaks clean)
Phase 10: Testing & QA (Week 9)
- Unit tests:
cargo test(validation, pricing, refund math, template rendering) — aim for >80% coverage - Integration tests:
wrangler dev+ D1 local + Stripe test mode - E2E tests: Playwright (full booking flow EN + ZH, staff workflow, surcharge, cancellation)
- Load tests: k6 (100 concurrent bookings, 500/min form submissions) — scripts written, not run
- Security tests: OWASP ZAP,
cargo audit, manual pen test - Smoke tests: post-deploy health check script
- Test data: Stripe test cards, fake D1 data, ClickSend test numbers
- Test both EN and ZH flows end-to-end
Exit Criteria
- All unit tests pass
- All integration tests pass
- All E2E tests pass (EN + ZH)
- Load test: p95 < 500ms, 0 errors
- Security tests pass
Phase 11: Launch Preparation (Week 10)
DNS & Domain
- Domain registered via CF Registrar
- DNS: A/AAAA record for customer-facing domain → CF Pages
- DKIM/SPF records for Resend email deliverability
- DKIM/SPF records for ClickSend SMS (if applicable)
Production Secrets
- Stripe production keys (restricted permissions)
- Cloudflare Worker secrets (via
wrangler secret put --env production) - OpenBao secrets seeded (
kvv2/redbeanco/detailing/*) - ExternalSecret applied to k3s
Production Backing Services
- D1 production database created + migrations applied
- R2 production bucket created (lifecycle rule pending)
- CF Queues production
- CF Cron Trigger production (5-min schedule)
- Garage
detailing-backupbucket + lifecycle configured
Deployments
- CF Pages production deployment
- CF Workers production deployment
- Rust service production deployment (via ArgoCD)
Physical
- QR code printed (weatherproof material, 10cm x 10cm)
- QR code placed at airport (multiple locations)
Documentation & Training
- Runbook finalized
- Staff training (PWA walkthrough, photo capture, surcharge flow)
- Legal: Privacy Policy + ToS published on site (EN + ZH)
- Data consent checkbox verified on booking form
Verification
- Backup verification: Garage mirror working, Restic backup
- Rollback plan tested (revert a deploy, verify it works)
- End-to-end test on production (test booking with real payment, then refund)
- Monitoring verified (all alerts would fire if something breaks)
Exit Criteria
- Production environment fully operational
- One test booking completed end-to-end (including photos + MMS)
- All monitoring active
- QR code in place at airport
Phase 12: Post-Launch (Ongoing)
First Week (Intensive Monitoring)
- Monitor metrics and alerts daily (Grafana + ntfy)
- Verify daily business summaries arrive
- Watch for any error patterns in CF Workers logs
- Collect customer feedback (SMS survey link post-service)
- Verify refund cron processes correctly (if any surcharges expire)
Ongoing
- Customer feedback collection (SMS survey post-service)
- Iterative improvements based on real usage data
- Feature flagging (D1-backed, for gradual rollouts)
- Regular dependency updates:
- Renovate for
flake.lock(weekly) cargo update(monthly)- npins for container images (weekly scheduled)
- Renovate for
- Quarterly security review (OWASP checklist, dependency audit, pen test)
- BAS/tax reporting automation (Rust service
/reports/basendpoint) - Monthly backup verification (restore drill)
- Quarterly Garage disk space review