# Phase 1I — Deploy pipeline + runbook contracts

**Parent plan:** `2026-04-14-phase-1-foundation-master.md`, section "1I — Deploy pipeline + runbook contracts"
**Date:** 2026-04-14

---

## Overview

This sub-plan delivers the deploy pipeline contracts: Docker images for both build targets (standalone SSR and remote static), health endpoint, graceful shutdown handler, CI workflow template, and an operational runbook.

## Constraints

- Do NOT modify `modern.config.ts` (middleware registration is a future integration step)
- Health and shutdown modules export factory functions, not auto-registered middleware
- Coexist with the existing ASP.NET `Dockerfile` and `Dockerfile.local` (write `Dockerfile.react` and `Dockerfile.remote`)
- Do NOT touch `ClientApp/`, ASP.NET files, or `wwwroot/`

---

## Tasks

### Task 1 — `Dockerfile.react` (standalone SSR image)

**File:** `Dockerfile.react` (repo root)

Multi-stage build:
1. Stage 1 (`deps`): Node 24 base, enable corepack pnpm, copy `package.json` + `pnpm-lock.yaml`, run `pnpm install --frozen-lockfile`
2. Stage 2 (`build`): copy `src/`, config files, run `pnpm build:standalone`
3. Stage 3 (`runtime`): Node 24 slim, copy `dist/standalone/`, entrypoint `node dist/standalone/index.js`

**Exit gate:** File exists, valid Dockerfile syntax.

### Task 2 — `Dockerfile.remote` (nginx static image)

**File:** `Dockerfile.remote` (repo root)

Multi-stage build:
1. Stage 1 (`deps`): same as Task 1
2. Stage 2 (`build`): copy source, run `pnpm build:remote`
3. Stage 3 (`runtime`): `nginx:alpine`, copy `dist/remote/` to `/usr/share/nginx/html`, expose port 80

**Exit gate:** File exists, valid Dockerfile syntax.

### Task 3 — Health endpoint (`src/server/routes/health.ts`)

**Files:** `src/server/routes/health.ts`, `src/server/routes/health.test.ts`

Export `healthMiddleware(options)` factory function that returns an Express-style `(req, res, next)` handler. The middleware:
- Pings the upstream API client on each request (with configurable timeout, default 5000ms)
- Tracks last successful ping timestamp
- Returns 200 `{ status: "ok" }` if last success < 60s ago
- Returns 503 `{ status: "degraded", reason: "upstream_unreachable" }` otherwise

**TDD:** Tests mock ApiClient, verify 200/503 responses, verify timeout behavior.

**Exit gate:** `pnpm test` passes health tests.

### Task 4 — Graceful shutdown (`src/server/shutdown.ts`)

**Files:** `src/server/shutdown.ts`, `src/server/shutdown.test.ts`

Export `registerGracefulShutdown(options)` factory function that:
- Registers a SIGTERM handler
- On SIGTERM: calls `server.close()`, waits up to `drainTimeoutMs` (default 30000), flushes logger transport, exits with code 0
- If drain times out, force-exits with code 1

**TDD:** Tests mock `process.on`, `server.close`, `logger` flush; verify shutdown sequence.

**Exit gate:** `pnpm test` passes shutdown tests.

### Task 5 — CI deploy workflow (`.github/workflows/deploy.yml`)

**File:** `.github/workflows/deploy.yml`

Template workflow triggered on push to `main`:
- Checkout, setup Node 24, install pnpm, `pnpm install --frozen-lockfile`
- Build both targets (`pnpm build:both`)
- Build Docker images (`Dockerfile.react`, `Dockerfile.remote`)
- Push to registry (placeholder)
- Deploy to testing environment (placeholder)

**Exit gate:** File exists, valid YAML syntax.

### Task 6 — Operational runbook (`docs/superpowers/phase-1/runbook.md`)

**File:** `docs/superpowers/phase-1/runbook.md`

Covers:
1. Incident response decision tree
2. Canary rollout procedure
3. Rollback procedure (auto + manual)
4. Health-check interpretation
5. Log query cookbook
6. Known-failure playbooks (6 scenarios)

**Exit gate:** File exists, covers all required sections.

---

## Verification

After all tasks:
```bash
pnpm typecheck && pnpm lint && pnpm test
```
