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 P01–P04 |
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 (P00–P06) 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.1–v0.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 (:443→127.0.0.1:8443, :80→127.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.