Commit Graph

71 Commits

Author SHA1 Message Date
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
Jon Chery ae6eb5a27b feat(P03,P04,P08): update stanza + lifecycle hooks + socket plumbing
P03 — Update stanza (rolling/canary/blue-green):
- internal/spec/schema/update.go: UpdateValidator (strategy enum, max_parallel
  1..count, duration parsing, canary int/% forms, auto_promote). 98.2% cov.
- internal/emitter/update.go: RenderUpdatePlan computes the step sequence
  (rolling batches, canary 1+promote+rest, blue-green all+cutover). Pure plan,
  no execution (v0.10-P10 is transactional). 73.7-100% cov.

P04 — Lifecycle hooks (systemd ExecStop semantics):
- Extended internal/emitter/systemd.go: post_start -> ExecStartPost=,
  pre_stop -> ExecStop=. Order: ExecStart -> ExecStartPost -> ExecStop ->
  socket lines. 8 lifecycle tests. 100% cov on systemd.go.

P08 — Socket plumbing (R-007):
- internal/emitter/socket.go: SocketEmitter renders RuntimeDirectory=orca/
  alloc-<id> per port (mode 0750, orca:orca). ExecStartPre TCP-bind marker
  when service.bind=127.0.0.1. SocketPath(allocID,portName) helper. 100% cov.
- Alloc-id is spec.Name placeholder; real id assigned by scheduler at submit.

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

---ci---
project: orca
phase: P03/P04/P08
milestone: v0.9
status: execute
---/ci---
2026-08-05 17:55:11 +00:00
Jon Chery 436641782c feat(P02): Service block + Traefik emitter + atomic reload (REQ-077, gate C-10)
P02 — Traefik dynamic config generation + atomic reload protocol.

Parser (internal/jobspec/markdown.go):
- Extended WorkloadSpec with Health, Constraints, Affinity, Lifecycle
  fields. Parsed restart/update/service/health/lifecycle/affinity/
  constraints blocks. HealthBlock, AffinityRule, LifecycleBlock types.

Schema (internal/spec/schema/schema.go):
- ServiceValidator: restart.mode enum (service/on-failure/never),
  update.strategy enum (rolling/canary/blue-green), health required,
  service.bind IP validation (R-007 loopback opt-in). 98.5% coverage.

Traefik emitter (internal/emitter/traefik.go, REQ-077):
- TraefikEmitter renders /etc/traefik/dynamic/orca-<name>.yaml with
  http.routers, http.services (servers = R-007 socket paths), TLS
  (certResolver=orca, trust domain), healthCheck. RenderDrain sets
  weight:0 per backend. RegisterTraefik wires process/podman/wasm.

Atomic reload (internal/emitter/traefik_atomic.go, gate C-10):
- WriteTraefikDynamic: write to path.tmp via WriteFileIdempotent, then
  mv -f path.tmp path (atomic POSIX rename, Traefik fsnotify observes
  IN_MOVED_TO). Traefik holds-last-good on malformed config. C-10 PASS.

22 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.
Coverage: emitter 96.5%, jobspec 88.8%, schema 98.5%, sshpush 93.0%.

---ci---
project: orca
phase: P02
milestone: v0.9
status: execute
---/ci---
2026-08-05 17:48:04 +00:00
Jon Chery e92b18197c feat(P01): SSH-push transport layer — connection pool, retry, fan-out, idempotent writes (REQ-073)
P01 — Load-bearing replacement for v0.8 mTLS transport (R-001).

Transport (internal/sshpush/transport.go, REQ-073):
- Transport struct with sync.Map connection pool (reuse *ssh.Client per peer).
- Exec with context timeout (10s default) + retry (100ms x2 cap 5s max 5
  attempts, +/-25% jitter — same backoff as v0.8 transport/retry.go).
- ReadFile, WriteFile (atomic heredoc + mv), Close.
- sshDialer + sshSession seams for testability. TOFU host-key verification
  reuses proxmox.TOFUHostKeyCallback. security.Flock for known_hosts.

Fan-out (internal/sshpush/fanout.go):
- ExecAll, WriteAll with errgroup + SetLimit semaphore (default 8 per I-B-001).
  Per-peer errors collected, don't cancel the group.

Idempotency (internal/sshpush/idempotency.go, C-18):
- WriteFileIdempotent: SHA-256 compare via ssh sha256sum; skip if content
  matches (written=false). Content-addressed idempotency replaces the v0.8
  X-Orca-Idempotency-Key header (C-18 capability map).

Tests: in-process fake SSH server (ssh.NewServerConn NoClientAuth ed25519)
for e2e + interface seams for pure-logic. 93.0% coverage. 20 packages pass.

---ci---
project: orca
phase: P01
milestone: v0.9
status: execute
---/ci---
2026-08-05 17:35:11 +00:00
Jon Chery 60b0357eb6 feat(P0c): Job/Service/DaemonSet schemas + emitter interface + systemd stub (REQ-074)
P0c — Kind-specific schema validators + Layer 4 emitter interface.

Schemas (internal/spec/schema/schema.go, REQ-074):
- Validator interface with JobValidator, ServiceValidator, DaemonSetValidator.
  JobValidator: count=1, no service block, optional schedule/timeout.
  ServiceValidator: ports required, count>=1, restart+update+runtime required.
  DaemonSetValidator: schedule mode required, no ports (D-175), no count.
  ValidatorFor(kind) dispatcher. 96.2% coverage.

Emitter interface (internal/emitter/emitter.go, REQ-074, I-B-002):
- File{Path,Content,Mode}, Emitter interface { Render(spec,node) []File },
  Registry keyed by kind:runtime, Register + Render lookup. 100% coverage.

Systemd stub (internal/emitter/systemd.go):
- SystemdEmitter for process runtime. Renders minimal [Service] unit at
  /etc/systemd/system/orca-v1-alloc-<name>.service (orca-v1- prefix per
  dual-write window REQ-090 — no overlap with v0.8 daemon's orca-<job>).

Flock test fix: TestFlock_concurrentBlocks rewritten to use non-blocking
tryFlockEx (LOCK_NB) instead of a leaked blocking goroutine. Eliminates
the temp-dir cleanup race.

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

---ci---
project: orca
phase: P0c
milestone: v0.9
status: execute
---/ci---
2026-08-05 17:17:02 +00:00
Jon Chery 667f20a7b3 feat(P0b): Markdown jobspec parser + dispatcher + fuzz harness (REQ-064,067)
P0b — Canonical Markdown+frontmatter jobspec parser (R-013/R-014).

Parser (internal/jobspec/markdown.go, REQ-064):
- WorkloadSpec/RuntimeBlock/PortSpec/VolumeSpec types. ParseMarkdown
  hand-rolled YAML frontmatter (no yaml.v3 dep). Kind validation (Job/
  Service/DaemonSet per R-012). BOM-stripped frontmatter, byte-exact body
  preservation (R-015) via the fuzz harness.

Dispatcher (internal/jobspec/dispatch.go, REQ-064):
- ParseFile/Dispatch routes on extension: .md->Markdown, .yaml/.yml->
  Markdown-with-empty-body, .hcl->ParseHCL adapter. HCL adapter converts
  Spec{Job,Tasks} to *WorkloadSpec (Kind=Job, Runtime.one_of=process).
  Backward compat preserved (REQ-090) — orca job run old-spec.hcl works.
- Legacy Parse renamed ParseHCLLegacy, marked // Deprecated per R-013.

Fuzz harness (internal/jobspec/markdown_fuzz_test.go, REQ-067, R-015):
- FuzzParseMarkdownRoundTrip with 10 seed corpus entries (CRLF, BOM,
  no-frontmatter, only-closing-separator, code-fence ---, trailing
  whitespace, empty body, etc). Asserts byte-exact body round-trip.

Tests: markdown_test.go (19 tests), dispatch_test.go (17 tests), fuzz
(10 seeds). jobspec package 89.2% coverage. cli 81.8% (no regression).

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

---ci---
project: orca
phase: P0b
milestone: v0.9
status: execute
---/ci---
2026-08-05 17:02:33 +00:00
Jon Chery 7bb31d4c09 feat(P0a2): namespace CRUD + inheritance engine (REQ-082)
P0a2 — Namespace inheritance resolver + orca ns CLI subcommands.

Resolver (REQ-082, internal/ns/resolve.go):
- Pure Resolve() function: DFS post-order chain assembly (most-specific
  first, _defaults implicit last D-185). Child-wins-scalar env merge, de-duped
  union constraints. Cycle detection with readable cycle path. Missing-parent
  + missing-_defaults + misordering (['_defaults','x']) rejection. Opt-out
  impossible (D-187). 89.6% coverage.

Parser (internal/ns/parse.go):
- ParseNSMd: hand-rolled YAML frontmatter (no yaml.v3 dep). Validates
  kind:Namespace + name, parses parents flow-array, inherits_env/secrets.
- ParseNSMdDir: walks root/*/ns.md, skips cluster/, requires _defaults.

CLI (internal/cli/ns.go, D-176):
- orca ns list/create/delete/inspect/validate. Inspect + validate use the
  resolver. Create refuses _defaults/cluster; delete refuses _defaults +
  non-empty namespaces. JSON output support. 85.2% coverage.
- Registered on rootCmd.

Tests: resolve_test.go (11 tests), parse_test.go (14 tests), ns_test.go
(21 tests). 18 packages pass, 20 bats pass, gofmt clean, verify-reqs 90
consistent.

---ci---
project: orca
phase: P0a2
milestone: v0.9
status: execute
---/ci---
2026-08-05 16:49:12 +00:00
Jon Chery 437aab39b4 feat(P0a1): multi-namespace path resolver + config demotion + known_hosts flock + CA migration spec (v0.9 P0a1)
P0a1 — Re-architecture Foundation (path resolver + config demotion).

Path resolver (REQ-070, R-002):
- internal/paths/paths.go: 23 functions for the multi-namespace layout
  (Root/ClusterDir/NamespaceDir/NS*/DefaultNamespace/CA/MasterKey/CacheDB/
  Txn/Peers/KnownHosts/SSH/Server/Config). Honors $ORCA_HOME. 100% coverage.
- internal/certpaths/certpaths.go: refactored as thin shim delegating to
  paths, preserving the v0.8 flat-layout API for backward compat during
  the dual-write window (REQ-090). Package doc explains the v0.10-P14
  migration plan. certpaths deleted after v0.10-P14. 100% coverage.

Config demotion (REQ-069, R-014):
- internal/config/markdown.go: minimal hand-rolled YAML frontmatter parser
  (no new dep — yaml.v3 not in go.mod). Returns same *Config struct as HCL.
- internal/config/config.go: renamed Load body to LoadHCL (// Deprecated
  per R-013), added dispatcher Load() routing on extension (.hcl->HCL,
  .md->Markdown, .yaml->Markdown). Signature preserved so root.go unchanged.
- dispatch_test.go + markdown_test.go: 89.8% coverage on config package.

Known_hosts flock (REQ-063, deferred P1 from REVIEW_v0.8 A2):
- internal/security/flock.go: stdlib syscall.Flock advisory lock helper.
- internal/proxmox/bootstrap.go: TOFUHostKeyCallback capture + ResetHostKey
  both acquire the flock before read-modify-write on known_hosts. Prevents
  concurrent writers under v0.9 parallel SSH fan-out. 3 flock tests.

CA migration spec (grill C-07):
- .ciagent/CA_MIGRATION_SPEC_v0.9.md: Option A (preserve trust root,
  RECOMMENDED) vs Option B (forced re-bootstrap). Pre-flight checks,
  migration steps, rollback, post-migration invariants, spike plan.

Verification: build pass, 17/17 Go packages pass, 20/20 bats pass, gofmt
clean, go vet clean, verify-reqs 90 consistent. Coverage: paths 100%,
certpaths 100%, config 89.8%, emit covered.

---ci---
project: orca
phase: P0a1
milestone: v0.9
status: execute
---/ci---
2026-08-05 16:38:26 +00:00
Jon Chery fc94326b0e feat(P00): deprecation sweep + bash tooling gate + render contract + doc banners (v0.9 P00)
P00 — Re-architecture Foundation (deprecation/migration/test-infra/persona/docs).

Deprecation sweep (REQ-068, REQ-072, REQ-089):
- Add // Deprecated: doc comments to internal/daemon (R-001), internal/transport
  (REQ-073), internal/security/ca.go+csr.go (D-101/REQ-076), internal/engine/
  dispatcher.go+peer.go (CLI-side scheduler), internal/cli/daemon.go.
- orca daemon emits slog.Warn deprecation banner on every run (ungated); fires
  R-001 + v0.10-P05 drain-and-stop + v0.10-P14 deletion.
- orca cert and orca node join (mTLS path) emit deprecation warnings; proxmox
  SSH path (the v0.9 replacement) does not warn.
- Add --no-deprecation-warnings global flag on root command (PersistentPreRunE)
  for orca upgrade migrations.
- 12 new daemon/cert/node deprecation tests in internal/cli/daemon_test.go
  (cli coverage 81.9%, warnDeprecated 100%).
- Add DEPRECATED banners to v0.8 sections of ARCHITECTURE.md (verified the
  v0.9 supersession section + Supersession Table from prior turn are present).

Bash tooling gate (grill C-06, C-15, C-16, C-17, C-18):
- scripts/tests/test_helper.bash + example_test.bash — bats framework + helpers.
- scripts/lib/orca-log.sh — slog-compatible JSON logging to syslog (C-17).
- scripts/orca-verify-render.sh — render-contract validator skeleton (C-16).
- scripts/tests/orca-log_test.bash + orca-verify-render_test.bash — 20 bats
  tests total (happy + failure paths per C-15).
- .shellcheckrc — project shellcheck config.
- Makefile: test-bash + lint-bash targets (graceful skip if tools missing);
  wired into test + lint targets.
- internal/emit/contract.go + contract_test.go — versioned JSON render
  contract (orca.emit/v1) between Go emitters and bash appliers (C-16).
- .ciagent/BASH_CAPABILITY_MAP_v0.9.md — maps shipped internal/transport
  capabilities to bash-side equivalents or accepted drops (C-18).
- D-186 recorded in PROJECT.md: bash exempt from Go coverage gate; compensating
  control is bats + shellcheck + shfmt (C-06).

verify-reqs: 90 requirements consistent. Build/test/lint/fmt all green.
20 bats tests pass. Go tests pass. No v0.8 code deleted — only marked deprecated
(deletion deferred to v0.10-P14 per REQ-090 dual-write window).

---ci---
project: orca
phase: P00
milestone: v0.9
status: execute
---/ci---
2026-08-05 16:26:26 +00:00
Jon Chery 11da458883 test(cli): --host-key-fingerprint non-proxmox validation (T02.11, REQ-058)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 12:04:28 +00:00
Jon Chery d66b3b9a0a test(proxmox,cli): end-to-end trust-surface integration tests (T02.10, REQ-058, REQ-059)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 12:04:24 +00:00
Jon Chery 2dcb14377a fix(doctor): TOFU capture-fix parity with bootstrap — v0.6 ship-defect (T02.9)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:56:45 +00:00
Jon Chery 13e6762f0f feat(cli): orca node key-reset <node> — local known_hosts reset (T02.8, REQ-059)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:51:47 +00:00
Jon Chery 325a5662f4 feat(proxmox): populate Result.HostKeyFingerprint (T02.7, REQ-058)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:48:00 +00:00
Jon Chery 8b0cbe10ae fix(proxmox): TOFU capture bug — v0.6 ship-defect first-connect join always failed (T02.6)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:46:38 +00:00
Jon Chery bd17e6e114 feat(proxmox): pinnedHostKeyCallback for --host-key-fingerprint (T02.5, REQ-058)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:45:53 +00:00
Jon Chery 7cb12c52ce feat(proxmox): HostKeyFingerprint field on Options (T02.4, REQ-058)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:38:39 +00:00
Jon Chery 08481d35ce feat(cli): --host-key-fingerprint flag on node join (T02.3, REQ-058)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:37:01 +00:00
Jon Chery 00869c6f5b refactor(security): export WriteAtomic (T02.2, REQ-059)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:36:42 +00:00
Jon Chery aa3462826b feat(security): SSHFingerprintSHA256 helper (T02.1, REQ-058)
---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
2026-08-04 11:35:59 +00:00