Files
orca/.ciagent/REQUIREMENTS.md
Jon Chery 3be86e6daf
Release / ci (push) Failing after 5m13s
Release / container-orca (push) Has been skipped
Release / container-traefik (push) Has been skipped
fix(P1): Gitea Actions clone auth + .coreci.yml native format rewrite (REQ-183,184)
REQ-183: Fix .gitea/workflows/release.yml — the git clone of the private
coreci repo failed because the clone command had no credentials. The
actions/checkout@v4 step only injects auth for the orca repo. Fix: pass
GITEA_TOKEN env to the Install CoreCI step and embed it in the clone
URL (https://cloudinit-bot:${GITEA_TOKEN}@git.cloudinit.dev/...).

REQ-184: Rewrite .coreci.yml from the invalid pipelines:/steps:/image:/
commands: format to CoreCI's native jobs:/plugin:/invoke:/vars: format
with a proper DAG (needs:). CoreCI's Pipeline struct only recognizes
jobs:/services:/env: top-level keys — unknown fields are silently dropped
by yaml.Unmarshal, producing an empty Jobs map → zero jobs execute.
The rewrite:
- 8 jobs: go-vet → fan-out (verify-reqs, gosec, govulncheck, gitleaks)
  → build → test → release
- plugin: docker://golang:1.25.12 + invoke: on each job (container path
  with shell-isolated fallback — Go is installed on the runner)
- GITEA_TOKEN via vars: with ${{ secrets.GITEA_TOKEN }} (resolved from
  env via CoreCI's secret resolver os.Getenv fallback)
- CI_COMMIT_BRANCH (tag name on tag push) and CI_COMMIT_SHA for version
  injection — no ${VAR} interpolation in YAML fields (shell expansion
  only works inside invoke: via sh -c)
- No apk add (runner is ubuntu, not alpine — uses curl for tool downloads)
- Release job handles duplicate release (ship workflow creates release
  first with title+body; coreci run attaches binary assets later via API
  fallback if tea releases create fails)
- Release job verifies asset count ≥ 2 (REQ-097 gate C-21) with retry

Root cause: all 87 releases in repo history had zero binary assets
because coreci run never executed any jobs (empty Jobs map from the
invalid format) and the Gitea Actions workflow failed before reaching
coreci run (private repo clone had no credentials).

---ci---
project: orca
phase: 1
milestone: v0.16
status: execute
requirements:
  covered: [183, 184]
  partial: []
---/ci---
2026-08-12 21:05:21 +00:00

71 KiB
Raw Permalink Blame History

Requirements: Orca

The canonical requirements table. Each row carries the REQ-ID, the milestone it belongs to, the requirement summary, priority, the phase that addresses it, and the current status. This single table is the source of truth — superseded any per-milestone status tables in earlier versions of this file.

ID Requirement Priority Phase Status
REQ-001 Go 1.25+ toolchain support High v0.1 P01 Complete
REQ-002 CLI-first interface for all operations (single binary) High v0.1 P01 Complete
REQ-003 Offline-first operational mode (no cloud deps) High v0.1 Complete
REQ-004 Basic task deployment (single-node process execution) Medium v0.1 P03 Complete (single-node); multi-node dispatch in v0.2 P02
REQ-005 Local state storage via modernc/sqlite (CGO-free) Medium v0.1 P02 Complete
REQ-006 Security-first audit logging via log/slog High v0.1 P04 Complete
REQ-007 CoreCI full release flow integration via .coreci.yml High v0.1 P06 Complete (per-phase releases)
REQ-008 Structured JSON logging (slog) High v0.1 P05 Complete
REQ-009 HCL/YAML job spec parsing Medium v0.1 P03 Complete
REQ-010 --json output flag for machine consumption High v0.1 P01 Complete
REQ-011 mTLS for inter-node communication Medium v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-012 ~/.orca/config.hcl and /etc/orca/orca.hcl config locations Low v0.1 P01 Complete (CLI uses ~/.orca/ + ORCA_DB env)
REQ-013 Pre-push git hook triggers CoreCI on every push High v0.1 P01 Complete
REQ-014 gosec + govulncheck in CI pipeline High v0.2 P03 Complete (P10 shipped v0.2.3)
REQ-015 MIT LICENSE Low v0.1 P01 Complete
REQ-016 README.md with quickstart Medium v0.1 P01 Complete
REQ-017 context.Context propagation in all I/O High v0.1 Complete
REQ-018 Error wrapping with fmt.Errorf("...: %w", err) High v0.1 Complete
REQ-019 Cobra CLI framework High v0.1 P01 Complete
REQ-020 HCL parser integration (hashicorp/hcl) Medium v0.1 P03 Complete
REQ-021 os/exec with WaitDelay (Go 1.25+) Medium v0.1 P03 Complete
REQ-022 iter.Seq for streaming job lists (Go 1.25+) Low v0.3 P01 Complete (v0.3 P01 shipped v0.3.1)
REQ-023 Self-signed mTLS cert generation Medium v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-024 Makefile with standard targets High v0.1 P01 Complete
REQ-025 Bounded cert rotation history: retain last N=3 server certs per node for rollback Medium v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-026 Trusted-CA fingerprint pinned in config; daemon refuses to start on mismatch High v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-027 govulncheck runs in offline mode in CI (no vuln.go.dev calls; pre-mirrored DB or -format json + jq gate) High v0.2 P03 Complete (P10 shipped v0.2.3)
REQ-028 HCL/YAML schema for NodeCapacity declaration (orca node join flag and/or ~/.orca/node.hcl) High v0.2 P02 Complete (P09 shipped v0.2.2; orca node capacity CLI)
REQ-029 gitleaks baseline file committed to repo to suppress pre-existing .env SHA-1 leak in git history Medium v0.2 P03 Complete (P10 shipped v0.2.3)
REQ-030 --watch output format mode: table (default) vs streaming one-line JSON per event Low v0.3 P01 Complete (v0.3 P01 shipped v0.3.1)
REQ-031 go test -race enabled in CI for all v0.2 packages High v0.2 P01P04 Complete (P10; .coreci.yml test pipeline runs -race)
REQ-032 orca doctor subcommand for diagnostics (CA/cert health, db integrity, peer reachability) Medium v0.2 P01 / v0.3 P02 Complete (cert checks P01 v0.2.1; network + db P02 v0.3.2)
REQ-033 Cert file mode enforcement: 0600 for keys, 0644 for certs (refuses to start on violation) High v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-034 Cert proactive rotation alarm: structured slog WARN 30 days before not_after Medium v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-035 orca cert show redacts private key material from default and --json output High v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-036 Server cert SAN validation: SAN entries (DNS + IP) populated at sign-time; refuses to sign a CSR without them High v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-037 X-Orca-Idempotency-Key header on cross-node POST; dispatcher retries only when header is present Medium v0.2 P02 Complete (P09 shipped v0.2.2; internal/transport/idempotency.go)
REQ-038 Structured slog fields for mTLS failures: event=mtls.handshake, peer, cert_fp, err Medium v0.2 P01 Complete (P01 shipped v0.2.1)
REQ-039 .gitleaks.toml extended with stopwords for test data paths and CA cert PEM blocks Medium v0.2 P03 Complete (P10 shipped v0.2.3)
REQ-040 .golangci.yml unified lint config superseding per-tool invocations Low v0.2 P03 Complete (P10 shipped v0.2.3)
REQ-041 Unified namespace root via ORCA_HOME for all components (db, certs, init, daemon) High v0.5 P1 Complete (P1 shipped v0.4.2)
REQ-042 --system flag selects system-level namespace root /root/.orca High v0.5 P1 Complete (P1 shipped v0.4.2)
REQ-043 install.sh 1-liner pulling release binary from public Gitea URL; user-level default, --system for system-level High v0.5 P2 Complete (P2 shipped v0.4.3)
REQ-044 install.sh in-place update preserves config/state; idempotent re-run High v0.5 P2 Complete (P2 shipped v0.4.3)
REQ-045 Gitea repo + releases publicly accessible (unauthenticated download) High v0.5 P0 Complete (P0 ship: repo + org visibility public)
REQ-046 Docker image published to Gitea container registry per release Medium v0.5 P3 Complete (P3 shipped v0.4.4)

v0.1 Milestone Summary

Status: Complete — all 6 phases shipped (P00P06) plus P07 backfill, 4-layer verification passed at every phase, tagged v0.2.0 per run.md versioning logic (next-minor after all feature-patches v0.1.1..v0.1.7 ship).

Coverage: 21/24 v0.1-declared requirements complete by v0.1 ship; the 3 deferred (REQ-011, REQ-014, REQ-022, REQ-023) all moved to v0.2. Plus REQ-025..REQ-040 (16 net-new) added by v0.2 IDEATE stage.

v0.2 Milestone Summary

Status: Functionally Complete (pending merge to main) — P08 (mTLS), P09 (scheduling), P10 (security scan) all shipped to the milestone/v0.2-networking-observability-security branch as v0.2.1, v0.2.2, v0.2.3. The milestone branch has NOT been merged to main yet. REQ-022/030 (iter.Seq streaming) and REQ-032 (doctor network/db) were deferred to v0.3.

v0.3 Milestone Summary

Status: Complete — P01 (iter.Seq streaming, v0.3.1) and P02 (doctor network+db, v0.3.2) both shipped. REQ-022, REQ-030, REQ-032 all complete. Re-init SPECIFY audit confirmed all other v0.2-deferred REQs (014, 027, 028, 029, 031, 037, 039, 040) already shipped in P08-P10.

Deferred to v0.4

  • pprof endpoint on orca daemon (idea I-308, 0.70 confidence): deferred to keep v0.2 lean; revisit in v0.3 once P02's dispatcher is stable.

v0.5 Milestone Summary

Status: Complete — all 3 execution phases + final review shipped. P0 (v0.4.1), P1 (v0.4.2), P2 (v0.4.3), P3 (v0.4.4), P4 final (v0.4.5). REQ-041..046 all complete. Repo + releases publicly accessible (REQ-045). Docker image published to Gitea container registry (REQ-046).

  • P0 (v0.4.1): pre-execution + repo visibility flipped to public (REQ-045).
  • P1 (v0.4.2): namespace unification — ORCA_HOME + --system (REQ-041/042).
  • P2 (v0.4.3): install.sh 1-liner + in-place update (REQ-043/044) + README quickstart (REQ-016).
  • P3 (v0.4.4): Docker release — distroless image + Gitea container registry (REQ-046).
  • P4 (v0.4.5): final review + audit + milestone release.

v0.6 Requirements — Node Bootstrap & Proxmox

ID Requirement Priority Phase Status
REQ-047 orca init auto-provisions CA + server cert + DB migrations + localhost node (idempotent; safe re-run) High v0.6 P1 Complete (P1 shipped v0.5.1)
REQ-048 orca init registers a default localhost node with auto-detected OS via /etc/os-release ID High v0.6 P1 Complete (P1 shipped v0.5.1)
REQ-049 Node schema extension: nodes.kind (localhost|linux|proxmox) + nodes.os columns (migration 0006, backward-compatible) High v0.6 P1 Complete (P1 shipped v0.5.1)
REQ-050 orca node join --type proxmox SSH bootstrap via golang.org/x/crypto/ssh (new direct dep); password auth, deploy orca pubkey, create orca user (config-overridable), assign PVE role, drop sudoers allowlist; idempotent High v0.6 P2 Complete (P2 shipped v0.5.2)
REQ-051 Proxmox least-privilege OrcaOperator PVE role (VM.Audit, Datastore.AllocateSpace, SDN.Use) + orca user + /etc/sudoers.d/orca allowlist (pct, qm, pvesh, apt-get, dpkg); config-overridable user/role names High v0.6 P2 Complete (P2 shipped v0.5.2; refined: pvesh excluded per AD-020, orca@pam per AD-019)
REQ-052 orca doctor extensions: doctor os (verify localhost OS detection matches stored node row) + doctor proxmox (SSH-probe each kind=proxmox node with pveversion/pvecmd status, 3s timeout, PASS/WARN/FAIL); audit log all bootstrap + join actions Medium v0.6 P3 Complete (P3 shipped v0.5.3)

v0.6 Milestone Summary

Status: Complete — all 3 execution phases + final review shipped. P0 (v0.5.0), P1 (v0.5.1), P2 (v0.5.2), P3 (v0.5.3), P4 final (v0.5.4). REQ-047..052 all complete.

  • P0 (v0.5.0): pre-execution (specify → clarify → research → plan). 8 decisions (D-030..D-037).
  • P1 (v0.5.1): orca init full bootstrap + schema 0006 (REQ-047/048/049).
  • P2 (v0.5.2): Proxmox SSH join + OrcaOperator role + sudoers (REQ-050/051).
  • P3 (v0.5.3): doctor os + doctor proxmox + audit logging (REQ-052).
  • P4 (v0.5.4): final review + audit + milestone release.

v0.7 Requirements — Hardening & Completion

ID Requirement Priority Phase Status
REQ-053 orca cert command tree registered on root command (cert ca-init, cert gen, cert show, cert renew, cert fingerprint) — code exists in internal/cli/cert.go but is never AddCommand'd; unreachable today High v0.7 P1 Complete (P1 shipped v0.6.1)
REQ-054 HCL config file parsing: internal/config package loads ~/.orca/config.hcl / /etc/orca/orca.hcl (keys: db_path, listen_addr, ca_path, server_cert_path, server_key_path, node_capacity); merge precedence flag > env > file > default; --config flag on root command High v0.7 P2 Complete (P2 shipped v0.6.2)
REQ-055 Test coverage uplift: every package ≥ 50% — adds tests for internal/engine (executor, dispatcher, peer), internal/transport (mtls, dispatch, handshake_log), internal/proxmox (bootstrap SSH path), internal/audit Medium v0.7 P3 Complete (P3 shipped v0.6.3)
REQ-056 --pprof <addr> opt-in flag on orca daemon (default disabled); net/http/pprof mounted on a separate mux, never on the mTLS daemon listener Low v0.7 P4 Complete (P4 shipped v0.6.4)

v0.8 Requirements — Coverage & Trust Hardening

ID Requirement Priority Phase Status
REQ-057 Test coverage uplift round 2: raise internal/engine (8.3%), internal/proxmox (5.1%), internal/cli (27.6%), internal/transport (26.3%), internal/store (46.7%), internal/jobspec (47.6%) to ≥ 70%; add first tests for internal/audit, internal/certpaths, cmd/orca (currently 0%) to ≥ 50% (D-047 tiered floor) High v0.8 P1 Complete (P1 shipped v0.7.1; all 9 packages exceeded floor)
REQ-058 --host-key-fingerprint <SHA256:base64> pre-pin flag on orca node join (validated when --type proxmox): when supplied, join fails fast if the SSH host key's OpenSSH SHA-256 fingerprint does not match; supersedes TOFU (D-035) for pre-pinned deployments (D-044, D-045) Medium v0.8 P2 Complete (P2 shipped v0.7.2)
REQ-059 orca node key-reset <node> command: clears the persisted SSH host key entry for the node from ~/.orca/known_hosts only (local, not remote authorized_keys — D-046); audit-logs event=node.key_reset; next doctor proxmox/dispatch re-pins via TOFU or --host-key-fingerprint Low v0.8 P2 Complete (P2 shipped v0.7.2)
REQ-060 Requirement-status hygiene sweep: REQUIREMENTS.md v0.7 rows were stale ("Pending" after ship); add a verify-stage assertion that every REQ listed as Complete in ROADMAP.md has a matching Complete row in REQUIREMENTS.md, enforced by make verify-reqs Medium v0.8 P3 Complete (P3 shipped v0.7.3)

v0.9/v0.10 Requirements — Re-architecture Foundation & Production Hardening

The v0.9/v0.10 milestones supersede the shipped v0.1v0.8 architecture per the adopted PRD (.ciagent/PRD_v0.9.md). The re-architecture is justified on six grounds recorded in the PROJECT.md Supersession Table. 30 net-new requirements (REQ-061..REQ-090) derive from the v0.9 IDEATION; their phase placement and binding grill conditions (C-01..C-19) are documented in IDEATION_v0.9.md and GRILL_v0.9.md.

ID Requirement Priority Phase Status
REQ-061 orca daemon deprecation command and build-tag removal path: v0.9 emits deprecation warning + still runs (dual-write window); v1.0 repurposes to orca daemon drain-and-stop (stops v0.8 daemons on peers via SSH, confirms workloads survive via systemd); post-v1.0 the command and internal/daemon/ are deleted. // Deprecated Go doc comments + slog.Warn on every run (I-M-001) High v0.11 P14b (drain-and-stop + rotate-lead) Complete
REQ-062 Coverage follow-ups: 3 zero-test packages (internal/audit, internal/certpaths, cmd/orca) + internal/cli to 70% floor; once daemon.go is deprecated/removed the exclusion reason disappears and the floor applies to the whole package; all net-new subsystems carry a 70% floor from their first phase (I-M-002) Medium v0.9 P0X + each new pkg Complete
REQ-063 known_hosts flock concurrency gap (deferred P1 from REVIEW_v0.8 A2): add flock-style advisory lock (stdlib syscall.Flock wrapper) around the read-modify-write in TOFUHostKeyCallback capture path (bootstrap.go:290-302) and ResetHostKey (bootstrap.go:479-523); lock file at cluster/known_hosts.lock (R-002) (I-M-003) Medium v0.9 P0a1 Complete
REQ-064 HCL→Markdown jobspec adapter/bridge layer: keep internal/jobspec/spec.go as legacy HCL path behind // Deprecated; add internal/jobspec/markdown.go (canonical) + internal/jobspec/dispatch.go (extension-based dispatcher: .md→Markdown, .hcl→legacy, .yaml→Markdown-with-empty-body); unified *WorkloadSpec populated via adapter; preserves orca job run old-spec.hcl during migration window (I-M-004) High v0.9 P0b Complete
REQ-065 orca doctor --legacy-paths detection: detects v0.8 residue (orca.db at ORCA_HOME root, ca.crt/ca.key, config.hcl, flat server.crt, namespace column in any *.db); outputs list of legacy artifacts with migration recommendations; the detection half of v0.10-P14 (I-M-005) Medium v0.11 P14c Complete
REQ-066 Legacy CA state migration to step-ca: orca upgrade --to-v1.0 --import-ca reads ~/.orca/ca.key, initializes step-ca with it, re-issues workload SVIDs; preserves audit history even if live trust root changes (I-M-006). Gated by C-07 High v0.11 P14a Complete
REQ-067 Fuzz test harness for Markdown frontmatter parser: testing.F fuzz target in internal/jobspec/markdown_test.go round-trips random frontmatter+body through ParseMarkdown asserting byte-exact body preservation; corpus of adversarial fixtures (CRLF, BOM, no-frontmatter, empty-frontmatter, frontmatter-with-only-separator) (I-M-007) Medium v0.9 P0b Complete
REQ-068 Deprecation warnings on removed/repurposed CLI subcommands: each removed/changed command (orca cert, orca node join mTLS semantics, orca job run <spec.hcl>) emits slog.Warn deprecation banner with v1.0 replacement except under orca upgrade; --no-deprecation-warnings global flag via root.go PersistentPreRunE (I-M-008) Low v0.9 P0X + v0.10 P13 Complete
REQ-069 internal/config/config.go HCL config demotion via adapter: keep internal/config/ as legacy_config.go with // Deprecated; add internal/config/markdown.go for new Markdown-frontmatter loader (R-014); root.go dispatches on file extension (.hcl→legacy, .md→new); --config semantics: .hcl read-only legacy, .md canonical (I-M-009) High v0.9 P0a1 Complete
REQ-070 internal/certpaths/ replacement with multi-namespace path resolver: new internal/paths package with paths.NamespaceDir(ns), paths.ClusterDir(), paths.CacheDB(), paths.MasterKey(), paths.NSDb(ns), paths.NSEnv(ns), paths.NSSecrets(ns); keep certpaths as thin shim for v0.8 compat then remove post-v1.0 (R-002) (I-M-010) — highest blast radius High v0.9 P0a1 Complete
REQ-071 internal/store/ schema: per-namespace DBs, drop namespace column: store.Open gains namespace parameter (or caller passes paths.NSDb(ns)); migrate.go runs migrations per namespace DB; cert_repo (0004) removed (step-ca handles certs); audit_log moves to CLI-side cache DB (R-008) (I-M-011) High v0.9 P0a1 + v0.10 P06 Complete
REQ-072 internal/transport/ deletion + SSH-push package: delete mtls.go, dispatch.go, handshake_log.go; extract retry/idempotency patterns into internal/sshpush/; existing transport.IdempotencyStore directly reusable (I-M-012). Deletion deferred to v0.10-P14 to keep dual-write window open High v0.9 P00 (delete v0.10 P14) Complete
REQ-073 SSH-push transport layer design: connection pooling (reuse *ssh.Client per peer), idempotency (content-addressed filenames), retry (exponential backoff 100ms×2 cap 5s max 5), timeout (30s SCP, 10s exec), fan-out (errgroup bounded concurrency default 8), known_hosts reuse proxmox.TOFUHostKeyCallback (I-B-001) High v0.9 P01 (design P0a1) Complete
REQ-074 Emitter template system (Layer 4): internal/emitter/ package with Emitter interface Render(spec *WorkloadSpec, node *Node) ([]File, error); implementations systemdEmitter/traefikEmitter/syncthingEmitter/socketEmitter; SSH-push SCPs []File atomically (write-to-tmp + rename); emitters registered per kind + runtime (I-B-002) High v0.9 P0c Complete
REQ-075 Lead applier execution model: CLI renders transaction bundle (tarball + apply.sh + verify.sh) on operator host, SCPs to lead's /run/orca/txns/<txn-id>/, lead's systemd timer runs apply.sh idempotently, CLI polls txn status via SSH; bash scripts generated by emitter not hand-written (I-B-003). Gated by C-09 High v0.11 P10a Complete
REQ-076 step-ca integration: orca init runs step ca init on lead; CLI SSHs to lead, installs step-ca via apt, stores step-ca.json; workload SVIDs via step ca token (JWE minted by CLI) → step ca certificate; SPIFFE ID as SAN; new internal/stepca/ package wraps step CLI via SSH (I-B-004). Reverses AD-010 per override justification ground 2 High v0.9 P07 + v0.10 P02 Complete
REQ-077 Traefik dynamic config generation + atomic reload: Traefik emitter renders /etc/traefik/dynamic/orca-<ns>-<svc>.yaml with backends (socket paths R-007), health checks, mTLS config pointing at step-ca root; atomic reload via tmpfile+fsync+rename triggering fsnotify; drain writes weight=0 or removes backend (I-B-005). Gated by C-10 High v0.9 P02 Complete
REQ-078 Runtime abstraction interface (5 backends): Runtime interface in internal/runtime/ with Prepare/Start/Stop/Status; processRuntime (wraps existing executor.go), wasmRuntime (wasmtime via SSH), podmanRuntime, pveVMRuntime (qm via proxmox SSH), pveCTRuntime (pct); runtimeRegistry keyed by runtime: frontmatter value; Alloc carries runtime field changeable on migration (I-B-006). Split P07a/b/c per PC-10. P07b gated by C-01 High v0.9 P07a/b/c Complete
REQ-079 Transaction bundle format + N-peer atomicity: bundle = tarball with desired-state.json + apply.sh + verify.sh + rollback.sh + manifest.sig (signed with master.key); content-addressed <txn-id>=sha256(desired-state.json) stored in cluster/txns/<txn-id>/; lead applies to self first then fans out; failure on any peer runs rollback.sh on applied peers (I-B-007). Gated by C-09 High v0.11 P10a Complete
REQ-080 Master key management + HKDF-SHA256 per-line .env.secrets encryption: cluster/master.key 32-byte random (generated at orca init using WriteAtomic pattern); each line `base64(nonce ciphertext
REQ-081 Syncthing config rendering + folder-ID content-addressing: per-namespace Syncthing folder orca-<ns> with content-addressed folder ID sha256(ns + master-key-fingerprint); CLI renders config.xml per peer; Syncthing runs as systemd unit (emitted by systemd emitter); CLI discovers peers via cluster/peers/; migration works because new node joins folder and syncs before workload starts (I-B-009). Gated by C-02 + C-14 Medium v0.9 P09 (spike v0.9 P00) Complete
REQ-082 Namespace inheritance resolver algorithm: DFS parent walker with visited set for cycle detection; _defaults/ implicit root (always exists, no parent); merge semantics: child overrides parent for scalars, arrays unioned (child adds to parent); pure function (no I/O) taking map[nsName→*NSConfig] returning map[nsName→*ResolvedNS] (I-B-010) High v0.9 P0a2 Complete
REQ-083 CLI-side scheduler redesign: Score(node, workload) (score int, fits bool) where fits checks runtime compatibility + constraints, score is bin-packing (most free capacity = highest); Services pick count distinct nodes (anti-affinity default); DaemonSets pick all matching nodes; Job = one-shot; CLI-side not daemon-side (R-001) (I-B-011) High v0.9 P05 (skeleton P0c) Complete
REQ-084 orca job lint category-driven lint engine: Linter runs Rule checks returning Finding{Category, Severity, Message, Explanation}; categories schema/runtime/security/migration/best-practice; --explain prints rationale; pure (no I/O) checks against static rules (I-B-012) Medium v0.11 P11 Complete
REQ-085 v0.8→v1.0 migration ordering: v0.9 ships new parser + kinds + runtime + SSH-push alongside old daemon (dual-write window); orca job run dispatches on extension (.md→SSH-push, .hcl→old daemon); v0.10-P05 drains old daemons; v0.10-P14 converts remaining .hcl specs and removes daemon (I-C-001). Most important cross-cutting idea High v0.9 P00 → v0.10 P14 Complete
REQ-086 "No orca on server" enforcement: orca doctor no-orca-on-server SSHs to each peer verifying no orca binary in PATH, no orca systemd service, no orca process, no /etc/orca/ directory; runs after v0.10-P05 before v0.10-P16; reuses v0.8 proxmox SSH session infrastructure (I-C-002). Implements grill C-13 High v0.11 P14c Complete
REQ-087 Test infrastructure: hermetic 3-linux + 1-proxmox cluster pipeline: test/integration/ with docker-compose/vagrant creating 4 containers/VMs; Go test harness SSHes to each, runs CLI, asserts end-to-end workflows (ns create → workload submit → migrate → drain); proxmox simulated via mock pct/qm; v0.8 e2e tests (bootstrapE2ESetup) are foundation (I-C-003) Medium v0.11 P08 Complete
REQ-088 Security-engineer + network-engineer persona reactivation: reactivate security-engineer (step-ca provisioner model, SSH-push blast radius, Traefik edge, .env.secrets crypto) and network-engineer (socket exposure R-007, Syncthing P2P ports, Traefik routing); cross-cutting review not single phase (I-C-004). Implements grill C-05 High v0.9 P00 → v0.10 P16 Complete
REQ-089 Documentation rewrite: ARCHITECTURE.md/PROJECT.md/README + AD-010 supersession: v0.9-P00 adds "v0.9 Architecture (Supersedes v0.8)" section + banners + Superseded Decisions table; v0.10-P15 rewrites README quickstart for new curl sh + orca init + orca ns create flow (I-C-005) Medium v0.9 P00 + v0.10 P15/P16
REQ-090 Dual-write window: v0.9 orca job run dispatches on extension (.md→SSH-push new path, .hcl→old daemon path) via parser dispatcher (REQ-064); daemon not removed until v0.10-P05; SSH-push path writes to separate systemd unit namespace (orca-v1-<alloc>.service) while daemon uses orca-<job>.service — no unit name overlap = no conflict (I-C-006) High v0.9 P00 Complete

v0.10 Docs & Install Milestone Requirements

The following requirements are scoped to the v0.10 docs/cli-examples milestone. They cover the CLI reference documentation, jobspec reference, ingress guide, full-stack example jobspecs, README refresh, namespace.md v0.9 layout update, and the release/install pipeline fix that guarantees every Gitea release carries a Linux binary asset.

ID Requirement Priority Phase Status
REQ-091 docs/cli.md comprehensive CLI reference: every command/subcommand with synopsis, flags (name/type/default/description), and one-line example; global flags (--json, --system, --config, --no-deprecation-warnings); output modes (text vs --json, --watch table vs NDJSON); exit codes; deprecated surface (orca daemon, orca cert, orca node join mTLS path, legacy .hcl jobspec) flagged with callout boxes pointing to v0.10 removal High v0.10 P2 Complete
REQ-092 docs/jobspec.md markdown frontmatter schema reference: all top-level keys, block reference (runtime, ports, env/secrets, volumes, restart, update, service, health, lifecycle, constraints, affinity, tasks), kinds matrix (Job/Service/DaemonSet required vs allowed), CEL subset grammar, body byte-exact preservation (R-015), deprecated HCL form callout High v0.10 P2 Complete
REQ-093 docs/ingress.md Traefik ingress reference: kind: Service implies Traefik route (D-175), R-007 socket-vs-TCP-bind semantics, generated Traefik YAML shape (routers/services/healthCheck), atomic reload (C-10), drain (weight: 0), TLS (certResolver, trust domain, step-ca), worked-example pointer to examples/full-stack/, v0.10 forward limitations (socket activation, transactional update) High v0.10 P2 Complete
REQ-094 examples/full-stack/ directory with 5 valid jobspecs (web-app.md, api.md, worker.md, log-shipper.md, postgres.md) exercising ports/service/health/restart/update/constraints/affinity/lifecycle/task-groups/volumes/replication/DaemonSet; rendered/ subdir showing the Traefik dynamic YAML + systemd units orca generates; README.md walkthrough (init → node join → capacity set → ns create → job run → list --watch → inspect rendered) High v0.10 P3 Complete
REQ-095 README.md refresh: status line (v0.9 complete, v0.10 in progress), install --version example updated to current tag, subcommand table expanded to all commands with deprecation markers, update-in-place example updated, development targets complete (verify-reqs, security-scan, test-race, changelog), new Documentation + Examples sections linking all docs/*.md and examples/ High v0.10 P4 Complete
REQ-096 docs/namespace.md v0.9 multi-namespace layout update: replace v0.8 flat path table with v0.9 layout (cluster/, _defaults/, per-ns db/jobs/alloc/ns.md), ORCA_HOME/--system resolution, orca ns subcommand cross-link, v0.8 flat layout flagged deprecated Medium v0.10 P4 Complete
REQ-097 scripts/release.sh release pipeline fix: cross-build linux-amd64 tarball regardless of host arch (GOOS=linux GOARCH=amd64 go build); post-create asset verification (query /releases/tags/$VERSION, assert the tarball in attachments, retry/fail loudly if missing). Guarantees every Gitea release carries the Linux binary asset (root cause of v0.4.5 install) High v0.10 P1 Complete
REQ-098 scripts/install.sh asset fallback walk: if the latest/pinned release lacks the matching orca-<ver>-<os>-<arch>.tar.gz, walk backward through /releases?limit=20 to the most recent release that has it, with a clear warning. Keeps pulling from releases (not main). Optional --check dry-run mode High v0.10 P1 Complete

v0.11 Production Hardening Milestone Requirements

The following requirements (REQ-099…REQ-NN) are scoped to the v0.11 production-hardening milestone. They cover the ingress hybrid default (R-017), drift detection (R-018/R-019/R-020), the systemd Path unit implementation (D-227…D-237), and five net-new CLI commands added per operator decision Q2=C.

Ingress hybrid (R-017, D-215…D-226)

ID Requirement Priority Phase Status
REQ-099 internal/emitter/nft.go: nftables emitter renders /etc/nftables.d/orca.nft with DNAT (:443127.0.0.1:8443, :80127.0.0.1:8080), SYN-flood tcp-flags filter, ora_rl rate-limit meter (default 100/s burst 200), orca_trusted_probes set; idempotent nft -f apply; atomic rule-set swap (R-017, D-217, D-218, D-222) High v0.11 P15.5 Complete
REQ-100 Traefik static config emitter update: entryPoints.websecure.address changes from :443 to 127.0.0.1:8443 (default); entryPoints.web.address changes to 127.0.0.1:8080; --public-binding=traefik-on-public-ip opt-out emits :443/:80 instead; certs/mTLS/dynamic config unchanged (R-017, D-220, D-216) High v0.11 P15.5 Complete
REQ-101 orca doctor nft: checks table inet orca-ingress exists, expected DNAT rules present, rate-limit meter present, /etc/nftables.d/orca.nft parses cleanly (nft -c -f), file hash matches latest applied txn; drift detection via hash comparison (R-018 critical_paths, D-221, D-226) High v0.11 P15.5 Complete
REQ-102 orca nft CLI: show [--peer], diff --against <txn-id>, doctor (alias for orca doctor nft), country block add <cc-list> (opt-in GeoIP), rate limit set --rate N/s; all Layer-5 orchestrators that SSH into peers and parse nft output (D-223, D-222) Medium v0.11 P15.5 Complete

Drift detection (R-018/R-019/R-020, D-227…D-237)

ID Requirement Priority Phase Status
REQ-103 internal/drift package: Detector interface (Watch, Aggregate, Remediate, Acknowledge), Event, Config, PathSpec, RemediationPolicy types; iter.Seq2[Event, error] per D-017; signal.NotifyContext per D-023 (R-018, D-236) High v0.11 P10 Complete
REQ-104 orca drift CLI tree: watch [--interval=2s] [--paths=...] [--json], show [--peer], acknowledge <peer> <path>, remediate <peer> <path> [--force], config show, config validate; uses iter.Seq2 + signal.NotifyContext (D-236) High v0.11 P10 Complete
REQ-105 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: NoNewPrivileges, ProtectSystem=strict); R-001-clean (R-018, D-227, D-228) High v0.11 P10 Complete
REQ-106 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 (event_id, ts, host, path, status, new_sha256, latest_txn, triggered_by); stateless, idempotent; flock for serialization (D-228) High v0.11 P10 Complete
REQ-107 scripts/orca-aggregate.sh extension: existing 10s aggregator cadence (C-11) now also rsyncs each peer's /etc/orca/state/drift-events/, validates event hashes against /etc/orca/state/applied/<txn>/manifest.json, triggers orca-remediate.sh for auto-remediable paths, consumes (deletes) event files on peers (D-229, D-237) High v0.11 P09 Complete
REQ-108 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 (transient failures retry next tick); cooldown state at /etc/orca/state/remediation-cooldown/ (D-231, D-232 refined per CLARIFY C4) High v0.11 P10 Complete
REQ-109 Drift cadence config in config.md (kind: ClusterConfig): drift.polling.{enabled,default_interval,max_concurrent_peers}, drift.paths.{critical,standard,excluded} (each with systemd_path_unit, interval, paths list), drift.remediate.{auto,auto_paths,require_approval_paths,notify_on_remediation}; critical defaults: Traefik dynamic, nftables, sudoers, orca-alloc services; secrets + /run/orca/* + drift-events dir excluded (R-018, D-231, D-234) High v0.11 P10 Complete
REQ-110 Pre-flight consistency gate in applier: orca-pull.sh (C-09) refuses new txns if drift detected on the target peer/namespace; --force flag overrides; per-namespace scoping means a drifted peer in ns-A does not block ns-B (R-020, Q4=A) High v0.11 P10 Complete
REQ-111 orca system user on peers: peer-setup emits useradd -r orca (system account, no login shell); orca-drift-*.service runs as User=orca Group=orca; SSH key access to lead for aggregator; idempotent at peer setup (net-new operational requirement from doc 5) High v0.11 P10 Complete
REQ-112 NFS detection at peer setup: orca node join / peer-setup detects NFS mounts on orca state dirs; if /etc/orca is on NFS, systemd Path units are disabled for those paths and polling is the only detection; logs a warning (D-233) Medium v0.11 P10 Complete
REQ-113 orca job restart <name>: restarts an allocation to pick up EnvironmentFile drift; goes through normal allocation lifecycle (not file-level remediation); triggers on drift of /etc/orca/allocs/<id>/env (D-235) Medium v0.11 P10 Complete

Net-new CLI surface (Q2=C — all five commands added to v0.11)

ID Requirement Priority Phase Status
REQ-114 orca cluster rotate-lead: moves cluster CA + lead state to a new bare-Linux peer (R-003 enforces bare-Linux-only lead); workloads keep running (certs already distributed); SSH key rotation; idempotent (Q2=C, folds into P14b daemon cutover) High v0.11 P14b Complete
REQ-115 orca upgrade --to-vX: thin wrapper around install.sh + orca restore (binary upgrade only, not full cluster rolling upgrade); handles Traefik binding cutover from :443 to 127.0.0.1:8443 for existing v0.9/v0.10 clusters (R-017 migration path, CLARIFY C1, C2=a thin wrapper); full cluster-rolling-upgrade defers to v1.x (Q2=C) High v0.11 P14a Complete
REQ-116 orca job migrate <name> --to <node>: drain+reschedule composite (uses P05 drain + P06 alloc history); live-migrate with storage replication defers to v1.x (CLARIFY C3=a); idempotent (Q2=C) Medium v0.11 P05 Complete
REQ-117 orca logs --all-nodes --since 5m: aggregates journald logs across peers via SSH; uses P06 alloc-history cache DB; iter.Seq streaming per D-017; --since duration flag; --all-nodes fans out (Q2=C, folds into P06) Medium v0.11 P06 Complete
REQ-118 orca doctor mTLS: verifies trust chain (CA → server cert → workload SVIDs exist + not expired) AND live mTLS handshake probe to each peer (reuses P01 metrics endpoint + P01.5 SPIFFE spike infra); both chain verification + live probe (CLARIFY C5, Q2=C, folds into P15.5) High v0.11 P15.5 Complete

Scope notes

  • REQ-099…REQ-118 = 20 net-new requirements (REQ count grows 98→118).
  • No new phases added (Q3=A folds ingress into P15.5; Q2=C folds CLI commands into existing phases).
  • P09 expands (REQ-107 aggregator extension); P10 expands (REQ-103…REQ-113, the largest phase); P15.5 expands (REQ-099…REQ-102 ingress + REQ-118 mTLS doctor).
  • P05 gains REQ-116 (migrate); P06 gains REQ-117 (logs --all-nodes); P14a gains REQ-115 (upgrade); P14b gains REQ-114 (rotate-lead).

v0.12 Milestone Summary — Security Hardening (Zero-Trust Identity)

Status: complete (shipped as v0.11.x tags; milestone release v0.11.28). 30 net-new requirements (REQ-119..REQ-148) derived from the v0.12 threat-model review (25 findings F1..F25) and the zero-trust identity model (R-021). See ROADMAP.md for the 29-phase plan (P0 + P01..P27 + P28 final) and RESEARCH_v0.12.md for the full threat model.

Wave A — Critical injection & traversal

ID Requirement Priority Phase Status
REQ-119 Command injection fix in internal/runtime/podman.go & wasm.go: shell-quote cmdStr via shellQuote in SSH exec interpolation (podman.go:57, wasm.go:39); add injection regression tests (bats + Go) covering ;, |, $(), backticks, newline injection (F3) High v0.12 P01 complete
REQ-120 Namespace path traversal fix: validateNamespaceName in internal/ns/ rejects .., /, leading -, null bytes, control chars in ns create/ns inherit/ns set-constraint; add fuzz test (F4) High v0.12 P02 complete
REQ-121 Txn apply path allowlist: apply.sh python heredoc validates every path in desired-state.json against a prefix allowlist (/etc/orca/, /etc/traefik/orca*, /etc/systemd/system/orca-*, /etc/nftables.d/orca*, /etc/syncthing/orca*); rejects otherwise; HMAC-signed manifest unchanged (F5) High v0.12 P03 complete

Wave B — Zero-trust identity

ID Requirement Priority Phase Status
REQ-122 ACL enforcement wiring: acl.Check invoked in daemon handlers (read/write/admin by route) and SSH-push applier (validates ORCA_OIDC_TOKEN env var against JWKS before applying any txn); deny-by-default enforced; actor recorded in audit (F1, foundational for REQ-145) High v0.12 P06 complete
REQ-123 Daemon auth hardening: mandatory mTLS (remove plaintext mode entirely); OIDC bearer accepted as second factor on human-facing endpoints; MaxBytesReader body limits; pprof loopback-only by default, refuse non-loopback without --pprof-allow-public confirmation (F6, F24) High v0.12 P09 complete
REQ-124 HTTP request body size limits: http.MaxBytesReader on all JSON-decoding handlers; MaxHeaderBytes set; rejects oversized bodies (F24) Medium v0.12 P09 complete
REQ-125 Audit log tamper-evidence: hash-chained entries (prev_hash = sha256(prev_row || payload)), HMAC-SHA256 under master key on the chain head; orca doctor audit verifies the chain; append-only enforcement via SQLite trigger blocking UPDATE/DELETE; actor field carries OIDC sub or SPIFFE SVID (F2) High v0.12 P10 complete
REQ-126 SVID chain validation: VerifySVID validates the full cert chain against the CA pool, not just the URI SAN; reject certs signed by unknown CAs even with correct URI (F9) High v0.12 P11 complete
REQ-127 Backup symlink validation: Restore rejects Linkname that's absolute, contains .., or points outside ORCA_HOME; add regression test with crafted tarball (F7) High v0.12 P12 complete
REQ-128 step-ca /tmp hardening: step ca certificate writes to 0600 temp under ClusterDir()/step-tmp/ (or TMPDIR override), not world-readable /tmp; cleanup in defer (F10) High v0.12 P13 complete
REQ-129 Master key rotation: orca secrets rotate-master re-encrypts all namespace secrets under a new master key; new master key re-sealed to OIDC as part of the same operation; --dry-run + atomic + automatic rollback to old sealed key on any ns failure; no passphrase (R-021) (F12) High v0.12 P14 complete
REQ-130 File-mode audit expansion: EnforceFileModes extended to SSH key, master key (sealed blob), server cert/key, known_hosts; orca doctor modes checks all; startup refuses to run on violation (F13) Medium v0.12 P15 complete
REQ-131 aggregate.sh JSON injection fix + drift-gate parse fix: replace printf interpolation with jq-based JSON construction (or Go-side aggregator emitting JSON); fix orca-pull.sh R-020 parsing to use jq instead of grep (F11, F18) High v0.12 P16 complete
REQ-132 install.sh checksum+GPG verification: release.sh publishes SHA256SUMS + SHA256SUMS.asc (GPG-signed) alongside tarball; install.sh verifies before tar -xzf; fail closed on mismatch (F14) High v0.12 P17 complete
REQ-133 nftables ruleset hardening: add conntrack bounds (ct state established,related accept), input default-deny on orca chain, drop invalid packets; orca doctor nft audits live ruleset against emitted one (F21) Medium v0.12 P18 complete
REQ-134 sudoers hardening: add NOEXEC to apt-get/dpkg (or remove if unused); orca doctor proxmox audits sudoers file against expected allowlist (F22) Medium v0.12 P19 complete
REQ-135 System user consistency: Proxmox bootstrap creates nologin system user (-r -s /usr/sbin/nologin), matching peer-setup; orca doctor flags inconsistency on existing peers; orca upgrade migrates (F23) Medium v0.12 P20 complete
REQ-136 SQLite file-mode + at-rest encryption: store.Open sets DB file mode 0600; optional --encrypt-db (CGO-free fallback per C-31: file-mode 0600 + documented threat if SQLCipher needs CGO); no CGO (F8) High v0.12 P21 complete
REQ-137 Migration safety: copyFile -> atomic temp+rename; migrateDBSchema runs in transaction with foreign_keys(ON); pre-migration backup step (uses internal/backup); document manual rollback; v0.11->v0.12 identity migration: orca upgrade refuses clusters using --password/bare-tokens without --accept-identity-migration (F19, C-34) High v0.12 P22 complete
REQ-138 Legacy CA/mTLS/daemon + step-ca password-provisioner deletion: remove internal/security/ca.go legacy CA, internal/transport/mtls.go deprecated path, daemon plaintext mode; migrate orca init/orca cert * to step-ca exclusively; certpaths (v0.8 layout) removed; delete step-ca --password-file provisioner (replaced by OIDC provisioner); gate: P06/P08/P09/P11 all shipped (F16) High v0.12 P23 complete
REQ-139 known_hosts tightening + transport hardening: Flock tightens pre-existing looser perms to 0600; classifyDialErr switched from substring to typed errors; add SSH-exec rate limiting (token bucket per peer) (F15, F25) Medium v0.12 P24 complete
REQ-140 Drift event authentication: drift events signed with per-peer HMAC key (derived from master key); aggregator rejects unsigned/forged events; orca-drift-notify.sh reads key from 0600 file owned by orca (F18) Medium v0.12 P25 complete
REQ-141 Security integration test suite: hermetic harness exercising injection, traversal, symlink, drift-forgery, audit-tamper, daemon-auth-negative, OIDC mock-IdP flow, ACL-with-OIDC-claims negative tests, unseal/seal, WebAuthn virtual-authenticator ceremony, password-removal regression (assert --password is rejected); gates in .coreci.yml validate (C-33) High v0.12 P26 complete
REQ-142 Zero-trust + OIDC + WebAuthn + threat-model docs: docs/threat-model.md (STRIDE + zero-trust model + OIDC data-flow), docs/oidc.md (configure your IdP, Dex offline quickstart, claim-to-namespace mapping), docs/webauthn.md (passkey registration, RP ID, secure context), docs/security-runbook.md (unseal/seal, master key rotation, incident response, sudoers audit, nft audit); README security section names "no orca credentials" as an invariant Medium v0.12 P27 complete
REQ-143 Final review + ship + audit: multi-persona review across all phases, ciagent-audit reconstruction test, milestone merge to main, tag v0.11.29 (= v0.12 milestone release per feature-milestone rule) High v0.12 P28 complete
REQ-144 OIDC client + bundled Dex: orca auth login/logout/status/init-idp; OIDC config block (oidc.issuer, client_id, client_secret, scopes); bundled Dex systemd unit + Traefik route on the lead; BYO external IdP override via oidc.issuer repoint; JWKS caching + refresh; token storage at ~/.orca/credentials.json (0600); --oidc flag on commands requiring identity; browser auth-code + PKCE + local loopback redirect; headless device-code fallback (D-238..D-247) High v0.12 P04 complete
REQ-145 ACL rewrite to OIDC claims: remove KindToken entirely; KindSpiffe stays for machine identity; new KindOidc maps sub+groups -> namespace permissions; acl.Check takes OIDC claims struct; deny-by-default enforced in daemon + SSH-push applier; acl.json mode tightened to 0600 (F1) High v0.12 P06 complete
REQ-146 Remove all password/token paths (breaking): delete --password/$ORCA_PROXMOX_PASSWORD from Proxmox join (replace with pre-staged-key-only or step ssh OIDC cert exchange); delete step-ca --password-file provisioner (migrate to OIDC provisioner); delete any bare-token CLI paths; documented in migration guide (R-021, C-34) High v0.12 P07 complete
REQ-147 Master key seal-to-OIDC + Shamir recovery: master key encrypted with key derived from OIDC token exchange at unseal; orca cluster unseal/seal; sealed blob at ClusterDir()/master.key.sealed (0600); raw key never on disk; Shamir 3-of-5 shards printed at seal time; recovery via --recovery + 3 shards; mTLS-only offline path derives seal key from cluster CA (D-241, C-35) High v0.12 P08 complete
REQ-148 WebAuthn connector for Dex (passkeys): orca-webauthn-connector (~300 LoC Go, go-webauthn); register/login ceremonies at /orca/webauthn/{register,login} behind Traefik; orca auth register browser flow; passkey storage SQLite ClusterDir()/webauthn-credentials.db (0600, public keys only); RP ID = cluster Traefik domain; secure context via step-ca cert; headless device-code fallback; virtual-authenticator integration tests (D-240, D-243, D-244, C-38) High v0.12 P05 complete

Scope notes (v0.12)

  • REQ-119..REQ-148 = 30 net-new requirements (REQ count grows 118 -> 148).
  • 29 phases (P0 + P01..P27 + P28 final); GRILL may split/merge.
  • P04 (OIDC+Dex) and P05 (WebAuthn) are the new feat phases; the rest are fix/chore/test/docs/refactor. Milestone type = feature (at least one feat).
  • Tags on v0.11.x patch line: v0.11.0 (P0) ... v0.11.29 (P28 final = v0.12 milestone release).
  • v1.0.0 production-ready tag stays deferred for post-v0.12 UAT (per v0.11 PRD).

Milestone v0.13: Production Hardening Round 2 + UAT Plan

Status: complete (2026-08-10). v0.12 (Security Hardening) is COMPLETE; v0.13 is the final hardening round before the v1.0.0 production-ready tag. v1.0.0 is gated on the UAT signoff script (scripts/uat-signoff.sh) delivered by this milestone.

Wave A — Toolchain & injection hardening

ID Requirement Priority Phase Status
REQ-149 Go toolchain bump to 1.25.12+ (closes 24 stdlib vulns: archive/tar GO-2025-4014/GO-2026-4869, crypto/tls GO-2026-5856/GO-2025-4008, crypto/x509 GO-2026-5037/4947/4946/GO-2025-4175/4155/4013, net/http GO-2026-4918/GO-2025-4012, net/url GO-2026-4601/4341/GO-2025-4010, encoding/pem GO-2025-4009, os GO-2026-4602); govulncheck -show verbose triage of 6 imported third-party vulns; bump deps with reachable traces High v0.13 P01 complete
REQ-150 Input validation & injection hardening: (a) orca logs --job validate against ^[A-Za-z0-9_-]+$, use shellQuote not %q (critical: backtick RCE via SSH fanout); (b) pprof isLoopback(":6060") treat empty host as non-loopback/bind-all, reject unless explicit public-allow flag wired; remove phantom --pprof-allow-public references, make loopback-only a hard invariant; (c) backup restore tar-slip fix: use filepath.Rel(target, dest) containment check instead of HasPrefix(name, ".."); (d) orca txn rollback validate txn ID against ^T-[0-9a-f]{16}$; (e) orca nft diff --against validate txn ID before filepath.Join; (f) drain stopAlloc validate allocID against ^[A-Za-z0-9_-]+$ before systemctl stop; (g) cluster_compat shellQuote(first) for peer dir name; (h) runtime/podman.go use shellQuote(image) not %q; (i) nft TrustedProbes validate each entry with net.ParseIP/net.ParseCIDR; (j) sudoers: validate --proxmox-user/--proxmox-role against ^[a-z_][a-z0-9_-]{0,31}$; write to fixed /etc/sudoers.d/orca; shellQuote all pveum/useradd; validateSudoers check the actual file written; (k) nft country block add validate ^[A-Z]{2}$ Critical v0.13 P02 complete

Wave B — Scheduler wiring & jobspec parser (architectural)

ID Requirement Priority Phase Status
REQ-151 Scheduler/deployment wiring: wire internal/scheduler.Schedule() into orca job run — replace local exec.CommandContext path with: evaluate constraints/capacity/affinity via scheduler → render systemd units via internal/emitter → SSH-push to target via internal/sshpush; --target overrides scheduler selection; capacity enforced (reject job if no node fits); CEL constraints evaluated; affinity weighted scoring; systemd-analyze verify on rendered unit before deploy; job run without --target uses scheduler bin-packing across registered nodes Critical v0.13 P03 complete
REQ-152 jobspec parser fixes: add case "schedule": and case "timeout": to top-level switch in internal/jobspec/markdown.go (currently silently dropped); fix DaemonSet — parser must not default Count to 1 for DaemonSet (validator rejects Count!=0); DaemonSet schedule block actually parsed and stored; timeout: on Jobs parsed and enforced (kill after duration); restart: policy translated to systemd Restart=/StartLimitBurst in emitter; add job lint warnings for advisory-only fields (cron, health, update, affinity) with honest "not enforced in this version" message Critical v0.13 P03 complete

Wave C — Zero-trust enforcement wiring

ID Requirement Priority Phase Status
REQ-153 ACL enforcement + WebAuthn registration auth: (a) wire acl.Check into all 5 daemon handlers (dispatch/jobs/nodes/tasks/health) — extract OIDC sub/SPIFFE SVID from mTLS peer cert, check against ACL for namespace+verb, deny-by-default; (b) wire acl.Check into sshpush applier + txn apply path (validate ORCA_OIDC_TOKEN bearer against JWKS); (c) thread OIDC sub/SVID into audit actor field (replaces "cli"/"daemon"); (d) fix acl.json mode 0644→0600; (e) fix WebAuthn unauthenticated registration — /orca/webauthn/register requires existing authenticated session or admin bootstrap token; do not allow overwriting existing credentials without re-auth; (f) add flock on acl.json for concurrent grant/revoke Critical v0.13 P04 complete
REQ-154 Seal/audit CLI + chain race + key zeroing: (a) implement orca cluster seal/unseal (OIDC token exchange→unwrap master key→zeroed on shutdown; Shamir 3-of-5 shards printed at seal time; sealed blob at ClusterDir()/master.key.sealed 0600); (b) implement orca doctor audit (invokes AuditRepo.VerifyChain); (c) implement orca doctor modes (invokes EnforceFileModes across ORCA_HOME); (d) fix audit hash-chain race — Append uses BEGIN IMMEDIATE transaction; (e) fix secrets rotate-master to actually re-seal to OIDC; (f) zero master key / namespace keys / SVID private keys after use (defense-in-depth against pprof heap extraction) High v0.13 P05 complete
REQ-155 auth init-idp real + auth register: (a) implement orca auth init-idp — render Dex systemd unit + config template + Traefik dynamic route from internal/webauthn/ connector at https://<cluster>/orca/webauthn/{register,login}; RP ID = cluster Traefik domain (C-38); HTTPS secure context via step-ca cert; atomic deploy with rollback; (b) implement orca auth register (browser flow to WebAuthn registration endpoint); (c) loadOIDCConfig config-file loading (oidc.issuer in config, not flags-only); (d) orca doctor oidc health check High v0.13 P06 complete

Wave D — Concurrency, transport, migration safety

ID Requirement Priority Phase Status
REQ-156 Concurrency safety: (a) SQLite busy_timeout(5000) + SetMaxOpenConns(1) on all DSNs (store, cache, recovery, webauthn); (b) secrets file flock (concurrent secrets set on same ns no longer loses data); (c) upgrade lock file (refuse concurrent orca upgrade); (d) backup lock file; (e) cache invalidation by write commands (node join/leave, ns create/delete, job run/stop invalidate relevant cache class — read-after-write consistency); (f) Executor.Run mutex scope fix (hold only for DB inserts, not whole job duration); (g) ns create atomic dir+ns.md write; (h) writeCurrentLead atomic write; (i) consolidate 3 divergent writeAtomic impls onto security.WriteAtomic; (j) WebAuthn session stores guarded with sync.Mutex High v0.13 P07 complete
REQ-157 Transport & SSH safety: (a) replace substring matching in transport.IsTransient AND sshpush.isTransient with typed sentinels (errors.Is); (b) rotateSSHKeys 2-phase atomic swap (stage new key on all peers → atomic swap → verify → cleanup old); (c) known_hosts flock field actually read by dial() (TOFU callback uses new field, not v0.8 certpaths.KnownHostsPath()); (d) IPv6 net.JoinHostPort in proxmox SSH dial + drain splitHostPort; (e) explicit timeouts for all SSH commands (peer-setup, drift remediate/ack, txn rollback, job restart — use context.WithTimeout); (f) verifyCutover use security.ClientTLSConfig with orca CA pool; (g) OIDC callback server ReadHeaderTimeout: 5s; (h) root SIGINT/SIGTERM handler for non-watch commands (clean SSH session + temp file cleanup) High v0.13 P08 complete
REQ-158 Migration & operational safety: (a) migration transaction + torn-write fix — migrateDBSchema wraps ALTER TABLE in transaction; crash after os.Rename but before schema fixup is recoverable; (b) job stop real systemctl stop via SSH (matches job restart pattern; honest semantics); (c) DB retention/compaction for jobs/tasks/audit_log tables (retention policy + orca doctor db compaction check); (d) orca logs --lines cap + --since upper bound (prevent OOM from unbounded journalctl output); (e) cache DB mode 0600 (matches store.Open); (f) upgrade.go cutover backup-file + atomic-rename (replace direct sed -i) High v0.13 P09 complete

Wave E — Observability, docs, UAT

ID Requirement Priority Phase Status
REQ-159 Observability expansion: metrics add orca_jobs_by_state histogram, orca_drift_events_total counter, orca_ssh_errors_total counter, orca_txn_apply_total/orca_txn_rollback_total counters, orca_acl_denials_total counter, orca_audit_chain_head gauge; new docs/metrics.md with Prometheus scrape config; security headers middleware on daemon (X-Content-Type-Options, X-Frame-Options) Medium v0.13 P10 complete
REQ-160 Doc drift round 2: (a) README — update status banner (v0.12+v0.13 complete), latest tag, subcommand table (add auth/nft/peer-setup/secrets rotate-master), correct "mTLS by default" claim (SSH-push is canonical, mTLS deprecated), add missing docs to table; (b) docs/cli.md — complete rewrite covering all ~40 subcommands; (c) CHANGELOG regen; (d) help text fixes (job run HCL→markdown, job stop daemon→SSH-push); (e) docs/webauthn.md add auth register; (f) docs/namespace.md add inherit/set-constraint; (g) docs/install.md+docker.md update version refs; (h) docs/security-runbook.md match P05 reality; (i) fix verify-reqs bold-format regex (currently bypasses v0.12); (j) fix ROADMAP/REQUIREMENTS v0.12 status hygiene; (k) docs/security-scanning.md gosec.json; (l) internal/proxmox/bootstrap.go comments (password→key auth); (m) deprecate orca status stub; (n) make verify-docs target (cli.md ↔ orca --help consistency) High v0.13 P11 complete
REQ-161 --type linux SSH-join: implement NodeKindLinux path (reserved at model/node.go:29); new internal/linux/bootstrap.go mirroring Proxmox pattern — orca pubkey deploy → orca system user → drift-events dir → no PVE role; key-auth only (R-021); orca node join --type linux --host <ip> --ssh-user root --ssh-key <path>; peer-setup.go kept as documented fallback High v0.13 P12 complete
REQ-162 UAT plan: docs/uat.md — 3-host topology (lead Ubuntu 22.04 + pve01 Proxmox VE 8/9 + worker01 Ubuntu 22.04); step-by-step with exact commands (bootstrap→onboard Proxmox→onboard Ubuntu worker→capacity→namespace→deploy full stack→migrate between hosts→exercise every claim); claim matrix mapping ~35 feature claims to UAT steps; signoff procedure (run scripts/uat-signoff.sh, paste output) Critical v0.13 P12 complete
REQ-163 UAT signoff script: scripts/uat-signoff.sh — idempotent, set -euo pipefail, ~35 named assertions covering all feature claims; read + non-mutating only (doctor, list, --dry-run); exit 0 iff all pass; scripts/uat-smoke.sh — pure-CLI subset for CI validate (version, acl file mode, doctor modes, no-password grep, metrics shape); tests for both scripts Critical v0.13 P12 complete

Scope notes (v0.13)

  • REQ-149..REQ-163 = 15 net-new requirements (REQ count grows 148 -> 163).
  • 14 phases (P0 + P01..P12 + P13 final); "no limit on phases" per operator.
  • P03 (scheduler wiring) and P12 (--type linux + UAT) are the feat phases; the rest are fix/chore/test/docs/refactor. Milestone type = feature (at least one feat).
  • Tags on v0.12.x patch line: v0.12.0 (P0) ... v0.12.13 (P13 final = v0.13 milestone release).
  • v1.0.0 production-ready tag stays deferred for post-v0.13 UAT signoff (operator runs scripts/uat-signoff.sh, paste output back).

Accepted residual risks (documented in threat-model, not fixed)

  • OIDC tokens plaintext at rest (0600) — sealing on every CLI invocation conflicts with "no orca binary on servers" model
  • HSTS on daemon — mTLS-only API, no browser-facing surface on daemon itself
  • DNS resolution timeout — bounded by net.Dialer{Timeout: 15s}
  • Temp file cleanup on SIGKILL — orphaned temp files, operator-visible, low impact
  • Flock timeout on NFS — stuck holder is rare; tryFlockEx exists if needed later
  • "WASM-first" pillar aspirational — document as "WASM runtime available, process is default"
  • arm64/armv7 release — D-193 deferred; install.sh detection is forward-looking
  • OIDC callback slowloris — loopback, short-lived, single CLI invocation

Milestone v0.14: Ingress Bootstrap Completeness

Scope: ensure that linux & proxmox types are properly bootstrapped with traefik during cluster init or node join. All cluster endpoints are provisioned as sockets (R-007); routing between jobs and services depends on traefik being present on the host and properly configured. The v0.13 traefik deployment shipped only a binary + systemd unit + empty dynamic dir — it never wrote the static config nor applied nft rules, so orca-traefik.service fails to start on a fresh orca init and orca doctor nft FAILs. v0.14 replaces the binary+systemd model with a podman container running a custom orca-traefik image, and completes the nft SNAT+DNAT ingress stack on every node type.

New load-bearing rule:

  • R-024 — Traefik runs exclusively as a podman container, deployed from the orca-traefik image published per release. Every orca-managed ingress surface bootstraps: nft DNAT (:443→127.0.0.1:8443, :80→127.0.0.1:8080) + SNAT/MASQUERADE postrouting + podman run -d --restart=unless-stopped --network host -v /etc/traefik/traefik.yml:/etc/traefik/traefik.yml:ro -v /etc/traefik/dynamic:/etc/traefik/dynamic:ro -v /etc/orca/step-ca-root.crt:/etc/orca/step-ca-root.crt:ro git.cloudinit.dev/coreci/orca-traefik:<tag>. No node joins without a functional podman-traefik ingress data plane. The image's baked static config is a default; host-side traefik.yml mounted :ro overrides it (preserves traefik-on-public-ip opt-out, REQ-100).

Three topologies (per operator constraints):

  • Linux: host → nft → podman run orca-traefik (host network)
  • Proxmox Native: host → nft → LXC (nesting=1) → podman run orca-traefik
  • Proxmox Floating-IP: LXC (owns floating IP) → nft (inside LXC) → podman run orca-traefik
ID Requirement Priority Phase Status
REQ-171 Dockerfile.traefik + release pipeline: build + publish git.cloudinit.dev/coreci/orca-traefik:<version> alongside the orca image per release; .coreci.yml container-publish-traefik step; image bakes default traefik.yml (entrypoints websecure 127.0.0.1:8443, web 127.0.0.1:8080, traefik 127.0.0.1:8081 + file provider watching /etc/traefik/dynamic + json log/accessLog); host-side /etc/traefik/traefik.yml mounted :ro overrides baked config (preserves traefik-on-public-ip opt-out REQ-100); no certificatesResolvers (traefik v3.3 only supports acme/tailscale); tls: {} in dynamic config for v0.14 (real mTLS deferred to v0.15) Critical v0.14 P1 complete
REQ-172 Replace internal/traefik/install.go binary+systemd install with a podman-container reconciler: podman pull orca-traefik:<tag> + podman run -d --restart=unless-stopped --network host --name orca-traefik -v /etc/traefik/traefik.yml:/etc/traefik/traefik.yml:ro -v /etc/traefik/dynamic:/etc/traefik/dynamic:ro -v /etc/orca/step-ca-root.crt:/etc/orca/step-ca-root.crt:ro <image>; idempotent (pull+run if absent, start if stopped); install podman if absent (C-50); v0.13→v0.14 upgrade: detect+stop+disable+remove legacy orca-traefik.service + /usr/local/bin/traefik (C-57); works locally + over SSH-push; enable podman-restart.service; remove systemd unit generation Critical v0.14 P2 complete
REQ-173 nft SNAT+DNAT ruleset render+apply: extend internal/emitter/nft.go with postrouting masquerade chain; new internal/ingress/bootstrap.go renders orca.nft + applies nft -f + ensures /etc/traefik/dynamic dir + pushes step-ca root CA + invokes podman traefik reconciler; wired into orca init (localhost lead) Critical v0.14 P3 complete
REQ-174 Remote ingress bootstrap via SSH-push for orca node join --type linux: push step-ca root CA, render+apply nft remotely, invoke podman traefik reconciler remotely; register node as linux Critical v0.14 P4 complete
REQ-175 Proxmox native ingress mode (--ingress-mode native, default): on PVE host, render+apply nft (vmbr-compatible, separate orca-ingress table avoids pve-firewall conflict); create unprivileged LXC with --features nesting=1,keyctl=1 running podman+orca-traefik; push step-ca root CA into LXC; nft DNAT target = LXC bridge IP; register PVE host as proxmox node; add IngressMode field to model.Node + schema migration Critical v0.14 P5 complete
REQ-176 Proxmox floating-IP mode (--ingress-mode floating-ip --floating-ip --gateway --mac [--net-prefix]): pct create Ubuntu LXC named ingress with net0 bridge=vmbr0,hwaddr=<mac>,ip=<floating-ip>/<prefix>,gw=<gateway> --features nesting=1,keyctl=1 --onboot 1; install podman + run orca-traefik inside LXC; apply nft DNAT+SNAT inside LXC; register LXC as managed linux node (name=ingress, addr=<floating-ip>:8443); interactive prompt for params when flags absent + not --json; validate IP/MAC/gateway; PVE host also registered as proxmox for workload dispatch Critical v0.14 P6 complete
REQ-177 orca doctor ingress [--peer]: verify orca-traefik container running (podman inspect), nft DNAT+SNAT applied, /etc/traefik/dynamic exists, step-ca root CA mounted; extend scripts/uat-signoff.sh with ingress assertions (40 podman_traefik, 41 nft_dnat_snat, 42 linux_worker, 43 proxmox_native_lxc / floating_ip_lxc) High v0.14 P7 complete
REQ-178 Docs: docs/cli.md (--ingress-mode + floating-IP flags + doctor ingress), docs/uat.md (native + floating-IP topologies), docs/ingress.md (podman-traefik image + volume mounts + certResolver), docs/docker.md (orca-traefik image), ARCHITECTURE.md (R-024 + ingress bootstrap section) High v0.14 P7 complete
REQ-179 Integration tests: hermetic harness fakes SSH; asserts init→podman traefik running + nft applied; linux join→remote podman+nft; proxmox native→LXC created with nesting + podman traefik; floating-ip→pct create with correct net0 args + LXC registered as linux node; release.sh builds orca-traefik image (Dockerfile.traefik parses) Critical v0.14 P7 complete

Scope notes (v0.14)

  • REQ-171..REQ-179 = 9 net-new requirements (REQ count grows 163 -> 172).
  • 9 phases (P0 + P1..P7 + P8 final); feature milestone (multiple feat phases).
  • Tags on v0.13.x patch line: v0.13.0 (P0) ... v0.13.8 (P8 final = v0.14 milestone release).
  • Milestone branch: milestone/v0.14-ingress-bootstrap.

Milestone v0.15: CI Release Pipeline Fix

Scope: fix the container image publishing pipeline. v0.14 shipped Dockerfile.traefik + Dockerfile but no container images were published to the Gitea registry because: (1) no Gitea Actions workflow existed to trigger on tag pushes, (2) the CoreCI trigger script stripped tag refs, (3) the .coreci.yml container-publish steps used Docker-in-Docker (docker:24-cli) which is prohibited. v0.15 adds a Gitea Actions workflow that triggers on tag pushes, installs the coreci binary on the runner, and runs coreci run. The .coreci.yml container-publish steps are rewritten to use kaniko (no Docker daemon required).

ID Requirement Priority Phase Status
REQ-180 Create .gitea/workflows/release.yml that triggers on push: tags: ['v*'], installs the coreci binary (from git.cloudinit.dev/coreci/coreci), injects PAT_TOKEN secret as GITEA_TOKEN env var, and runs coreci run — which executes the full .coreci.yml pipeline (validate, build, test, release) locally on the Gitea Actions runner Critical v0.15 P1 complete
REQ-181 Replace docker:24-cli DinD steps in .coreci.yml with kaniko (gcr.io/kaniko-project/executor:debug): write /kaniko/.docker/config.json from GITEA_TOKEN (base64 auth), run /kaniko/executor --dockerfile=<Dockerfile> --context=dir://. --destination=<registry/image:tag> --skip-tls-verify-registry. Applies to both container-publish (orca image) and container-publish-traefik (orca-traefik image) Critical v0.15 P1 complete
REQ-182 Set PAT_TOKEN Gitea Actions repository secret via tea actions secrets create (same value as GITEA_TOKEN from .env). Gitea reserves the GITEA_ prefix for built-in secrets, so the secret must be named PAT_TOKEN, not GITEA_PAT High v0.15 P0 complete

Scope notes (v0.15)

  • REQ-180..REQ-182 = 3 net-new requirements (REQ count grows 172 -> 175).
  • 3 phases (P0 + P1 + P2 final); fix milestone (no feat phases — CI infrastructure).
  • Tags on v0.14.x patch line: v0.14.0 (P0) ... v0.14.2 (P2 final = v0.15 milestone release).
  • Milestone branch: milestone/v0.15-ci-release-pipeline.
  • REQ-182 is complete: PAT_TOKEN secret created via tea actions secrets create PAT_TOKEN <value> --repo coreci/orca.

Milestone v0.16: Release Binary Asset Fix

Scope: fix the root cause of releases shipping with zero binary assets. v0.15 added a Gitea Actions workflow but it never executed successfully due to two compounding bugs: (1) the git clone of the private coreci repo in the workflow had no credentials, causing the "Install CoreCI" step to fail; (2) the .coreci.yml used an invalid pipelines:/steps:/image:/commands: format that CoreCI does not recognize (CoreCI's native format is jobs: with plugin:/invoke: /vars: and a DAG via needs:). Both issues must be fixed for the release pipeline to actually build and upload binaries.

ID Requirement Priority Phase Status
REQ-183 Fix .gitea/workflows/release.yml "Install CoreCI" step: the git clone of the private coreci repo fails because the clone command has no credentials. The actions/checkout@v4 step only injects auth for the orca repo (via http.https://git.cloudinit.dev/.extraheader), not for the subsequent bare git clone of the coreci repo. Fix: embed the PAT_TOKEN in the clone URL (https://cloudinit-bot:${GITEA_TOKEN}@git.cloudinit.dev/coreci/coreci.git) and pass GITEA_TOKEN: ${{ secrets.PAT_TOKEN }} as env to the "Install CoreCI" step Critical v0.16 P1 complete
REQ-184 Rewrite .coreci.yml from the invalid pipelines:/steps:/image:/commands: format to CoreCI's native jobs:/plugin:/invoke:/vars: format with a proper DAG (needs:). CoreCI's Pipeline struct only has Jobs/Services/Env fields — unknown top-level keys and unknown job fields are silently dropped by yaml.Unmarshal, producing an empty Jobs map. coreci run then executes zero jobs (validate does not reject empty jobs). The rewrite must: (a) convert each pipeline to a job with plugin: docker://golang:1.25.12 and invoke: for the commands, (b) use needs: for DAG ordering (validate→build→test→release), (c) pass GITEA_TOKEN via vars: { GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} } (resolved from env via CoreCI's secret resolver os.Getenv fallback), (d) use CI_COMMIT_BRANCH (tag name on tag push, from CoreCI's github.go CI context) and CI_COMMIT_SHA for version injection, (e) handle the case where the release already exists (created by the CIAgent ship workflow with title+body but no binary) by falling back to Gitea API asset attachment, (f) verify assets are actually attached after release creation (REQ-097 gate C-21) Critical v0.16 P1 complete

Scope notes (v0.16)

  • REQ-183..REQ-184 = 2 net-new requirements (REQ count grows 175 -> 177).
  • 3 phases (P0 + P1 + P2 final); fix milestone (no feat phases — CI infrastructure).
  • Tags on v0.15.x patch line: v0.15.0 (P0) ... v0.15.2 (P2 final = v0.16 milestone release).
  • Milestone branch: milestone/v0.16-release-binary-fix.
  • Root cause analysis confirmed: all 87 releases in the repo's history have zero binary assets — this has never worked. The releases are created by the CIAgent ship workflow (via Gitea API, title+body only); the binary upload is exclusively the .coreci.yml release job's job, and that job has never executed.