Decision Log¶
This document records key technical decisions made during the project.
Stage 0 Decisions¶
D001: SQL Server over PostgreSQL¶
Date: January 2026
Context: Need to choose a database for the prototype.
Decision: Use SQL Server (RDS SQL Server Express)
Rationale: - Fastmarkets uses Azure SQL in production - SQL Server → Azure SQL migration is straightforward - Ensures prototype can be deployed to Fastmarkets infrastructure
Consequences: - Slightly higher RDS costs than PostgreSQL - Need to handle SQL Server-specific connection string format - Prisma supports SQL Server well
D002: App Runner over ECS/Fargate¶
Date: January 2026
Context: Need to choose container hosting for the prototype.
Decision: Use AWS App Runner
Rationale: - Simpler than ECS/Fargate for small team - Built-in auto-scaling - No load balancer management - Faster deployments
Consequences: - Less configuration flexibility than ECS - VPC connector required for private RDS access - VPC connector blocks outbound internet (affects Cloudflare key fetching)
D003: Cloudflare Access for Environment Protection¶
Date: January 2026
Context: Need to protect dev environment without building full auth.
Decision: Use Cloudflare Access (Zero Trust) at the infrastructure level
Rationale: - Immediate protection without code changes - Email-based authentication (no passwords to manage) - Same team (@luminarium.ai, @fastmarkets.com) access model - Defers application-level auth to later stage
Consequences: - JWT validation required in middleware - Cloudflare public keys required for JWT validation (see D007 for key strategy) - /api/health must be excluded for CI health checks
D004: Embed Cloudflare Keys at Build Time (superseded by D007)¶
Date: January 2026
Context: Middleware needs Cloudflare public keys to validate JWTs, but VPC connector blocks outbound internet.
Decision: Fetch keys at build time via scripts/fetch-cf-keys.mjs, embed in src/lib/cloudflare-keys.generated.ts
Rationale: - App Runner with VPC connector has no internet egress - Keys are stable (rotate infrequently) - Build-time embedding ensures keys are always available
Consequences: - Keys may become stale if Cloudflare rotates them - Need to rebuild/redeploy if keys change - Added complexity in build process
Superseded by D007 (June 2026) — runtime JWKS fetching eliminates the redeploy-on-rotation requirement.
D007: Runtime JWKS Fetching with Build-Time Seed Fallback¶
Date: June 2026
Context: D004's build-time key embedding meant any Cloudflare key rotation broke auth ("Invalid or expired token") until a full redeploy. This created unacceptable operational risk.
Decision: Fetch Cloudflare Access public keys at runtime from the tenant certs endpoint (/cdn-cgi/access/certs) using jose's createRemoteJWKSet. Build-time keys (cloudflare-keys.generated.ts) are retained as a non-authoritative cold-start seed used only when the live endpoint is unreachable.
Rationale: - Key rotations are picked up automatically — no redeploy needed - jose handles in-memory caching, cooldown, and unknown-kid triggered refresh - Seed fallback is bounded by CF_JWKS_MAX_STALE_MS (default 6 h), so an emergency key revocation by Cloudflare is honoured within that window (fail-closed after) - Algorithm pinned to RS256; issuer enforced; token-supplied key URLs (jku/x5u) are never honoured
Consequences: - Requires outbound HTTPS to luminariumai.cloudflareaccess.com from App Runner; if blocked, seed keys serve as fallback up to CF_JWKS_MAX_STALE_MS - Optional env var CF_ACCESS_AUD enforces the Access Application audience tag - Optional env var CF_JWKS_MAX_STALE_MS overrides the 6-hour stale-key window - /api/health exposes JWKS status (everRemoteSuccess, lastRemoteSuccessAgeMs) for runtime diagnostics
D005: Next.js 15 with App Router¶
Date: January 2026
Context: Need to choose a web framework.
Decision: Use Next.js 15 with App Router
Rationale: - Modern React patterns (Server Components) - Built-in API routes - Excellent TypeScript support - Easy Docker deployment with output: 'standalone'
Consequences: - Team needs to learn App Router patterns - Some ecosystem libraries still catching up to App Router
D006: Terraform for Infrastructure¶
Date: January 2026
Context: Need to manage AWS and Cloudflare resources.
Decision: Use Terraform with S3 backend
Rationale: - Single tool for AWS and Cloudflare - State management with locking - Plan/apply workflow for safe changes - Easy to add environments later
Consequences: - Team needs Terraform knowledge - State must be managed carefully - CI/CD needs AWS and Cloudflare credentials
Pending Decisions¶
P001: Logfire vs Alternative Observability¶
Status: Pending evaluation in Stage 3
Options: 1. Logfire (Pydantic) - Python-first, good LangChain integration 2. LangSmith - LangChain native, good for LLM tracing 3. Datadog - Enterprise standard, comprehensive
Evaluation criteria: - LLM call tracing quality - Cost at prototype scale - Integration with LangChain - Team familiarity
P002: LLM Provider Selection¶
Status: Pending evaluation in Stage 2
Options: 1. GPT-5.1 (OpenAI) - Preferred if viable 2. Claude Opus 4.5 (Anthropic) - Strong reasoning 3. Gemini 3 Pro (Google) - Good multimodal
Evaluation criteria: - Accuracy on price assessment tasks - Cost per assessment - Latency - Context window size