Serving
Serving
AWS cloud-base for every Quantapix surface. Four CloudFront-fronted static sites, one t4g.small EC2 fronting two FastAPI apps, GitHub OIDC for CI, KMS-CMK-backed SecureStrings.
Static sites — R53 → CloudFront → S3 (OAC)
One stamp per site. Identical in shape; differs only in bucket name, hostname, ACM ARN, Route 53 zone. Today two stamps; four after Qnarre + Qresev launch.
Browser
End user. Resolves the apex hostname (e.g. quantapix.com, femfas.net) and pulls HTML/CSS/JS over HTTPS. No cookies, no auth, no JS state — every site under designing/web/+documenting/web/ is fully static.
Route 53
One hosted zone per apex domain. Apex A+AAAA ALIAS records point at the CloudFront distribution; www. redirects to apex via a separate distribution or CNAME. Two zones live today (quantapix.com, femfas.net); two more arrive when Qnarre + Qresev launch.
CloudFront
One distribution per site. PriceClass_100 (US/EU edges only — cheapest). HTTP/2 + HTTP/3, HSTS via security-headers policy, OAC origin-access. Cache: *.html short (300s, must-revalidate); everything else immutable (1y). 403/404 both map to /404.html.
CloudFront Fn
Viewer-request CloudFront Function astro-rewrite-index. Rewrites /route → /route/index.html so the S3 REST origin can serve Astro static builds. Required because OAC is the origin-access mode (the static-website endpoint would skip this step but loses bucket-policy scoping).
S3 (OAC)
One bucket per site, named for the apex (quantapix.com, femfas.net, …). REST endpoint, BlockPublicAccess on, versioning + 30-day noncurrent lifecycle. Bucket policy grants s3:GetObject only to the CloudFront service principal scoped by AWS:SourceArn — no public reads, no IAM users.
static dist/
Output of pnpm -C <sub>/web build. Astro emits /<route>/index.html for every page + hashed asset bundles under _astro/. scripts/deploy.sh wipes the bucket then runs two-pass aws s3 sync --delete (HTML short-cache, everything else immutable) + a CloudFront /* invalidation.
ACM cert
One ACM cert per distribution, in us-east-1 (CloudFront's only certificate region). DNS-validated via the matching Route 53 zone. Auto-renewed; pinned to the distribution's viewer-protocol-policy (HTTPS-only, modern TLS).
App APIs — R53 → EIP → EC2 (Caddy + dual FastAPI)
Browsers reach api.qnarre.* + api.qresev.* over a direct A→EIP record. Bypasses CloudFront because SSE long-poll connections do not pass cleanly through it.
Browser
End-user browser holds an open text/event-stream connection to the API for the duration of a verifier or evaluator run. CloudFront cannot proxy these reliably (idle-timeout caps + buffering), so the apex API hostname A-records the EIP directly.
Route 53 A
Two A-records, api.qnarre.com and api.qresev.com, both pointing at the same Elastic IP. Apex domains qnarre.com+qresev.com still go through CloudFront — only the API hostname bypasses it.
Elastic IP
Single account-allocated IPv4 association on the EC2 instance. Stable across stop/start so the DNS A-records do not need to chase a new public IP. ~$0/mo while attached, $3.60/mo if released without re-association.
EC2 t4g.small
Single Graviton t4g.small (2 vCPU, 2 GB RAM, ARM64) + 8 GB gp3 root volume + 4 GB gp3 lake volume. Termination protection on, IMDSv2 required, EBS encryption with the account CMK. Sized for two concurrent users; vertical-scale to t4g.medium is the first scaling lever (PLAN § 11).
Caddy
TLS terminator + reverse proxy. Auto-provisions Let's Encrypt certs for api.qnarre.com+api.qresev.com via HTTP-01. Two virtual-host blocks (one per product) each forward to the matching uvicorn loopback port. Streaming-friendly (no buffer; flush_interval -1).
uvicorn :8787
FastAPI app from verifying/server/. SSE driver subprocesses proving/ Lean kernels, classifies stdout into typed events ({stage, msg, kind}) per the SSE-adapter pattern, streams to the React island in verifying/web/. Bound to localhost; only Caddy can reach it.
uvicorn :8788
FastAPI app from evaluating/server/. Same SSE shape as Qnarre but sub-processes accounting/ portfolio kernels. Hard-refuses any options leg outside the six-strategy defined-risk allow-list at the API boundary, before reaching the kernel.
lake build
Lean 4 build helper invoked by both FastAPI apps to compile proving/+accounting/ on demand. Bounded execution: capped wall-clock + memory, killed if it overruns. Output cached on the lake volume (/var/lib/qagents/lake/).
qagents-app-role
Instance role attached at launch. Grants the app process narrow read-only access — s3:GetObject on the artifacts bucket, ssm:GetParameter under /qagents/*, kms:Decrypt via the account CMK, logs:* under /aws/qagents/*. No write access to site buckets; no ec2:*; no iam:*.
Identity — three IAM principals
Domain separation by design: deploy user cannot SSH or modify EC2; EC2 role cannot deploy site buckets; OIDC role mirrors deploy with short-lived federated tokens.
ikifor (laptop)
Developer machine. Long-term IAM keys live in the macOS Keychain via aws-vault add qagents; ~/.aws/credentials stays empty. Every command runs aws-vault exec qagents -- ... which mints a 1-hour STS session bound to MFA. No plaintext access keys ever touch disk.
GitHub Actions
CI runs deploys via short-lived federated tokens. The repo trusts the AWS account through GitHub's OIDC provider; the workflow assumes qagents-deploy-ci with a sub-claim scoped to repo:quantapix/qagents:ref:refs/heads/main. No long-lived secrets in repo or org variables.
EC2 instance
The single t4g.small instance from the App APIs diagram. Source of sts:AssumeRole calls against qagents-app-role. Trust policy condition pins the source-account + the EC2 instance metadata service principal.
qagents-deploy
The IAM user assumed from the laptop. Carries permission boundary qagents-deploy-boundary capping every action under it. Inline policy at <sub>/web/scripts/iam-policy-deploy.json per consumer site — minimum-privilege, single bucket + single distribution scope.
qagents-deploy-ci
The CI mirror of qagents-deploy. Same managed policies attached, same permission boundary. Trust policy accepts only GitHub OIDC tokens with the pinned sub-claim. Phase 6 deliverable; not yet wired (status pill = NOT_YET_LIVE).
qagents-app-role
The EC2 instance role. Read-mostly: pulls SecureStrings from SSM, decrypts via the account CMK, writes to its own log group, fetches build artifacts from S3. Cannot list or write any site bucket; cannot touch IAM, EC2, Route 53, or CloudFront.
s3:* on 4 sites
Target: the four site buckets — quantapix.com, femfas.net, qnarre.com, qresev.com. Reachable from qagents-deploy + qagents-deploy-ci only. The app role explicitly cannot.
s3:* on artifacts
Target: the build-artifacts bucket. Holds CDK assets, Lake build caches, Janet narration intermediates. SSE-KMS encrypted with the account CMK. Both deploy and app principals reach it; deploy writes, app reads.
cloudfront:Invalidate
Target: the four CloudFront distributions matching the four sites. Restricted to cloudfront:CreateInvalidation + cloudfront:GetDistribution — no edits to behaviors, origins, or policies from the deploy principal. Distribution edits go through CDK, not deploy scripts.
ssm:GetParameter
Target: SSM Parameter Store entries under /qagents/* — Anthropic + Perplexity + Alpaca API keys, Caddy admin token. SecureString type, KMS-encrypted at rest with the account CMK. Read by the app role; written manually via SSM CLI.
kms:Decrypt
Target: the account customer-managed key (alias/qagents-cmk). Used to unwrap SSM SecureStrings + read SSE-KMS objects on the artifacts bucket. Key policy grants Decrypt + GenerateDataKey to the app role; the deploy role gets Encrypt+Decrypt+GenerateDataKey for artifact uploads.
logs:* /aws/qagents/*
Target: log groups under /aws/qagents/* — one per app (Qnarre/Qresev), Caddy access logs, lake-build logs. 7-day retention by default; CMK-encrypted. App role writes; alarms (CW Alarms + SNS) read.
Deny ec2:*
Explicit deny on ec2:* applied via the permission boundary. The deploy principal cannot stop, terminate, or reconfigure the EC2 instance even if a future managed policy mistakenly grants it. Hard floor.
Deny iam:* / r53:*
Explicit deny on iam:* + route53:*. Stops the deploy principal from creating new identities or rewriting DNS. Identity surface (IAM, OIDC trust, R53 zones) is CDK-only territory; deploy scripts never reach it.
Service inventory — what runs in v0.1
Eighteen services in v0.1; five deferred. Total run rate ~$20–25/mo. Lions share is EC2 + EBS; everything else is pennies.
S3
Five buckets total: four site buckets (one per apex domain) + one shared qagents-artifacts. Site buckets are versioned with a 30-day noncurrent lifecycle; artifacts is SSE-KMS encrypted with the account CMK. ~$0.50/mo combined.
CloudFront
One distribution per site. PriceClass_100, OAC origin access, HTTP/2 + HTTP/3, custom 403/404 → /404.html. Cache by content type (HTML short, assets immutable). ~$1–2/mo combined at current traffic.
CF Functions
Single shared viewer-request function astro-rewrite-index, attached to all four distributions. Rewrites /route → /route/index.html for Astro static-build directory-index resolution. Free under the CloudFront free tier.
ACM
One TLS certificate per CloudFront distribution, all issued in us-east-1 (CloudFront's only allowed cert region). DNS-validated against the matching Route 53 hosted zone. Auto-renewed; free.
Route 53
Two hosted zones live today: quantapix.com + femfas.net. Two more arrive at Qnarre + Qresev launch. Each zone hosts apex ALIAS to CloudFront + the relevant API A-record to the EIP. ~$1/mo combined.
IAM
Three principals (qagents-deploy, qagents-deploy-ci, qagents-app-role) + the GitHub OIDC identity provider. All managed by CDK's IdentityStack; permission boundary qagents-deploy-boundary caps deploy permissions. Free.
EC2
Single Graviton t4g.small (2 vCPU, 2 GB RAM). Termination protection on, IMDSv2 required, EBS-encrypted with the account CMK. Hosts both Qnarre + Qresev FastAPI apps + Caddy + the lake build process. ~$11/mo on-demand, ~$7/mo with a 1-year savings-plan commit.
Elastic IP
One IPv4 EIP associated with the EC2 instance. Stable across stop/start so DNS A-records do not chase. $0/mo while attached.
Default VPC
The account default VPC, public subnet only. No NAT Gateway (saves ~$32/mo). EC2 reaches public package mirrors over its own EIP; no internal-private workloads to NAT for at v0.1.
Security Group
Single SG attached to the EC2 instance. Inbound: TCP/443 from 0.0.0.0/0 only. SSM Session Manager works without an inbound rule (uses outbound to the SSM service). No SSH (TCP/22) — explicitly omitted.
SSM Session Mgr
Shell access path. aws-vault exec qagents -- aws ssm start-session --target i-xxxx opens an authenticated, audited, MFA-gated shell to the instance over the AWS data plane. Replaces SSH entirely.
SSM Param Store
SecureString parameters under /qagents/*: Anthropic, Perplexity, Alpaca, Caddy admin tokens. KMS-encrypted with the account CMK. Read by the EC2 app role; rotated manually via SSM CLI. Free for standard parameters.
KMS
One customer-managed symmetric key, alias alias/qagents-cmk. Encrypts EBS volumes, SSM SecureStrings, SSE-KMS S3 artifacts, CloudTrail logs, CloudWatch log groups. ~$1/mo + per-API-call charges (negligible at v0.1 traffic).
CloudWatch Logs
One log group per app (/aws/qagents/qnarre, /aws/qagents/qresev) + Caddy + lake build. KMS-encrypted, 7-day retention by default. ~$0.50/mo at current volume.
CW Alarms + SNS
Three alarms wired to a single SNS topic with email subscription: EC2 CPU > 80% for 15min, EBS disk-usage > 85%, AWS Budgets actual cost > 80% / 100% / 120% of $50/mo. Free under the CloudWatch + SNS free tiers.
AWS Budgets
One monthly cost budget, threshold $50. Three alert tiers (80% forecast, 100% actual, 120% actual) all routed through the SNS topic. Free for the first two budgets per account.
GuardDuty
Account-wide threat detection. Watches CloudTrail, VPC DNS logs, S3 data-events, EKS audit logs (no EKS yet). Findings route to the SNS topic via an EventBridge rule. ~$3–5/mo at v0.1 telemetry volume.
Cost Explorer
Cost-monitoring dashboard. Used to ground-truth the budget alerts and spot drift early. Free.
ALB
Deferred. Adds ~$16/mo + LCU charges. Only justified at horizontal scale (≥2 EC2 instances) or when the API surface needs path-based routing across services. Not at v0.1.
Auto Scaling Group
Deferred. The first scaling lever is vertical (t4g.small → t4g.medium); horizontal-scale via ASG comes after that. Free in itself but requires ALB + adapted state management.
AWS Backup
Deferred. EBS snapshots cover the disaster-recovery story at v0.1; AWS Backup adds policy-managed retention + cross-region copy. Revisit when there is real user data on the instance worth retaining.
AWS WAF
Deferred. CloudFront security-headers + Caddy bot-detection cover the v0.1 threat model. WAF adds ~$5/mo + per-rule + per-request charges; revisit if the Qnarre/Qresev APIs see scraping.
AWS Config
Deferred. CDK-as-source-of-truth + CloudTrail + GuardDuty cover compliance posture at v0.1 scale. Config recorder + rules add ~$10–15/mo; revisit at audit-readiness.
Security baseline — 23 controls
8 enforce, 12 adopt, 3 defer. Modern AWS controls (OAC, OIDC, IMDSv2, GuardDuty, KMS CMK, SSM Session Mgr) — not OAI, not SSH, not plaintext keys.
No long-lived keys
Enforce. No IAM access-key pair lives in plaintext on disk. Long-term keys go to macOS Keychain via aws-vault add qagents; every command runs inside aws-vault exec qagents -- ... which mints a 1-hour STS session. ~/.aws/credentials stays empty.
MFA on STS
Enforce. The deploy user policy requires aws:MultiFactorAuthPresent = true on every action. STS sessions inherit this when minted with --token-code. Without an MFA-bound token, every API call denies.
Permission boundary
Enforce. A single permission-boundary policy attached to qagents-deploy + qagents-deploy-ci caps every action regardless of attached managed policies. Even if a managed policy gets over-permissive, the boundary holds the line on iam:*, ec2:*, route53:*.
GitHub OIDC
Adopt. CI authenticates via GitHub's OIDC provider, not stored access keys. Trust policy condition pins the sub-claim (repo:quantapix/qagents:ref:refs/heads/main) so only main-branch workflows can assume the role. Lands in CDK Phase 6.
CloudFront OAC
Adopt. Origin Access Control replaces the deprecated Origin Access Identity for S3-backed distributions. Bucket policy grants s3:GetObject only to the CloudFront service principal scoped by AWS:SourceArn; no signed URLs needed for normal traffic.
ACM certs
Enforce. Every public TLS endpoint uses an ACM-issued cert in us-east-1 (CloudFront's only allowed region). DNS-validated, auto-renewed. No self-signed certs anywhere; no Caddy-issued certs on CloudFront origins.
Security headers
Adopt. One shared CloudFront response-headers policy attached to all four distributions: HSTS, X-Content-Type-Options, X-Frame-Options DENY, Referrer-Policy strict-origin, Permissions-Policy minimal. Per-site CSP comes via the factory below.
HSTS preload
Adopt. Strict-Transport-Security max-age=63072000; includeSubDomains; preload. Each apex domain submitted to the HSTS preload list once verified, so first-visit HTTP requests are upgraded by the browser before they leave.
CSP
Adopt. Content-Security-Policy generated per-site by a CDK factory (each site has different acceptable origins for analytics, fonts, embed sources). Reports go to a console-only endpoint at v0.1; report-to directive added at scale.
S3 BlockPublicAccess
Enforce. BlockPublicAccess on at the account level and at every bucket. CloudFront reaches buckets via OAC, not public reads. Even an accidentally-permissive bucket policy cannot expose objects to 0.0.0.0/0.
S3 SSE
Enforce. The artifacts bucket requires SSE-KMS with the account CMK on every put. Site buckets use SSE-S3 (cheaper; no secret data on those). Bucket policy denies any s3:PutObject without the right encryption header.
EBS encryption
Enforce. Account-default EBS encryption set with the customer-managed CMK. Every new volume — including the EC2 root + lake volumes — encrypted at rest with the same key. No unencrypted snapshot can be created.
No SSH
Adopt. The EC2 security group has no TCP/22 rule. Shell access is exclusively via SSM Session Manager — authenticated, audited, MFA-gated, no public attack surface. ~/.ssh on the instance carries no authorized keys.
KMS CMK
Adopt. One customer-managed key (alias/qagents-cmk) does encryption duty across EBS, SSM SecureStrings, SSE-KMS S3, CloudTrail, CW Logs. Key policy scoped to the three principals; rotated annually.
CloudTrail org-wide
Adopt. Single management-events trail, account-wide. Log-file validation enabled (each digest file is signed); KMS-encrypted; multi-region. Immutable by design — bucket policy denies s3:DeleteObject from any non-org principal.
GuardDuty
Adopt. Account-wide threat detection. Watches CloudTrail + VPC DNS + S3 data events. Findings ≥ medium severity route to the SNS topic via EventBridge. ~$3–5/mo at v0.1 telemetry volume.
AWS Budgets
Adopt. Monthly $50 budget with three alert tiers (80% forecast, 100% actual, 120% actual). Surfaces both runaway-cost (a forgotten NAT) and credential compromise (cryptojacking) before they invoice into real money.
Alternate contacts
Adopt. AWS Account Alternate Contacts populated for security + billing. AWS reaches the right inbox for abuse reports, security advisories, billing anomalies — bypasses root-account email, which is high-friction by design.
IMDSv2 only
Enforce. EC2 launch template sets HttpTokens=required, HttpPutResponseHopLimit=1. SSRF attacks against the instance metadata service blocked by design — no token, no metadata. IMDSv1 disabled outright.
EC2 termination prot
Adopt. DisableApiTermination=true on the EC2 instance. A panicked or misclicked terminate-instances bounces; intentional teardown requires disabling the flag first. Cheap insurance against accident.
VPC Flow Logs
Defer. Useful for traffic forensics; adds ~$1–2/mo at v0.1 volume + storage. GuardDuty already covers anomaly detection; revisit if a real incident demands packet-level provenance.
WAF
Defer. CloudFront security-headers + Caddy bot-detection cover v0.1. WAF adds ~$5/mo + per-rule + per-request; revisit when Qnarre/Qresev APIs see scraping or attack traffic.
AWS Config
Defer. CDK-as-source-of-truth + CloudTrail + GuardDuty cover compliance posture at this scale. Config recorder + rules add ~$10–15/mo; revisit at audit-readiness.
Metrics
- siteCount
- 4
- ec2Count
- 0
- distributionCount
- 4
- runRateUsdPerMo
- 20–25