GRILL_v0.8.md (30KB): 9-axis adversarial review, overall verdict PROCEED-WITH-CONDITION (confidence 0.78). 7 PROCEED + 4 PROCEED-WITH-CONDITION + 0 REPLAN findings. 4 binding plan changes applied to PLAN_v0.8.md: #1 T02.6 relabeled as v0.6 ship-defect bugfix (not v0.8 feature); P04 audit must record ship-defect closure #2 T02.9 doctor proxmox parity — P02 not complete until both bootstrap + doctor callbacks use capture-fix wrapper #3 T01.6 cli coverage escape valve — ship at 65% if 70% not reached after Wave 2 (RESEARCH §1.4 flags 55-65% realistic); do not block P02/P03 on the last 5% #4 T03.1 verify-reqs regex substring-tolerant (matches v0.2 header variant) + reverse-direction assertion (REQUIREMENTS Complete ↔ ROADMAP COMPLETE); scope note: doc-vs-doc drift only T02.10 case 7 added (known_hosts pre-populated v0.6→v0.8 migration path). No escalations; all axes resolved at confidence ≥ 0.60. ---ci--- project: orca phase: 0 milestone: v0.8 status: grill ---/ci---
34 KiB
Phase Plans: Orca v0.8 — Coverage & Trust Hardening
All 4 execution phases + final review with vertical-slice structure, wave
ordering, persona assignment, and REQ-ID mapping. v0.8 scope: Coverage &
Trust Hardening — round-2 test coverage uplift across 9 packages (tiered
floor: ≥70% for 6 retested, ≥50% for 3 zero-test per D-047), SSH trust
hardening (--host-key-fingerprint pre-pin + orca node key-reset + latent
TOFU capture-fix + Result.HostKeyFingerprint population), and a
requirements-hygiene gate (make verify-reqs).
Branching: phase/01-coverage-round2..phase/04-final-review-ship on the
milestone/v0.8-coverage-trust-hardening branch (numbering restarts per
milestone per branch-strategy.md).
Milestone type: NFR (P01 test, P02 chore on the trust surface per D-043,
P03 chore, P04 docs/review). Tags run on the v0.7.x patch line: v0.7.0
(P0) … v0.7.4 (P04 = milestone release).
Vertical-slice integrity: each phase is independently shippable.
- P01 ships tests-only (no production code changes except the proxmox
sessionRunnerseam, a backward-compatible interface extraction, and the enginepeerDispatcherseam per RESEARCH §1.3). - P02 ships the SSH trust features + TOFI bugfix +
Resultpopulation. - P03 ships the hygiene gate (Go program + Makefile + CI hook).
- P04 is review + ship + audit (no new REQs).
Out of scope for v0.8 (candidate for v0.9, noted not added):
- Lifting the 3 zero-test packages from 50% → 70% (D-047 explicitly toes-holds them; v0.9 can raise the floor).
- A
peerDispatcherinterface seam in engine beyond what P01 needs for 70% coverage (httptest.NewTLSServer suffices; the seam is only added if coverage cannot otherwise hit 70%). - Pre-populating
known_hostsfrom a remote keyscan API (TOFU + manual--host-key-fingerprintcover the v0.8 trust surface). verify-reqsreverse-direction check (REQUIREMENTS Complete ↔ ROADMAP COMPLETE both ways) — forward direction (ROADMAP-shipped → REQUIREMENTS Complete) is the priority per the v0.7 drift that motivated REQ-060.
Carried-forward research findings (RESEARCH_v0.8.md, must incorporate):
- §1.1 per-package coverage strategies + tiered floors (D-047).
- §1.3 injected seams: reuse
sshDialer(proxmox),LocalExecutor(engine),Dispatcher(transport),watchInterval(store),openTestDB/withFastWatch/initTestEnv/resetRootFlags/stubDispatcherhelpers. - §1.4 realism flags: cli excludes
daemon.go;cmd/orca50% toe-hold only; proxmox needs thesessionRunnerseam to hit 70%. - §2.1 latent TOFU capture bug (knownhosts.New returns KeyError{Want:[]} on first connect and does NOT auto-write — current BootstrapProxmox treats it as a dial failure).
- §2.2
Result.HostKeyFingerprintis declared but never populated (always""); P02 must addssh.FingerprintSHA256computation. - §2.3
--host-key-fingerprintplugs in atinternal/cli/node.go(flag) +internal/proxmox/bootstrap.go(pinned callback). - §2.4
key-resetis local-known_hosts-only (D-046), atomic rewrite (AD-029). - §3 verify-reqs is a Go program at
cmd/verify-reqs/main.go(~80 LOC, stdlib only, AD-030) +make verify-reqs+.coreci.ymlvalidate hook. - §4 AD-025..AD-030 (renumbered AD-027..AD-030 in research for SSH/trust; AD-025/AD-026 from earlier milestones are stable).
- §5 10 pitfalls carried into the risk register at the end of this file.
Dependencies (RESEARCH §6): v0.8 adds zero new direct dependencies.
ssh.FingerprintSHA256, knownhosts.Line/Normalize/KeyError are in the
existing golang.org/x/crypto v0.54.0 dep. verify-reqs is stdlib-only.
go.mod is unchanged by v0.8.
Phase 1: Coverage Uplift Round 2 (REQ-057)
Branch: phase/01-coverage-round2
REQ Coverage: REQ-057
Tag: v0.7.1
Depends on: Phase 0 (this plan + clarify + research)
Source research: RESEARCH_v0.8.md §1 (per-package strategies, helpers, seams)
Tiered floor (D-047)
| Package | Current | Floor | Owner persona |
|---|---|---|---|
internal/engine |
8.3% | ≥ 70% | backend-engineer |
internal/proxmox |
5.1% | ≥ 70% | backend-engineer |
internal/cli |
27.6% | ≥ 70% (excluding daemon.go) |
lead-developer |
internal/transport |
26.3% | ≥ 70% | backend-engineer |
internal/store |
47.2% | ≥ 70% | data-engineer |
internal/jobspec |
47.6% | ≥ 70% | data-engineer |
internal/audit |
0% (no tests) | ≥ 50% toe-hold | data-engineer |
internal/certpaths |
0% (no tests) | ≥ 50% toe-hold | data-engineer |
cmd/orca |
0% (no tests) | ≥ 50% toe-hold | lead-developer |
Wave 1 — Seams + foundational test helpers (no production logic changes)
These are backward-compatible interface extractions that unlock the bulk of
coverage in Wave 2. They are the only production-code changes in P01; all
other P01 tasks add _test.go files only.
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T01.1 | backend-engineer | 1 | Y | Add sessionRunner interface seam to proxmox |
internal/proxmox/bootstrap.go |
Extract a sessionRunner interface (CombinedOutput(cmd string) ([]byte, error)) ~10 LOC; default impl wraps *ssh.Client.NewSession().CombinedOutput(...); runRemote/deployPubKey/createLinuxUser/createPVERole/createPVEUser/assignPVEACL/writeSudoers/validateSudoers use the seam. Backward compatible: existing callers unchanged. go build ./internal/proxmox PASS. (RESEARCH §1.3 gap #1, §5 pitfall #3) |
| T01.2 | backend-engineer | 1 | N | Add peerDispatcher seam to engine (only if needed for 70%) |
internal/engine/dispatcher.go |
Extract a peerDispatcher interface (Submit(ctx, spec, key) (*SubmitResponse, error)) so dispatchToPeer is testable without httptest.NewTLSServer. Only add if T01.5 cannot otherwise hit 70% via httptest.NewTLSServer alone. If added, backward compatible. (RESEARCH §1.3 gap #2, §5 pitfall #8) |
Wave 2 — Per-package coverage tests (build on Wave 1 seams)
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T01.3 | backend-engineer | 2 | Y | internal/transport tests → ≥ 70% |
internal/transport/mtls_test.go (NEW), internal/transport/dispatch_test.go (NEW), internal/transport/handshake_log_test.go (NEW), internal/transport/retry_test.go (NEW, extend) |
httptest.NewTLSServer with a test CA (reuse security.CAInit/GenerateCSR/SignCSR per RESEARCH §1.2) for mTLS handshake paths; stubDispatcher (daemon/dispatch_test.go:24) pattern for Dispatch RPC; capture slog via a test slog.Handler for handshake_log. go test -cover ./internal/transport → ≥ 70% (was 26.3%). |
| T01.4 | backend-engineer | 2 | Y | internal/engine tests → ≥ 70% |
internal/engine/executor_test.go (NEW), internal/engine/dispatcher_test.go (NEW), internal/engine/peer_test.go (NEW), internal/engine/scheduler_test.go (extend), internal/engine/registry_test.go (NEW, if registry exists) |
Executor.Start/Wait lifecycle (echo/false/ctx-cancel/Env propagation per REQ-021); Dispatcher.Submit with stubbed LocalExecutor + (if T01.2 added) stubbed peerDispatcher OR httptest.NewTLSServer; PeerRegistry in-memory Add/Remove/All/Get. Reuse openTestDB (node_repo_test.go:12). go test -cover ./internal/engine → ≥ 70% (was 8.3%). |
| T01.5 | backend-engineer | 2 | Y | internal/proxmox tests → ≥ 70% |
internal/proxmox/bootstrap_test.go (extend) |
Swap sshDialer (existing seam) for a fake returning a mock *ssh.Client; swap sessionRunner (T01.1 seam) for a fake that returns canned CombinedOutput bytes. Assert full bootstrap sequence calls the right shell commands in order; idempotent re-run ("already exists" → no-op); SSH auth failure → wrapped error; no password logged (D-031). go test -cover ./internal/proxmox → ≥ 70% (was 5.1%). |
| T01.6 | lead-developer | 2 | Y | internal/cli tests → ≥ 70% (excluding daemon.go) with GRILL condition #3 escape valve |
internal/cli/node_test.go (NEW), internal/cli/job_test.go (NEW), internal/cli/cert_test.go (NEW), internal/cli/doctor_test.go (NEW), internal/cli/audit_test.go (NEW), internal/cli/status_test.go (NEW), internal/cli/version_test.go (NEW), internal/cli/node_capacity_test.go (NEW) |
Table-driven rootCmd.Execute() against temp ORCA_HOME per subcommand (reuse initTestEnv/resetRootFlags/discardWriter per RESEARCH §1.2). Mock the proxmox path via sshDialer + sessionRunner seams. daemon.go is excluded — covered by internal/daemon/server_test.go. go test -cover ./internal/cli → ≥ 70% of non-daemon files (document the exclusion in a test-file comment). GRILL condition #3 escape valve: if 70% is not reached after Wave 2 effort and ≥ 65% is achieved (RESEARCH §1.4 flags 55-65% as realistic for one phase), ship cli at 65% and do NOT block P02/P03 on the last 5%; record the shortfall + rationale in the P01 verification commit. |
| T01.7 | data-engineer | 2 | Y | internal/store tests → ≥ 70% (incl. missing cert_repo_test.go) |
internal/store/cert_repo_test.go (NEW — v0.7 P01 leftover, RESEARCH §1.1), internal/store/node_repo_test.go (extend), internal/store/job_task_repo_test.go (extend), internal/store/audit_repo_test.go (extend), internal/store/capacity_repo_test.go (extend) |
cert_repo_test.go: Insert/Get/List/ListByNode/LatestForKind/PruneOlderThan/Delete + N=3 rotation history per REQ-025 + duplicate-serial error. Reuse openTestDB/withFastWatch (RESEARCH §1.2). go test -cover ./internal/store → ≥ 70% (was 47.2%). |
| T01.8 | data-engineer | 2 | Y | internal/jobspec tests → ≥ 70% |
internal/jobspec/spec_test.go (extend), internal/jobspec/testdata/*.hcl (NEW golden fixtures) |
Golden-file HCL fixtures (multi-task, env vars, args) + error-path table (missing job, no tasks, missing command, malformed HCL, empty file, nonexistent file for ParseFile). go test -cover ./internal/jobspec → ≥ 70% (was 47.6%). |
| T01.9 | data-engineer | 2 | Y | internal/audit first tests → ≥ 50% toe-hold |
internal/audit/audit_test.go (NEW) |
Construct Audit with real engine.Audit backed by :memory: sqlite (via store.NewAuditRepo + engine.NewAudit); assert rows in audit_log table; capture slog via a test slog.Handler for LogHandshakeOK/LogHandshakeFailed. go test -cover ./internal/audit → ≥ 50% (was 0%). |
| T01.10 | data-engineer | 2 | Y | internal/certpaths first tests → ≥ 50% toe-hold |
internal/certpaths/certpaths_test.go (NEW) |
Temp dir + t.Setenv("ORCA_HOME", dir) + assert each *Path() returns filepath.Join(dir, <file>); test ORCA_DB override; test default-to-~/.orca fallback. Model on namespace_test.go (cli). go test -cover ./internal/certpaths → ≥ 50% (was 0%). |
| T01.11 | lead-developer | 2 | Y | cmd/orca smoke test → ≥ 50% toe-hold |
cmd/orca/main_test.go (NEW), possibly cmd/orca/main.go (refactor main() into run() int for testability) |
Refactor main() to run() int (returns exit code; main() calls os.Exit(run())) so the test can call run() directly with a forced error path and assert non-zero exit + stderr contains "error:". Low-effort toe-hold — do NOT over-invest (RESEARCH §1.1, §5 pitfall #6). go test -cover ./cmd/orca → ≥ 50% (was 0%). |
Wave 3 — Coverage gate verification
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T01.12 | lead-developer | 3 | Y | Coverage-gate verification (all 9 packages hit tiered floor) | none (verification only) | go test -cover ./internal/engine ./internal/proxmox ./internal/cli ./internal/transport ./internal/store ./internal/jobspec → each ≥ 70%; go test -cover ./internal/audit ./internal/certpaths ./cmd/orca → each ≥ 50%. go test -race ./... PASS. Any races fixed in-phase (not deferred). |
Phase 1 Must-Haves (summary)
All 9 packages hit their tiered floor (D-047): T01.1, T01.3, T01.4, T01.5, T01.6, T01.7, T01.8, T01.9, T01.10, T01.11, T01.12. T01.2 is conditional (only if needed for engine 70%).
Phase 1 Verification
go build ./...PASSgo vet ./...PASSgo test -race ./...PASS- Per-package coverage hits the tiered floor (T01.12)
- The proxmox
sessionRunnerseam is backward compatible (existingBootstrapProxmoxcallers unchanged) - No new direct deps (
go.modunchanged)
Phase 2: SSH Trust Hardening (REQ-058, REQ-059)
Branch: phase/02-ssh-trust-hardening
REQ Coverage: REQ-058, REQ-059
Tag: v0.7.2
Depends on: Phase 1 (proxmox sessionRunner seam from T01.1 is in place;
the trust-surface code is now testable)
Source research: RESEARCH_v0.8.md §2 (TOFU bug, fingerprint computation,
flag wiring, key-reset atomic rewrite) + §4 AD-027..AD-029
Phase type: chore (trust-surface hardening per D-043 — refines existing
orca node join --type proxmox flow + existing TOFU known_hosts store; no
new orchestration capability)
Wave 1 — Trust-surface foundations (security helpers + flag declarations)
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T02.1 | backend-engineer | 1 | Y | Add security.SSHFingerprintSHA256 helper (AD-027) |
internal/security/sshkey.go (extend) OR internal/security/fingerprint.go (extend) |
Thin wrapper over ssh.FingerprintSHA256(pubKey ssh.PublicKey) string returning the canonical SHA256:base64 string. Do NOT reuse security.Fingerprint (X.509 hex — different domain per RESEARCH §2.2). Unit test: known Ed25519 pub key → known SHA256: string. |
| T02.2 | backend-engineer | 1 | Y | Export security.WriteAtomic (AD-029 enabler) |
internal/security/ca.go |
Rename writeAtomic → WriteAtomic (export) + update existing in-package callers. The key-reset atomic known_hosts rewrite (T02.7) needs it. Alternatively copy the ~20-LOC pattern into proxmox if export is undesirable — recommend export (RESEARCH §5 pitfall #10). go build ./internal/security PASS. |
| T02.3 | backend-engineer | 1 | Y | Add --host-key-fingerprint flag on orca node join (D-044) |
internal/cli/node.go |
nodeJoinCmd.Flags().StringVar(&joinHostKeyFP, "host-key-fingerprint", "", "SSH host key SHA256:base64 fingerprint (pre-pin; supersedes TOFU for --type proxmox)") in the flag-registration block (node.go:344-354). Add joinHostKeyFP string to the var block (node.go:47-60). Validation in RunE: if joinHostKeyFP != "" and --type != proxmox, emit a clear error ("--host-key-fingerprint requires --type proxmox today"). Flag is generic for future SSH-joined kinds (D-044). |
| T02.4 | backend-engineer | 1 | Y | Add HostKeyFingerprint field to proxmox.Options |
internal/proxmox/bootstrap.go |
Add HostKeyFingerprint string to the Options struct (bootstrap.go:55). Pass-through from internal/cli/node.go joinProxmox (node.go:158-166): HostKeyFingerprint: joinHostKeyFP. |
Wave 2 — Trust features + bugfix (build on Wave 1)
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T02.5 | backend-engineer | 2 | Y | Implement pinnedHostKeyCallback (REQ-058, AD-028) |
internal/proxmox/bootstrap.go |
pinnedHostKeyCallback(expectedSHA256Base64 string) (ssh.HostKeyCallback, error): validate SHA256: prefix up front (reject raw hex with a clear error per D-045); callback receives server's ssh.PublicKey, computes ssh.FingerprintSHA256(key) (via T02.1 helper or inline), compares full strings to the operator-supplied value; returns nil on match, error on mismatch (fail closed). In BootstrapProxmox: if opts.HostKeyFingerprint != "" use pinnedHostKeyCallback, else fall back to the TOFU callback (T02.6). Unit test: match → callback returns nil; mismatch → returns error mentioning REQ-058; non-SHA256:-prefixed input → constructor returns error. |
| T02.6 | backend-engineer | 2 | Y | BUGFIX (v0.6 ship-defect): FIX the latent TOFU capture bug (RESEARCH §2.1, §5 pitfall #1, GRILL condition #1) | internal/proxmox/bootstrap.go |
Wrap knownhosts.New(...) with a custom callback that: on *knownhosts.KeyError{Want: []} (host unknown) captures the server-presented ssh.PublicKey, writes a line via knownhosts.Line([]string{knownhosts.Normalize(addr)}, key) to certpaths.KnownHostsPath() using security.WriteAtomic (T02.2, AD-029), and returns nil (allow the dial to proceed). On *knownhosts.KeyError{Want: [knownKey]} (mismatch) returns the error (MITM detection). On nil (host present + match) returns nil. This fixes the v0.6 latent ship-defect where first-connect Proxmox join always failed (verified against golang.org/x/crypto@v0.54.0/ssh/knownhosts/knownhosts.go:370-385). P04 audit must record this as ship-defect closure. Unit test: first-connect captures the key + writes known_hosts; second-connect matches; mismatch-connect fails. |
| T02.7 | backend-engineer | 2 | Y | Populate Result.HostKeyFingerprint (RESEARCH §2.2, §5 pitfall #2) |
internal/proxmox/bootstrap.go |
In the capture path (T02.6) and the pinned path (T02.5), set Result.HostKeyFingerprint = ssh.FingerprintSHA256(hostKey) (via T02.1). The field is currently declared (bootstrap.go:83-85) but always "". After T02.7, orca node join --type proxmox output includes the real fingerprint. Unit test: Result.HostKeyFingerprint is non-empty + SHA256:-prefixed after a successful bootstrap. |
| T02.8 | backend-engineer | 2 | Y | Implement orca node key-reset <node> (REQ-059, D-046, AD-029) |
internal/cli/node.go, internal/proxmox/bootstrap.go (new ResetHostKey helper OR inline in cli) |
New nodeKeyResetCmd (&cobra.Command{Use: "key-reset <node>", Args: cobra.ExactArgs(1), RunE: ...}) registered via nodeCmd.AddCommand(nodeKeyResetCmd) (node.go:358-360). RunE: (1) resolve <node> arg via nodeRegistry() (node.go:37) → get node row → use node.Name (the host address for proxmox nodes) as the known_hosts match key; (2) call proxmox.ResetHostKey(host) error which reads certpaths.KnownHostsPath(), filters lines whose host field (before first whitespace, normalized via knownhosts.Normalize) matches, rewrites via security.WriteAtomic (T02.2); (3) audit-log event=node.key_reset with actor+node+host via engine.Audit.Record; (4) print ✓ Host key reset for <node> (next connect will re-pin via TOFU or --host-key-fingerprint). Local only — do NOT revoke remote authorized_keys (D-046). Unit test: known_hosts with 2 entries for the target host + 1 for another host → after reset, target's 2 lines removed, other host's line intact; audit row inserted. |
| T02.9 | backend-engineer | 2 | Y | Apply the TOFU capture-fix to doctor proxmox probe (GRILL condition #2 — doctor parity with bootstrap) |
internal/doctor/doctor.go |
The doctor proxmox probe (doctor.go:412-415) uses the same knownhosts.New(...) callback pattern as bootstrap. Apply the same capture-fix wrapper (T02.6) so doctor proxmox on a first-connect node doesn't fail. P02 is not complete until both bootstrap (T02.6) and doctor (T02.9) callbacks use the capture-fix wrapper — GRILL condition #2 binding parity check. (If the doctor probe already relies on a prior node join having populated known_hosts, the fix is still correct — it makes the doctor robust to a missing entry.) |
Wave 3 — End-to-end integration + verification
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T02.10 | backend-engineer | 3 | Y | End-to-end trust-surface integration tests | internal/proxmox/bootstrap_test.go (extend), internal/cli/node_test.go (extend) |
(1) --host-key-fingerprint with a correct pin → bootstrap succeeds + Result.HostKeyFingerprint matches the pin; (2) --host-key-fingerprint with a wrong pin → bootstrap fails fast with the REQ-058 mismatch error; (3) no --host-key-fingerprint + first connect (empty known_hosts) → TOFU captures the key + writes known_hosts + bootstrap succeeds; (4) no flag + second connect (known_hosts has the key) → matches + succeeds; (5) no flag + mismatch (known_hosts has a different key) → fails with MITM error; (6) orca node key-reset <node> → known_hosts entry removed + audit row inserted + next connect re-pins; (7) known_hosts pre-populated (v0.6→v0.8 migration path: existing entry from a prior join) → second-connect matches without re-capture, covering the upgrade path. |
| T02.11 | backend-engineer | 3 | Y | --host-key-fingerprint non-proxmox type validation test |
internal/cli/node_test.go (extend) |
orca node join --type linux --host-key-fingerprint SHA256:... → clear error ("--host-key-fingerprint requires --type proxmox today"). Validates D-044 RunE check from T02.3. |
Phase 2 Must-Haves (summary)
- T02.1, T02.2, T02.3, T02.4 (Wave 1 foundations)
- T02.5 (
--host-key-fingerprintpinned callback — REQ-058) - T02.6 (TOFU capture-fix — latent bug)
- T02.7 (
Result.HostKeyFingerprintpopulated) - T02.8 (
orca node key-reset— REQ-059) - T02.9 (doctor proxmox TOFU fix)
- T02.10, T02.11 (integration + validation)
Phase 2 Verification
go build ./...PASSgo vet ./...PASSgo test -race ./internal/proxmox/... ./internal/cli/... ./internal/doctor/... ./internal/security/...PASS./bin/orca node join --helpshows--host-key-fingerprintflag./bin/orca node key-reset --helpshows the key-reset subcommand- Pinned mismatch → fail closed (T02.10 case 2)
- TOFU first-connect → captures + succeeds (T02.10 case 3)
Result.HostKeyFingerprintis non-empty after bootstrap (T02.7)key-resetremoves only the target host's known_hosts lines + audit-logs (T02.8)- No new direct deps
Phase 3: Requirements-Hygiene Gate (REQ-060)
Branch: phase/03-verify-reqs
REQ Coverage: REQ-060
Tag: v0.7.3
Depends on: Phase 2 (P03 is independent of P02 code, but ships after per
ROADMAP ordering; the verify-reqs program parses the .ciagent/ markdown
which is stable by P03)
Source research: RESEARCH_v0.8.md §3 (Makefile, .coreci.yml, parsing
approach, AD-030) + §4 AD-030
Wave 1 — Go program
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T03.1 | lead-developer | 1 | Y | cmd/verify-reqs/main.go — Go program (~80 LOC, stdlib only, AD-030, GRILL condition #4 regex + reverse direction) |
cmd/verify-reqs/main.go (NEW) |
Parses .ciagent/ROADMAP.md + .ciagent/REQUIREMENTS.md using regexp (stdlib). Forward assertion: for every REQ-ID in REQUIREMENTS.md whose Phase column references a milestone that ROADMAP marks COMPLETE (substring-match COMPLETE within the bold span — NOT exact \*\*COMPLETE\*\* which misses v0.2's **COMPLETE (merged to main via v0.3)** header at ROADMAP.md:23), the REQUIREMENTS Status must be Complete. Reverse assertion (GRILL condition #4): for every REQ-ID in REQUIREMENTS.md marked Complete, the corresponding milestone in ROADMAP.md must be marked COMPLETE. Regex: REQUIREMENTS row ^|\s*(REQ-\d+)\s*|.*?|\s*\*\*(Complete|Pending)\*\*\s*|; ROADMAP milestone-complete ^##\s*Milestone\s+v0\.\d+:.*—\s*\*\*COMPLETE[^\*]*\*\* (substring tolerant); map milestone → REQs via the REQUIREMENTS Phase column (e.g. v0.7 P1 → milestone v0.7). Exit 0 on consistency; exit 1 with a diff listing (REQ-ID + current status + expected status + direction) on drift. CLI: go run ./cmd/verify-reqs .ciagent/ROADMAP.md .ciagent/REQUIREMENTS.md (args optional; defaults to those paths). Scope note (GRILL): REQ-060 catches doc-vs-doc drift only; code-vs-doc drift (e.g. the REQ-053 cert_repo_test.go omission — verified missing) is out of scope for this gate and handled by P04 ciagent-audit. |
| T03.2 | lead-developer | 1 | Y | cmd/verify-reqs/main_test.go — golden-file tests |
cmd/verify-reqs/main_test.go (NEW), cmd/verify-reqs/testdata/ (NEW: roadmap_clean.md, requirements_clean.md, roadmap_drift.md, requirements_drift.md) |
(1) Clean pair (ROADMAP v0.X COMPLETE + REQUIREMENTS REQ-XXX Complete) → exit 0, no diff; (2) Drift pair (ROADMAP v0.X COMPLETE + REQUIREMENTS REQ-XXX Pending) → exit 1 + diff lists the stale REQ; (3) Multiple drifts → all reported; (4) Missing args → uses defaults; (5) Malformed markdown → clear error (not a silent pass). |
Wave 2 — Makefile + CI hook
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T03.3 | lead-developer | 2 | Y | make verify-reqs target |
Makefile |
Add verify-reqs target: go run ./cmd/verify-reqs .ciagent/ROADMAP.md .ciagent/REQUIREMENTS.md. Add to .PHONY. make verify-reqs exits 0 on the current repo (REQUIREMENTS was corrected during v0.8 SPECIFY). |
| T03.4 | lead-developer | 2 | Y | .coreci.yml validate-pipeline hook |
.coreci.yml |
Add a verify-reqs step to the validate pipeline (after go-version, alongside gosec/govulncheck/gitleaks per RESEARCH §3.2): image: golang:1.25, commands: [make verify-reqs]. Pipeline fails on drift. |
Wave 3 — Synthetic drift verification
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T03.5 | lead-developer | 3 | Y | Synthetic drift verification (REQ-060 acceptance) | none (verification only; temporarily flip a REQUIREMENTS row to Pending in a scratch commit, run make verify-reqs, assert exit 1 + diff, then revert) |
(1) make verify-reqs on the current repo → exit 0; (2) flip one v0.7 REQ row to Pending in a scratch edit → make verify-reqs → exit 1 + diff lists that REQ-ID; (3) revert the scratch edit → exit 0. This is the REQ-060 acceptance criterion ("passes on current repo + fails on synthetic drift"). |
Phase 3 Must-Haves (summary)
T03.1, T03.2, T03.3, T03.4, T03.5 — all must complete for the hygiene gate to ship.
Phase 3 Verification
go build ./cmd/verify-reqsPASSgo test ./cmd/verify-reqs/...PASS (golden-file tests)make verify-reqs→ exit 0 on the current repo- Synthetic drift →
make verify-reqsexit 1 + diff (T03.5) .coreci.ymlvalidate pipeline includes theverify-reqsstep- No new direct deps (stdlib only)
Phase 4: Final Review + Ship + Audit (no new REQs)
Branch: phase/04-final-review-ship
REQ Coverage: all (REQ-057..060)
Tag: v0.7.4 (milestone release)
Depends on: Phase 1 + Phase 2 + Phase 3
Source: milestone-release checklist (matches PLAN_v0.7 P05 structure)
Wave 1 — Review + audit
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T04.1 | lead-developer | 1 | Y | Multi-persona code review across all v0.8 phases | none (review only) | ciagent-review across P01..P03; P0 issues fixed in-phase; P1+ recorded in .ciagent/ for post-hoc. |
| T04.2 | lead-developer | 1 | Y | Audit: reconstruction test + branch hygiene + commit discipline | none (audit only) | ciagent-audit: git log matches .ciagent/ files; branch hygiene clean; commit discipline enforced. |
Wave 2 — Ship
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T04.3 | lead-developer | 2 | Y | Merge phase/04 → milestone/v0.8-coverage-trust-hardening | none | Fast-forward merge (or rebase-then-fast-forward per config). |
| T04.4 | lead-developer | 2 | Y | Merge milestone/v0.8 → main | none | Rebase-then-fast-forward per config. |
| T04.5 | lead-developer | 2 | Y | Tag v0.7.4 (milestone release) |
none | git tag v0.7.4 on the merged main HEAD. Per-phase tags v0.7.0..v0.7.4 all present. |
| T04.6 | lead-developer | 2 | Y | Create Gitea release v0.7.4 with milestone summary |
none | Release notes cover all 4 phases + REQ-057..060 + coverage deltas + trust-surface additions. |
Wave 3 — Post-ship bookkeeping
| Task ID | Owner | Wave | Must | Title | Files touched | Acceptance criterion |
|---|---|---|---|---|---|---|
| T04.7 | lead-developer | 3 | Y | Update REQUIREMENTS.md — mark REQ-057..060 Complete | .ciagent/REQUIREMENTS.md |
All 4 v0.8 REQ rows show **Complete** with phase + ship tag. make verify-reqs still passes (self-consistency). |
| T04.8 | lead-developer | 3 | Y | Update ROADMAP.md — mark v0.8 COMPLETE | .ciagent/ROADMAP.md |
v0.8 milestone section shows **COMPLETE**; all phase checkboxes [x]. make verify-reqs still passes. |
| T04.9 | lead-developer | 3 | Y | Write + clear checkpoint | .ciagent/ checkpoint |
{phase: 4, stage: "complete", phase_role: "final", milestone_complete: true}; then clear checkpoint (milestone complete; next run starts a new milestone). |
Phase 4 Must-Haves (summary)
All tasks (T04.1..T04.9) are must-haves — the final-review phase has no optional work.
Phase 4 Verification
make buildPASSmake testPASSmake lintPASSmake verify-reqsPASSgo vet ./...PASSgit logon main shows all v0.8 phase commitsgit tag --list 'v0.7.*'shows v0.7.0..v0.7.4- REQUIREMENTS.md shows REQ-057..060 as Complete
- ROADMAP.md shows v0.8 as COMPLETE
- Gitea release
v0.7.4published with milestone summary
Phase 5: Final Review (next milestone, not part of v0.8 execution)
Per the v0.8 ROADMAP, there are 4 execution phases (P01..P04). P04 IS the final review + ship + audit phase. There is no separate P05 in v0.8 (unlike v0.7 which had P05). The orchestrator's next-milestone P0 begins after T04.9 clears the checkpoint.
Risk Register (carried forward from RESEARCH_v0.8.md §5)
| # | Pitfall | Phase(s) affected | Mitigation |
|---|---|---|---|
| 1 | TOFU capture is currently BROKEN: knownhosts.New returns KeyError{Want:[]} on first connect and does NOT auto-write; current BootstrapProxmox treats it as a dial failure. |
P02 | T02.6 wraps the callback to capture-and-persist on KeyError{Want:[]} via knownhosts.Line + security.WriteAtomic. This is a v0.6 latent bug that P02 closes. |
| 2 | Result.HostKeyFingerprint is declared but never populated (always ""). D-045's rationale references "existing output" that doesn't exist. |
P02 | T02.7 adds ssh.FingerprintSHA256(hostKey) computation in both the capture and pinned paths. 1-line addition once the host key is available. |
| 3 | No sessionRunner seam in proxmox — testing the SSH command sequence without a real SSH server is impossible. |
P01 | T01.1 adds a 1-interface ~10-LOC sessionRunner seam in Wave 1. Unlocks ~40% of proxmox coverage. Backward compatible. |
| 4 | internal/store/cert_repo.go has NO test — v0.7 P01 REQ-053 was supposed to add cert_repo_test.go but it's missing (v0.7 leftover). |
P01 | T01.7 adds cert_repo_test.go (Insert/Get/List/ListByNode/LatestForKind/PruneOlderThan/Delete + N=3 rotation). Directly lifts store coverage toward 70%. |
| 5 | internal/cli/daemon.go starts a long-running mTLS server — testing it in cli requires a lifecycle harness; it's already covered by internal/daemon/server_test.go. |
P01 | T01.6 excludes daemon.go from the cli 70% target; documents the exclusion in a test-file comment. Avoids double-testing. |
| 6 | cmd/orca 50% toe-hold is low-value (15 LOC of glue; effort:coverage ratio is poor). |
P01 | T01.11 keeps it at the 50% toe-hold per D-047; does NOT over-invest. A small run() int refactor enables a smoke test. |
| 7 | go: no such tool "covdata" for zero-test packages — a Go toolchain quirk when a package has no test files; NOT a real 0% number. |
P01 | T01.9, T01.10, T01.11 each add a _test.go file, which makes coverage computable. Don't treat the tooling error as a measurement. |
| 8 | transport.dispatchToPeer has no seam — testing the remote-dispatch branch requires a new interface OR httptest.NewTLSServer. |
P01 | T01.3 uses httptest.NewTLSServer (no refactor needed). T01.2 (conditional peerDispatcher seam) is only added if engine cannot otherwise hit 70%. |
| 9 | knownhosts.Line + knownhosts.Normalize are the helpers for the TOFU-capture fix and key-reset matching. |
P02 | T02.6 + T02.8 use Normalize to match host strings consistently (handles host:22 vs host). |
| 10 | security.writeAtomic is unexported (ca.go:305); key-reset's atomic known_hosts rewrite needs it. |
P02 | T02.2 exports WriteAtomic (recommended) OR copies the ~20-LOC pattern. Export is preferred — it's already used across ca.go + sshkey.go. |
REQ-ID → Task mapping (traceability)
| REQ-ID | Phase | Tasks |
|---|---|---|
| REQ-057 | P01 | T01.1, T01.2 (conditional), T01.3, T01.4, T01.5, T01.6, T01.7, T01.8, T01.9, T01.10, T01.11, T01.12 |
| REQ-058 | P02 | T02.1, T02.3, T02.4, T02.5, T02.7, T02.10, T02.11 |
| REQ-059 | P02 | T02.2, T02.8, T02.10 |
| REQ-060 | P03 | T03.1, T03.2, T03.3, T03.4, T03.5 |
| (latent TOFU bug) | P02 | T02.6, T02.9 (not a REQ — closes a v0.6 gap surfaced by RESEARCH §2.1) |
| (milestone release) | P04 | T04.1..T04.9 |
Task counts
| Phase | Tasks | Must-haves | Waves |
|---|---|---|---|
| P01 | 12 | 11 (T01.2 conditional) | 3 |
| P02 | 11 | 11 | 3 |
| P03 | 5 | 5 | 3 |
| P04 | 9 | 9 | 3 |
| Total | 37 | 36 | — |