Phase 0 — Foundation Walkthrough¶
Weeks 1–6 · Sprints 1–3
A step-by-step setup guide for every platform and service needed before development begins. Written so that a non-developer product owner can work through it alongside the junior developer.
How to use this guide
- Work through it in order. Each step depends on the previous ones.
- Purple "What this is" blocks explain concepts before procedures.
- Code blocks contain commands the junior types. PO should see them run.
- Tick off each step as it's completed.
- Phase 0 is done when the final verification passes.
Time Budget¶
| Sprint | Weeks | Key deliverables |
|---|---|---|
| Sprint 1 | 1–2 | All accounts created, AWS provisioned, FastAPI health endpoint deployed |
| Sprint 2 | 3–4 | Cognito, Entra ID federation, JWT middleware, tenant registry, React login |
| Sprint 3 | 5–6 | CloudFront, WAF, Redis, PowerSync, two tenants verified end-to-end |
Do not skip ahead
AWS resources have dependencies — the VPC must exist before RDS, IAM roles before ECS tasks. The order in this document is the order to follow.
Section 1 — Accounts to Create¶
Before any technical work, you need accounts on all the platforms we'll use. Set these up first to avoid waiting on verification or provisioning later.
| Platform | Purpose | Account type | Setup time |
|---|---|---|---|
| AWS | All cloud infrastructure | Root account + billing card | 30 min + 24h verification |
| GitHub | Source code, CI/CD | Organisation (paid recommended) | 10 min |
| Microsoft 365 Developer | Entra ID testing | Free developer tenant | 20 min + 24h provisioning |
| Linear | Project management | Workspace | 10 min |
| Sentry | Error tracking | Team account | 10 min |
| PowerSync | Offline sync service | Cloud account | 10 min |
| Anthropic | Claude API for V2 features | API account | 10 min |
| Cursor | AI IDE for the junior | Pro subscription | 5 min |
| Better Uptime | Uptime monitoring | Standard account | 5 min |
| 1Password Teams | Shared credentials | Teams plan | 10 min |
| Notion | Documentation | Team workspace | 5 min |
| Domain registrar | Platform domain | One-off purchase | 15 min |
1.1 Domain name first¶
Register the domain before anything else. It's referenced throughout the setup that follows.
- Choose a domain name — short, memorable,
.comor.co.uk. No hyphens. - Check availability on Cloudflare Registrar (recommended — cost price, no markup, excellent DNS tooling) or Namecheap.
- Register for 1 year (~£8–15).
- Enable WHOIS privacy (usually free).
- Enable two-factor authentication on the registrar account.
- Store the registrar login in 1Password (set up next).
Tip
You'll configure DNS records in Sprint 3 when CloudFront is ready. For now, you just need to own the domain.
1.2 1Password Teams¶
- Go to 1password.com/teams and sign up for the Teams Starter plan (~£15/month).
- Create a Team Vault called Construo — Shared.
- Invite the junior developer by email.
- Enable two-factor authentication for both accounts (authenticator app, not SMS).
- Create vault sections:
AWS,GitHub,Microsoft,Third-party services,Domain & DNS. - Save the domain registrar login as the first entry.
Rule from day one
No credential is ever sent over email, Slack, or text. Everything goes in 1Password.
1.3 GitHub Organisation¶
- Go to github.com and sign in with your business email.
- Click your avatar → Your organisations → New organisation.
- Choose the Team plan (~£4/user/month). Free tier works but Team gives private CI/CD minutes.
- Name the organisation (e.g.
construo-io). - Invite the junior developer as Member (not Owner).
- Settings → Security: enable two-factor authentication for all members.
- Settings → Member privileges: set Base permissions to Read.
- Save the GitHub login and the junior's invite to 1Password.
Tip
Don't create the code repository yet — we'll do that in Section 3 after the structure is agreed.
1.4 AWS Account¶
- Go to aws.amazon.com and click Create an AWS Account.
- Use a business email that goes to a shared inbox (e.g.
aws@construo.io) — not a personal one. - Choose Business account type.
- Enter a credit or debit card. AWS will charge £1 temporarily to verify.
- Verify by phone.
- Choose the Basic support plan (free) for now.
Critical post-signup hardening — do within 24 hours:
- Go to IAM → dashboard → action all security recommendations:
- Activate MFA on root user (authenticator app; save backup codes to 1Password)
- Do not create access keys for the root account
- Account → Billing Preferences: enable billing alerts and free tier alerts.
- Billing → Budgets → Create budget: £200/month, alerts at 50%, 80%, and 100%.
- Save root account credentials and MFA backup codes to 1Password.
Root account
After this setup, do not use the root account again unless absolutely necessary (e.g. changing billing details). We'll create restricted IAM users next.
1.5 Microsoft 365 Developer Tenant¶
This gives you a free Entra ID tenant to test enterprise SSO integration.
- Go to developer.microsoft.com/microsoft-365/dev-program and click Join now.
- Sign in with a personal Microsoft account (keep separate from your business Microsoft account).
- Choose Instant sandbox — gives you 25 pre-populated test users.
- Set an admin username (
admin@yourname.onmicrosoft.com). - Store the password in 1Password.
- Wait for provisioning (5–15 minutes), then sign in at admin.microsoft.com.
Tip
This subscription renews every 90 days as long as you sign in occasionally. Set a monthly calendar reminder.
1.6 Remaining accounts¶
For each below: sign up with a business email, enable 2FA, save credentials to 1Password.
- linear.app → Start building → sign in with work email
- Create workspace Construo
- Create team Engineering
- Invite the junior developer
- Free tier sufficient initially
- sentry.io → sign up via GitHub (one less password)
- Create organisation construo
- When prompted: choose Python/FastAPI and JavaScript/React projects
- Save DSN strings to 1Password — you'll use them later
- powersync.com → Start Free → sign up via GitHub
- Create an organisation, then an instance — choose region EU
- Choose Free plan to start (upgrade to Pro at $49/month when real users arrive)
- Save the instance URL and access token to 1Password
- Junior goes to cursor.com and downloads Cursor
- Sign in with GitHub
- Upgrade to Pro (~£15/month) — bill to business card
- Cursor includes Claude and GPT-4 access built in
- console.anthropic.com → sign up with business email
- Add a payment card and £10 credit balance (enough for V1 development)
- Go to API Keys → create key
dev-environment→ save to 1Password
- Sign up at notion.so for a Team workspace
- Create top-level pages:
Onboarding,User Guide (draft),Runbooks,ADRs,Sprint Notes - Invite the junior developer
1.7 Section 1 completion checklist¶
- Domain registered, WHOIS privacy on
- 1Password Teams set up, junior invited, shared vault created
- GitHub organisation created, junior invited, 2FA enforced
- AWS account created, root MFA enabled, budget alert at £200/month
- Microsoft 365 Developer tenant provisioned, admin login saved
- Linear, Sentry, PowerSync, Anthropic, Cursor, Better Uptime, Notion all set up
- Every credential is in 1Password — nothing in email or notes
- 2FA enabled on every account
Section 2 — AWS Foundations¶
2.1 Set default region to London¶
- Sign in to the AWS Console with the root account.
- Click the region selector (top-right) — probably shows N. Virginia.
- Choose Europe (London) — eu-west-2.
Warning
AWS resources are region-specific. An RDS database in London is invisible from a console set to N. Virginia. Always check the region selector before troubleshooting.
2.2 Create IAM admin user¶
- Search
IAMin the AWS console → open IAM dashboard. - Users → Create user
- Username:
yourname-admin - Check Provide user access to the AWS Management Console
- Choose I want to create an IAM user
- Username:
- Set a strong password. Uncheck "must change password at next sign-in".
- Permissions → Attach policies directly → AdministratorAccess
- Create user. AWS shows a sign-in URL like
https://123456789012.signin.aws.amazon.com/console— save this to 1Password. - Sign out of root. Sign back in using the new URL and admin credentials.
- Set up MFA on this user immediately: IAM → Users → your user → Security credentials → Assign MFA.
Repeat for the junior developer (same process, same AdministratorAccess for Phase 0):
- Username:
junior-dev(or their actual name) - Check "must change password at next sign-in"
- Send sign-in URL and temporary password via 1Password shared item
2.3 Create AWS access keys for Terraform¶
- Sign in as your admin user.
- IAM → Users → your username → Security credentials → Create access key
- Choose Command Line Interface (CLI). Add tag:
terraform-local-dev. - Copy both the Access Key ID and Secret Access Key → save to 1Password (only time the secret is shown).
- Repeat for the junior's user — they need their own keys.
Danger
If access keys are ever leaked (committed to GitHub, screenshotted), rotate them immediately. AWS auto-detects keys in public GitHub repos and emails you within minutes.
2.4 Configure AWS CLI¶
Junior runs these on their laptop:
# Mac
brew install awscli
# Windows — download MSI from aws.amazon.com/cli
# Verify
aws --version
# Configure
aws configure
# AWS Access Key ID: paste from 1Password
# AWS Secret Access Key: paste from 1Password
# Default region name: eu-west-2
# Default output format: json
# Test
aws sts get-caller-identity
Expected output: JSON showing your user ARN. An error means the keys were entered
incorrectly — run aws configure again.
2.5 Enable CloudTrail¶
- AWS Console → CloudTrail → Create trail
- Trail name:
audit-trail-all-regions - Storage: Create new S3 bucket. Name:
construo-cloudtrail-logs-[account-id] - Log file SSE-KMS encryption: enabled
- Log file validation: enabled
- Events: Management events, both Read and Write
- Create trail
2.6 Section 2 completion checklist¶
- Default region set to eu-west-2 (London)
- Admin IAM user created with MFA
- Developer IAM user created with MFA
- AWS CLI installed and configured on junior's laptop —
aws sts get-caller-identityreturns JSON - CloudTrail enabled, logs going to S3
- Monthly budget alerts at 50%, 80%, 100%
- No access keys exist on the root account
Section 3 — Source Code and Repository¶
3.1 Create the repository¶
- GitHub organisation → Repositories → New
- Repository name:
platform - Visibility: Private
- Initialise with: README, .gitignore (template: Node)
- Create repository
3.2 Protect the main branch¶
- Settings → Branches → Add branch protection rule
- Branch name pattern:
main - Check:
- Require a pull request before merging
- Require approvals: 1
- Dismiss stale PR approvals when new commits are pushed
- Require status checks to pass before merging
- Require branches to be up to date before merging
- Do not allow bypassing — apply to administrators
- Save
Tip
You won't see status checks listed until CI is set up in Section 5. Come back and add the specific checks (lint, test, build) once they exist.
3.3 Create the folder structure¶
Junior runs from the cloned repo:
git clone git@github.com:construo-io/platform.git && cd platform
mkdir -p \
apps/api/src/{core,modules} apps/api/tests apps/api/alembic \
apps/web/src/{core,modules} apps/web/public \
packages/{types,db,shared} \
infra/terraform/{modules,environments/{dev,staging,prod}} \
.github/workflows \
docs/adr
# Create .gitkeep in each empty folder
find . -type d -empty -not -path "./.git/*" -exec touch {}/.gitkeep \;
git add . && git commit -m "Initial folder structure" && git push
3.4 Write the first ADR¶
Create docs/adr/0001-use-fastapi.md:
# ADR 0001: Use FastAPI for the backend
Status: Accepted
Date: 2025-05-23
## Context
We need a backend framework. Options considered: FastAPI, Django, Flask,
Next.js API routes.
## Decision
Use FastAPI (Python 3.12).
## Reasoning
- Python ecosystem needed for V2 AI features (Smart Import, Licence Scanning)
- Automatic OpenAPI generation supports frontend type generation
- Pydantic validation is built-in
- Async support is production-grade
- Smaller learning curve than Django for a junior developer
## Consequences
- Two-language team (Python backend + TypeScript frontend)
- Mitigated by auto-generated TypeScript types from OpenAPI spec
Tip
Write an ADR for any decision a future developer would question. Takes 15 minutes; saves hours of re-litigating decisions later.
Section 4 — Infrastructure as Code (Terraform)¶
4.1 Install Terraform¶
# Mac
brew install terraform
# Windows — download from terraform.io, add to PATH
terraform version
# Expected: Terraform v1.7 or higher
4.2 Create the Terraform state bucket¶
This must be created manually — Terraform needs it to function.
- S3 Console → Create bucket
- Name:
construo-terraform-state(must be globally unique) - Region: eu-west-2
- Block all public access: ✅ KEEP CHECKED
- Versioning: ENABLE
- Default encryption: SSE-S3
- Name:
- DynamoDB Console → Create table
- Name:
terraform-state-lock - Partition key:
LockID(String)
- Name:
4.3 Write the provider configuration¶
Create infra/terraform/environments/dev/backend.tf:
terraform {
required_version = ">= 1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "construo-terraform-state"
key = "environments/dev/terraform.tfstate"
region = "eu-west-2"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
provider "aws" {
region = "eu-west-2"
default_tags {
tags = {
Project = "construo"
Environment = "dev"
ManagedBy = "terraform"
}
}
}
Initialise:
Expected: Terraform has been successfully initialized!
4.4 VPC module¶
The VPC creates three subnet tiers — public (ALB), private (ECS), data (RDS/Redis).
VPC (10.0.0.0/16)
├── Public Subnet AZ-A (10.0.1.0/24) ← ALB
├── Public Subnet AZ-B (10.0.2.0/24)
├── Private Subnet AZ-A (10.0.11.0/24) ← ECS Fargate
├── Private Subnet AZ-B (10.0.12.0/24)
├── Data Subnet AZ-A (10.0.21.0/24) ← RDS, Redis
├── Data Subnet AZ-B (10.0.22.0/24)
├── Internet Gateway
└── NAT Gateway
Junior generates the Terraform VPC module with Claude (pass the architecture document and ask for the module). Review every line, then:
terraform plan -out=tfplan # Review output with PO before applying
terraform apply tfplan # Takes 3–5 minutes
Verify in AWS Console: VPC → confirm construo-dev exists with all subnets.
Section 5 — Backend and Deployment¶
5.1 FastAPI health endpoint¶
apps/api/src/main.py:
from fastapi import FastAPI
from datetime import datetime
app = FastAPI(title="Construo API", version="0.1.0")
@app.get("/health")
def health_check():
return {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
"version": "0.1.0",
}
Test locally:
cd apps/api
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
uvicorn src.main:app --reload
# Open http://localhost:8000/health
# Open http://localhost:8000/docs ← FastAPI's auto-generated API docs
5.2 Containerise¶
apps/api/Dockerfile:
FROM python:3.12-slim
WORKDIR /app
RUN pip install uv
COPY pyproject.toml ./
RUN uv pip install --system -e .
COPY src/ ./src/
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
docker build -t construo-api . && docker run -p 8000:8000 construo-api
# Confirm http://localhost:8000/health works
5.3 ECS Fargate + ALB¶
Junior writes Terraform modules for: ECS cluster, ALB, security groups, IAM execution role, ECS service and task definition.
Use Claude: paste the architecture document Section 4 and ask for production-ready Terraform modules. Review every line.
Key configuration:
- ECS task: 0.5 vCPU, 1 GB memory
- Desired count: 2 (for HA across AZs)
- ALB health check: path
/health, healthy threshold 2 - Security groups: ALB allows 80/443 from internet; ECS allows 8000 only from ALB
After apply:
terraform output alb_dns_name
curl http://[alb-dns-name]/health
# Expected: the same JSON health response, now from AWS
5.4 GitHub Actions CI/CD¶
- Create AWS IAM user
github-actionswith minimal permissions (ECR push, ECS update only) - Add keys as GitHub repository secrets:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY - Create
.github/workflows/api-deploy.yml— junior generates with Claude, key stages:test: lint with ruff, run pytestdeploy(on merge to main): build Docker image → push to ECR → update ECS service
- Commit and push. Confirm the Actions tab shows the workflow running green.
- Return to branch protection rules and add the CI checks as required status checks.
Section 6 — Authentication (Sprint 2)¶
6.1 Create the Cognito User Pool¶
- AWS Console → Cognito → User pools → Create user pool
- Configure sign-in: Email only. Include Federated identity providers.
- Security: MFA optional (enforced for admins via app logic). Recovery: email only.
- Sign-up: Self-service disabled (users are invited only). Required attributes: email, name.
- App integration:
- User pool name:
construo-dev - Hosted UI: enabled
- Domain:
construo-dev(becomesconstruo-dev.auth.eu-west-2.amazoncognito.com) - App client name:
construo-web - No client secret
- Auth flows:
ALLOW_USER_SRP_AUTH,ALLOW_REFRESH_TOKEN_AUTH - Callback URLs:
http://localhost:5173/auth/callback - OAuth scopes:
openid, email, profile
- User pool name:
- Create. Save User Pool ID and App Client ID to 1Password.
6.2 Configure Entra ID SAML federation¶
Step 1 — In Entra ID (Microsoft side):
- entra.microsoft.com → Identity → Applications → Enterprise applications → New application
- Name:
Construo (Dev). Choose "Integrate any other application (Non-gallery)". - Once created: Single sign-on → SAML
- Basic SAML Configuration:
- Identifier (Entity ID):
urn:amazon:cognito:sp:[user-pool-id] - Reply URL:
https://construo-dev.auth.eu-west-2.amazoncognito.com/saml2/idpresponse
- Identifier (Entity ID):
- Download Federation Metadata XML (Section 3 of the SAML config page).
- Users and groups → Add user: assign at least one test user (e.g. Megan Bowen).
Step 2 — In Cognito (AWS side):
- User pool → Sign-in experience → Federated identity provider → Add identity provider → SAML
- Provider name:
EntraID-DevTenant - Metadata document: upload the XML from Step 1
- Attribute mapping:
email→http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddressname→http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
- Save. Go to App integration → your app client → Edit hosted UI → add EntraID-DevTenant.
Step 3 — Test:
- Visit the hosted UI URL.
- Click Continue with EntraID-DevTenant.
- Microsoft sign-in appears. Sign in as a test user.
- Should redirect back with a
codequery parameter in the URL.
Tip
Most SAML failures are: typo in Entity ID, test user not assigned to the Entra app, or attribute mapping mismatch. Paste the error to Claude with the URL parameters.
Section 7 — Tenant Isolation (Sprint 2)¶
7.1 Provision RDS PostgreSQL¶
Junior writes Terraform RDS module. Key config:
- Engine:
postgres, version16 - Instance class:
db.t3.medium - Allocated storage: 100 GB, gp3, encrypted
- Multi-AZ: false for dev (cost), true for prod
- Subnet group: data subnets from VPC module
- Security group: allow port 5432 only from the ECS security group
- Master password: generated by Terraform, stored in AWS Secrets Manager
- Backup retention: 7 days
- Deletion protection: true
7.2 Create the tenant registry¶
Junior creates an Alembic migration creating the public.tenants table (see full schema in Architecture Design):
7.3 Tenant middleware¶
The middleware extracts the tenant from the Host header, looks it up in Redis (fallback to DB), and sets PostgreSQL's search_path to the tenant's schema for the duration of the request.
Test locally by adding to /etc/hosts:
Then:
curl -H "Host: acme.local.test" http://localhost:8000/whoami
# Returns: {"tenant_slug": "acme", "tenant_name": "Acme Construction"}
curl -H "Host: beta.local.test" http://localhost:8000/whoami
# Returns: {"tenant_slug": "beta", "tenant_name": "Beta Construction"}
Section 8 — Edge Security, Sync, and Final Verification (Sprint 3)¶
8.1 CloudFront + WAF¶
Junior writes Terraform for CloudFront distribution and WAF web ACL.
- Origin: the ALB
- Redirect HTTP to HTTPS
- Custom SSL cert from ACM (
*.construo.io— must be in us-east-1 for CloudFront) - WAF: AWS Managed Rules (Common Rule Set, Known Bad Inputs, IP Reputation)
- Rate rule: block IPs making >2,000 requests per 5 minutes
After apply, configure DNS at your registrar:
Wait 5–60 minutes for DNS propagation, then:
8.2 Redis cache¶
Add ElastiCache (cache.t3.micro, data subnets, encryption in transit) via Terraform.
Update the tenant middleware to use Redis as a cache layer (5-minute TTL).
8.3 PowerSync¶
- In PowerSync dashboard: create an instance pointing at the RDS database.
- Create a PostgreSQL replication user:
- Upload sync rules YAML to PowerSync dashboard (sync a single test table initially).
- In the React app:
- Initialise PowerSync with the instance URL and a token (issued by FastAPI
/sync-tokenendpoint). - Build a test page that reads from local SQLite and shows results.
- Verify: insert a row via psql; it appears in the browser within seconds.
8.4 React login flow¶
Junior creates:
/— dashboard (protected)/login— redirects to Cognito hosted UI/auth/callback— receives OAuth code, exchanges for tokens, stores in memory, redirects to dashboard
Dashboard shows: "Welcome, [name]. You are signed into [tenant name]."
Warning
Use Cursor with Claude to generate the auth flow. Provide the Cognito config and ask for a TypeScript implementation with React Router. Review every file — auth bugs are silent until exploited.
End-of-Phase 0 Verification¶
Walk through every item below together — product owner and junior, verifying with their own eyes.
Verification steps¶
- Open browser →
https://acme.construo.io - Click Sign in with company account → redirected to Microsoft sign-in
- Sign in as Megan Bowen → dashboard shows "Welcome Megan, signed into Acme Construction"
- Sign out
- Go to
https://beta.construo.io - Sign in with Cognito email/password → dashboard shows Beta Construction data
- Open DevTools → Network tab. Confirm:
- All requests are HTTPS
- No CORS errors
- Response headers include WAF security headers
- In AWS Console, confirm:
- ECS service shows 2/2 tasks healthy
- RDS Performance Insights shows recent queries
- CloudWatch Logs show recent API requests
- Sentry has no unhandled exceptions
- In PostgreSQL, confirm:
public.tenantshas rows foracmeandbeta- Both schemas exist with identical tables
- Data in
acmeis not visible from a session withsearch_path = beta
- Check AWS Budgets — tracking under £200/month
Sign-off¶
| Role | Name | Signature | Date |
|---|---|---|---|
| Product Owner | |||
| Developer |
Phase 0 complete
If everything above passes, Phase 0 is complete. Move to Sprint 4 with confidence.
Phase 0 incomplete
If anything fails, do not start Phase 1 until the failure is resolved. A broken foundation cascades into every subsequent sprint.