# DEPLOYMENT_SPEC.md

## 1. Environments

| Env | Where | Purpose | URL |
|---|---|---|---|
| **dev** | Local (Docker compose dev stack) | Inner loop, HMR | localhost:4321 |
| **staging** | Same server, separate compose project | Pre-release verification on real infra (headers, proxy, relay SMTP against a mailbox test) | `staging.cesnimda.co.uk` (basic-auth gated at the proxy, `noindex` everywhere) |
| **prod** | Server, compose stack `resumesite` | Live | `cesnimda.co.uk` |

Staging exists chiefly for the **cutover** and for occasional risky changes; day-to-day content edits may go dev → prod (single-owner pragmatism, gates make it safe).

## 2. Pipeline (Gitea Actions, all self-hosted)

```
push / PR to main
  ├─ lint + typecheck + astro check
  ├─ unit (vitest: i18n parity, slug-map bijection, JSON-LD builders)
  ├─ build site (static)  ──► Playwright e2e vs built output + relay container
  │                        ──► axe a11y scan (both locales, both themes)
  │                        ──► Lighthouse CI budgets (fail = red)
  ├─ build + push images  :sha and :latest  → Gitea registry        (main only)
  └─ deploy job (main only, manual approval toggle):
        ssh to server → compose pull → compose up -d → smoke check
        (curl /, /no/, /api/contact healthz, spot-check headers) → notify
```

- **Rollback:** redeploy previous `:sha` tag (one command, documented runbook); HTML no-cache means rollback is immediate. Registry retains last N images.
- **Content-only changes** ride the same pipeline — content errors are build errors by design (schema + parity tests), which is the safety that replaces CMS preview.
- Scheduled workflow (weekly): external link check (DATA_MODEL §8.6) + Lighthouse drift report + Renovate PRs.

## 3. Cutover plan (WordPress → new site)

1. Pre-launch: staging fully verified (both locales, CV downloads, form → real mailbox, redirects incl. `/Linkedin`, 410 map, OG previews via LinkedIn/Slack debuggers, Search Console fetch).
2. URL inventory + redirect/410 map finalised (SEO_SPEC §6).
3. Cutover = move the `cesnimda.co.uk` Traefik Host router from the WordPress container to the `site` container (a label change + `compose up -d`; WP left running dark for 2 weeks as instant rollback).
4. Post-launch checklist: submit sitemaps, watch Search Console coverage + relay logs for form spam patterns, then decommission WP + its PHP/MySQL surface (attack-surface win worth noting on the colophon).

## 4. Operational guardrails

- **Uptime:** monitored by existing homelab monitoring (add checks: `/` 200 + content match, `/no/` 200, relay healthz); alerting via his current channel.
- **TLS:** unchanged at the existing edge proxy (already terminating for Gitea etc.).
- **Logs:** site-nginx access logs minimal (no query bodies, standard rotation); relay logs metadata only (timestamp, IP hash, outcome) — privacy stance documented on colophon.
- **CV updates:** replace PDF in repo → pipeline redeploys; stable URLs mean previously shared links always fetch the newest CV — an explicit product decision.
- **Secrets rotation:** SMTP credential rotation runbook (env file update + `compose up -d relay`), noted in deploy README.

## 5. Definition of Done for Phase 3 (deployment perspective)

1. All CI gates green on main; images in registry.
2. Staging sign-off checklist complete (incl. native-Norwegian content review — I18N_SPEC §6).
3. Cutover executed; WP dark-standby; monitoring green 48h.
4. Search Console: sitemaps accepted, no coverage regressions after 4 weeks (watch item, not blocker).
5. Colophon page published describing the stack honestly (the P2 hook).
