MuseHub Production-Readiness Checklist
Master checklist for promoting MuseHub from staging to production, automating deployments, sharing operational ownership across the team (Gabriel, Aaron, and interns Sol/Toby/Ben), and applying security, reliability, backup, documentation, and infrastructure best practices.
Team access tiers (decided 2026-08-08, see
production-readiness/runbook-deploy-onboarding.md): Gabriel and Aaron have full staging + production access; interns (Sol, Toby, Ben) are staging-only by design. Interim: the current shared AWS credential is not distributed to interns — real staging access for them waits on Section 3's individual-identity rollout, which is now scoped for all 5 people from the start.
Working method: sections are swept in order, once each. Unresolved items are flagged inline and rolled up into the Decision Log / Accepted Risks tables at the bottom rather than blocking progress on later sections. See
docs/production-readiness/for the per-section companion docs with full verification detail.
0. Decisions and Inventory
Full detail:
production-readiness/00-inventory-and-decisions.md
- [ ] Document the current staging architecture.
- [x] AWS account and region —
992382692655,us-east-1(verified live) - [x] Compute platform: ECS, EC2, App Runner, Elastic Beanstalk, Lambda, EKS, etc. — raw EC2 (
t3.small/t3.medium), Docker Compose, no managed compute service - [x] Load balancer, API Gateway, CloudFront, or other ingress — none; Cloudflare proxy → nginx on the instance
- [x] Database engine and topology — self-hosted PostgreSQL 16, single instance, no RDS, no replica
- [x] Redis, queues, object storage, search, background workers — background worker (
musehub-runner) confirmed; object storage confirmed live on Cloudflare R2 (bucketmusehub-staging, proven by a successful push this session —docs/infrastructure.mdcorrected); no Redis/search identified - [x] VPC, subnets, route tables, NAT gateways, and security groups — default VPC, no custom subnets, no NAT gateway,
musehub-sgverified live (443 from Cloudflare ranges only) - [x] Secrets and configuration sources — hand-provisioned
.envper instance (not SSM-automated); confirmed it includes working R2 credentialsdocs/infrastructure.mddidn't document. Open question for Gabriel: should production adopt the drafted SSM pipeline (deploy/secrets.sh) from day one instead of hand-provisioning like staging? - [x] Logging and monitoring — corrected (see Section 12): structured JSON logs are centralized live in CloudWatch (
/musehub/staging, ~1.7GB, no retention limit set); alarms/SNS/dashboards on top of them do not exist - [x] Current deployment process — documented: manual, human-triggered
deploy/push.sh, no CI/CD automation
- [x] AWS account and region —
- [x] Inventory every manually created AWS resource. — 2 EC2 + 2 EIP, 1 SG, 1 IAM user, 1 IAM instance role, 1 ECR repo, 1 S3 bucket, 3 SSM params (all listed in companion doc)
- [ ] Inventory every external dependency.
- [x] GitHub — backup-mirror only, never VCS/CI/collaboration (ecosystem policy)
- [x] Namecheap — registrar + DNS host for
musehub.ai - [x] Email provider — confirmed none exists
- [x] OAuth providers — confirmed none exist (MSign/Ed25519 only)
- [x] Payment or billing providers — confirmed none exist
- [x] Error tracking — confirmed none exists
- [ ] Third-party APIs — Cloudflare R2/Worker usage inferred from scripts but not independently verified against the Cloudflare dashboard
- [x] Identify all stateful and mission-critical data. — Postgres DB, object store,
musehub-releasesS3 bucket, SSM params (all listed in companion doc) - [ ] Define expected initial traffic and reasonable growth assumptions. — not yet defined, needs Gabriel
- [ ] Define availability expectations. — not yet defined, needs Gabriel
- [ ] Define acceptable downtime during deployment. — not yet defined, needs Gabriel
- [ ] Define recovery objectives.
- [ ] RPO: maximum acceptable data loss — not yet defined, needs Gabriel
- [ ] RTO: maximum acceptable restoration time — not yet defined, needs Gabriel
- [ ] Decide the canonical public URLs.
- [x] Root (
musehub.ai) — exists, live - [x]
www.musehub.ai— exists, live - [ ] API hostname — none exists; MuseHub serves API + web from the same origin, no
api.subdomain in use today - [x] Staging (
staging.musehub.ai) — exists, live
- [x] Root (
- [ ] Choose either the root domain or
wwwas canonical. — not yet decided, needs Gabriel - [ ] Redirect the noncanonical hostname permanently to the canonical one. — no redirect logic found in nginx config; not implemented
- [ ] Decide whether DNS remains hosted at Namecheap or moves to Route 53. — not yet decided, needs Gabriel
- [x] Decide whether production and non-production will use separate AWS accounts. — Decided: single account accepted as technical debt for now (see Decision Log)
1. Ownership and Shared Access
No shared human accounts, shared passwords, or shared AWS access keys. Full detail:
production-readiness/01-ownership-and-access.md. "GitHub organization" items below are the MuseHub-native equivalent per the Decision Log.
- [ ] Confirm the domain is held by the company rather than an individual wherever practical. — needs Gabriel (Namecheap console)
- [ ] Confirm AWS account ownership and billing contacts. — needs Gabriel (AWS console)
- [x] Confirm MuseHub repo ownership/collaboration model. —
gabrielowns both repos;aaronrenehaswritecollaborator access onmusehubandmuse(verified viamuse hub collaborator list) - [ ] Add both Gabriel and Aaron as appropriate organizational owners or administrators. — done for MuseHub repos (Aaron has
write); not done for AWS/Namecheap — needs Gabriel - [ ] Ensure neither person is the sole recovery path for:
- [ ] Namecheap — needs Gabriel
- [ ] AWS — needs Gabriel
- [x] MuseHub — Aaron already has
writeaccess independent of Gabriel - [ ] DNS — needs Gabriel
- [ ] Database — needs Gabriel (currently only reachable via
musehub-infrashared AWS credential) - [ ] Production secrets — needs Gabriel
- [ ] Billing — needs Gabriel
- [ ] Backups — needs Gabriel (also blocked on resolving Section 0 Contradiction 2)
- [ ] Use company-controlled email addresses for infrastructure accounts. — needs Gabriel
- [ ] Configure at least two trusted recovery paths. — needs Gabriel
- [ ] Store emergency recovery documentation in a location accessible to both founders. — needs Gabriel
- [ ] Create a documented joiner–mover–leaver access procedure. — not started
- [ ] Create a quarterly access-review process. — not started
- [ ] Record ownership for every operational area:
- [ ] AWS administration — currently Gabriel only, undocumented
- [ ] DNS and certificates — currently Gabriel only, undocumented
- [ ] Deployments — currently Gabriel only (shared
musehub-infracredential), undocumented - [ ] Database administration — currently Gabriel only, undocumented
- [ ] Incident response — no process exists
- [ ] Billing — currently Gabriel only, undocumented
- [ ] Backups — no automated backups exist yet (Section 0 Contradiction 2)
- [ ] Create an emergency break-glass procedure. — not started
- [ ] Test that Aaron can independently:
- [ ] Sign in to AWS — cannot test — no AWS identity exists for Aaron yet
- [ ] Inspect production — cannot test
- [ ] Deploy staging — cannot test as designed — today's deploy uses a shared AWS credential (
musehub-infra), not an individual one; this violates the "no shared AWS access keys" principle this section opens with - [ ] Approve or deploy production — cannot test — prod deploy is currently broken regardless (Section 0: no IAM instance profile on the prod instance)
- [ ] Inspect logs and alarms — cannot test — no logs/alarms exist yet (Section 12)
- [ ] Restore from backup — cannot test — no backups exist yet (Section 0 Contradiction 2)
- [ ] Update DNS when authorized — cannot test — Aaron's Namecheap access unverified
- [ ] Test that Gabriel can perform the same tasks without relying on undocumented knowledge. — not started; current process is entirely undocumented outside
docs/infrastructure.md
2. AWS Account Structure
Full detail:
production-readiness/02-aws-account-structure.md
Recommended Target
- [ ] Create or verify an AWS Organization. — deferred, see Decision Log (single account accepted as technical debt)
- [ ] Use distinct AWS accounts for:
- [ ] Management — deferred
- [ ] Production — deferred
- [ ] Non-production — deferred
- [ ] Consider separate security and log-archive accounts as the operation grows. — deferred
- [ ] Place production and non-production into separate organizational units. — deferred (no Organization exists)
- [ ] Apply Service Control Policies only after testing them carefully. — N/A while deferred
- [ ] Centralize billing. — N/A, single account
- [ ] Enable account-level budget alerts. — not done; tracked under Section 15 (Cost Controls) instead since it applies within the single account regardless of structure — needs Gabriel (AWS console access required)
- [ ] Set alternate security, operations, and billing contacts on every account. — needs Gabriel
- [ ] Restrict enabled AWS regions to the regions MuseHub actually uses, where feasible. — needs Gabriel; note the account is shared with Stori (region
us-east-2bucket observed), so any region restriction must account for both projects - [x] Document any temporary decision to keep staging and production in one AWS account as accepted technical debt. — done, see Decision Log and Accepted Risks in this checklist
3. Identity, Authentication, and Permissions
Full detail:
production-readiness/03-identity-and-permissions.mdDocument-only pass — no IAM changes made. Real build happens after the full sweep.
Root and Recovery Access
- [ ] Protect every AWS root account with phishing-resistant MFA where possible. — needs Gabriel (AWS console)
- [ ] Do not create root access keys. — needs Gabriel to confirm
- [ ] Do not use root for routine administration. — needs Gabriel to confirm
- [ ] Secure the root email account with MFA. — needs Gabriel
- [ ] Store root recovery information securely. — needs Gabriel
- [ ] Document the legitimate uses of root. — not started
- [ ] Configure multi-person approval for emergency root use if practical. — not started
Human Access
- [ ] Configure AWS IAM Identity Center. — not started; visibility blocked (
sso-admindenied) — needs Gabriel to check/build - [ ] Create separate identities for Gabriel and Aaron. — not started (blocks Section 1's independent-operation test)
- [ ] Require MFA for both. — not started
- [ ] Prefer hardware security keys or passkeys. — not started
- [x] Define permission sets, for example:
- [x]
Administrator— decided: Gabriel - [x]
ProductionOperator— decided: Aaron (staging + production) - [x]
StagingOperator— decided: Sol, Toby, Ben (interns — staging-only, no production access; seerunbook-deploy-onboarding.md) - [ ]
ReadOnly— not started - [ ]
Billing— not started
- [x]
- [ ] Give routine access through the least-privileged applicable role. — not started
- [ ] Require explicit elevation for administrative work. — not started
- [ ] Avoid permanent
AdministratorAccessfor everyday use where practical. — not started - [ ] Use IAM Access Analyzer to inspect and refine permissions. — not started; needs broader read access
- [ ] Review unused roles, users, policies, and credentials. — not started; needs broader read access
- [ ] Document who can approve and perform production changes. — not started
Workload Access
- [x] Give every application or service its own IAM role. — 3 separate IAM users confirmed live (
musehub-infra,musehub-app,stori-assets-app), no shared workload credential across services - [x] Do not place AWS access keys in source code. — confirmed via grep, none found
- [x] Do not place long-lived AWS access keys in GitHub secrets. — N/A, no GitHub Actions/secrets used (see Decision Log); equivalent check for MuseHub-runner CI config deferred to Section 9
- [ ] Restrict every deployment role to:
- [ ] The correct repository — N/A, no deployment role exists yet (deploys use a human + shared credential)
- [ ] The correct branch, tag, or GitHub environment — N/A, same reason
- [ ] The correct AWS account — N/A, same reason
- [ ] The required AWS resources only — partially true of
musehub-infra's scoping, but it's a human-shared credential, not a deployment role
- [ ] Separate staging and production deployment roles. — not done — both environments share the
musehub-infracredential - [ ] Validate trust policies and permission boundaries. — not started; needs broader read access
- [ ] Prefer temporary credentials everywhere. — not done — all current credentials are long-lived IAM user access keys
4. Infrastructure as Code
Full detail:
production-readiness/04-iac.mdDocument-only pass. No IaC written yet — deferred to the build phase after the full sweep.
- [ ] Select one Infrastructure-as-Code system:
- [ ] Terraform/OpenTofu — recommended, not yet formally chosen by Gabriel
- [ ] AWS CDK
- [ ] CloudFormation
- [ ] Capture the existing staging infrastructure in code. — not started; full resource list is in the Section 0 doc
- [ ] Stop making undocumented console-only changes. — behavioral change, not yet in effect
- [ ] Build reusable modules rather than duplicating entire configurations. — N/A until IaC exists
- [ ] Provide separate environment configuration for:
- [ ] Staging — N/A until IaC exists
- [ ] Production — N/A until IaC exists
- [ ] Keep production and staging state isolated. — N/A until IaC exists
- [ ] Encrypt remote IaC state. — N/A until IaC exists
- [ ] Enable versioning and locking for remote state. — N/A until IaC exists
- [ ] Restrict access to IaC state because it may contain sensitive metadata. — N/A until IaC exists
- [ ] Pin provider, module, and action versions. — N/A until IaC exists
- [ ] Add formatting and validation checks. — N/A until IaC exists
- [ ] Add static security scanning. — N/A until IaC exists
- [ ] Require a reviewed plan before production changes. — N/A until IaC exists
- [ ] Detect configuration drift. — N/A until IaC exists
- [ ] Document how to import existing resources into IaC. — not started; note this must be import, not recreate, since real resources already exist and are live
- [ ] Add environment-standard tags:
- [ ] Application — not applied anywhere currently
- [ ] Environment — not applied anywhere currently
- [ ] Owner — not applied anywhere currently
- [ ] Managed-by — not applied anywhere currently
- [ ] Cost-center — not applied anywhere currently
- [ ] Data-classification — not applied anywhere currently
- [ ] Make a fresh environment reproducible from source control and documented prerequisites. — not started
- [ ] Add a teardown guard so production cannot be accidentally destroyed. — N/A until IaC exists
- [ ] Add deletion protection to critical resources. — not started (also relevant to Section 8 — DB deletion protection specifically)
- [ ] Add policy checks that prevent production resources from being publicly exposed unintentionally. — N/A until IaC exists
- [ ] Ensure IaC changes are reviewed by someone other than the author when practical. — N/A until IaC exists
5. Production Architecture
Full detail:
production-readiness/05-production-architecture.md
- [ ] Reassess whether staging's sizing and topology are production-appropriate. — flagged: staging is
t3.medium, prod ist3.small— inconsistent, no documented reason - [x] Do not copy staging data into production unless explicitly sanitized and required. — not done, and correctly not done — no evidence of staging→prod data copying anywhere
- [ ] Use separate production:
- [x] Database — separate instance entirely (different EC2 host)
- [ ] Credentials — unresolved pending Section 0 Contradiction 2
- [ ] Secrets — unresolved pending Section 0 Contradiction 2
- [ ] Object-storage buckets — unresolved pending Section 0 Contradiction 1
- [ ] Queues — none exist in either environment
- [ ] Cache — none exist in either environment
- [ ] Logs — not separated in any meaningful way; no centralized logging exists yet (Section 12)
- [ ] Encryption keys where appropriate — not reviewed yet
- [ ] Deploy production across at least two Availability Zones where supported and justified. — not done; single instance, no ASG, documented as intentional SPOF below
- [x] Keep databases and internal services in private subnets. — partially true in spirit (DB only reachable via the app's Docker network, not the public internet) but not true architecturally (default VPC, no real private subnet); security group is the actual control here
- [x] Expose only the public ingress layer. — confirmed: only 443 open, restricted to Cloudflare IP ranges
- [x] Eliminate direct public database access. — confirmed, DB has no public exposure
- [x] Use security groups by service relationship rather than broad CIDR ranges. — reasonable substitution given the architecture: scoped to Cloudflare's published IP ranges, not
0.0.0.0/0 - [x] Prohibit unrestricted administrative ports such as public SSH. — confirmed, no port 22 open
- [x] Use AWS Systems Manager Session Manager rather than public SSH where applicable. — confirmed, this is already the only access path
- [x] Configure health checks. — confirmed:
/healthzprobes both DB and object storage, gates blue/green flips - [ ] Configure graceful application shutdown. — not verified — no SIGTERM handling found in
musehub/main.py; uvicorn defaults apply - [ ] Configure connection draining. — partial: blue/green waits for new slot health before flip, but old slot's in-flight requests during
docker rmaren't verified graceful - [x] Set sensible application and load-balancer timeouts. — confirmed: deliberate per-route nginx timeouts (5s health, 60s default, up to 3600s for streaming/MCP)
- [ ] Configure autoscaling or document why fixed capacity is appropriate. — neither done; fixed capacity with no written justification
- [ ] Set resource requests, limits, or instance sizes deliberately. — instance types chosen but not justified with load data
- [ ] Verify production can survive a routine instance or task replacement. — not verified; single-instance architecture means instance failure has no automatic failover (documented SPOF below)
- [x] Confirm background jobs are idempotent or safely retryable. — confirmed:
claim_next_jobusesSELECT ... FOR UPDATE SKIP LOCKEDfor safe concurrent claiming,reclaim_stale_jobsresets crashed-worker jobs back to pending (see Section 9 doc) - [ ] Confirm database migrations are compatible with rolling deployments. — pipeline runs migrations before slot flip (right shape); expand/contract discipline not reviewed — deferred to Section 8
- [x] Add rate limiting and abuse controls. — confirmed:
slowapi-based per-route limits + global baseline + bot-throttle middleware, already implemented - [ ] Consider AWS WAF for public endpoints. — N/A as literally stated (no ALB/CloudFront); Cloudflare is the substitute layer, but its WAF/rate-limit config is unverified — needs Gabriel to check Cloudflare dashboard
- [x] Configure DDoS protections appropriate to the architecture. — Cloudflare edge proxy already provides this; not independently verified beyond "Cloudflare is in front of everything"
- [ ] Establish AWS service quota monitoring for relevant resources. — not started
- [x] Document every intentional single point of failure. — done, see companion doc: single EC2 per environment, single Postgres instance, single AWS account (shared with Stori), single Cloudflare account, single human deployer with shared credential
- [ ] Create a plan to remove unacceptable single points of failure. — needs Gabriel — depends on RPO/RTO/availability targets that are still undefined (Section 0)
6. DNS, Domain, and TLS
Full detail:
production-readiness/06-dns-domain-tls.md⚠️ Finding: DNS is actually hosted at Cloudflare, not Namecheap — verified via livedig NS musehub.ai.docs/infrastructure.mdis stale on this point. Namecheap is registrar only. This changes several items below from "Namecheap vs Route 53" to "Cloudflare vs Route 53."
Namecheap Account
- [ ] Enable MFA on Namecheap for every authorized administrator. — needs Gabriel
- [ ] Use unique individual access rather than shared credentials where the account model permits. — needs Gabriel
- [ ] Enable domain lock. — needs Gabriel
- [ ] Verify registrant and recovery contact details. — needs Gabriel
- [ ] Enable automatic renewal. — needs Gabriel
- [ ] Verify the payment method will not unexpectedly expire. — needs Gabriel
- [ ] Document who may change nameservers or DNS. — needs Gabriel; note DNS itself is now on Cloudflare, so this also means documenting Cloudflare access, not just Namecheap
- [ ] Export or document all existing DNS records before making changes. — needs Gabriel, from the Cloudflare dashboard, not Namecheap's Advanced DNS tab
- [ ] Preserve mail-related MX, SPF, DKIM, and DMARC records. — needs Gabriel to confirm these exist and are preserved in Cloudflare's DNS
- [ ] Confirm both Gabriel and Aaron have an approved recovery path. — needs Gabriel, for both Namecheap and Cloudflare
DNS Design
- [ ] Decide between:
- [x] Namecheap-hosted DNS — not the current state; superseded by the finding above
- [ ] Route 53-hosted DNS with Namecheap remaining registrar — open; recommendation below
- [x]
Prefer Route 53...— recommendation revised: current DNS host is Cloudflare (not Namecheap), and Cloudflare is also doing edge proxy/TLS/possibly R2 — recommend staying on Cloudflare DNS rather than fragmenting the stack by moving to Route 53, unless Gabriel has a specific reason to want AWS-native alias records - [ ] Create a Route 53 hosted zone if selected. — N/A per above recommendation, pending Gabriel's decision
- [x] Reproduce every required existing record before switching nameservers. — N/A, no switch planned per recommendation
- [ ] Lower DNS TTL ahead of cutover. — deferred to Section 18 (Cutover Plan)
- [x] Configure the root/apex record. — exists, live, resolves via Cloudflare
- [x] Configure
www. — exists, live - [ ] Configure
apiif needed. — noapi.hostname exists; MuseHub serves API + web from one origin — confirm this is intentional - [x] Keep staging on a clearly separate hostname. — confirmed,
staging.musehub.ai - [x] Do not expose internal AWS service hostnames unnecessarily. — confirmed, only the public hostnames resolve; no raw AWS DNS names exposed
- [ ] Verify IPv4 and IPv6 behavior. — not tested in this pass
- [ ] Configure a canonical-host redirect. — not implemented — no redirect logic found; root and
wwwboth resolve without a canonical redirect - [ ] Test DNS from multiple resolvers after cutover. — deferred to Section 18
- [ ] Raise TTL again after the deployment is stable. — deferred to Section 18
- [ ] Manage DNS records through IaC where practical. — deferred to Section 4 build phase (Terraform's
cloudflareprovider)
TLS
- [ ] Request an AWS Certificate Manager certificate. — likely N/A for this architecture — TLS terminates at Cloudflare's edge, not an AWS ALB/CloudFront; needs Gabriel to confirm there's no AWS-facing TLS need I'm missing
- [ ] Include every required hostname. — N/A pending above
- [ ] Prefer DNS validation. — N/A pending above
- [ ] Retain ACM validation records for automatic renewal. — N/A pending above
- [x] Redirect all HTTP traffic to HTTPS. — Cloudflare/nginx setup already enforces HTTPS-only (port 80 not open at all)
- [x] Use modern TLS policies. — Cloudflare-managed edge TLS; verified live cert is current (Let's Encrypt, issued 2026-08-01)
- [x] Configure HSTS only after HTTPS and subdomain behavior are verified. — already live:
Strict-Transport-SecuritywithincludeSubDomainsconfirmed in nginx config - [ ] Confirm certificate-expiration monitoring. — not done — no alerting exists yet (Section 12); manual verification only (as done earlier this session)
- [x] Test the full certificate chain. — verified via
openssl s_clientthis session; valid, correctly issued, correct CN - [ ] Confirm cookies use:
- [x] N/A — MuseHub uses header-based MSign auth, no cookies exist anywhere in the app
7. Secrets and Application Configuration
Full detail:
production-readiness/07-secrets-and-config.md
- [x] Inventory all secrets. — done, 6 secrets identified (DB password, webhook key, runner token, R2 credentials, worker key, worker URL)
- [x] Remove secrets from source control and repository history. — confirmed via grep, none found
- [ ] Store secrets in AWS Secrets Manager or SSM Parameter Store as appropriate. — the real work item:
deploy/secrets.shimplements this but only 3/7 required SSM params exist; not live yet - [ ] Encrypt secrets with KMS. — partial; the 3 existing SSM params are SecureString/KMS-encrypted, rest aren't in SSM
- [x] Use separate staging and production secrets. — confirmed: separate
.env, separate R2 buckets, separate SSM prefixes - [ ] Give workloads access only to the secrets they require. — likely N/A for a single-process-per-instance app; not force-fit
- [x] Do not expose production secrets to pull-request workflows. — N/A, no PR-based CI exists
- [ ] Do not print secrets in logs. — not verified; no centralized logs exist yet to audit (Section 12)
- [ ] Configure secret rotation where supported. — not done
- [ ] Rotate credentials during the production launch. — deferred to Section 18
- [ ] Document emergency rotation procedures. — not done
- [x] Separate secrets from nonsecret configuration. — confirmed:
musehub/config.py'sSettingsclass already separates these cleanly in code - [x] Validate required configuration at application startup. — confirmed via
pydantic_settings.BaseSettings - [x] Fail safely when critical configuration is missing. — confirmed, same mechanism plus
secrets.sh's own preflightdiechecks - [x] Remove default credentials and sample keys. — confirmed, no placeholder secrets in
config.py;secrets.shalso actively rejects known-weak passwords - [ ] Audit OAuth callback URLs and API allowlists for production. — N/A, no OAuth exists
- [ ] Audit every external webhook secret. —
WORKER_INTERNAL_KEYidentified; validation logic inmusehub/worker.pynot independently reviewed in this pass - [ ] Document ownership and rotation frequency for every production secret. — not done, blocked on Section 1
8. Database Security, Migrations, and Backups
Full detail:
production-readiness/08-database-backups-migrations.md⚠️ Verified live via SSM: there is currently no usable backup of the database, anywhere.
Database Security
- [ ] Enable encryption at rest. — not verified in this pass
- [ ] Enforce encryption in transit. — not verified in this pass
- [ ] Use a production-specific KMS key where appropriate. — not started
- [ ] Store credentials in Secrets Manager. — see Section 7
- [ ] Avoid administrator credentials for normal application traffic. — not done — single
musehubrole used for app traffic, migrations, and admin access - [ ] Create separate roles for:
- [ ] Application runtime — not done
- [ ] Migrations — not done
- [ ] Read-only operations — not done
- [ ] Administration — not done
- [x] Keep the database off the public internet. — confirmed
- [x] Restrict security-group access to the application layer. — confirmed
- [ ] Enable deletion protection. — not applicable in the RDS sense (self-hosted); EBS/snapshot-level protection not configured
- [ ] Enable database-level audit logging where appropriate. — not enabled
- [ ] Monitor storage, CPU, memory, connections, locks, and slow queries. — partial: app-level slow-query logging exists (
slow_query_threshold_msinmusehub/config.py); no infrastructure-level DB monitoring exists
Backups and Recovery
- [ ] Enable automated backups. — not done, verified live: no crontab installed,
rclonenot present, one stale 20-byte manual backup from ~2 months ago is the only thing that exists - [ ] Set an explicit retention period. — N/A, no backups running
- [ ] Enable point-in-time recovery where supported. — not available for self-hosted Postgres without WAL archiving, which isn't configured
- [ ] Configure scheduled longer-term snapshots. — not done
- [ ] Configure cross-account backup copies for critical production data. — not done
- [ ] Consider cross-region copies based on the agreed disaster model. — not done
- [ ] Protect backup vaults from routine production credentials. — N/A, no backups exist
- [ ] Encrypt backup recovery points. — N/A, no backups exist
- [ ] Enable backup-vault lock or equivalent immutability where justified. — N/A, no backups exist
- [ ] Monitor backup-job failures. — N/A, no backup job exists
- [ ] Define retention and deletion policies. — not done
- [ ] Document the restoration procedure. — not done
- [ ] Perform an actual restore test before launch. — cannot happen until backups exist
- [ ] Schedule recurring restore tests. — N/A yet
- [ ] Measure restoration time against the RTO. — N/A, RTO undefined (Section 0) and no backups exist
- [ ] Confirm restored data satisfies the RPO. — N/A, same reasons
- [ ] Record the latest successful restore test. — N/A
- [ ] Verify backups are restorable by both Gabriel and Aaron. — N/A
- [ ] Back up any other mission-critical state, including object storage and configuration data. — object storage is content-addressed R2 (durable by design, per Cloudflare);
.envconfiguration data has no backup of its own beyond what's in this documentation
Schema Migrations
- [x] Run migrations automatically but as an explicit deployment step. — confirmed, part of
deploy.sh, before slot swap - [ ] Ensure only one migration runner executes at a time. — not independently verified; no explicit lock mechanism found
- [ ] Back up before high-risk migrations. — moot until backups exist
- [ ] Prefer expand-and-contract migrations. — not reviewed across the 75 existing migrations in this pass
- [ ] Avoid combining destructive schema changes with dependent application changes in one irreversible step. — not reviewed
- [ ] Test migrations against a production-like dataset. — not verified
- [ ] Define rollback or forward-repair procedures. — not documented
- [x] Record migration state and duration. — implicitly via Alembic's
alembic_versiontable and deploy logs; no structured/queryable record beyond that - [ ] Alert on migration failure. — not done, no alerting exists (Section 12)
- [x] Ensure older and newer application versions can coexist during rolling or blue/green deployments where required. — pipeline ordering (migrate-then-swap) is the right shape; ⚠️ found a real risk: on
alembic upgrade headfailure,deploy.shrunsalembic stamp --purge headand retries, which could mask a genuinely broken migration as a "stale revision ID" — mitigated by a hard-failing schema-parity gate afterward, but worth explicit awareness since that gate is the only thing standing between this pattern and a silently-broken deploy
9. CI Pipeline
Full detail:
production-readiness/09-ci-pipeline.md⚠️ Bigger finding than a typical checklist item: MuseHub-native equivalents of branch protection, required status checks, and CODEOWNERS don't exist as product features yet — this section requires building platform capability, not just configuring deployment. What does exist and is genuinely good: a concurrency-safe internal background job queue.
- [ ] Require proposals (MuseHub's PR-equivalent) for protected branches. — not started, feature doesn't exist
- [ ] Protect
main/dev. — not started - [ ] Require CI checks before merging. — not started;
merge_proposalhas no CI-status gate - [ ] Require review from another maintainer for sensitive changes. — not started
- [ ] Define required-reviewer paths (
CODEOWNERS-equivalent) for:- [ ] Infrastructure — not started
- [ ] Deployment workflows — not started
- [ ] Authentication and security — not started
- [ ] Database migrations — not started
- [ ] Run:
- [ ] Formatting — manual only, not automated on push
- [ ] Linting — manual only
- [ ] Type checking — manual only
- [ ] Unit tests — manual only
- [ ] Integration tests — manual only
- [ ] Migration checks — runs at deploy time (
schema_gate, see Section 8), not at CI/proposal time - [ ] Dependency vulnerability scanning — not automated
- [ ] Secret scanning — not automated
- [ ] Container or artifact scanning — not automated
- [ ] IaC validation and security scanning — N/A, no IaC exists yet (Section 4)
- [ ] Generate immutable, versioned build artifacts. — partial: ECR image tags exist and are immutable once pushed, but built manually per-environment rather than automatically per-commit
- [x] Record the Git commit SHA in every artifact. — N/A concept (no git); MuseHub commit IDs are content-addressed (
sha256:...) and could be embedded in image tags — not currently done, worth adding - [ ] Produce the artifact once and promote the same artifact through environments. — not done by default;
IMAGE_TAGredeploy support inpush.shmakes promotion possible but isn't the default flow - [ ] Generate an SBOM where practical. — not started
- [x] Pin third-party GitHub Actions to immutable commit SHAs for sensitive workflows. — N/A, no GitHub Actions used
- [x] Minimize GitHub Actions token permissions. — N/A, no GitHub Actions used
- [ ] Avoid executing untrusted pull-request code with production-capable credentials. — moot until proposal-triggered CI execution exists at all
- [ ] Configure dependency update automation. — not started
- [ ] Define an urgent-patch workflow. — not documented
- [ ] Retain CI and deployment logs for an explicit period. — N/A, no CI exists to log yet
- [ ] Prevent force-pushes and branch deletion on protected branches. — not started
10. Automated Staging Deployment
Full detail:
production-readiness/10-11-automated-deployment.mdDeploys today are 100% manual (bash deploy/push.sh staging, human-triggered, shared credential).
- [ ] Create a MuseHub-native staging-environment concept (GitHub environment equivalent). — doesn't exist
- [ ] Deploy automatically after successful merges. — not started, no trigger mechanism exists
- [ ] Authenticate to AWS through short-lived/OIDC-equivalent credentials. — not started
- [x] Do not store long-lived AWS keys in CI config. — moot today (no CI config exists); the actual problem is the shared laptop credential (Section 1/3), not a CI secret leak
- [ ] Restrict the staging role to the staging AWS account and resources. — not started (Section 3)
- [ ] Run infrastructure planning. — N/A, no IaC yet (Section 4)
- [ ] Apply approved or policy-compliant infrastructure changes. — N/A, same reason
- [x] Deploy the immutable application artifact. — already true today, manually
- [x] Run database migrations. — already true, automatic within the deploy
- [ ] Run smoke tests. — not automated as a gate;
deploy/smoke_muse.shexists but its coverage wasn't reviewed in this pass - [x] Run health checks. — already true, gates the blue/green flip
- [ ] Publish the deployment URL and commit SHA. — not automated
- [ ] Notify both maintainers of success or failure. — not automated
- [ ] Preserve deployment logs. — not done; currently only in the deployer's terminal
- [ ] Automatically roll back application deployment when health checks fail, where safe. — not automated, but effectively moot: the health-gated blue/green means a failing new slot never goes live in the first place, rather than going live and needing rollback
- [ ] Keep database rollback separate and explicit. — not formalized
- [ ] Prevent concurrent staging deployments from colliding. — not enforced
- [ ] Make the deployment status visible in MuseHub. — not built
- [ ] Record artifact digest, migration version, deployer, and timestamp. — not recorded anywhere structured
11. Automated Production Deployment
Full detail:
production-readiness/10-11-automated-deployment.md⚠️ Reminder: prod deploy is currently broken regardless of automation — no IAM instance profile on the prod instance (Section 0), sopush.sh prodfails outright even run manually today.
- [ ] Create a MuseHub-native production-environment concept. — doesn't exist
- [ ] Use a distinct production AWS role. — not started (Section 3)
- [ ] Restrict that role to the production account and resources. — not started
- [ ] Trigger production deployment through an explicit release action:
- [ ] Version tag — MuseHub has
muse release add/muse hub release createalready; not wired to deploy - [ ] MuseHub release — same as above
- [ ] Manual promotion of a verified artifact — possible via
IMAGE_TAG=<tag>, not the default flow
- [ ] Version tag — MuseHub has
- [ ] Do not rebuild the application for production. — not done by default today
- [ ] Promote the exact artifact tested in staging. — possible, not automatic
- [ ] Require successful staging deployment and tests first. — not enforced
- [ ] Require production approval. — not built
- [ ] Prevent self-approval when practical. — not built
- [ ] Permit both Gabriel and Aaron to approve. — not built; also blocked since Aaron has no independent deploy identity yet (Section 1/3)
- [ ] Require approval from someone other than the person initiating the deployment when practical. — not built
- [ ] Display the complete deployment plan before approval. — not built
- [ ] Back up stateful systems before risky changes. — currently impossible to do meaningfully — no backups exist (Section 8)
- [x] Run migrations as a controlled step. — already true
- [ ] Deploy using:
- [ ] Rolling deployment — N/A, using blue/green instead
- [x] Blue/green deployment — already true, this is the current strategy
- [ ] Canary deployment — N/A, not the chosen strategy
- [ ] Run post-deployment smoke tests. — not automated
- [ ] Monitor error rate, latency, and health during rollout. — not possible yet, no monitoring exists (Section 12)
- [ ] Stop or roll back when thresholds are exceeded. — N/A, no thresholds/monitoring exist
- [ ] Record who approved and initiated each deployment. — not recorded
- [ ] Record commit SHA, artifact identifier, and migration version. — not recorded
- [ ] Generate release notes. — not automated
- [ ] Notify both operators after completion. — not automated
- [ ] Prevent concurrent production deployments. — not enforced
- [x] Add a documented manual deployment path for emergencies. — exists (it's the current default path)
- [x] Add a documented rollback command or workflow. — exists (
IMAGE_TAG=<previous-tag> bash deploy/push.sh prod) - [ ] Test rollback before launch. — not yet done, explicitly required
12. Observability
Full detail:
production-readiness/12-observability.md⚠️ Correction to Section 0/5: logging is further along than earlier stated in this sweep — structured JSON logs ARE centralized live in CloudWatch (/musehub/staging, ~1.7GB, verified viaaws logs tail). It's specifically alarms/dashboards/metrics that don't exist, not logging itself.
- [x] Centralize application logs. — confirmed live, CloudWatch Logs
/musehub/staging - [x] Use structured logs. — confirmed, JSON with rich per-request fields (
musehub/logging_config.py) - [ ] Include:
- [x] Timestamp — present
- [ ] Environment — missing from the JSON schema
- [ ] Service — implicit via
loggerfield, not an explicitservicefield - [x] Request or correlation ID — present (
request_id) - [ ] Release version — missing from the JSON schema
- [x] Severity — present (
level)
- [x] Prevent credentials, tokens, and sensitive personal data from entering logs. — confirmed,
PiiFilterscrubs Bearer tokens/passwords/secrets already - [ ] Set explicit log-retention periods. — not done, currently unlimited retention on the live log group
- [ ] Encrypt logs. — CloudWatch default at-rest encryption applies; customer-managed KMS key not confirmed
- [ ] Collect infrastructure and application metrics. — partial: app-level data flows into logs (could feed metric filters); true instance-level metrics (CPU/disk/memory via CloudWatch Agent) not confirmed installed
- [ ] Build dashboards for:
- [ ] Request rate — data exists in logs, no dashboard
- [ ] Error rate — data exists in logs, no dashboard
- [ ] Latency — data exists in logs (
duration_ms), no dashboard - [ ] Saturation — not collected
- [ ] Task or instance health —
/healthzexists but isn't dashboarded - [ ] Database capacity — not collected
- [ ] Database connections — not collected
- [ ] Queue depth — the internal job queue (Section 9) has no depth metric exposed
- [ ] Background-job failures — job status is tracked in the DB (
attempt,status) but not surfaced to any dashboard
- [ ] Add external uptime checks. — not done
- [ ] Configure alerts for:
- [ ] Site unavailable — not done
- [ ] Elevated 5xx responses — not done (data available, not wired)
- [ ] Elevated latency — not done (data available, not wired)
- [ ] Unhealthy deployment — not done
- [ ] Database storage pressure — not done
- [ ] CPU or memory exhaustion — partial signal exists (
[memory] HIGH RSSapp-level warnings already fire), not wired to an actual alert - [ ] Backup failure — N/A, no backup job exists yet (Section 8)
- [ ] Certificate problems — not done
- [ ] Queue backlog — not done
- [ ] Security findings — not done
- [ ] Unexpected cost growth — not done (Section 15)
- [ ] Route alerts to both Gabriel and Aaron. — N/A, no alerts exist
- [ ] Define warning versus page-worthy alert severity. — not done
- [ ] Test alert delivery. — N/A
- [ ] Ensure every urgent alert links to a runbook. — N/A
- [ ] Configure error tracking with release correlation. — not done, no tool exists
- [ ] Define service-level indicators and initial service objectives. — not done, blocked on Section 0's undefined availability targets
- [ ] Add deployment markers to dashboards. — N/A, no dashboards exist
- [ ] Verify logs and metrics remain available during an application outage. — not tested, but likely true by construction (CloudWatch Logs is a separate AWS service from the EC2 instance)
13. Security Monitoring and Auditability
Full detail:
production-readiness/13-security-monitoring.mdMost items blocked on the broader-read-access grant Gabriel agreed to earlier this session — re-run once that lands.
- [ ] Enable CloudTrail. — blocked, cannot confirm current state
- [ ] Use a multi-region trail for management activity. — blocked
- [ ] Include read and write management events. — blocked
- [ ] Encrypt CloudTrail logs. — blocked
- [ ] Enable log-file validation. — blocked
- [ ] Store logs in a nonpublic, access-restricted bucket. — blocked
- [ ] Send relevant CloudTrail events to CloudWatch Logs. — blocked
- [ ] Enable AWS Config where cost and scope permit. — blocked
- [ ] Enable GuardDuty. — blocked
- [ ] Enable Security Hub and AWS Foundational Security Best Practices controls. — blocked
- [ ] Enable IAM Access Analyzer. — blocked
- [ ] Review public and cross-account resource access. — blocked
- [ ] Enable S3 Block Public Access at the account level unless explicitly incompatible. — checked one bucket directly:
musehub-releaseshas all 4 settings disabled — likely intentional (serves public/install.sh), needs Gabriel's explicit sign-off; account-level setting itself is blocked - [x] Enable ECR or image vulnerability scanning if containers are used. — confirmed NOT enabled —
imageScanStatusisnullon the latest image; easy, low-risk fix for the build phase - [ ] Monitor changes to IAM, KMS, CloudTrail, DNS, and security groups. — N/A until CloudTrail exists
- [ ] Alert on root-account activity. — N/A until CloudTrail/GuardDuty exist
- [ ] Alert on failed console logins and suspicious authentication. — N/A, same reason
- [ ] Define a vulnerability-remediation SLA. — not done; can't be meaningful before scanning exists
- [ ] Run a production security review before launch. — this checklist effort is a step toward it; a dedicated pass still needed closer to launch
- [ ] Record and assign every security finding. — no tracking process exists yet
- [ ] Review high-severity findings before every production launch. — N/A, no findings exist yet
14. Application Security
Full detail:
production-readiness/14-application-security.md✅ Fixed:/_debug/memorywas exposed unconditionally in production (missing theif settings.debug:guard/docs//redoccorrectly have) — patched this session.
- [ ] Document the authentication and authorization model. — MSign is well-implemented in code but not written up as a standalone doc yet
- [ ] Test authorization independently of UI visibility. — not verified in this pass
- [x] Enforce server-side access control. — confirmed via MSign fresh-signature requirement on privileged MCP tool calls
- [x] Use secure password hashing if MuseHub stores passwords. — N/A, no passwords exist (MSign is signature-based)
- [ ] Require email verification where relevant. — N/A, no email-based flows exist
- [x] Implement secure account recovery. — N/A in the traditional sense; equivalent is Ed25519 mnemonic recovery, already documented at the ecosystem level (
muse auth recover) - [x] Rate-limit login and recovery endpoints. — confirmed,
slowapi-based limits + bot throttling (Section 5) - [ ] Protect against CSRF where applicable. — not explicitly reviewed; likely lower risk given no cookie-based sessions, not confirmed for every state-changing endpoint
- [x] Validate and sanitize untrusted input. — Pydantic models validate at the API boundary throughout
- [x] Use parameterized database queries. — confirmed, no raw SQL string formatting found anywhere
- [x] Restrict CORS to deliberate origins. — confirmed, fails closed (
cors_origins: []default) with a wildcard-in-production warning - [x] Define a Content Security Policy. — confirmed, set in
musehub/main.py - [x] Add standard security headers. — confirmed:
X-Frame-Options,X-Content-Type-Options, HSTS, CSP, Referrer-Policy - [x] Validate uploaded file type, size, and storage behavior. — confirmed:
mpack_max_bytes/mpack_max_commits/mpack_max_objectsand per-user/per-repo/daily quotas - [x] Protect against path traversal and unsafe archive extraction. — confirmed: traversal-char rejection in coordination module,
mpack_max_decompressed_byteszip-bomb quarantine - [ ] Scan uploaded content if the risk model warrants it. — not done beyond structural/size limits
- [x] Set request-body and upload limits. — confirmed, see above
- [ ] Review SSRF exposure from URL-fetching features. — no SSRF-prone code found in main request handling;
musehub/worker.py's Cloudflare Worker calls specifically not yet reviewed - [x] Ensure production debug mode is disabled. — confirmed,
debug: bool = Falsedefault, and/docs//redoccorrectly gated behind it - [ ] Ensure stack traces are not returned to users. — not independently verified in this pass
- [ ] Review WebSocket or SSE authentication if used. — MCP/SSE endpoints exist; auth model for the streaming case specifically not independently verified
- [ ] Document data classification and retention. — not done
- [ ] Add an account and data deletion path where applicable. — not verified whether one exists
- [ ] Run an OWASP-oriented review before launch. — this pass covers several categories but isn't a formal pass; deferred to Section 17
- [ ] Review session expiration and token revocation behavior. — N/A in the traditional sense (no sessions); MSign's per-request timestamp/replay-window logic not independently verified
- [x] Verify privileged actions require fresh authorization where appropriate. — confirmed, MCP tool calls require a fresh MSign signature, not just a valid prior session
15. Cost Controls
Full detail:
production-readiness/15-cost-controls.md
- [ ] Apply consistent cost-allocation tags. — not done; confirmed inconsistent (prod has only
Name, staging hasName+Environment, S3 bucket has none) - [ ] Create monthly AWS budgets. — blocked on broader access grant
- [ ] Alert Gabriel and Aaron at multiple thresholds. — N/A until budgets exist
- [ ] Enable Cost Anomaly Detection. — blocked
- [ ] Review NAT Gateway, data-transfer, and logging costs. — N/A for NAT (none exists); logging cost is real and growing (Section 12's unlimited-retention log group)
- [x] Set log-retention limits. — tracked under Section 12, cross-referenced here
- [ ] Set backup-retention limits consistent with recovery requirements. — N/A, no backups exist yet
- [ ] Right-size production after load testing. — not done, no load testing has occurred
- [ ] Review idle resources. — staging appears to run continuously despite
docs/infrastructure.mdsuggesting it be stopped when idle — worth Gabriel deciding whether to actually do that - [x] Document expected baseline monthly cost. — already done,
docs/infrastructure.md(~$35/mo for both environments) - [ ] Document the cost implications of autoscaling. — N/A, no autoscaling exists
- [ ] Configure service quotas and limits deliberately. — not done
- [ ] Review production cost weekly during the first month. — deferred to Section 19
- [ ] Add owner and purpose tags to every billable resource. — not done, see tagging findings
- [ ] Alert on untagged or unexpectedly expensive resources where practical. — not done
16. Documentation and Runbooks
Full detail:
production-readiness/16-19-docs-testing-cutover-postlaunch.md
- [ ] Create a system architecture diagram. — not created
- [ ] Create a data-flow diagram. — not created
- [x] Document every AWS account and region. — done, Section 0
- [x] Document DNS and certificate ownership. — done, Section 6
- [ ] Document environment differences. — scattered, not consolidated
- [ ] Document the IaC repository structure. — N/A, no IaC exists yet
- [ ] Document local development setup. — not reviewed in this pass
- [x] Document staging deployment. — done,
docs/infrastructure.md, plus a team-onboarding runbook:production-readiness/runbook-deploy-onboarding.md - [x] Document production deployment. — done,
docs/infrastructure.md(though prod is currently broken) - [x] Document rollback. — documented, not yet tested
- [ ] Document database migration procedures. — partial; rollback/forward-repair not documented
- [ ] Document database restoration. — cannot be meaningful yet, no backups exist
- [ ] Document secret rotation. — not done
- [ ] Document certificate and DNS troubleshooting. — not done
- [ ] Document user-access provisioning and removal. — not done
- [ ] Document break-glass access. — not done
- [ ] Document incident response. — not done
- [ ] Document backup retention and restoration tests. — N/A, no backups exist
- [ ] Document common alarms and remediation. — N/A, no alarms exist
- [ ] Create a service inventory with owners. — partial (inventory yes, owners no)
- [x] Create a dependency inventory. — done, Section 0
- [x] Create an operational decision log. — done, this checklist's Decision Log
- [x] Record accepted risks and deferred work. — done, this checklist's Accepted Risks table
- [x] Keep documentation versioned beside the relevant code where practical. — done by construction
- [ ] Confirm Aaron can follow the documentation without Gabriel's help. — not tested
- [ ] Confirm Gabriel can follow it without relying on personal memory. — not tested
- [ ] Add a production launch checklist and rollback decision tree. — Section 18 is the launch checklist; decision tree specifically doesn't exist
- [ ] Add contact information and escalation paths. — not done
- [ ] Review documentation after every incident or major deployment. — process doesn't exist yet
17. Testing Before Launch
Full detail:
production-readiness/16-19-docs-testing-cutover-postlaunch.md
- [ ] Run the complete automated test suite. — verified the suite exists and collects cleanly: 7,028 tests, zero collection errors; actually running it pass/fail is real Section 17 work, not done in this doc-sweep pass
- [ ] Run production-like integration tests. — not verified
- [ ] Test authentication and authorization. — likely covered somewhere in the 7,028 tests; not confirmed
- [ ] Test a clean infrastructure deployment in a disposable environment. — N/A, no IaC exists yet
- [ ] Test migration from the current schema. — not done
- [ ] Test application rollback. — not done, explicitly required
- [ ] Test failed migration handling. — not done
- [ ] Test a database restore. — cannot happen, no backups exist (Section 8)
- [ ] Test backup integrity. — same blocker
- [ ] Test task or instance failure. — not done
- [ ] Test deployment during active traffic. — not done
- [ ] Test autoscaling if enabled. — N/A, none exists
- [ ] Run basic load and soak tests. — not done
- [ ] Verify timeout behavior. — deliberate timeouts confirmed by code review (Section 5), not load-tested
- [ ] Verify retry behavior. — not load-tested
- [ ] Verify idempotency for jobs and webhooks. — confirmed by code review for background jobs (Section 9); webhooks not reviewed
- [ ] Test DNS and TLS from external networks. — partially done this session (live cert verification); not a full external sweep
- [ ] Test both root and
www. — both confirmed resolving; not a full test - [ ] Verify canonical redirects. — cannot pass, no canonical redirect exists yet (Section 6)
- [ ] Test monitoring and alert delivery. — N/A, no alerts exist yet
- [ ] Run a tabletop incident exercise with Gabriel and Aaron. — not done
- [ ] Test the break-glass process. — N/A, doesn't exist yet
- [ ] Verify neither maintainer is a single point of operational failure. — currently fails, per Section 1 (shared credential, no Aaron AWS identity)
- [ ] Test production access from a clean workstation. — not done
- [ ] Test what happens when a secret, database, queue, or external API is unavailable. — not done
18. Cutover Plan
Full detail:
production-readiness/16-19-docs-testing-cutover-postlaunch.mdEntirely future work — every item here depends on Sections 0–15 being substantially complete (backups existing, RPO/RTO defined, monitoring live, Aaron having independent access). Not walking each item individually since none are meaningfully startable yet. One prerequisite worth repeating: prod deploy is currently broken (no IAM instance profile, Section 0) — nothing in this section can happen until that's fixed, independent of everything else.
- [ ] Freeze unrelated infrastructure changes during cutover.
- [ ] Confirm the production environment is healthy.
- [ ] Confirm production secrets are installed.
- [ ] Confirm migrations have been rehearsed.
- [ ] Confirm current backups succeeded.
- [ ] Confirm a restoration has been tested.
- [ ] Confirm dashboards and alerts are active.
- [ ] Confirm Aaron and Gabriel are both available for the cutover.
- [ ] Lower DNS TTL in advance.
- [ ] Deploy the approved production artifact.
- [ ] Run migrations.
- [ ] Run internal smoke tests.
- [ ] Update DNS.
- [ ] Verify:
- [ ] DNS resolution
- [ ] TLS
- [ ] Root domain
- [ ]
www - [ ] API
- [ ] Authentication
- [ ] Critical user paths
- [ ] Background workers
- [ ] Outbound email
- [ ] Object uploads and downloads
- [ ] Monitor logs, error rate, and latency closely.
- [ ] Keep the previous environment available for rollback during the agreed window.
- [ ] Execute rollback if predetermined thresholds are crossed.
- [ ] Raise DNS TTL after stability is confirmed.
- [ ] Record the launch artifact and configuration versions.
- [ ] Announce launch status to stakeholders.
- [ ] Record all manual actions taken during cutover.
- [ ] Confirm production backups run successfully after cutover.
19. Post-Launch
Full detail:
production-readiness/16-19-docs-testing-cutover-postlaunch.mdEntirely contingent on launch having happened — not walking each item individually. The recurring-schedule items are worth setting up as real calendar reminders once launch is real; several (e.g. quarterly DR exercise) are only meaningful once their underlying capability exists (backups + restore procedure, for that example).
- [ ] Review production metrics after the first hour.
- [ ] Review again after 24 hours.
- [ ] Review again after one week.
- [ ] Inspect AWS costs after 24 hours and one week.
- [ ] Review security findings.
- [ ] Confirm backups continue to succeed.
- [ ] Perform another restore test after production has real data.
- [ ] Remove temporary cutover access and resources.
- [ ] Remove obsolete staging-derived credentials.
- [ ] Resolve or formally record every launch exception.
- [ ] Conduct a blameless launch retrospective.
- [ ] Update documentation with lessons learned.
- [ ] Schedule:
- [ ] Monthly dependency updates
- [ ] Monthly access review during the early production period
- [ ] Quarterly disaster-recovery exercise
- [ ] Quarterly permissions review
- [ ] Quarterly cost review
- [ ] Annual domain and recovery-contact review
Recommended Implementation Order
P0 — Before Creating Production
- [ ] Inventory staging.
- [ ] Establish shared organizational ownership and MFA.
- [ ] Decide AWS account boundaries.
- [ ] Select and implement Infrastructure as Code.
- [ ] Define RPO, RTO, and domain topology.
- [ ] Decide the canonical hostname and DNS provider.
P1 — Build Production
- [ ] Provision the production account and environment through IaC.
- [ ] Configure identity, networking, encryption, and secrets.
- [ ] Configure the database, automated backups, and restore testing.
- [ ] Configure GitHub OIDC and separate deployment roles.
- [ ] Build staging and production pipelines.
- [ ] Add logs, dashboards, alarms, and audit trails.
- [ ] Complete operational documentation.
P2 — Launch
- [ ] Configure certificates and DNS.
- [ ] Rehearse migrations, deployment, and rollback.
- [ ] Run security and load tests.
- [ ] Complete the cutover.
- [ ] Validate production and document the outcome.
Suggested Ownership Split
| Workstream | Primary | Reviewer / Backup | Status |
|---|---|---|---|
| Staging architecture inventory | Gabriel | Claude Code | Done — Section 0 |
| AWS Organizations and accounts | Gabriel | Decided (deferred as tech debt) — Section 2 | |
| IAM Identity Center and permissions | Gabriel | Documented gap, not built — Section 3 | |
| Infrastructure as Code | Gabriel | Tool recommended, not built — Section 4 | |
| Networking and production architecture | Gabriel | Documented, SPOFs identified — Section 5 | |
| Namecheap, DNS, and TLS | Gabriel | Documented, Cloudflare-hosting correction made — Section 6 | |
| Database and backups | Gabriel | ⚠️ Documented — no usable backup exists, highest-priority risk — Section 8 | |
| MuseHub-native CI/CD | Gabriel | Documented — platform features (branch protection, required checks) don't exist yet — Sections 9-11 | |
| Logging, metrics, and alerts | Gabriel | Logging is live and solid; alerts/dashboards don't exist — Section 12 | |
| Security review | Gabriel | Partial — one live finding fixed-pending (/_debug/memory); account-wide checks blocked on access grant — Sections 13-14 |
|
| Documentation and runbooks | Gabriel | Claude Code | This sweep is the start of it — Section 16 |
| Cutover coordination | Correctly blocked until earlier sections close — Sections 18-19 |
Decision Log
| Date | Decision | Rationale | Owner |
|---|---|---|---|
| 2026-08-04 | Accept single AWS account (992382692655, shared with Stori) as technical debt rather than splitting into AWS Organizations before launch |
Keeps focus on getting production launched; account split can happen later without blocking | Gabriel |
| 2026-08-04 | Architecture target (harden current EC2/Docker-Compose vs. redesign to ECS+ALB+RDS) left open | Insufficient information at Section 0 time; revisit once inventory and RPO/RTO are settled | Gabriel |
| 2026-08-04 | IaC tool selection deferred; Terraform/OpenTofu recommended given the actual footprint (2 EC2, 1 SG, 1 ECR repo, 1 S3 bucket, a handful of SSM params) | Small, EC2-centric footprint doesn't need CDK's language ceremony or CloudFormation's verbosity | Gabriel |
| 2026-08-04 | Every "GitHub" checklist item is translated to its MuseHub equivalent (proposals for PRs, MuseHub branch protection, musehub-runner CI, MuseHub collaborator roles) |
Absolute ecosystem policy — git/GitHub are never the VCS, CI, or collaboration platform for this project; GitHub is a passive backup mirror only | Gabriel (policy), applied throughout by Claude Code |
| 2026-08-08 | Section 6's "Namecheap vs. Route 53" question reframed as "Cloudflare vs. Route 53" | Live dig NS musehub.ai confirms DNS is already hosted at Cloudflare (registrar-only at Namecheap); docs/infrastructure.md was stale on this. Recommend staying on Cloudflare DNS given it also does edge proxy/TLS/possibly R2 |
Claude Code (finding), decision pending Gabriel |
| 2026-08-08 | Deploy team expanded from 2 (Gabriel, Aaron) to 5 (+ interns Sol, Toby, Ben); interns get staging-only access; Section 3's IAM Identity Center rollout is scoped for all 5 from the start | Sharing one AWS credential 5 ways has no accountability and no environment separation; the current key should not be distributed to interns before individual identities exist | Gabriel |
Accepted Risks and Deferred Work
⚠️ Highest-priority item in this table: there is currently no usable database backup, verified live on staging (no cron job, no rclone, one stale 20-byte manual dump). If the production database is lost today, there is no recovery path. This should not wait for the rest of the checklist sweep to be addressed.
| Item | Risk | Mitigation | Target Date | Owner |
|---|---|---|---|---|
| No usable database backup exists (verified live: no cron, no rclone, one stale 20-byte manual dump from ~2 months ago) | Total, unrecoverable data loss on any DB failure, corruption, or operator error | Wire deploy/backup.sh in for real: install rclone, configure R2 remote, install the cron job, verify a real non-empty backup lands, then perform an actual restore test |
ASAP — before any other Section 8 work | Gabriel |
| Single Postgres role used for app runtime, migrations, and admin access | An app-level SQL injection or bug would have migration/admin-level DB privileges, not just runtime privileges | Split into separate roles (app runtime, migrations, read-only, admin) per the checklist's explicit ask | Before Section 8 sign-off | Gabriel |
Migration failure-handling (alembic stamp --purge head on upgrade failure) could mask a genuinely broken migration as a stale-revision-ID issue |
A broken migration could silently ship if the follow-up schema-parity gate ever has a false negative | Document the reliance on schema_gate explicitly; consider distinguishing failure causes rather than blanket stamp-and-retry |
Before Section 8 sign-off | Gabriel |
| MuseHub-native branch protection, required status checks, and CODEOWNERS-equivalent don't exist as product features (Sections 9-11 assumed they just needed configuring, like GitHub Actions would) | Sections 9-11 require building MuseHub platform capability, not just deployment config — bigger scope than the checklist implies | Scope this explicitly as product work before estimating Sections 9-11's timeline; prioritize required-status-check gating on merge_proposal first since everything else depends on it |
Before Section 9 build phase begins | Gabriel |
/_debug/memory endpoint exposed unconditionally in production |
Fixed this session — guarded behind if settings.debug: like /docs//redoc; ships on next deploy |
Done 2026-08-08 | Claude Code | |
| Single AWS account shared with Stori | Blast radius: a MuseHub compromise/misconfig can affect Stori resources and vice versa; billing and ownership entangled | Revisit AWS Organizations split post-launch; musehub-infra IAM user already scoped narrowly (verified via access-denied tests on IAM/CloudTrail/GuardDuty/Route53/etc.) |
TBD | Gabriel |
docs/infrastructure.md is stale — object storage is actually Cloudflare R2, not a local Docker volume as documented |
Anyone reading the doc (including Aaron) gets the wrong mental model of where pushed content lives | Correct docs/infrastructure.md directly (separate from this checklist effort); resolved here via live proof this session |
Soon | Gabriel |
Secrets are hand-provisioned per instance, not via the drafted SSM pipeline (deploy/secrets.sh) — no rotation, no audit trail beyond manual memory |
Rotation and auditability (Section 7's actual goals) aren't met by the current hand-provisioned .env approach |
Gabriel to decide whether production adopts the SSM pipeline from day one rather than repeating staging's hand-provisioning | Before Section 7 sign-off | Gabriel |
| No RPO/RTO, traffic, or availability targets defined | Can't size backup cadence, DR posture, or capacity correctly without them | Define before Section 5 (architecture) and Section 8 (backups) are finalized | Before P1 build phase | Gabriel |
Canonical hostname (root vs. www) and DNS host (Namecheap vs. Route 53) undecided |
Blocks finalizing Section 6 and the cutover plan | Decide before Section 18 cutover | Before cutover | Gabriel |
Deploys use a single shared AWS credential (musehub-infra in ~/.aws/credentials), not individual identities |
Violates "no shared AWS access keys"; no accountability for who ran what; single point of failure if Gabriel is unavailable | Resolve via Section 3 (IAM Identity Center + per-person roles) before Section 1's independent-operation test can pass | Before Section 1 sign-off | Gabriel |
| Aaron has no AWS identity or verified Namecheap access today | Aaron cannot operate independently — fails the checklist's explicit "test that Aaron can independently…" requirement | Create Aaron's AWS identity via Section 3; confirm/grant Namecheap access | Before Section 1 sign-off | Gabriel |