This document defines the architectural patterns, operational invariants, and workflow standards for AI agents (Antigravity, Claude Code, Cursor, Codex) and human developers contributing to this repository and projects derived from it.
- Default Branch (
dev):- All active feature branches branch off
devand merge back intodev. - Pushes directly to
devnever trigger automated deployments. CI runs are manual (workflow_dispatch) by default.
- All active feature branches branch off
- Release & GHCR Workflow (
scripts/release.sh vX.Y.Z):- Releases are cut exclusively from
devusing Semantic Versioning (vX.Y.Z). - Pushing the release tag
v*triggers.github/workflows/ci.yml, which:- Executes unit and integration tests via
uv run pytest. - Builds the multi-stage Docker
runnerimage. - Pushes tagged images (
vX.Y.Z,vX.Y,latest) to GitHub Container Registry (ghcr.io). - Publishes a GitHub Release with auto-generated release notes.
- Executes unit and integration tests via
- Releases are cut exclusively from
- Environment Promotion via Pull Requests:
- Staging (
stgbranch): Promoted by opening and merging a PR fromdevintostg(bash scripts/release.sh pr stg). Merging automatically triggers the CD workflow targeting thestagingenvironment. - Production (
mainbranch): Promoted by opening and merging a PR fromstgintomain(bash scripts/release.sh pr main). Merging automatically triggers the CD workflow targeting theproductionenvironment. - Nothing enters
stgormaindirectly: no unreviewed direct pushes and never a force push.
- Staging (
- Strict Environment Symmetry:
Both
stagingandproductionrun the exact same deployment job in.github/workflows/cd.yml. - The 5 Deployment Variables / Secrets:
Each GitHub Environment (
stagingandproduction) must define exactly:NETBIRD_KEYorNETBIRD_SETUP_KEY(Secret): Setup key for NetBird VPN mesh connection.SSH_PRIVATE_KEY(Secret): SSH private key for host authentication.SERVER_HOST(Variable): Target server IP or NetBird mesh hostname.SERVER_USER(Variable): Target host SSH username.SERVER_PROJECT_DIR(Variable): Remote application directory on host.
- No Silent Fallbacks in Deployment Workflows:
Avoid hardcoding fallback values for server hosts, users, or secrets. Missing credentials must fail immediately during the pre-flight check step (
.github/actions/preflight-check).
- Single Environment Template (
.env.example):- Do not reintroduce
local.envor multiple.env.*.examplefiles. - Local setup is always
cp .env.example .env. - In remote environments, the server host stores its environment in
.env.
- Do not reintroduce
- No Default Fallback Variables in Compose:
- Variables in
docker-compose.ymlmust be explicitly supplied by.env(e.g.${PROJECT_NAME},${REDIS_PASSWORD},${FASTAPI_PORT},${REDIS_PORT}). - Do not use inline fallbacks like
${PROJECT_NAME:-python-project-starter}or${REDIS_PASSWORD:-redispassword}. - Services use explicit
environment:mappings indocker-compose.ymlinstead ofenv_file:to clearly define container contracts and isolate container networking (e.g.redis://:${REDIS_PASSWORD}@redis:6379/0) from hostlocalhost.
- Variables in
- Traefik Reverse Proxy Overlay:
- In local development, the app maps ports directly to
http://localhost:8000(COMPOSE_FILE=docker-compose.yml). - In deployed environments behind a shared Traefik gateway, enable the overlay in the server's
.env:COMPOSE_FILE=docker-compose.yml:docker-compose.traefik.yml APP_HOSTNAME_DOMAIN=api-stg.ait.ac.th
docker-compose.traefik.ymljoins the externaltraefik_publicnetwork and dynamically registers ingress routing and SSL certificates with Let's Encrypt.
- In local development, the app maps ports directly to
- When executing multi-line shell scripts over SSH from GitHub Actions runners, never pass the script as a direct command string argument (
ssh host "$CMD"). - Always pass the command via an environment variable and pipe it into
stdinusing<<< "$SSH_CMD"to prevent quote escaping issues and client-side shell expansions:env: SSH_CMD: ${{ inputs.cmd }} run: | ssh -T -o StrictHostKeyChecking=no -o ConnectTimeout=15 \ "${{ inputs.server-user }}@${{ inputs.server-host }}" \ "bash -se" <<< "$SSH_CMD"
- Automated Smoke Testing:
- Every deployment must execute
scripts/smoke-test.shbefore declaring success. - Verifies
/healthresponds withstatus: ok, checks web UI routes, and validates Django system integrity. Never rely solely on container port reachability.
- Every deployment must execute
- Persistent Host Baseline (
.deploy_success):- Remote deployment workflows record the deployed commit SHA in
.deploy_successonly after deployment and smoke tests succeed. - If deployment or smoke tests fail, the server immediately rolls back to the last verified commit in
.deploy_success.
- Remote deployment workflows record the deployed commit SHA in
- Fail Forward Policy:
- Release tags are immutable: never delete, move, or force-push Git tags.
- If a release deployment fails, the server rolls back automatically. Fixes are merged into
devand released under the next sequential SemVer tag (e.g.,v0.1.0fails → fix ondev→ releasev0.1.1).
- Sensitive Data Guard (
scripts/check-data-guard.sh):- Never commit credentials, database dumps (
*.sql,*.dump), spreadsheets (*.xlsx,*.xls), or real user PII to Git. - All staging and pre-release checks must pass
bash scripts/check-data-guard.sh.
- Never commit credentials, database dumps (
- Python Tooling:
- Dependency management and virtual environments are managed exclusively via
uv(uv sync,uv run pytest).
- Dependency management and virtual environments are managed exclusively via