Files
orca/.ciagent/PLAN_v0.7.md
T
Jon Chery 561bf61317 docs(P00): correct v0.7 tag line to v0.6.x per branch-strategy.md
Tags run on the previous minor's patch line. v0.7 milestone → v0.6.x
tags (v0.6.0 P0 … v0.6.5 P05 milestone release). Prior commits
incorrectly referenced v0.5.x (the v0.6 milestone's line).

---ci---
project: orca
phase: 0
milestone: v0.7
status: plan
---/ci---
2026-08-03 23:52:19 +00:00

14 KiB

Phase Plans: Orca v0.7 — Hardening & Completion

All 4 execution phases + final review with vertical-slice structure, wave ordering, and REQ-ID mapping. v0.7 scope: Hardening & Completion — register the unreachable orca cert command, add HCL config file parsing, uplift test coverage in core packages, and add the long-deferred pprof endpoint.

Branching: phase/01-cert-register..phase/05-final-review-ship on the milestone/v0.7-hardening-completion branch (numbering restarts per milestone per branch-strategy.md).

Milestone type: NFR (all phases are fix/test/chore; no feat phases). Tags run on the v0.6.x patch line: v0.6.0 (P0) … v0.6.5 (P05 = milestone release).


Phase 1: Register orca cert Command Tree + cert_repo Tests (Wave 1)

Branch: phase/01-cert-register REQ Coverage: REQ-053 Persona leads: lead-developer (cert registration + smoke test), data-engineer (cert_repo tests) Source ideas: I-401, I-402, I-412

Must-Haves

lead-developer territory

  • internal/cli/cert.go — add init() that calls rootCmd.AddCommand(NewCommand(slog.Default())). This is the one-line fix that makes the entire cert ca-init | gen | show | renew | fingerprint tree reachable. (AD-022)
  • internal/cli/cert_test.go (NEW) — regression test asserting rootCmd.Commands() contains a child whose Use == "cert"; assert each subcommand (ca-init, gen, show, renew, fingerprint) is present on the cert child.
  • internal/cli/cert_smoke_test.go (NEW) — end-to-end smoke test against a temp ORCA_HOME:
    • orca cert ca-init --cn test-ca → succeeds, ca.crt + ca.key exist with modes 0644/0600
    • orca cert gen --cn test-server --san localhost --san 127.0.0.1 → succeeds, server.crt + server.key exist with modes 0644/0600
    • orca cert show → outputs PEM with no PRIVATE KEY blocks (REQ-035 redaction)
    • orca cert fingerprint --which ca → outputs a 64-char hex SHA-256
    • orca cert fingerprint --which server → outputs a 64-char hex SHA-256
    • orca cert renew → succeeds, server cert file mtime updates
  • internal/cli/root_test.go — extend the existing root test to assert orca cert is in the command tree (belt-and-suspenders with cert_test.go)

data-engineer territory

  • internal/store/cert_repo_test.go (NEW) — table-driven tests for CertRepo:
    • Insert a cert row → Get by serial returns matching row
    • Insert duplicate serial_hex → returns error (UNIQUE constraint, I-107)
    • List returns certs ordered by issued_at desc
    • Rotation history: Insert 4 certs for the same node → only last N=3 retained (REQ-025); oldest is pruned
    • GetActive returns the most-recent cert for a node
    • Delete removes a cert by serial

Verification

  • go build ./... PASS
  • go vet ./... PASS
  • go test ./internal/cli/... ./internal/store/... PASS
  • go test -race ./... PASS
  • ./bin/orca cert → prints help (no longer "unknown command")
  • ./bin/orca cert ca-init on a temp ORCA_HOME → succeeds
  • ./bin/orca cert show → no private key material in output (REQ-035)
  • cert_repo_test.go covers Insert/Get/List/rotation-prune/duplicate-serial

Phase 2: HCL Config File Parsing (Wave 1)

Branch: phase/02-config-parser REQ Coverage: REQ-054 Persona leads: backend-engineer (config package), lead-developer (root command --config flag wiring) Source ideas: I-406, I-408 Depends on: Phase 1 (cert registration lands first so the CLI surface is complete before config extends it)

Must-Haves

backend-engineer territory

  • internal/config/config.go (NEW package) — Config struct with HCL tags:
    • DBPath string hcl:"db_path,optional"``
    • ListenAddr string hcl:"listen_addr,optional"``
    • CAPath string hcl:"ca_path,optional"``
    • ServerCertPath string hcl:"server_cert_path,optional"``
    • ServerKeyPath string hcl:"server_key_path,optional"``
    • NodeCapacity *CapacityConfig hcl:"node_capacity,block"` (optional block)
    • Load(paths ...string) (*Config, error) — loads the first existing file from paths via hclsimple.Decode (reuse the jobspec pattern, internal/jobspec/spec.go:40); returns a zero-value Config if no file exists (no error)
    • (*Config).MergeOverrides(flags Flags, env Environ) *Config — applies precedence flag > env > file > default (D-039). Only non-zero flag values override; only set env vars override; file values are the base; missing fields fall back to certpaths.* defaults.
    • No package-level state (AD-023). Load is a pure function.
  • internal/config/config_test.go (NEW) — table-driven tests:
    • Load from a valid HCL file → all fields populated
    • Load from a missing file → zero Config, no error
    • Load from a malformed HCL file → error
    • MergeOverrides: flag wins over env wins over file wins over default (all 4 layers exercised)
    • MergeOverrides: empty flag does NOT override a set env value
    • MergeOverrides: empty env does NOT override a set file value
    • Optional node_capacity block parsed correctly

lead-developer territory

  • internal/cli/root.go — add --config string persistent flag (default ""). In PersistentPreRunE, if --config is set, call config.Load(flag) and stash the *Config in cmd.Context() via a context key. If --config is empty, config.Load is not called (zero overhead; existing flag/env behavior unchanged).
  • internal/cli/daemon.go — in the daemon command, if a *Config is present in the context, use cfg.ListenAddr as the default addr (flag still overrides per D-039).
  • internal/cli/root_test.go — extend with --config <tmpfile> test: pass a config file, assert the merged values reach the daemon command.
  • testdata/config.hcl (NEW) — example config file for tests:
    db_path       = "/tmp/orca/test.db"
    listen_addr   = "127.0.0.1:9999"
    ca_path       = "/tmp/orca/ca.crt"
    server_cert_path = "/tmp/orca/server.crt"
    server_key_path  = "/tmp/orca/server.key"
    

Verification

  • go build ./... PASS
  • go vet ./... PASS
  • go test ./internal/config/... ./internal/cli/... PASS
  • go test -race ./... PASS
  • ./bin/orca --config testdata/config.hcl daemon --help → no error
  • Precedence test: flag value overrides config file value for the same key
  • No new direct deps (hashicorp/hcl/v2 already in go.mod)

Phase 3: Test Coverage Uplift (Wave 1)

Branch: phase/03-coverage-uplift REQ Coverage: REQ-055 Persona leads: lead-developer (engine/transport/audit tests), data-engineer (store coverage) Source ideas: I-403, I-404, I-405, I-410 Depends on: Phase 1 + Phase 2 (tests build on the now-reachable cert tree + config package)

Must-Haves

lead-developer territory — internal/engine

  • internal/engine/executor_test.go (NEW) — test Executor.Start/Wait lifecycle:
    • Start a command (/bin/echo hello) → Wait → exit code 0, stdout captured
    • Start a failing command (/bin/false) → exit code non-zero
    • Cancel via ctx → process killed, WaitDelay honored (REQ-021)
    • Env propagation: Env=["FOO=bar"] → child process sees FOO=bar
  • internal/engine/dispatcher_test.go (NEW) — test Dispatcher.Submit/Dispatch:
    • Submit a job → dispatched to the correct peer (mock peer client)
    • Idempotency key present → retry on transient failure (mock returns error twice then succeeds)
    • Idempotency key absent → no retry (REQ-037)
    • Bounded queue backpressure: fill the channel → Submit blocks (with timeout assertion)
  • internal/engine/peer_test.go (NEW) — test the peer HTTP client:
    • httptest.NewTLSServer mock → peer client POSTs a dispatch request
    • TLS handshake failure → structured error with peer + err fields

lead-developer territory — internal/transport

  • internal/transport/mtls_test.go (NEW) — test mTLS handshake:
    • httptest.NewTLSServer with a test CA → client with valid cert handshakes OK
    • Client with expired cert → handshake fails with event=mtls.handshake log assertion
    • Client with wrong CA → handshake fails
  • internal/transport/dispatch_test.go (NEW) — test Dispatch RPC:
    • Successful dispatch → 200 OK
    • Dispatch with X-Orca-Idempotency-Key → idempotent
    • Dispatch without key → 400 (per REQ-037)
  • internal/transport/handshake_log_test.go (NEW) — assert LogHandshakeOK/LogHandshakeFailed emit the correct slog fields (event, peer, cert_fp, err)

lead-developer territory — internal/audit

  • internal/audit/audit_test.go (NEW) — test the Audit wrapper:
    • Emit with ActionCertIssued + ResultSuccessengine.Record called with correct args (mock engine.Audit)
    • EmitWithErrengine.Record called with result=failure + err in metadata
    • LogHandshakeOK → slog output contains event=mtls.handshake, result=ok, peer, cert_fp
    • LogHandshakeFailed → slog output contains result=failed + err
    • Nil-safe: (*Audit)(nil).Emit(...) → no panic

data-engineer territory — internal/proxmox

  • internal/proxmox/bootstrap_test.go — extend the existing test:
    • Mock the sshDialer interface (already present at bootstrap.go:211) → assert the full bootstrap sequence calls the right shell commands in order (user create, role create, role assign, sudoers drop, pubkey deploy)
    • Idempotent re-run: mock returns "already exists" for user create → bootstrap succeeds without re-creating
    • SSH auth failure → bootstrap returns wrapped error
    • Assert no password is logged (D-031)

CI gate (I-410)

  • .coreci.yml — add a coverage-gate step in the test pipeline that runs go test -cover ./internal/engine ./internal/transport ./internal/proxmox ./internal/audit and fails if any package < 50% (AD-025). Use a small shell snippet + awk/grep to parse coverage percentages.

Verification

  • go build ./... PASS
  • go test -race ./... PASS
  • go test -cover ./internal/engine → ≥ 50% (was 8.3%)
  • go test -cover ./internal/transport → ≥ 50% (was 26.3%)
  • go test -cover ./internal/proxmox → ≥ 50% (was 5.1%)
  • go test -cover ./internal/audit → ≥ 50% (was 0%)
  • CI coverage gate step passes
  • Any races uncovered by -race are fixed in this phase (not deferred)

Phase 4: --pprof Opt-in on orca daemon (Wave 1)

Branch: phase/04-pprof-daemon REQ Coverage: REQ-056 Persona leads: lead-developer (daemon flag + pprof server) Source ideas: I-407, I-409 Depends on: Phase 3 (daemon tests exist; pprof adds a new daemon path)

Must-Haves

lead-developer territory

  • internal/daemon/pprof.go (NEW) — StartPprof(addr string, log *slog.Logger) (*http.Server, error):
    • Create a dedicated *http.ServeMux (NOT http.DefaultServeMux)
    • import _ "net/http/pprof" → register pprof.Index, pprof.Cmdline, pprof.Profile, pprof.Symbol, pprof.Trace, pprof.Handler on the dedicated mux
    • Return a *http.Server listening on addr with the dedicated mux
    • Log a WARN: pprof endpoint exposed unauthenticated on <addr> — operator-only, do not expose publicly
    • Never touch the mTLS daemon listener (AD-024)
  • internal/daemon/server.go — add a pprofAddr string field to Options (default "" = disabled). In Start, if pprofAddr != "", call StartPprof and store the *http.Server for Shutdown.
  • internal/daemon/pprof_test.go (NEW) — test:
    • StartPprof("127.0.0.1:0", ...) → server starts, GET /debug/pprof/ returns 200
    • GET /debug/pprof/cmdline returns the cmdline
    • Shutdown stops the pprof server
    • The mTLS daemon server (if running) is unaffected by pprof start/stop
  • internal/cli/daemon.go — add --pprof string flag (default "" = disabled). Pass it into daemon.Options.PprofAddr. Document in --help: "enable pprof endpoint on (e.g. :6060); unauthenticated, operator-only".
  • internal/cli/daemon_test.go — extend: --pprof 127.0.0.1:0 → daemon starts with pprof; flag absent → no pprof server.

Verification

  • go build ./... PASS
  • go vet ./... PASS
  • go test -race ./internal/daemon/... PASS
  • ./bin/orca daemon --pprof 127.0.0.1:0 (in background) → curl http://127.0.0.1:<port>/debug/pprof/ returns 200
  • ./bin/orca daemon (no --pprof) → no pprof listener, /debug/pprof/ not reachable on the daemon port
  • pprof mux is separate from the mTLS daemon mux (asserted in test)

Phase 5: Final Review + Ship + Audit (Wave 1)

Branch: phase/05-final-review-ship REQ Coverage: all (REQ-053..056) Persona leads: lead-developer (review + audit + ship)

Must-Haves

  • Multi-persona code review across all v0.7 phases (ciagent-review)
  • Audit: reconstruction test (git log matches .ciagent/ files), branch hygiene, commit discipline (ciagent-audit)
  • Fix any P0 issues found by review; record P1+ in .ciagent/ for post-hoc
  • Merge phase/05milestone/v0.7-hardening-completion
  • Merge milestone/v0.7main (rebase-then-fast-forward per config)
  • Tag v0.6.5 (final phase patch = milestone release)
  • Create Gitea release with full milestone summary (all phases, all REQs)
  • Update .ciagent/REQUIREMENTS.md — mark REQ-053..056 complete
  • Update .ciagent/ROADMAP.md — mark v0.7 complete
  • Write checkpoint: {phase: 5, stage: "complete", phase_role: "final", milestone_complete: true}
  • Clear checkpoint (milestone complete; next run starts a new milestone)

Verification

  • make build PASS
  • make test PASS
  • make lint PASS
  • go vet ./... PASS
  • git log on main shows all v0.7 phase commits
  • git tag --list 'v0.6.*' shows v0.6.0..v0.6.5
  • REQUIREMENTS.md shows REQ-053..056 as Complete
  • ROADMAP.md shows v0.7 as COMPLETE