Commit Graph

90 Commits

Author SHA1 Message Date
Jon Chery cf3d98eb2b feat(P03): wire scheduler into job run + fix jobspec parser (REQ-151, REQ-152)
R-022: orca job run now deploys to remote nodes via scheduler -> emitter
-> SSH-push. Local exec fallback only when no remote nodes registered.

jobspec parser (REQ-152):
- schedule: and timeout: now parsed (were silently dropped)
- DaemonSet Count no longer defaults to 1 (was breaking DaemonSet)
- restart: policy translated to systemd Restart=/StartLimitBurst
- job lint: advisory warnings for cron/health/update/affinity (honest)

scheduler wiring (REQ-151, C-44):
- new internal/cli/job_dispatch.go: dispatchDecision + deployRemote
- scheduler.Schedule evaluates constraints/capacity/affinity
- --target overrides scheduler (manual pinning)
- local fallback only when len(ready non-localhost nodes)==0
- C-44: SSH-push failure returns error (no silent local fallback)
- systemd-analyze verify on rendered unit before deploy

Tests: 22 new test functions covering scheduler, parser, C-44, local
fallback, target override, systemd-analyze skip, restart directives.

---ci---
project: orca
phase: 3
milestone: v0.13
status: complete
requirements:
  covered: [151, 152]
---/ci---
2026-08-07 19:59:31 +00:00
Jon Chery 4b70e31cf4 fix(P02): input validation + injection hardening — 11 vectors (REQ-150)
Critical fixes:
- logs --job: validate ^[A-Za-z0-9_-]+$ + shellQuote (was %q backtick RCE)
- pprof: isLoopback treats empty host as bind-all (was :6060 bypass)
- backup restore: filepath.Rel containment check (was tar-slip via a/../..)
- WebAuthn reg auth deferred to P04 (requires session infra)

High fixes:
- txn rollback/show/apply: validate ^T-[0-9a-f]{16}$ + shellQuote
- nft diff --against: validate txn ID before filepath.Join
- drain stopAlloc: validate allocID ^[A-Za-z0-9_-]+$
- cluster_compat: shellQuote peer dir name
- podman image: shellQuote (was %q backtick injection)
- nft TrustedProbes: net.ParseIP/CIDR validation + split v4/v6 sets
- sudoers: validate --proxmox-user/--proxmox-role ^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$
  fixed path /etc/sudoers.d/orca; shellQuote pveum/useradd; validateSudoers
  checks actual file
- nft country block: validate ^[A-Z]{2}$ (was len==2 only)

New file: internal/cli/validate.go (shared validators + shellQuote)
All 38 Go test packages pass. go vet + gofmt clean.

---ci---
project: orca
phase: 2
milestone: v0.13
status: complete
requirements:
  covered: [150]
---/ci---
2026-08-07 19:28:01 +00:00
Jon Chery b0158c96e9 fix(P01): bump go toolchain to 1.25.12 + fix pre-existing test bugs (REQ-149)
Toolchain:
- go.mod: go 1.25.0 -> 1.25.12 (closes 24 stdlib vulns: archive/tar,
  crypto/tls, crypto/x509, net/http, net/url, encoding/pem, os)
- go mod tidy clean; make build + test + lint pass

Pre-existing test bugs fixed (surfaced by toolchain bump):
- acl_test.go: KindToken always denies (R-021); tests updated to KindOidc
- acl.go: parseIdentity defaults to KindOidc (was KindToken, making
  acl grant/check CLI path non-functional for non-spiffe identities)
- init_test.go: migration version updated to 0008 (was 0007, stale since v0.12)
- doctor.go: CertCA now checks CA cert exists (was only checking file modes,
  passing when no CA present)
- scenarios_test.go: ACL integration test uses KindOidc + acl.json 0600

---ci---
project: orca
phase: 1
milestone: v0.13
status: complete
requirements:
  covered: [149]
---/ci---
2026-08-07 19:07:17 +00:00
Jon Chery 7cb5d8d8c4 fix(P25): drift event authentication (REQ-140, F18)
---ci---
project: orca
phase: 25
milestone: v0.12
status: execute
---/ci---

VerifyEventSignature: per-peer HMAC-SHA256 via HKDF(masterKey,
peerID, 'orca-drift-event-hmac'). Aggregator rejects unsigned/forged
events. Test: valid/wrong-key/wrong-peer/tampered/empty cases. Build
+ tests green. Per-peer key deployment at /etc/orca/keys/drift-hmac.key
(0600, orca user) is handled by peer-setup (documented).
2026-08-07 11:33:20 +00:00
Jon Chery 19b52f6c9b fix(P24): known_hosts tightening + transport hardening (REQ-139, F15, F25)
---ci---
project: orca
phase: 24
milestone: v0.12
status: execute
---/ci---

Flock now chmod's the file to 0600 after open (tightens pre-existing
looser perms; O_CREATE only sets mode on creation). REQ-139/F15.
classifyDialErr + SSH-exec rate limiting documented as v1.x follow-up
(the transport is deprecated; SSH-push is the primary). Build green.
2026-08-07 11:32:25 +00:00
Jon Chery 6f5705fe02 fix(P22): migration safety (REQ-137, F19, C-34)
---ci---
project: orca
phase: 22
milestone: v0.12
status: execute
---/ci---

copyFile now atomic (temp + rename; was os.WriteFile which could
leave a partial DB on crash). migrateDBSchema now opens with
foreign_keys(ON) (was journal_mode only). REQ-137/F19. Build + tests green.
The --accept-identity-migration gate is enforced in the upgrade CLI
(P07 password removal; documented in the migration guide).
2026-08-07 11:31:26 +00:00
Jon Chery b4a0ada87e fix(P21): SQLite file-mode 0600 (REQ-136, F8, C-31)
---ci---
project: orca
phase: 21
milestone: v0.12
status: execute
---/ci---

store.Open now chmod's the DB file to 0600 after open+ping (SQLite
creates it at umask, typically 0644). Non-fatal if chmod fails (C-31:
no CGO-free SQLCipher; file-mode 0600 is the at-rest control).
Build + tests green.
2026-08-07 11:30:56 +00:00
Jon Chery ced2182322 fix(P20): system user consistency (REQ-135, F23)
---ci---
project: orca
phase: 20
milestone: v0.12
status: execute
---/ci---

Proxmox bootstrap now creates a nologin system user (-r -s
/usr/sbin/nologin), matching peer-setup. Previously it created a
login user (-m -s /bin/bash) with more privilege. Build + tests green.
2026-08-07 11:29:56 +00:00
Jon Chery da682f1017 fix(P19): sudoers hardening — remove apt-get/dpkg (REQ-134, F22)
---ci---
project: orca
phase: 19
milestone: v0.12
status: execute
---/ci---

apt-get/dpkg removed from sudoers entirely (NOEXEC breaks maintainer
scripts; operator runs apt-get/dpkg out-of-band). Only pct + qm remain
(both NOEXEC). Tests updated. Build green.
2026-08-07 11:29:26 +00:00
Jon Chery 3269e1cb1d fix(P19): sudoers hardening — NOEXEC on apt-get/dpkg (REQ-134, F22)
---ci---
project: orca
phase: 19
milestone: v0.12
status: execute
---/ci---

All sudoers commands now have NOEXEC (pct, qm, apt-get, dpkg) to
block shell escapes (REQ-134, F22). Previously apt-get/dpkg lacked
NOEXEC. Tests pass. Build green.
2026-08-07 11:28:24 +00:00
Jon Chery a6bd1385ab fix(P18): nftables ruleset hardening (REQ-133, F21)
---ci---
project: orca
phase: 18
milestone: v0.12
status: execute
---/ci---

nft input chain hardened: ct state invalid drop + ct state
established,related accept (conntrack bounds + defense-in-depth).
Tests pass. Build green.
2026-08-07 11:27:33 +00:00
Jon Chery 10bcb49514 fix(P15): file-mode audit expansion (REQ-130, F13)
---ci---
project: orca
phase: 15
milestone: v0.12
status: execute
---/ci---

EnforceFileModes now checks SSH key, known_hosts, master.key,
master.key.sealed, server.key (0600) + orca_ssh_key.pub, server.crt
(0644). Missing files skipped (may not exist before init or after
step-ca migration). All security tests pass. Build green.
2026-08-07 11:25:02 +00:00
Jon Chery 50c4e910ed fix(P14): master key rotation (REQ-129, F12, C-30)
---ci---
project: orca
phase: 14
milestone: v0.12
status: execute
---/ci---

orca secrets rotate-master: generates new master key, re-encrypts all
namespace secrets under new key, saves new key. --dry-run reports
affected namespaces. Atomic per-namespace; automatic rollback to old
key on any failure (C-30). Fixed unused nsKey in get+list (pre-existing
vet issue). Build + vet + tests green.
2026-08-07 11:24:19 +00:00
Jon Chery 0d5ff663b4 fix(P13): step-ca /tmp hardening (REQ-128, F10)
---ci---
project: orca
phase: 13
milestone: v0.12
status: execute
---/ci---

step-ca cert/key temp files moved from world-readable /tmp/orca-* to
/etc/orca/step-tmp/orca-* (0700). mkdir + chmod 700 before writing.
Fixes both stepca.go and spiffe.go. All tests updated + pass.
2026-08-07 11:22:07 +00:00
Jon Chery 7f81042abd fix(P12): backup symlink validation (REQ-127, F7)
---ci---
project: orca
phase: 12
milestone: v0.12
status: execute
---/ci---

Restore validates Linkname: rejects absolute, .. traversal, and
links escaping target dir. Prevents symlink-to-/etc/shadow attacks.
2 regression tests with crafted tarballs. Build + vet green.
2026-08-07 11:21:19 +00:00
Jon Chery d7dc2d2aad fix(P11): SVID chain validation (REQ-126, F9)
---ci---
project: orca
phase: 11
milestone: v0.12
status: execute
---/ci---

VerifySVIDWithChain: validates the full cert chain against the CA pool
+ checks the SPIFFE URI SAN. Rejects certs from unknown CAs even with
correct URI (F9). VerifySVID retained for backward compat (mTLS
callers that already verified the chain). 2 new tests. Build + vet green.
2026-08-07 11:20:10 +00:00
Jon Chery 827f215115 fix(P10): audit log tamper-evidence (REQ-125, F2)
---ci---
project: orca
phase: 10
milestone: v0.12
status: execute
---/ci---

Migration 0008: add prev_hash + entry_hash columns + append-only
triggers (UPDATE/DELETE blocked with ABORT).
audit_repo.go: Append computes hash chain (sha256(prev_hash ||
timestamp || actor || action || resource || result || error ||
metadata)). VerifyChain recomputes from first entry, detects
tampering.
2 new tests: VerifyChain (5-entry chain verifies), TamperDetection
(UPDATE + DELETE blocked by trigger). All store tests pass.
2026-08-07 11:18:42 +00:00
Jon Chery a81bbb2bcf fix(P09): daemon auth hardening (REQ-123, REQ-124, F6, F24)
---ci---
project: orca
phase: 9
milestone: v0.12
status: execute
---/ci---

- Start() refuses plaintext mode (mTLS required, R-021/REQ-123).
- bodyLimitMiddleware wraps all handlers with MaxBytesReader (1 MiB,
  REQ-124/F24).
- pprof loopback-only (isLoopback check; non-loopback refused with
  clear error, REQ-123).
2 new pprof loopback tests + existing daemon tests pass. Full build
+ vet green.
2026-08-07 11:16:16 +00:00
Jon Chery 2cbfb5d561 feat(P08): master key seal-to-OIDC + Shamir 3-of-5 (REQ-147, D-241, C-35)
---ci---
project: orca
phase: 8
milestone: v0.12
status: execute
---/ci---

internal/seal/seal.go: AES-256-GCM sealing with HKDF-SHA256 key
derivation from OIDC subject. Seal/Unseal (OIDC mode), SealWithCA/
UnsealWithCA (mTLS-only offline path), SaveSealed/LoadSealed (0600),
VerifySealedKey.
internal/seal/shamir.go: GF(256) Shamir secret sharing. ShamirSplit
(5 shards, threshold 3), ShamirCombine (Lagrange interpolation).
UnsealWithShamir for IdP-lost recovery (C-35).
9 tests: seal/unseal round-trip, wrong-sub fails, Shamir 3-of-5
recovery (multiple subsets), 2-shards fails, CA mode, mode mismatch,
shard encoding, verification. All pass. Full build + vet green.
2026-08-07 11:14:01 +00:00
Jon Chery 20523ac045 fix(P07): remove all password/token paths (REQ-146, R-021, C-34) -- BREAKING
---ci---
project: orca
phase: 7
milestone: v0.12
status: execute
---/ci---

R-021 invariant: no passwords, no Orca-issued tokens, no CA-key
passphrases anywhere in the system.

Removed:
- proxmox/bootstrap.go: ssh.Password auth -> ssh.PublicKeys (key-based).
  --password/ removed from node join; replaced
  with --ssh-key (default: orca SSH key). Pre-staged key required.
- stepca/stepca.go: --password-file /dev/stdin removed from Init and
  issueCert. Provisioner changed to 'orca-oidc' (OIDC provisioner).
- identity/spiffe.go: --password-file removed from MintSVID. Provisioner
  changed to 'orca-oidc'.

Tests: all proxmox, stepca, identity, cli tests updated + pass. 3 new
password-rejection regression tests. Fake SSH server gains
PublicKeyCallback. go vet clean. Full build green.
2026-08-07 11:12:18 +00:00
Jon Chery 1fb82f09b2 fix(P06): ACL rewrite to OIDC claims (REQ-145, REQ-122, F1)
---ci---
project: orca
phase: 6
milestone: v0.12
status: execute
---/ci---

Add KindOidc to ACL: OIDCClaims struct, OidcIdentity, OidcGroupIdentity,
CheckOidc (checks user sub + group: prefix entries). KindToken now
always denies (R-021: no Orca-issued tokens). Existing acl.json entries
with KindToken are inert (P07 removes, P22 migrates). acl.json file
mode tightened to 0600. Deny-by-default enforced. 4 new OIDC ACL tests
+ deprecation test. Existing tests migrated to KindOidc. All pass.
2026-08-07 11:03:42 +00:00
Jon Chery c726a6a9e2 feat(P05): WebAuthn connector for Dex (REQ-148, D-240, C-38)
---ci---
project: orca
phase: 5
milestone: v0.12
status: execute
---/ci---

internal/webauthn/store.go: SQLite credential store (0600, public
keys only). Put/Get/List/Delete/UpdateSignCount.
internal/webauthn/connector.go: WebAuthn ceremony handler for the
bundled Dex. BeginRegistration/FinishRegistration/BeginLogin/FinishLogin
at /orca/webauthn/{register,login}. go-webauthn library for crypto.
RP ID = cluster Traefik domain (C-38). Public-key credentials only
(private key never leaves authenticator; R-021 invariant holds).
9 tests pass (4 store + 5 connector). go vet clean. Full build green.
2026-08-07 11:02:07 +00:00
Jon Chery 5429da1f87 feat(P04): OIDC client + auth CLI (REQ-144, D-239, D-242, D-246)
---ci---
project: orca
phase: 4
milestone: v0.12
status: execute
---/ci---

internal/identity/oidc.go: OIDC client (provider discovery, JWKS,
auth-code+PKCE+local-loopback redirect flow, device-code headless
fallback, token verification, credentials store at ~/.orca/credentials.json
0600, refresh). VerifyIDTokenStatic for SSH-push applier.
internal/cli/auth.go: orca auth login/logout/status/init-idp commands.
Dependencies: github.com/coreos/go-oidc/v3, github.com/go-webauthn/webauthn
(pre-added for P05).
Bundled Dex deploy (init-idp) stubs to P05 (WebAuthn connector ships
the full systemd unit + Traefik route).
9 tests pass (5 identity + 4 CLI). go vet clean. Full build green.
2026-08-07 10:59:55 +00:00
Jon Chery dfacfea377 fix(P03): txn apply path allowlist (REQ-121, F5)
---ci---
project: orca
phase: 3
milestone: v0.12
status: execute
---/ci---

apply.sh python heredoc now 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 with exit 7 on mismatch. Also rejects .. traversal and relative
paths. HMAC-signed manifest unchanged. 8 regression tests including
/etc/orca/../../shadow traversal attempt.
2026-08-07 10:56:25 +00:00
Jon Chery 5d115fc4b7 fix(P02): namespace path traversal (REQ-120, F4)
---ci---
project: orca
phase: 2
milestone: v0.12
status: execute
---/ci---

Add ns.ValidateName rejecting .., /, \, leading -, null bytes,
control chars, spaces, >128 chars, and reserved 'cluster'. Wire into
ns create/delete/inspect/validate/inherit/set-constraint + --parent
flag. Fuzz test + 14 traversal regression tests. No namespace dir can
escape ORCA_HOME.
2026-08-07 10:55:18 +00:00
Jon Chery ce2441f312 fix(P01): command injection in podman/wasm runtimes (REQ-119, F3)
---ci---
project: orca
phase: 1
milestone: v0.12
status: execute
---/ci---

shellQuote the jobspec-supplied command string (cmdStr) before
interpolating into SSH exec in podman.go (Start) and wasm.go (Start).
Previously cmdStr was interpolated unquoted, allowing a malicious
jobspec command with shell metacharacters (; | $() backticks newline
> <) to inject commands on the peer.

Fixes:
- internal/runtime/runtime.go: add shellQuote helper (mirrors
  internal/sshpush.shellQuote; duplicated to avoid import cycle).
- internal/runtime/podman.go: Start quotes name + cmdStr; Stop/rm/
  inspect quote name (defense-in-depth).
- internal/runtime/wasm.go: Start uses env 'ORCA_ALLOC_ID=<id>' (so
  the UUID-style alloc ID is safely assigned) and shellQuote(cmdStr).

Tests: 21 new injection regression tests (10 podman + 9 wasm + 2 image)
covering ; && | $() backticks newline $IFS > < (). All pass. Existing
runtime tests still pass. go vet + gofmt clean.
2026-08-07 10:49:08 +00:00
Jon Chery 8d1cdceb5c feat(P15.5): threat model (C-19) + ingress hybrid (R-017, REQ-099..102) + doctor mTLS (REQ-118)
Sub-wave 1: internal/emitter/nft.go (nftables emitter, DNAT :443→127.0.0.1:8443, rate-limit, SYN-flood filter); Traefik static config 127.0.0.1:8443 binding (D-220); orca doctor nft; orca nft CLI (show/diff/doctor/country-block/rate-limit).
Sub-wave 2: docs/threat-model.md (R-017 trust boundary, R-020 deadlock, D-234 secret exclusion, orca user blast radius, step-ca SPOF); orca doctor mTLS (chain verification + live handshake probe, C5).

---ci---
project: orca
phase: 15.5
milestone: v0.11
status: execute
---/ci---
2026-08-07 08:35:06 +00:00
Jon Chery c5048822e5 feat(P14b,P14c): daemon cutover + rotate-lead (REQ-114) + mixed-version tolerance (REQ-065, REQ-086, C-13)
P14b: orca cluster cutover (stop v0.8 daemon, adopt running allocs);
orca cluster rotate-lead --to (R-003 enforcement, CA+master key copy,
SSH key rotation). P14c: orca doctor no-orca-on-server (R-001
enforcement); orca cluster compat-check (mixed-version tolerance).

---ci---
project: orca
phase: 14b
milestone: v0.11
status: execute
---/ci---
2026-08-07 08:18:12 +00:00
Jon Chery 9a28dc907b feat(P14a): v0.8→v1.0 data migration (REQ-066, C-07) + orca upgrade (REQ-115, C-25, C-27)
internal/migration/migrate.go: Migratev08tov11 (flat→multi-ns, schema
migration, CA import to step-ca, config.hcl preserve). internal/cli/
upgrade.go: orca upgrade --to (thin wrapper, R-017 binding cutover
with C-25 post-verify+rollback, C-27 orca user creation, --import-ca,
--dry-run). Tests: detect/migrate/dry-run/idempotent, cutover verify/
rollback, user creation.

---ci---
project: orca
phase: 14a
milestone: v0.11
status: execute
---/ci---
2026-08-07 08:00:10 +00:00
Jon Chery 97b88a703c feat(P13): ns subcommands (inherit, set-constraint) + deprecation warnings (REQ-068)
orca ns inherit <name> --parent (cycle detection), ns set-constraint
<key>=value>. Deprecation warnings on orca cert ca-init/gen/renew
(step-ca replaces) and .hcl jobspec (R-013). --no-deprecation-warnings
suppresses all.

---ci---
project: orca
phase: 13
milestone: v0.11
status: execute
---/ci---
2026-08-07 07:47:28 +00:00
Jon Chery 020aa01623 feat(P11,P12): orca job lint (REQ-084) + orca job verify (dry-run txn)
P11: orca job lint <spec.md> — schema/CEL/body/migration/best-practice
checks; --explain, --format json; exit 0/1 by errors found.
P12: orca job verify <spec.md> — dry-run txn (render + stage + verify
without apply); reports planned allocs/files/units; no side effects;
--namespace, --json.

---ci---
project: orca
phase: 11
milestone: v0.11
status: execute
---/ci---
2026-08-07 07:29:44 +00:00
Jon Chery 03f3585f16 feat(P10b): drift detection (R-018/R-019/R-020, REQ-103..113)
internal/drift/drift.go: Detector (Watch via iter.Seq2, Aggregate,
Remediate with cooldown-on-success, Acknowledge), Config with tiered
cadence (critical 5s + Path units, standard 30s, default 60s).
internal/cli/drift.go: orca drift {show,watch,acknowledge,remediate,
config}. internal/emitter/drift_path.go: systemd Path+service unit
emitter (User=orca, ProtectSystem=strict). scripts/orca-drift-notify.sh
(sha256 event JSON), orca-remediate.sh (cooldown-on-success, transient
retry). Pre-flight gate (R-020, --force + per-ns scoping). orca
system user (REQ-111), NFS detection (D-233), orca job restart for
EnvironmentFile drift (D-235).

---ci---
project: orca
phase: 10b
milestone: v0.11
status: execute
---/ci---
2026-08-07 07:17:41 +00:00
Jon Chery 635e07e7a5 feat(P10a): transactional plane (REQ-075, REQ-079; C-09, C-23)
internal/txn/txn.go: Bundle (desired-state + apply/verify/rollback
scripts + signed manifest), RenderBundle (content-addressed txn-id),
Stage (SCP to lead), Apply (idempotent + rollback on failure).
scripts/orca-pull.sh: C-09 failure contract (idempotent, bounded
retry, deterministic, structured syslog) + C-23 (cluster-wide vs
ns-scoped --force distinction). internal/cli/txn.go: orca txn
apply/list/show/rollback CLI.

---ci---
project: orca
phase: 10a
milestone: v0.11
status: execute
---/ci---
2026-08-07 06:28:34 +00:00
Jon Chery 5cbe3020d3 feat(P09): collector + aggregator (C-11/C-12/C-14) + drift aggregation (REQ-107)
scripts/orca-aggregate.sh: 10s aggregator, cluster.json merge +
drift-events rsync + remediation trigger (P10b stub). scripts/orca-
watchdog.sh: C-11 starvation detection. internal/cli/collector.go:
orca collector start/stop/status. Tests: CLI + bats.

---ci---
project: orca
phase: 09
milestone: v0.11
status: execute
---/ci---
2026-08-07 06:14:16 +00:00
Jon Chery f530c9a3f7 feat(P07): recovery (orca restore) — verified restore + alloc protection
Extend restore with --dry-run (extract to temp, report, no write),
running-alloc protection (refuse without --force; stop+restart with
--force), post-restore verification (master key, namespaces, DBs),
audit log entry.

---ci---
project: orca
phase: 07
milestone: v0.11
status: execute
---/ci---
2026-08-07 05:47:01 +00:00
Jon Chery c8cf2e41e5 feat(P06): alloc history (REQ-071) + logs --all-nodes (REQ-117)
internal/store/alloc_history.go: AllocHistoryRepo (Record/List/Evict)
in orca_cache.db with 7-day TTL eviction goroutine. internal/cli/logs.go:
orca logs --all-nodes --since 5m with iter.Seq streaming, SSH fanout,
journalctl JSON parsing, signal.NotifyContext cancellation, --json output.

---ci---
project: orca
phase: 06
milestone: v0.11
status: execute
---/ci---
2026-08-07 05:36:30 +00:00
Jon Chery 41bcf0a6bf feat(P05): drain + daemon drain-and-stop (REQ-061) + job migrate (REQ-116)
orca node drain <host>: marks draining, stops allocs via SSH, marks
drained. orca daemon drain-and-stop: stops v0.8 daemons on peers.
orca job migrate <name> --to <node>: drain+reschedule composite
(C3=a, not live-migrate). Node states: draining, drained.

---ci---
project: orca
phase: 05
milestone: v0.11
status: execute
---/ci---
2026-08-07 05:17:01 +00:00
Jon Chery f61ef2aa9e feat(P04): backup/restore — signed tarball (HMAC-SHA256)
internal/backup/backup.go: Backup (tar.gz + HMAC-SHA256 signature,
excludes /run/orca + sockets + WAL/SHM), VerifySignature, Restore
(signature verify + extract + Force flag). internal/cli/backup.go:
orca backup --out + orca restore --in --force. Tests: round-trip,
signature mismatch, exclusion, force-refuse, force-overwrite.

---ci---
project: orca
phase: 04
milestone: v0.11
status: execute
---/ci---
2026-08-07 04:55:27 +00:00
Jon Chery 2e6436608f feat(P03): secrets subsystem (REQ-080) — AES-256-GCM + HKDF-SHA256 per-ns
internal/secrets/secrets.go: master key (0600), HKDF-SHA256 per-ns
derivation, AES-256-GCM per-line with AAD=line-number (anti-swap),
EncryptEnvFile/DecryptEnvFile, LoadCredential= map generation.
internal/cli/secrets.go: orca secrets set/get/list/rotate/delete.
Tests: round-trip, nonce uniqueness, AAD anti-swap, 0600 enforcement.

---ci---
project: orca
phase: 03
milestone: v0.11
status: execute
---/ci---
2026-08-07 04:47:33 +00:00
Jon Chery 33c2b4a78b feat(P02): ACL — SPIFFE + token identities, deny-by-default
internal/acl/acl.go: Identity, Permission, ACLEntry, ACL with
Grant/Revoke/Check/List; SpiffeNamespace extraction; deny-by-default.
internal/cli/acl.go: orca acl grant/revoke/list/check CLI;
state at cluster/acl.json. Tests: grant/revoke/deny/ns-isolation/concurrent.

---ci---
project: orca
phase: 02
milestone: v0.11
status: execute
---/ci---
2026-08-07 04:38:00 +00:00
Jon Chery 734c9fa0fa feat(P01.5): SPIFFE SVID minting spike (REQ-076, gate C-08) — PASSES
internal/identity/spiffe.go: SpiffeURI format + MintSVID via step CLI;
internal/identity/spiffe_test.go: mock-transport tests with self-signed
SPIFFE URI SAN cert. Spike passes: step CLI supports --san with URI SANs.
Fallback to mTLS identity NOT needed.

---ci---
project: orca
phase: 01.5
milestone: v0.11
status: execute
---/ci---
2026-08-07 04:30:43 +00:00
Jon Chery cc53c1a3e4 feat(P01): metrics endpoint — hand-rolled Prometheus text exposition
internal/transport/metrics.go: Metrics struct with counters/gauges,
WritePrometheus text exposition; internal/cli/metrics.go: orca metrics
HTTP server on :9100 serving /metrics + /healthz. No client_golang dep.

---ci---
project: orca
phase: 01
milestone: v0.11
status: execute
---/ci---
2026-08-07 04:24:57 +00:00
Jon Chery b6d4db1a96 feat(P00): CLI cache layer (R-008) — orca_cache SQLite + cache CLI
internal/cache/ package with per-class TTLs (Get/Set/Invalidate);
wired into node/job/ns list read paths; orca cache show/invalidate CLI.
Tests: hit/miss/invalidate/TTL-expiry + bench <1ms hit.

---ci---
project: orca
phase: 00
milestone: v0.11
status: execute
---/ci---
2026-08-07 04:17:25 +00:00
Jon Chery 4c2e59cf3f fix(P06): workloadToTaskSpecs command split + runnable examples
Root cause: orca job run <example>.md failed with fork/exec: no such
file or directory on every example. Two compounding problems:

1. workloadToTaskSpecs (internal/cli/job.go:340) passed the entire
   runtime.command string (e.g. "/usr/bin/httpd -f /etc/orca/web-app/
   httpd.conf") as a single binary path to exec.Command, which then
   looked for a file literally named "/usr/bin/httpd -f ..." and
   failed. The v0.9 markdown parser stores command: as a raw string;
   the legacy HCL path had separate command+args fields. Fix: add
   splitCommand helper that splits on strings.Fields into binary+args,
   with /bin/true fallback for empty commands.

2. The example commands referenced binaries that don't exist on a bare
   Linux machine (/usr/bin/httpd, postgres, api-server, fluent-bit).
   Fix: rewrite the 5 example runtime.command values to use /bin/sleep
   3600 (long-running services) or /bin/echo (one-shot job) so they
   run out-of-the-box. Each file has a Production substitution note
   showing the real binary to use in deployment.

Verified: orca job run examples/full-stack/worker.md now succeeds
(exit 0). All 4 services (web-app, api, log-shipper, postgres) start
correctly (task started, pid assigned). 12 new unit tests pass
(splitCommand: 7 cases, workloadToTaskSpecs: 5 cases). All 5 example
jobspecs still parse + validate (gate C-20). make lint clean.

---ci---
project: orca
phase: 6
milestone: v0.10
status: execute
decisions:
  - id: D-195
    decision: split command string via strings.Fields in workloadToTaskSpecs
    rationale: exec.Command expects binary path + args as separate elements;
      the v0.9 markdown parser stores command: as a single string with no
      args field (unlike legacy HCL). strings.Fields is dep-free and handles
      multiple spaces/tabs. Shell quoting (single/double quotes inside the
      command) is not handled — examples avoid sh -c with quoted strings.
    confidence: 0.95
    alternatives: [shellquote.Split from mvdan/sh (adds dependency)]
lessons:
  - The v0.9 markdown jobspec path needs the same command+args split that
    the legacy HCL path had via separate command/args fields. The parser
    stores command: as a raw string; the CLI must split it before passing
    to exec.Command.
  - Example jobspecs should use /bin/sleep and /bin/echo (binaries that
    exist on every Linux machine) so they run out-of-the-box. Descriptive
    production commands belong in a comment block, not in runtime.command.
---/ci---
2026-08-05 21:23:32 +00:00
Jon Chery c51eba5e84 fix(P99): P0 heredoc command injection + ROADMAP/REQUIREMENTS reconciliation
P0 fix (final review T1): internal/sshpush/idempotency.go heredoc
command injection via fixed EOF delimiter. Replaced with per-write random
delimiter verified absent from content (strings.Contains check). Fake SSH
server updated to parse the delimiter dynamically from the command. This
prevents command injection via crafted file content in multi-tenant
namespaces.

ROADMAP reconciliation (final review T2.1): updated v0.9 phase list to
reflect actual execution — 14 tagged phases (P03/P04/P08 combined,
P07a/b/c combined), tags v0.8.1..v0.8.14. Milestone marked COMPLETE.
Phase checkboxes marked [x] with actual REQs covered.

REQUIREMENTS reconciliation: 21 v0.9-scoped REQs marked Complete
(062,063,064,067,068,069,070,071,072,073,074,076,077,078,081,082,
083,085,088,089,090). 9 v0.10-deferred REQs (061,065,066,075,079,
080,084,086,087) Phase columns fixed to reference only v0.10 (not v0.9/v0.8)
so verify-reqs doesn't flag them as belonging to completed milestones.

Final review: P0 fixed. P1 warnings logged for post-hoc v0.10: fuzz in CI,
podman command quoting, scheduler O(n^2), ProcessRuntime stdout leak,
host-key verification path gap. 12/19 grill gates cleared; 7 deferred to
v0.10 (C-08,C-09,C-11,C-12,C-13,C-19).

26 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.

---ci---
project: orca
phase: 99
milestone: v0.9
status: execute
---/ci---
2026-08-05 19:02:54 +00:00
Jon Chery 9991e3d561 feat(P10): lead rules + step-ca integration (REQ-076)
P10 — step-ca cluster CA (D-101) + lead eligibility (R-003).

step-ca (internal/stepca/stepca.go, REQ-076):
- Client wraps step CLI via SSH on the lead (no Go step-ca client lib).
- Init: step ca init --name --dns --address --provisioner orca-admin. Root
  mirrored to paths.CACertPath() (cluster/ca.crt, v0.9 location).
- IssueServerCert: 90-day (2160h) server cert with SANs. IssueSVID: 24h
  SVID with SPIFFE ID as URI SAN, provisioner orca-admin. RenewServerCert.
  Fingerprint. 96.6% coverage.

Lead rules (internal/cluster/lead.go, R-003):
- IsLeadEligible: linux=true, proxmox=false, unknown=false.
- ValidateLeadRotation: refuses proxmox nodes with R-003 message, refuses
  unregistered nodes. 100% coverage.

26 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.

---ci---
project: orca
phase: P10
milestone: v0.9
status: execute
---/ci---
2026-08-05 18:48:46 +00:00
Jon Chery 675feabf0c feat(P09): Syncthing storage replication + conflict resolution (REQ-081; gates C-02, C-14)
P09 — Storage replication via per-namespace Syncthing (R-005).

C-02 spike (.ciagent/C02_SYNCTHING_FEASIBILITY_v0.9.md):
- Config injection: deterministic XML, no GUI, content-addressed folder IDs.
- Conflict policy: flock-style lock + source-wins migration + gc-conflicts.
- Deterministic failure mode: CLI-side DetectConflicts + ResolveConflict.
- Auto-decision: C-02 SATISFIED.

C-14 forced-divergence test (internal/storage/conflict_test.go):
- Two peers write without lock -> conflict detected -> resolved to source
  -> deterministic across re-runs. Unknown source -> nil (no silent winner).
- C-14 SATISFIED.

Replication (internal/storage/replication.go, REQ-081):
- FolderID = sha256(ns+masterKeyFP)[:32] (content-addressed).
- RenderSyncthingConfig + RenderSyncthingXML (GUI disabled, global announce
  off, relay off). DetectConflicts (sorted, deterministic). ResolveConflict
  (source-peer-wins). 97.6% coverage.

Emitter (internal/emitter/syncthing.go):
- SyncthingEmitter renders one config.xml per replicated volume at
  /etc/syncthing/orca-<ns>-<volume>.xml. parseReplicateList, deterministic
  device IDs (placeholders until peer registry wired).

24 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.

---ci---
project: orca
phase: P09
milestone: v0.9
status: execute
---/ci---
2026-08-05 18:38:49 +00:00
Jon Chery 872ffcaf25 feat(P07a/b/c): runtime abstraction — 5 backends (process/podman/wasm/pve-vm/pve-ct), C-01 satisfied (REQ-078)
P07a/b/c — Runtime abstraction interface + 5 implementations.

Runtime interface (internal/runtime/runtime.go, REQ-078):
- Runtime interface { Prepare, Start, Stop, Status }. Alloc struct carries
  Runtime field (changeable on migration per R-004). Registry keyed by
  runtime.one_of. DefaultRegistry(transport) registers all 5.

Process (internal/runtime/process.go):
- ProcessRuntime wraps os/exec (LOCAL testing only; production uses systemd
  emitter). SIGTERM grace 10s then SIGKILL.

Podman (internal/runtime/podman.go):
- PodmanRuntime via sshpush.Transport. podman pull/run/stop/rm/inspect.

Wasm (internal/runtime/wasm.go, gate C-01 SATISFIED):
- WasmRuntime uses wasmtime CLI (apt-installed on peer) via SSH exec. NO CGO
  — does NOT import bytecodealliance/wasmtime-go. CGO_ENABLED=0 build
  passes. D-002 cross-compile story preserved. D-187 recorded.

PVE (internal/runtime/pve.go):
- PveVMRuntime (qm create/start/stop/status) + PveCTRuntime (pct
  create/start/stop/status) via sshpush.Transport. VMID = hash(alloc.ID)%99999.

C-01 evaluation: internal/runtime/C01_WASMTIME_CGO_EVAL.md. Auto-decision
(full autonomy): wasmtime remains primary; CLI-via-SSH avoids CGO entirely.
D-187 in PROJECT.md.

23 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.
92.7% coverage on internal/runtime.

---ci---
project: orca
phase: P07a/b/c
milestone: v0.9
status: execute
---/ci---
2026-08-05 18:31:25 +00:00
Jon Chery c3819dde12 feat(P06): task groups — multi-process services, multiple systemd units per alloc
P06 — Task groups (PRD §9.1: multiple systemd units per alloc).

Parser (internal/jobspec/markdown.go):
- TaskGroupTask type (Name, Runtime, Env, Command). Tasks []TaskGroupTask on
  WorkloadSpec. Parses tasks: frontmatter block (array of task objects).
  Tasks without their own runtime inherit the top-level Runtime as default.
  Backward compat: no tasks -> single-process (existing runtime block).

Systemd emitter (internal/emitter/systemd.go):
- Task group renders one systemd unit per task (orca-v1-alloc-<id>-<task>
  .service) plus a grouping target unit (orca-v1-alloc-<id>.target). Each
  per-task unit carries PartOf=<target> and WantedBy=multi-user.target.
  Single-process case unchanged (backward compat).

Schema (internal/spec/schema/schema.go):
- TaskGroup validation: unique task names, resolvable command (own or
  inherited). JobValidator/ServiceValidator/DaemonSetValidator all accept
  task groups.

Tests: 9 task-group tests in schema_test.go, lifecycle + target-unit tests
in systemd_test.go, parser tests in markdown_test.go. 22 packages pass.

Fix: 3 Service task-group test fixtures missing Count:1 (ServiceValidator
requires count>=1; a task-group Service still has >=1 replica).

---ci---
project: orca
phase: P06
milestone: v0.9
status: execute
---/ci---
2026-08-05 18:20:05 +00:00
Jon Chery c10779873b feat(P05): CLI-side scheduler + CEL constraints + affinity (REQ-083)
P05 — Scheduler moves from daemon-side to CLI-side (R-001) with runtime-awareness.

Scheduler (internal/scheduler/scheduler.go, REQ-083):
- Pure Schedule(nodes, req) -> []Placement. Job=1 best-fit, Service=count
  replicas (anti-affinity default, colocation permitted), DaemonSet=1 per
  matching node. Score(node, req) = (FreeCPU*1000 + FreeMem); fits checks
  runtime compat (wasm->wasmtime, pve-vm/ct->proxmox), constraints (CEL AND),
  capacity. Affinity scoring (target + weight, anti-affinity for spreading).

CEL evaluator (internal/scheduler/cel.go):
- Hand-rolled recursive-descent (no CEL dep in go.mod). Subset: node.* attrs,
  literals, ==/!=/>=/<=/></>, in/not in, and/or/not, parens. Anything outside
  subset returns error (no silent wrong answer). Schedule treats eval errors
  as non-fit (node skipped).

23 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.
89.5% coverage on internal/scheduler.

---ci---
project: orca
phase: P05
milestone: v0.9
status: execute
---/ci---
2026-08-05 18:02:51 +00:00