Files
Jon Chery 5b99bbd2e8 docs(P00): create v0.11 phase plan (8 waves, 23 phases, 20 new REQs)
PLAN stage: 8-wave structure covering 23 phases. Wave 0 (P00 cache), Wave 1 (P01/P01.5/P02 observability+identity), Wave 2 (P03/P15.5 security+ingress hybrid+doctor mTLS), Wave 3 (P04/P06 backup+alloc history+logs), Wave 4 (P05/P07 drain+migrate+recovery), Wave 5 (P10/P11/P12 txn plane+drift+lint+verify), Wave 6 (P09/P13 aggregator+ns), Wave 7 (P14a/b/c migration+upgrade+rotate-lead), Wave 8 (P08/P15/P16 integration+docs+ship). P10 is the largest phase; grill may split into P10a/P10b.

---ci---
project: orca
phase: 0
milestone: v0.11
status: plan
---/ci---
2026-08-07 03:39:27 +00:00

361 lines
25 KiB
Markdown

# Plan: v0.11 Production Hardening
## Milestone: v0.11 — Production Hardening
- **Type**: feature (multiple `feat` phases)
- **Tags**: `v0.10.0` (P0) → `v0.10.1``v0.10.20` (P00…P15.5) → `v0.10.21` (P16 = v0.11.0 milestone release)
- **Branch**: `milestone/v0.11-production-hardening`
- **New rules adopted**: R-017 (ingress hybrid), R-018/R-019/R-020 (drift detection)
- **New decisions**: D-215…D-237 (23 net-new, no collisions)
- **New REQs**: REQ-099…REQ-118 (20 net-new; REQ count 98→118)
## Wave ordering
### Wave 0 — Foundation (serial)
- **P00** — CLI cache layer (R-008). Unblocks all subsequent CLI commands that need cached reads.
### Wave 1 — Observability + identity (serial, gate-heavy)
- **P01** — Metrics endpoint (hand-rolled text exposition). Unblocks `orca doctor mTLS` live probe (C5).
- **P01.5** — SPIFFE SVID minting spike (**gate C-08** — if spike fails, fall back to mTLS identity). Gates P02 ACL.
- **P02** — ACL (SPIFFE + token identities). Depends on P01.5.
### Wave 2 — Security + secrets (serial)
- **P03** — Secrets subsystem (REQ-080; **gate C-19** threat model).
- **P15.5** — Threat model + security review (**gate C-19**) + **ingress hybrid (R-017; REQ-099..REQ-102)** + **`orca doctor mTLS` (REQ-118)**. Per Q3=A, ingress folds in here. This phase grows ~30% but stays one phase.
### Wave 3 — Data durability (serial)
- **P04** — Backup/restore (tar + signed). Unblocks P07 recovery.
- **P06** — Alloc history (CLI-side SQLite; R-008 cache DB) + **`orca logs --all-nodes --since` (REQ-117)**. The logs command uses the alloc-history cache DB.
### Wave 4 — Lifecycle (serial)
- **P05** — Drain + daemon drain-and-stop (REQ-061) + **`orca job migrate --to` (REQ-116; C3=drain+reschedule composite)**. Migrate composes P05 drain + P06 alloc history.
- **P07** — Recovery (`orca restore`). Depends on P04 backup.
### Wave 5 — Transactional plane (serial, the big one)
- **P10** — Transactional plane (REQ-075, REQ-079; **gate C-09**) + **drift detection (R-018/R-019/R-020; REQ-103..REQ-113)**. This is the largest phase. **Grill may split into P10a (txn plane) + P10b (drift) if vertical slice is too large.**
- **P11** — `orca job lint` (REQ-084). Depends on P10 txn plane for dry-run validation.
- **P12** — `orca job verify` (dry-run txn through lead). Depends on P10.
### Wave 6 — Namespace + aggregation (parallel)
- **P09** — Collector + aggregator (opt-in; **gates C-11, C-12, C-14**) + **drift-event aggregation extension (REQ-107, D-237)**. The aggregator timer is extended to pull drift-events/ and remediate. C-11 watchdog monitors this timer.
- **P13** — `orca ns` subcommands (full surface) + deprecation warnings (REQ-068).
### Wave 7 — Migration (serial, gate-heavy)
- **P14a** — v0.8→v1.0 data migration (REQ-066; **gate C-07**) + **`orca upgrade --to-vX` (REQ-115; C2=thin wrapper, handles R-017 binding cutover)**.
- **P14b** — Daemon cutover + running-allocation adoption + **`orca cluster rotate-lead` (REQ-114)**.
- **P14c** — Mixed-version tolerance + no-orca-on-server enforcement (REQ-065, REQ-086; implements C-13).
### Wave 8 — Integration + docs + ship (serial)
- **P08** — Integration tests (expand hermetic harness, REQ-087) + **drift-detection integration tests (auto-remediation success, NFS fallback, rate-limit cooldown, secret exclusion)**.
- **P15** — README quickstart (REQ-089; **Q5=A Nomad-inspired framing, honest-trade-offs table from doc 3**). All cited CLI commands must exist by this phase.
- **P16** — Final review + ship + audit — **v0.11.0 milestone release**.
## Phase task tables
### Phase P00 — CLI cache layer (Wave 0)
**REQs**: R-008 (cache floor)
**Persona**: backend-engineer
**Territory**: `internal/cache/`, `internal/store/orca_cache.go`
**Vertical slice**: a CLI command that reads cached state → a cache-hit returns in <1ms, a cache-miss populates from the lead.
| Task | Description | REQ |
|------|-------------|-----|
| P00-T1 | `internal/cache/` package: `orca_cache` SQLite schema (per-class TTLs), `Get(class, key)`, `Set(class, key, val, ttl)`, `Invalidate(class)`; stdlib `database/sql` + modernc/sqlite | R-008 |
| P00-T2 | Wire cache into `orca node list`, `orca job list`, `orca ns list` (read path only; writes bypass cache) | R-008 |
| P00-T3 | `orca cache show` / `orca cache invalidate` CLI for debugging | R-008 |
| P00-T4 | Tests: cache-hit/miss/invalidate/TTL-expiry; bench <1ms cache-hit | R-008 |
**Must-haves**: cache-hit <1ms; TTL-based invalidation; CLI commands use cache on read path.
### Phase P01 — Metrics endpoint (Wave 1)
**REQs**: (new; metrics text exposition)
**Persona**: backend-engineer
**Territory**: `internal/cli/metrics.go`, `internal/transport/metrics.go`
**Vertical slice**: `curl localhost:9100/metrics` → prometheus text exposition.
| Task | Description | REQ |
|------|-------------|-----|
| P01-T1 | `internal/transport/metrics.go`: hand-rolled Prometheus text exposition (no client_golang dep); counters for txns applied/drifted/remediated; gauges for peers/nodes/allocs | new |
| P01-T2 | `orca daemon --metrics :9100` flag (or sidecar listener); `/metrics` endpoint | new |
| P01-T3 | Tests: exposition format validity; counter increments on txn apply | new |
**Must-haves**: `/metrics` returns valid Prometheus text; no client_golang dependency.
### Phase P01.5 — SPIFFE SVID minting spike (Wave 1, gate C-08)
**REQs**: REQ-076
**Persona**: security-engineer
**Territory**: `internal/identity/spiffe.go`
**Gate**: C-08 — if spike fails, fall back to mTLS identity (decision recorded).
| Task | Description | REQ |
|------|-------------|-----|
| P01.5-T1 | Spike: mint a SPIFFE SVID via step-ca; verify URI SAN format (`spiffe://orca.local/ns/<ns>/sa/<sa>/<alloc-id>`) | REQ-076 |
| P01.5-T2 | Decision record: if spike passes, proceed to P02 with SPIFFE; if fails, fall back to mTLS identity + record in PROJECT.md | REQ-076 |
**Must-haves**: spike passes or fails with a recorded decision; C-08 gate cleared.
### Phase P02 — ACL (Wave 1)
**REQs**: (new; ACL with SPIFFE + token identities)
**Persona**: backend-engineer + security-engineer
**Territory**: `internal/acl/`, `internal/cli/acl.go`
**Depends on**: P01.5 (SPIFFE or mTLS fallback)
| Task | Description | REQ |
|------|-------------|-----|
| P02-T1 | `internal/acl/` package: identity → permissions mapping; SPIFFE URI → namespace scope; token identities for operators | new |
| P02-T2 | `orca acl` CLI: `grant`, `revoke`, `list`, `check`; scoped to namespace paths per R-002 | new |
| P02-T3 | Tests: SPIFFE identity grants ns-scoped access; token grants operator-scoped access; deny by default | new |
**Must-haves**: deny-by-default; SPIFFE URI maps to namespace; tokens for operator access.
### Phase P03 — Secrets subsystem (Wave 2, gate C-19)
**REQs**: REQ-080
**Persona**: security-engineer
**Territory**: `internal/secrets/`, `internal/cli/secrets.go`
**Gate**: C-19 (threat model must land in P15.5; P03 implements the crypto)
| Task | Description | REQ |
|------|-------------|-----|
| P03-T1 | `internal/secrets/` package: AES-256-GCM encrypt/decrypt with master.key (R-011); per-line nonce; `LoadCredential=` integration | REQ-080 |
| P03-T2 | `orca secrets` CLI: `set`, `get`, `rotate`, `list`; scoped to namespace `.env.secrets` | REQ-080 |
| P03-T3 | Tests: encrypt/decrypt round-trip; rotation re-encrypts; master.key 0600 enforced | REQ-080 |
**Must-haves**: AES-256-GCM; per-line nonce; master.key 0600; `LoadCredential=` integration.
### Phase P15.5 — Threat model + ingress hybrid + doctor mTLS (Wave 2, gate C-19)
**REQs**: REQ-099, REQ-100, REQ-101, REQ-102, REQ-118; C-19
**Persona**: security-engineer + network-engineer + backend-engineer
**Territory**: `internal/emitter/nft.go`, `internal/emitter/traefik.go`, `internal/cli/nft.go`, `internal/cli/doctor_mtls.go`, threat-model doc
**Vertical slice**: `orca init` on a fresh cluster → Traefik binds 127.0.0.1:8443 + nft DNAT → `orca doctor nft` + `orca doctor mTLS` pass.
| Task | Description | REQ |
|------|-------------|-----|
| P15.5-T1 | `internal/emitter/nft.go`: nftables emitter renders `/etc/nftables.d/orca.nft` (DNAT :443→127.0.0.1:8443, :80→127.0.0.1:8080; SYN-flood filter; `ora_rl` rate-limit meter; `orca_trusted_probes` set); idempotent `nft -f` apply; atomic rule-set swap (D-217, D-218) | REQ-099 |
| P15.5-T2 | `internal/emitter/traefik.go` update: static config `address: 127.0.0.1:8443` (default); `--public-binding=traefik-on-public-ip` opt-out emits `:443`; certs/mTLS/dynamic config unchanged (D-220, D-216) | REQ-100 |
| P15.5-T3 | `orca doctor nft`: checks table exists, DNAT rules present, rate-limit meter present, file parses (`nft -c -f`), hash matches latest txn (D-221, D-226) | REQ-101 |
| P15.5-T4 | `orca nft` CLI: `show [--peer]`, `diff --against <txn-id>`, `doctor`, `country block add <cc-list>`, `rate limit set --rate N/s` (D-223, D-222) | REQ-102 |
| P15.5-T5 | `orca doctor mTLS`: trust-chain verification (CA → server cert → workload SVIDs exist + not expired) + live mTLS handshake probe to each peer (reuses P01 metrics endpoint + P01.5 SPIFFE infra); C5=both | REQ-118 |
| P15.5-T6 | Threat model doc: covers R-017 ingress trust boundary, R-020 drift deadlock, secret exclusion D-234, `orca` system user blast radius; clears C-19 | C-19 |
| P15.5-T7 | Tests: nft emitter output validates (`nft -c -f`); Traefik static config has `127.0.0.1:8443`; doctor nft passes on a clean cluster; doctor mTLS passes with valid chain + live probe | REQ-099..102, 118 |
**Must-haves**: fresh `orca init` produces hybrid binding; `orca doctor nft` + `orca doctor mTLS` pass; C-19 cleared; opt-out flag works.
### Phase P04 — Backup/restore (Wave 3)
**REQs**: (new; tar + signed backup)
**Persona**: backend-engineer
**Territory**: `internal/backup/`, `internal/cli/backup.go`
| Task | Description | REQ |
|------|-------------|-----|
| P04-T1 | `internal/backup/` package: tar `ORCA_HOME` (excl. secrets? or incl. with master.key?); sign with master.key (HMAC-SHA256); `orca backup --out snap.tar.gz` | new |
| P04-T2 | `orca restore --in snap.tar.gz` (P07 owns the full recovery; P04 owns the backup format + signing) | new |
| P04-T3 | Tests: backup→restore round-trip; signature verification; backup excludes `/run/orca/*` | new |
**Must-haves**: backup is a signed tarball; restore verifies signature.
### Phase P06 — Alloc history + logs --all-nodes (Wave 3)
**REQs**: REQ-071 (cache DB), REQ-117
**Persona**: backend-engineer
**Territory**: `internal/store/alloc_history.go`, `internal/cli/logs.go`
**Depends on**: P00 (cache DB)
| Task | Description | REQ |
|------|-------------|-----|
| P06-T1 | `internal/store/alloc_history.go`: CLI-side SQLite retention for alloc state transitions; TTL-based eviction | REQ-071 |
| P06-T2 | `orca logs --all-nodes --since 5m`: aggregates journald logs across peers via SSH fanout; `iter.Seq` streaming (D-017); `--since` duration; `--all-nodes` fans out (Q2=C) | REQ-117 |
| P06-T3 | Tests: alloc history retention/eviction; logs --all-nodes fans out + streams + cancels via ctrl-c | REQ-071, 117 |
**Must-haves**: alloc history retained in cache DB; `--all-nodes` aggregates across peers with streaming.
### Phase P05 — Drain + migrate (Wave 4)
**REQs**: REQ-061, REQ-116
**Persona**: backend-engineer
**Territory**: `internal/cli/drain.go`, `internal/cli/migrate.go`
**Depends on**: P06 (alloc history for migrate)
| Task | Description | REQ |
|------|-------------|-----|
| P05-T1 | `orca node drain <host>`: drain a node (stop new allocs; migrate existing per `update` config); daemon drain-and-stop (REQ-061) | REQ-061 |
| P05-T2 | `orca job migrate <name> --to <node>`: drain+reschedule composite (C3=a); uses P05 drain + P06 alloc history; idempotent (Q2=C) | REQ-116 |
| P05-T3 | Tests: drain stops new allocs; migrate reschedules to target node; daemon drain-and-stop works | REQ-061, 116 |
**Must-haves**: drain stops new allocs + migrates existing; migrate reschedules to a specific node.
### Phase P07 — Recovery (Wave 4)
**REQs**: (new; `orca restore`)
**Persona**: backend-engineer
**Territory**: `internal/cli/restore.go`
**Depends on**: P04 (backup format)
| Task | Description | REQ |
|------|-------------|-----|
| P07-T1 | `orca restore --in snap.tar.gz`: verify signature, extract, reconcile with live state (don't clobber running allocs unless `--force`) | new |
| P07-T2 | Tests: restore from signed backup; signature mismatch fails; `--force` clobbers running allocs | new |
**Must-haves**: restore verifies signature; doesn't clobber running allocs without `--force`.
### Phase P10 — Transactional plane + drift detection (Wave 5, gate C-09)
**REQs**: REQ-075, REQ-079, REQ-103..REQ-113; C-09; R-018/R-019/R-020
**Persona**: backend-engineer + devops-engineer + security-engineer
**Territory**: `internal/drift/`, `internal/cli/drift.go`, `scripts/orca-drift-notify.sh`, `scripts/orca-remediate.sh`, `internal/emitter/systemd.go` (Path units), `internal/paths/paths.go`
**Vertical slice**: operator edits `/etc/traefik/dynamic/orca.yml` on a peer → drift detected in ~10s → auto-remediated → `orca drift watch` shows the event.
**Note**: This is the largest phase. **Grill may split into P10a (txn plane, REQ-075/079) + P10b (drift detection, REQ-103..113) if the vertical slice is too large.**
| Task | Description | REQ |
|------|-------------|-----|
| P10-T1 | `internal/txn/` package: render txn bundle (tarball + apply.sh + verify.sh) on operator host; SCP to lead's `/run/orca/txns/<txn-id>/`; lead's systemd timer runs `apply.sh` idempotently; CLI polls txn status via SSH (REQ-075, C-09) | REQ-075 |
| P10-T2 | `scripts/orca-pull.sh` with C-09 failure contract: idempotent re-run, bounded retry, deterministic state, structured syslog (C-09) | REQ-079, C-09 |
| P10-T3 | `internal/drift/` package: `Detector` interface (`Watch`, `Aggregate`, `Remediate`, `Acknowledge`), `Event`, `Config`, `PathSpec`, `RemediationPolicy`; `iter.Seq2[Event, error]` (D-017); `signal.NotifyContext` (D-023) (D-236) | REQ-103 |
| P10-T4 | `orca drift` CLI tree: `watch [--interval=2s] [--paths=...] [--json]`, `show [--peer]`, `acknowledge <peer> <path>`, `remediate <peer> <path> [--force]`, `config show`, `config validate` (D-236) | REQ-104 |
| P10-T5 | systemd Path unit emitter: for each critical path, emit `orca-drift-<name>.path` (`PathChanged=`, `RateLimitIntervalSec=1s`, `RateLimitBurst=5`) + `orca-drift-<name>.service` (`Type=oneshot`, `ExecStart=/usr/local/bin/orca-drift-notify.sh %f`, `User=orca`, security hardening); R-001-clean (D-227, D-228) | REQ-105 |
| P10-T6 | `scripts/orca-drift-notify.sh`: receives changed path as `$1`, computes sha256 (or "DELETED"), writes event JSON to `/etc/orca/state/drift-events/<event-id>.json`; stateless, idempotent; `flock` (D-228) | REQ-106 |
| P10-T7 | `scripts/orca-remediate.sh`: re-pushes latest applied txn's per-peer render tree via rsync, runs peer-side applier; 5-min cooldown per path applies ONLY on successful remediation (C4 refinement); transient failures retry next tick (D-231, D-232) | REQ-108 |
| P10-T8 | Drift cadence config in `config.md` (`kind: ClusterConfig`): `drift.polling`, `drift.paths.{critical,standard,excluded}`, `drift.remediate`; critical defaults: Traefik dynamic, nftables, sudoers, orca-alloc services; secrets + `/run/orca/*` + drift-events dir excluded (R-018, D-231, D-234) | REQ-109 |
| P10-T9 | Pre-flight consistency gate in `orca-pull.sh`: refuses new txns if drift detected on target peer/namespace; `--force` overrides; per-namespace scoping (drifted peer in ns-A doesn't block ns-B) (R-020, Q4=A) | REQ-110 |
| P10-T10 | `orca` system user on peers: peer-setup emits `useradd -r orca` (system account, no login shell); `orca-drift-*.service` runs as `User=orca`; idempotent (REQ-111) | REQ-111 |
| P10-T11 | NFS detection at peer setup: `orca node join` detects NFS mounts on orca state dirs; disables Path units for NFS paths; logs warning; falls back to polling (D-233) | REQ-112 |
| P10-T12 | `orca job restart <name>`: restarts an alloc to pick up EnvironmentFile drift; normal allocation lifecycle (not file-level remediation) (D-235) | REQ-113 |
| P10-T13 | Tests: txn apply idempotent + retry on failure; drift detected via Path unit ~10s; auto-remediation re-pushes; cooldown prevents loop; `--force` overrides pre-flight gate; per-ns scoping isolates drift; NFS fallback; secret exclusion | REQ-075..113 |
**Must-haves**: txn apply idempotent (C-09); drift detected ~10s on critical paths; auto-remediation with cooldown; `--force` + per-ns override; `orca` user created; NFS detection works.
### Phase P11 — `orca job lint` (Wave 5)
**REQs**: REQ-084
**Persona**: backend-engineer
**Territory**: `internal/cli/job_lint.go`
**Depends on**: P10 (txn plane for dry-run validation)
| Task | Description | REQ |
|------|-------------|-----|
| P11-T1 | `orca job lint <spec.md>`: validates jobspec schema (kinds, blocks, CEL constraints, body preservation); reports errors with line numbers | REQ-084 |
| P11-T2 | Tests: valid spec passes; invalid spec reports errors with line numbers | REQ-084 |
**Must-haves**: lint catches schema errors; reports line numbers.
### Phase P12 — `orca job verify` (Wave 5)
**REQs**: (new; dry-run txn through lead)
**Persona**: backend-engineer
**Territory**: `internal/cli/job_verify.go`
**Depends on**: P10 (txn plane)
| Task | Description | REQ |
|------|-------------|-----|
| P12-T1 | `orca job verify <spec.md>`: dry-run txn through lead (no apply); reports what would change (allocs created/removed, config files written) | new |
| P12-T2 | Tests: verify reports planned changes without applying; fails on pre-flight drift | new |
**Must-haves**: verify is a true dry-run (no side effects); reports planned changes.
### Phase P09 — Collector + aggregator + drift-event aggregation (Wave 6)
**REQs**: (existing collector/aggregator) + REQ-107; C-11, C-12, C-14
**Persona**: devops-engineer + backend-engineer
**Territory**: `scripts/orca-aggregate.sh`, `internal/cli/collector.go`
**Gates**: C-11 (watchdog), C-12 (opt-in), C-14 (syncthing)
| Task | Description | REQ |
|------|-------------|-----|
| P09-T1 | `scripts/orca-aggregate.sh`: existing 10s cadence (C-11); now also rsyncs each peer's `/etc/orca/state/drift-events/`, validates event hashes against applied txn manifest, triggers `orca-remediate.sh` for auto-remediable paths, consumes (deletes) event files on peers (D-229, D-237) | REQ-107 |
| P09-T2 | Lead-side watchdog meta-timer (C-11): fires on `orca-pull.sh` starvation (>N seconds without successful run); structured alert path | C-11 |
| P09-T3 | `orca collector` CLI: opt-in collector for per-peer state snapshots; writes to `cluster.json` (C-12 opt-in) | C-12 |
| P09-T4 | Tests: aggregator pulls drift-events + triggers remediation; watchdog fires on starvation; collector opt-in | REQ-107, C-11 |
**Must-haves**: aggregator pulls drift-events + remediation; watchdog fires on starvation; collector opt-in.
### Phase P13 — `orca ns` subcommands + deprecation warnings (Wave 6)
**REQs**: REQ-068
**Persona**: lead-developer
**Territory**: `internal/cli/ns.go`
| Task | Description | REQ |
|------|-------------|-----|
| P13-T1 | Full `orca ns` surface: `list`, `create`, `delete`, `inspect`, `validate`, `inherit`, `set-constraint` (per R-002 namespace-as-path) | REQ-068 |
| P13-T2 | Depprecation warnings: `orca daemon`, `orca cert` (v0.8 mTLS path), `.hcl` jobspec → printed on use; `--no-deprecation-warnings` suppresses (REQ-068) | REQ-068 |
| P13-T3 | Tests: all ns subcommands work; deprecation warnings fire on deprecated surface | REQ-068 |
**Must-haves**: full ns surface; deprecation warnings on deprecated surface.
### Phase P14a — v0.8→v1.0 data migration + `orca upgrade` (Wave 7, gate C-07)
**REQs**: REQ-066; C-07; REQ-115
**Persona**: data-engineer + backend-engineer
**Territory**: `internal/migration/`, `internal/cli/upgrade.go`
**Gate**: C-07 (CA migration spec)
| Task | Description | REQ |
|------|-------------|-----|
| P14a-T1 | `internal/migration/` package: v0.8 flat layout → v0.9/v0.11 multi-namespace layout; schema migration (0006→next); CA migration per spec (C-07) | REQ-066 |
| P14a-T2 | `orca upgrade --to-vX`: thin wrapper (C2=a) around `install.sh` + `orca restore`; handles R-017 Traefik binding cutover (`:443``127.0.0.1:8443`) for existing v0.9/v0.10 clusters (Q2=C) | REQ-115 |
| P14a-T3 | Tests: v0.8 layout migrates to v0.11 layout; `orca upgrade` handles binding cutover; idempotent | REQ-066, 115 |
**Must-haves**: v0.8 data migrates to v0.11; `orca upgrade` handles binding cutover; C-07 cleared.
### Phase P14b — Daemon cutover + rotate-lead (Wave 7)
**REQs**: (existing daemon cutover) + REQ-114
**Persona**: backend-engineer + lead-developer
**Territory**: `internal/cli/daemon.go`, `internal/cli/rotate_lead.go`
| Task | Description | REQ |
|------|-------------|-----|
| P14b-T1 | Daemon cutover: `orca daemon` becomes `drain-and-stop` (REQ-061 from P05); running-allocation adoption (orphaned allocs adopted by SSH-push path) | existing |
| P14b-T2 | `orca cluster rotate-lead`: moves cluster CA + lead state to a new bare-Linux peer (R-003); workloads keep running (certs distributed); SSH key rotation; idempotent (Q2=C) | REQ-114 |
| P14b-T3 | Tests: daemon cutover adopts running allocs; rotate-lead moves CA + workloads keep running | REQ-114 |
**Must-haves**: daemon cutover adopts running allocs; rotate-lead moves CA without downtime.
### Phase P14c — Mixed-version tolerance (Wave 7)
**REQs**: REQ-065, REQ-086; C-13
**Persona**: backend-engineer
**Territory**: `internal/transport/`, `internal/cli/`
| Task | Description | REQ |
|------|-------------|-----|
| P14c-T1 | Mixed-version tolerance: lead and peers can run different orca versions during upgrade window; no-orca-on-server enforcement (R-001) | REQ-065, REQ-086 |
| P14c-T2 | Tests: mixed-version cluster operates; orca-on-server detected + refused | REQ-065, 086 |
**Must-haves**: mixed-version tolerance during upgrade; R-001 enforced.
### Phase P08 — Integration tests + drift-detection tests (Wave 8)
**REQs**: REQ-087
**Persona**: devops-engineer
**Territory**: `tests/integration/`, `scripts/tests/`
| Task | Description | REQ |
|------|-------------|-----|
| P08-T1 | Expand hermetic test harness: multi-peer setup, txn apply, drift injection, remediation verification (REQ-087) | REQ-087 |
| P08-T2 | Drift-detection integration tests: auto-remediation success (edit Traefik config → detect ~10s → remediated); NFS fallback (NFS mount → Path units disabled → polling); rate-limit cooldown (repeated drift → cooldown blocks loop); secret exclusion (edit `/etc/orca/credentials/*` → no drift event) | REQ-087 |
| P08-T3 | Tests pass in CoreCI `integration` pipeline (nft exclusively, D-224) | REQ-087 |
**Must-haves**: integration tests cover drift detection; pass in CoreCI.
### Phase P15 — README quickstart (Wave 8)
**REQs**: REQ-089
**Persona**: lead-developer + docs-engineer (phase-specific)
**Territory**: `README.md`
**Framing**: Q5=A (Nomad-inspired, OS-as-cluster; honest-trade-offs table from doc 3; Proxmox as one node type, not the identity)
| Task | Description | REQ |
|------|-------------|-----|
| P15-T1 | README.md: status line (v0.11 complete, v1.0 UAT-gated); install example; subcommand table expanded to ALL v0.11 commands (incl. drift, nft, migrate, rotate-lead, upgrade, logs --all-nodes, doctor mTLS); honest-trade-offs table (doc 3 §4.6); Nomad-inspired framing (Q5=A) | REQ-089 |
| P15-T2 | Verify all cited CLI commands exist in `internal/cli/` (C-22-style grounding gate) | REQ-089 |
**Must-haves**: README subcommand table matches `internal/cli/` exactly; honest-trade-offs table present; Nomad-inspired framing.
### Phase P16 — Final review + ship + audit (Wave 8)
**REQs**: all (REQ-099..REQ-118 + existing v0.11 REQs)
**Persona**: lead-developer
**Vertical slice**: milestone complete → merged to main, tagged, released.
| Task | Description | REQ |
|------|-------------|-----|
| P16-T1 | Code review across all phases; auto-apply P0 fixes, flag P1+ for post-hoc | all |
| P16-T2 | Audit: reconstruction test (git log matches `.ciagent/`), file discipline, branch hygiene, commit discipline | all |
| P16-T3 | Milestone ship: merge phase/16 → milestone → main; tag `v0.10.21` (= v0.11.0 milestone release); release with Linux binary asset; delete all milestone branches | all |
| P16-T4 | Complete milestone: mark all v0.11 REQs complete in REQUIREMENTS.md; mark v0.11 complete in ROADMAP.md; clear checkpoint | all |
**Must-haves**: milestone merged to main; release carries Linux binary; all REQs marked complete; checkpoint cleared.
## Risks
- **R1: P10 sizing** — P10 is the largest phase (txn plane + drift detection, 13 tasks). Mitigation: grill may split into P10a/P10b. The plan is structured so P10a (T1-T2, txn plane) and P10b (T3-T13, drift) are separable.
- **R2: R-020 deadlock** — hard-gate refusal could block all new txns if a peer is permanently drifted on a require_approval path. Mitigation: `--force` + per-ns scoping (Q4=A); documented in C-09 failure contract.
- **R3: Ingress default migration** — existing v0.9/v0.10 clusters run Traefik on `:443`. R-017 makes `127.0.0.1:8443` + nft the default. Mitigation: `orca upgrade` (REQ-115, P14a) handles the binding cutover.
- **R4: `orca` system user** — creating a system user on every peer is a new operational requirement. Mitigation: peer-setup emits `useradd -r orca` idempotently (REQ-111); documented in P10.
- **R5: Scope ceiling** — v0.11 stays at 23 phases (no new phases), but P09/P10/P15.5 grow substantially. Mitigation: wave ordering isolates the largest work (Wave 5) so it can be split without affecting other waves.