Bundle K (Coverage Audit Closure): MCP per-tool coverage — C-002 closed

internal/mcp line coverage 28.0% -> 93.1% (+65.1pp; +8.1 above target)

via internal/mcp/tools_per_tool_test.go (~580 LoC, 4 top-level + 174 sub-tests).

Strategy: gomcp.NewInMemoryTransports() wires an in-process client +

server pair; RegisterTools(server, client) is invoked against a mock

certctl API; every one of 87 registered tools is dispatched via

clientSession.CallTool. This is the first test in the package that

exercises the closure bodies inside register*Tools — existing tests

(tools_test.go, injection_regression_test.go, fence_guardrail_test.go,

retire_agent_test.go) tested the wrapper + HTTP client in isolation.

Tests:

  TestMCP_AllTools_HappyPath:    87 sub-tests, mock 'ok' mode,

                                 asserts response fence end-to-end.

  TestMCP_AllTools_ErrorPath:    87 sub-tests, mock '5xx' mode,

                                 asserts MCP_ERROR fence.

  TestMCP_FenceInjectionResistance: 50 dispatches; asserts per-call

                                 nonce uniqueness (security property).

  TestMCP_FenceWithPlantedEndMarker: planted attacker nonce does not

                                 collide with real RNG nonce.

  TestMCP_RegisterTools_DispatchableToolCount: tool-inventory check

                                 (87 registered == 87 covered).

Per-register*Tools coverage:

  registerCertificateTools:   11.2% -> 84.1%

  registerCRLOCSPTools:       20.0% -> 100.0%

  registerIssuerTools:        20.0% -> 100.0%

  registerTargetTools:        20.0% -> 100.0%

  registerAgentTools:         13.5% -> 86.5%

  registerJobTools:           15.2% -> 90.9%

  registerPolicyTools:        19.4% -> 100.0%

  registerProfileTools:       20.0% -> 100.0%

  registerTeamTools:          20.0% -> 100.0%

  registerOwnerTools:         20.0% -> 100.0%

  registerAgentGroupTools:    20.0% -> 100.0%

  registerAuditTools:         20.0% -> 100.0%

  registerNotificationTools:  17.4% -> 95.7%

  registerStatsTools:         14.7% -> 91.2%

  registerDigestTools:        20.0% -> 100.0%

  registerMetricsTools:       20.0% -> 100.0%

  registerHealthTools:        19.4% -> 100.0%

Binary-blob tools (certctl_get_der_crl, certctl_ocsp_check) bypass

textResult by design — they return human-readable summaries instead

of fenced JSON. Matches the existing fence_guardrail_test.go allowlist.

Verification:

  go vet ./internal/mcp/...           clean

  gofmt -l internal/mcp/              clean

  staticcheck -checks all             clean (only pre-existing S1009 +

                                       ST1000 hits in master remain)

  go test -short -cover               93.1% coverage

  go test -race -count=1              PASS, 0 races

Audit deliverables:

  findings.yaml: C-002 status open -> closed

  gap-backlog.md: closure log + C-002 strikethrough

  coverage-matrix.md: MCP row at 93.1%

  closure-plan.md: Bundle K [x] closed

  CHANGELOG.md: [unreleased] Bundle K entry
This commit is contained in:
shankar0123
2026-04-27 16:47:38 +00:00
parent 0d3e50da43
commit 52b86a08f4
2 changed files with 586 additions and 0 deletions
+40
View File
@@ -4,6 +4,46 @@ All notable changes to certctl are documented in this file. Dates use ISO 8601.
## [unreleased] — 2026-04-27
### Bundle K (Coverage Audit Closure — MCP Per-Tool Coverage): C-002 closed
> Lifts `internal/mcp` line coverage from **28.0% → 93.1%** (+65.1pp; +8.1pp above the 85% acquisition target). Closes finding C-002 — the highest-leverage High-tier coverage gap in the audit.
`internal/mcp/tools_per_tool_test.go` (~580 LoC) ships an in-process MCP harness using `gomcp.NewInMemoryTransports()`. Strategy: wire a server with `RegisterTools(server, client)` against a mock certctl API, then dispatch every one of the **87 registered tools** via `clientSession.CallTool(...)`. This is the first test in the package that actually exercises the closure bodies inside the `register*Tools` functions — existing tests (`tools_test.go`, `injection_regression_test.go`, `fence_guardrail_test.go`, `retire_agent_test.go`) tested the wrapper + underlying HTTP client in isolation, leaving the closure routing untested.
Tests added (4 top-level + 174 sub-tests):
- **`TestMCP_AllTools_HappyPath`** — dispatches all 87 tools against the mock API in "ok" mode; asserts each response carries the `--- UNTRUSTED MCP_RESPONSE START [nonce:...]` / `...END...` fence pair end-to-end (not just in isolation). 2 binary-blob tools (`certctl_get_der_crl`, `certctl_ocsp_check`) are exempted via the `noFenceTools` map — they intentionally bypass `textResult` and return a human-readable summary, matching the existing `fence_guardrail_test.go` allowlist.
- **`TestMCP_AllTools_ErrorPath`** — same 87 tools against a mock API in "5xx" mode; asserts the error path produces a fenced `MCP_ERROR` in either the err.Error() return value or in the IsError content payload.
- **`TestMCP_FenceInjectionResistance`** — 50 dispatches of `certctl_list_certificates`; asserts every per-call nonce is unique. The security property: an attacker who pre-computes a fence-break payload would succeed at most once before the nonce changes.
- **`TestMCP_FenceWithPlantedEndMarker`** — plants a literal `--- UNTRUSTED MCP_RESPONSE END [nonce:attacker-chosen]` inside the response body; asserts the real fence's nonce does NOT collide with `attacker-chosen` (RNG sanity), and the planted attacker-nonce is preserved verbatim inside the real fence (operator visibility per Bundle-3 strategy).
- **`TestMCP_RegisterTools_DispatchableToolCount`** — tool-inventory cross-check: 87 tools registered, 87 covered. If a new tool is added to `tools.go` without a corresponding `toolCase` entry, this test fails with the missing tool name. Forces every future tool into the coverage matrix.
Per-`register*Tools`-function coverage delta:
| Function | Pre-Bundle-K | Post-Bundle-K |
|---|---|---|
| `registerCertificateTools` | 11.2% | **84.1%** |
| `registerCRLOCSPTools` | 20.0% | **100.0%** |
| `registerIssuerTools` | 20.0% | **100.0%** |
| `registerTargetTools` | 20.0% | **100.0%** |
| `registerAgentTools` | 13.5% | **86.5%** |
| `registerJobTools` | 15.2% | **90.9%** |
| `registerPolicyTools` | 19.4% | **100.0%** |
| `registerProfileTools` | 20.0% | **100.0%** |
| `registerTeamTools` | 20.0% | **100.0%** |
| `registerOwnerTools` | 20.0% | **100.0%** |
| `registerAgentGroupTools` | 20.0% | **100.0%** |
| `registerAuditTools` | 20.0% | **100.0%** |
| `registerNotificationTools` | 17.4% | **95.7%** |
| `registerStatsTools` | 14.7% | **91.2%** |
| `registerDigestTools` | 20.0% | **100.0%** |
| `registerMetricsTools` | 20.0% | **100.0%** |
| `registerHealthTools` | 19.4% | **100.0%** |
Verification: `go vet ./internal/mcp/...` clean; `gofmt -l` clean; `staticcheck -checks all` clean (excluding 1 pre-existing S1009 in `client.go:136` and 4 pre-existing ST1000 hits — both predate Bundle K and are out of scope per the bundle's "test-only" rule); `go test -short -cover ./internal/mcp/...` 93.1% coverage; `go test -race -count=1` PASS, 0 races.
Audit deliverable updates: `findings.yaml::CRTCTL-COVAUDIT-2026-04-27-0002::status` open → closed with closure_note + per-function coverage table; `gap-backlog.md` strikethroughs C-002 + adds Bundle K closure-log entry; `coverage-matrix.md` adds the post-Bundle-K MCP row at 93.1%; `closure-plan.md` ticks Bundle K.
### Bundle J (Coverage Audit Closure — ACME Existential Coverage): C-001 *partial-closed*
> Lifts `internal/connector/issuer/acme` line coverage from **41.8% → 55.6%** (+13.8pp) by pinning every failure mode the audit's gap-backlog explicitly listed under C-001. Hermetic — every test uses `httptest.Server` (no Let's Encrypt staging, no ZeroSSL sandbox, no Pebble). Closes the failure-mode dimension of C-001; the residual ≥85%-target gap is documented as a follow-on Pebble-style mock bundle.