Skip to content

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


Decision Template

### DXXX: Title

**Date:** Month Year

**Context:** What is the situation?

**Decision:** What did we decide?

**Rationale:**
- Why this option?
- What factors influenced the decision?

**Consequences:**
- What are the trade-offs?
- What follow-up work is needed?