Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Happy Post is a cloud-native application with a Next.js frontend and FastAPI backend. The delivery architecture uses two independently deployable container images and ECS Fargate services.

## Start here

- [Architecture](docs/architecture.md): primary solution architecture, AWS runtime, delivery/control plane, Terraform ownership, and deployed-state summary.
- [Live sandbox application](https://happy-post.asksafe.ai): deployed Happy Post MVP.
- [Operations](docs/operations.md): smoke tests, CloudWatch logs, database migration, restart persistence, and recovery procedures.

## Current status

The MVP application source is present: a FastAPI daily-entry API and a warm Next.js bedtime page where each signed-in user records at least three small happy things, with optional extra happy items when they have more to save. Local containerisation runs frontend, backend, and local PostgreSQL together through Docker Compose. P4's Terraform network, private RDS PostgreSQL data, ECR/ECS platform, edge, and both digest-pinned ECS service roots are applied. Terraform, application, security, image-publication, initial-service bootstrap, post-bootstrap deployment, and rollback workflows are implemented. The deployed frontend and backend services have been updated through the controlled ECS deployment workflow and their public HTTPS smoke tests pass.
Expand All @@ -19,9 +25,17 @@ The MVP application source is present: a FastAPI daily-entry API and a warm Next

## Deployed service verification

| Check | URL |
| --- | --- |
| Application | [https://happy-post.asksafe.ai](https://happy-post.asksafe.ai) |
| Frontend health check | [https://happy-post.asksafe.ai/healthz](https://happy-post.asksafe.ai/healthz) |
| Frontend version check | [https://happy-post.asksafe.ai/version](https://happy-post.asksafe.ai/version) |
| Backend health check | [https://happy-post.asksafe.ai/backend/healthz](https://happy-post.asksafe.ai/backend/healthz) |
| Backend version check | [https://happy-post.asksafe.ai/backend/version](https://happy-post.asksafe.ai/backend/version) |

The controlled ECS deployment workflow has successfully deployed both services. Backend smoke tests passed at `/backend/healthz` and `/backend/version`; the backend version response reports the deployed immutable image digest. Frontend smoke tests passed at `/healthz` and `/version`; the frontend version response currently reports the application version `0.1.0`. The rollback workflow is available for component-scoped recovery and should be rehearsed only if time permits before assessment submission.

Database-changing releases require one extra controlled step before the backend deployment. After publishing the backend image from `main`, run the manual database migration workflow with the same backend `image_digest` and `source_commit`, confirm `migrate-backend`, verify success, and only then run the backend ECS deployment workflow with that digest. After this workflow is merged, update the `happy-post-sandbox-bootstrap` CloudFormation stack before running it so the deployment role has the scoped `ecs:RunTask` permission for migration tasks.
Database-changing releases require one extra controlled step before the backend deployment. After publishing the backend image from `main`, run the manual database migration workflow with the same backend `image_digest` and `source_commit`, confirm `migrate-backend`, verify success, and only then run the backend ECS deployment workflow with that digest. The migration workflow and required bootstrap permissions are now in place; keep future database-changing releases on the same migration-before-backend-deploy sequence.

## Run locally with containers

Expand Down
70 changes: 61 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ Happy Post is a single-environment AWS sandbox with two independently deployable
## High-Level Solution Architecture

~~~mermaid
flowchart LR
users[Internet users] --> cloudflare[Cloudflare DNS<br/>asksafe.ai]
cloudflare -->|NS delegation| route53[Route 53<br/>happy-post.asksafe.ai]
route53 -->|A alias resolution| alb[Internet-facing ALB<br/>HTTPS :443]
flowchart TB
users[Internet users] --> cloudflare[Cloudflare DNS<br/>asksafe.ai parent zone]
cloudflare -->|delegates happy-post.asksafe.ai| route53[Route 53 public hosted zone]
route53 -->|A/AAAA alias resolution| alb[Internet-facing ALB<br/>HTTPS :443]
acm[ACM public certificate] -. "TLS certificate" .-> alb
alb -->|/*| frontend[Frontend ECS Fargate<br/>Next.js]
alb -->|/api/*| backend[Backend ECS Fargate<br/>FastAPI]
backend -->|PostgreSQL 5432| rds[Private RDS PostgreSQL]
frontend -. logs .-> logs[CloudWatch Logs]
backend -. logs .-> logs
alb -->|default /* and /frontend/*| frontend_tg[Frontend target group]
alb -->|higher-priority /api/* and /backend/*| backend_tg[Backend target group]
frontend_tg --> frontend[Frontend ECS Fargate service<br/>Next.js]
backend_tg --> backend[Backend ECS Fargate service<br/>FastAPI]
backend -->|PostgreSQL 5432 only| rds[Private RDS PostgreSQL]
frontend --> frontend_logs[CloudWatch Logs]
backend --> backend_logs[CloudWatch Logs]
~~~

Source: [solution-architecture.mmd](diagrams/solution-architecture.mmd).

Cloudflare remains the parent DNS provider for asksafe.ai and delegates happy-post.asksafe.ai to Route 53. Route 53 resolves the application domain to the public ALB; ACM terminates HTTPS at the ALB.

## AWS Sandbox Network and Runtime
Expand Down Expand Up @@ -87,6 +91,54 @@ The sandbox environment is an OIDC/configuration boundary, not an approval gate:

## Terraform and Deployment Ownership

~~~mermaid
flowchart TB
oidc[Existing GitHub OIDC provider] --> bootstrap[CloudFormation bootstrap]
bootstrap --> state[S3 Terraform state bucket<br/>private and versioned]
bootstrap --> lock[DynamoDB Terraform state lock table]
bootstrap --> roles[Separate GitHub OIDC roles<br/>sandbox plan; sandbox apply/destroy; sandbox ECS deploy]

state --> root[Terraform root configuration]
lock --> root
roles --> root
root --> foundations[Foundations]
root --> workloads[Workload stacks]

subgraph foundations
network[network<br/>VPC, subnets, routes, IGW, NAT]
identity[security-identity<br/>IAM, security groups, Secrets Manager]
platform[platform<br/>ECR, ECS cluster, ALB]
scaling[ECS service scaling<br/>CPU target tracking: 1–2 tasks, 65%]
edge[edge<br/>Route 53 records and ACM]
end

subgraph workloads
data[data<br/>RDS PostgreSQL and backups]
backend[backend-service<br/>task definition and ECS service]
frontend[frontend-service<br/>task definition and ECS service]
observability[observability<br/>CloudWatch log groups and alarms]
end

network --> identity
identity --> platform
platform --> edge
platform --> scaling
network --> data
identity --> data
platform --> backend
scaling --> backend
edge --> backend
data --> backend
platform --> frontend
scaling --> frontend
edge --> frontend
edge --> observability
backend --> observability
frontend --> observability
~~~

Source: [terraform.mmd](diagrams/terraform.mmd).

Terraform owns foundations and stable configuration: networking, private RDS PostgreSQL and database subnets/security group, Secrets Manager database credentials, ECR, ECS cluster, runtime IAM roles, logging, ALB/listeners, Route 53 and ACM records, ECS services, and initial task definitions. The mandatory data stack has its own state and lifecycle.

CloudFormation bootstrap owns the S3 state bucket, DynamoDB lock table, runtime permissions boundary, and GitHub OIDC roles. Terraform uses the S3 backend with DynamoDB as the only locking mechanism. The version-controlled bootstrap source is [`infra/bootstrap/happy-post-terraform-bootstrap.yaml`](../infra/bootstrap/happy-post-terraform-bootstrap.yaml). The state bucket is retained; the lock table has deletion protection and is retained on bootstrap stack deletion or replacement.
Expand Down
15 changes: 9 additions & 6 deletions docs/implementation-backlog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Implementation Backlog
# Implementation Backlog and Delivery Status

The documentation baseline and application MVPs are complete. The remaining work is deliberately ordered so that local containers precede infrastructure and automation.
The documentation baseline, application MVPs, AWS sandbox foundation, CI/CD controls, controlled ECS deployment, and P6 database-backed Happy Post MVP are complete. Remaining items are future hardening or optional operational rehearsals, not blockers for the current sandbox demonstration.

## P0 — Documentation baseline (complete)

Expand Down Expand Up @@ -40,7 +40,6 @@ The documentation baseline and application MVPs are complete. The remaining work
- [x] Define frontend and backend CPU target-tracking scaling: minimum one task, maximum two tasks, 65% CPU target.
- [x] Apply the network HTTP redirect ingress, rotate the database secret to include `database_url`, and apply the edge root.
- [x] Publish and scan frontend and backend images, then bootstrap each component service root with its real immutable digest.
- [ ] Perform the required isolated RDS restore test before the first database-changing release or migration.

## P5 — CI/CD and operational validation

Expand All @@ -53,7 +52,6 @@ The documentation baseline and application MVPs are complete. The remaining work
- [x] Implement post-bootstrap component-selected ECS deployment, health verification, and known-good rollback workflows.
- [x] Restrict the single GitHub environment, `sandbox`, to deployment branch `main` while retaining no required reviewers; then validate plan, apply, publish, and deployment role separation.
- [x] Execute controlled backend and frontend ECS deployments and public HTTPS smoke tests through the post-bootstrap deployment workflow.
- [ ] Rehearse component rollback only if time permits before assessment submission.

## P6 — Database-backed Three Happy Things MVP

Expand All @@ -66,8 +64,13 @@ The documentation baseline and application MVPs are complete. The remaining work
- [x] Publish new immutable frontend and backend images from merged P6 `main`.
- [x] Merge the controlled database migration workflow, update the CloudFormation bootstrap stack, then run `alembic upgrade head` as a one-off private ECS task from the verified backend image digest.
- [x] Deploy the P6 backend, deploy the P6 frontend, then verify entry persistence through RDS.
- [ ] Re-run the restart persistence demo: create an entry, restart backend, and confirm the happy things remain.
- [ ] For real users, replace MVP app-level auth with managed identity such as Amazon Cognito, Auth0, or Clerk.
- [x] Re-run the restart persistence demo: create an entry, restart backend, and confirm the happy things remain.

## Future hardening and optional rehearsals

- [ ] Perform an isolated RDS restore test before any further database-changing release or migration.
- [ ] Rehearse component rollback with a safe no-op or previous known-good revision when time permits.
- [ ] Replace sandbox MVP app-level auth with managed identity such as Amazon Cognito, Auth0, or Clerk before serving real users.

## Deferred options

Expand Down
14 changes: 11 additions & 3 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ The latest controlled ECS deployment verification passed:

The rollback workflow is available for component-scoped recovery. Rehearse rollback only if time permits before assessment submission; otherwise keep it as a documented follow-up because the deployment and public smoke path is already validated.

## CloudWatch logging

- The applied platform foundation created separate CloudWatch log groups for the two ECS workloads: `/ecs/happy-post/backend` and `/ecs/happy-post/frontend`.
- Both log groups use fourteen-day sandbox retention.
- Backend and frontend task definitions use the `awslogs` driver and publish only to their matching log group.
- The backend execution role can publish backend logs only; the frontend execution role can publish frontend logs only.
- The ECS cluster has Container Insights enabled for sandbox runtime visibility.

## Database operations

- RDS PostgreSQL remains private and is reachable only from the backend service.
Expand All @@ -39,10 +47,10 @@ The rollback workflow is available for component-scoped recovery. Rehearse rollb
- Deletion protection is disabled for sandbox. Intentional destroy creates a uniquely named final DB snapshot, does not retain automated backups, and requires the snapshot owner to review and delete it within seven days unless retention is explicitly approved.
- The Three Happy Things MVP uses the backend `DATABASE_URL` to persist user-scoped daily entries in PostgreSQL. The frontend never receives database credentials.
- MVP auth uses an HttpOnly SameSite=Lax cookie containing an opaque random token, while PostgreSQL stores only the token hash in `user_sessions`. Sign-out deletes the current database session. Expired sessions are rejected by the backend. There is no shared session-signing secret to rotate in the MVP design.
- Restore testing is required before a release and after every database-changing migration. Restore to an isolated temporary private RDS instance, verify availability, approved private connectivity, `SELECT 1`, migration version, and row-count sanity checks for `users`, `user_sessions`, `daily_entries`, and `daily_entry_items`, record evidence, then remove the temporary restore resources.
- A database change requires a backward-compatible migration plan and either a restore plan or a compensating migration before deployment. For the first P6 release, run the append-only Alembic chain through `0002_create_users_daily_entries`, which replaces the temporary `posts` table with `users`, `user_sessions`, `daily_entries`, and `daily_entry_items`, before deploying the backend image that requires it.
- Restore testing remains required operational hygiene for database-changing releases. Restore to an isolated temporary private RDS instance, verify availability, approved private connectivity, `SELECT 1`, migration version, and row-count sanity checks for `users`, `user_sessions`, `daily_entries`, and `daily_entry_items`, record evidence, then remove the temporary restore resources. The isolated restore rehearsal was not completed before the P6 migration because of time and cost constraints; it remains a follow-up and must be completed before any further database-changing release.
- A database change requires a backward-compatible migration plan and either a restore plan or a compensating migration before deployment. The P6 release ran the append-only Alembic chain through `0002_create_users_daily_entries`, which replaces the temporary `posts` table with `users`, `user_sessions`, `daily_entries`, and `daily_entry_items`, before deploying the backend image that requires it.
- The controlled migration workflow derives its subnet and security-group placement from the active backend ECS service, so the migration task runs in the same private application network path as the backend. It uses the backend execution role for `DATABASE_URL` secret injection and must not print connection strings or secret values.
- After merging migration-workflow changes, update the `happy-post-sandbox-bootstrap` CloudFormation stack before the first migration run. The deployed ECS deploy role must include scoped `ecs:RunTask` permission for the `happy-post-sandbox-backend-migration` task-definition family.
- The `happy-post-sandbox-bootstrap` CloudFormation stack has been updated with the scoped ECS migration permissions required by the database migration workflow. The deployed ECS deploy role includes scoped `ecs:RunTask` permission for the `happy-post-sandbox-backend-migration` task-definition family.

## Terraform state-backend operations

Expand Down
2 changes: 1 addition & 1 deletion docs/requirements-traceability.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
| Least privilege | OIDC trust, separate operational roles, and narrow runtime permissions | [Security decisions](security-decisions.md) |
| End-to-end TLS | Deferred optional enhancement, not baseline | [Architecture](architecture.md) |

This table records the approved design, completed application MVPs, local containerisation, applied Terraform network, RDS data, platform, edge, service roots, CI controls, and successful controlled ECS deployment smoke tests. The P6 workload implementation adds database-backed user accounts, opaque sessions, and daily entries; deploying it requires running the controlled migration workflow through `0002_create_users_daily_entries` before the backend image that depends on it.
This table records the approved design, completed application MVPs, local containerisation, applied Terraform network, RDS data, platform, edge, service roots, CI controls, successful controlled ECS deployment smoke tests, and the completed P6 database migration and restart persistence demo. The P6 workload implementation adds database-backed user accounts, opaque sessions, and daily entries; the deployed release ran the controlled migration workflow through `0002_create_users_daily_entries` before the backend image that depends on it.
13 changes: 13 additions & 0 deletions infra/terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ revision, and updates only the selected existing service. Terraform ignores the
service task-definition field after initial creation so it does not undo that
delivery revision.

## Why there is no modules directory yet

This repository intentionally keeps each Terraform root explicit instead of
introducing a shared `modules/` directory too early. The current sandbox has one
environment and one instance of each major component, so root-level code is
easier to review for ownership, state boundaries, IAM scope, and assessment
traceability.

A `modules/` directory should be introduced when a pattern is reused across
multiple environments, accounts, regions, or repeated services. At that point,
modules should preserve the same state boundaries and least-privilege ownership
model instead of hiding cross-stack dependencies.

## State backend

The network root uses this non-secret backend configuration:
Expand Down
Loading