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---
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— addinit()that callsrootCmd.AddCommand(NewCommand(slog.Default())). This is the one-line fix that makes the entirecert ca-init | gen | show | renew | fingerprinttree reachable. (AD-022)internal/cli/cert_test.go(NEW) — regression test assertingrootCmd.Commands()contains a child whoseUse == "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 tempORCA_HOME:orca cert ca-init --cn test-ca→ succeeds,ca.crt+ca.keyexist with modes 0644/0600orca cert gen --cn test-server --san localhost --san 127.0.0.1→ succeeds,server.crt+server.keyexist with modes 0644/0600orca cert show→ outputs PEM with noPRIVATE KEYblocks (REQ-035 redaction)orca cert fingerprint --which ca→ outputs a 64-char hex SHA-256orca cert fingerprint --which server→ outputs a 64-char hex SHA-256orca cert renew→ succeeds, server cert file mtime updates
internal/cli/root_test.go— extend the existing root test to assertorca certis in the command tree (belt-and-suspenders with cert_test.go)
data-engineer territory
internal/store/cert_repo_test.go(NEW) — table-driven tests forCertRepo:Inserta cert row →Getby serial returns matching rowInsertduplicateserial_hex→ returns error (UNIQUE constraint, I-107)Listreturns certs ordered byissued_at desc- Rotation history: Insert 4 certs for the same node → only last N=3 retained (REQ-025); oldest is pruned
GetActivereturns the most-recent cert for a nodeDeleteremoves a cert by serial
Verification
go build ./...PASSgo vet ./...PASSgo test ./internal/cli/... ./internal/store/...PASSgo test -race ./...PASS./bin/orca cert→ prints help (no longer "unknown command")./bin/orca cert ca-initon a tempORCA_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) —Configstruct with HCL tags:DBPath stringhcl:"db_path,optional"``ListenAddr stringhcl:"listen_addr,optional"``CAPath stringhcl:"ca_path,optional"``ServerCertPath stringhcl:"server_cert_path,optional"``ServerKeyPath stringhcl:"server_key_path,optional"``NodeCapacity *CapacityConfighcl:"node_capacity,block"` (optional block)Load(paths ...string) (*Config, error)— loads the first existing file frompathsviahclsimple.Decode(reuse the jobspec pattern,internal/jobspec/spec.go:40); returns a zero-valueConfigif 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 tocertpaths.*defaults.- No package-level state (AD-023).
Loadis 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_capacityblock parsed correctly
lead-developer territory
internal/cli/root.go— add--config stringpersistent flag (default""). InPersistentPreRunE, if--configis set, callconfig.Load(flag)and stash the*Configincmd.Context()via a context key. If--configis empty,config.Loadis not called (zero overhead; existing flag/env behavior unchanged).internal/cli/daemon.go— in the daemon command, if a*Configis present in the context, usecfg.ListenAddras 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 ./...PASSgo vet ./...PASSgo test ./internal/config/... ./internal/cli/...PASSgo 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/v2already 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) — testExecutor.Start/Waitlifecycle:- 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,
WaitDelayhonored (REQ-021) - Env propagation:
Env=["FOO=bar"]→ child process seesFOO=bar
- Start a command (
internal/engine/dispatcher_test.go(NEW) — testDispatcher.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.NewTLSServermock → peer client POSTs a dispatch request- TLS handshake failure → structured error with
peer+errfields
lead-developer territory — internal/transport
internal/transport/mtls_test.go(NEW) — test mTLS handshake:httptest.NewTLSServerwith a test CA → client with valid cert handshakes OK- Client with expired cert → handshake fails with
event=mtls.handshakelog assertion - Client with wrong CA → handshake fails
internal/transport/dispatch_test.go(NEW) — testDispatchRPC:- 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) — assertLogHandshakeOK/LogHandshakeFailedemit the correct slog fields (event,peer,cert_fp,err)
lead-developer territory — internal/audit
internal/audit/audit_test.go(NEW) — test theAuditwrapper:EmitwithActionCertIssued+ResultSuccess→engine.Recordcalled with correct args (mockengine.Audit)EmitWithErr→engine.Recordcalled withresult=failure+ err in metadataLogHandshakeOK→ slog output containsevent=mtls.handshake,result=ok,peer,cert_fpLogHandshakeFailed→ slog output containsresult=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
sshDialerinterface (already present atbootstrap.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)
- Mock the
CI gate (I-410)
.coreci.yml— add acoverage-gatestep in thetestpipeline that runsgo test -cover ./internal/engine ./internal/transport ./internal/proxmox ./internal/auditand fails if any package < 50% (AD-025). Use a small shell snippet +awk/grepto parse coverage percentages.
Verification
go build ./...PASSgo test -race ./...PASSgo 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
-raceare 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(NOThttp.DefaultServeMux) import _ "net/http/pprof"→ registerpprof.Index,pprof.Cmdline,pprof.Profile,pprof.Symbol,pprof.Trace,pprof.Handleron the dedicated mux- Return a
*http.Serverlistening onaddrwith 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)
- Create a dedicated
internal/daemon/server.go— add approfAddr stringfield toOptions(default""= disabled). InStart, ifpprofAddr != "", callStartPprofand store the*http.ServerforShutdown.internal/daemon/pprof_test.go(NEW) — test:StartPprof("127.0.0.1:0", ...)→ server starts, GET/debug/pprof/returns 200- GET
/debug/pprof/cmdlinereturns the cmdline Shutdownstops the pprof server- The mTLS daemon server (if running) is unaffected by pprof start/stop
internal/cli/daemon.go— add--pprof stringflag (default""= disabled). Pass it intodaemon.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 ./...PASSgo vet ./...PASSgo 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/05→milestone/v0.7-hardening-completion - Merge
milestone/v0.7→main(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 buildPASSmake testPASSmake lintPASSgo vet ./...PASSgit logon main shows all v0.7 phase commitsgit 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