pilot-cd-gate · git:20260522.7d424d1 · 2026-05-22 · sha256 251fefaaae4d38cc

pilot-cd-gate git:20260522.7d424d1A

Immutable. This exact content never changes and is served at /api/v1/blob/251fefaaae4d38cc.

---
name: pilot-cd-gate
description: 'Use when Pilot (or another acceptance-judge tool) is making a PRE-DEPLOY GATE DECISION in a CD pipeline using cub-scout''s structured evidence. Natural phrasings the operator brings to Pilot: "should this deploy go?", "is this release safe to promote?", "what does cub-scout say about this strategy?", "Pilot, gate the apply", "block the deploy if source-truth disagrees", "PASS/WATCH/ASK/BLOCK on payments-api release". Pilot consumes cub-scout''s `compare source-truth --strategy <s>` (with optional `receipt verify --strategy <s> --fail-on any-non-pass` for typed evidence + exit-2 CI gate) and renders a verdict. cub-scout NEVER decides PASS/BLOCK — it produces evidence; Pilot makes the call. Do NOT load for: a Pilot post-deploy verification (use pilot-release-verification — coming in batch B), a real-time event-driven response (use pilot-watch-alert-response), a multi-cluster periodic audit (use pilot-fleet-conformance), or any cub-scout authoring command. Pilot mutates via `cub` / Argo / Flux — never via cub-scout.'
phase: cross-cutting
allowed-tools: Bash(./cub-scout compare source-truth *) Bash(cub-scout compare source-truth *) Bash(cub scout compare source-truth *) Bash(./cub-scout compare three-way *) Bash(cub-scout compare three-way *) Bash(cub scout compare three-way *) Bash(./cub-scout receipt verify *) Bash(cub-scout receipt verify *) Bash(cub scout receipt verify *) Bash(./cub-scout receipt show *) Bash(cub-scout receipt show *) Bash(cub scout receipt show *) Bash(./cub-scout receipt validate *) Bash(cub-scout receipt validate *) Bash(cub scout receipt validate *) Bash(./cub-scout explain *) Bash(cub-scout explain *) Bash(cub scout explain *) Bash(./cub-scout trace *) Bash(cub-scout trace *) Bash(cub scout trace *) Bash(./cub-scout doctor *) Bash(cub-scout doctor *) Bash(cub scout doctor *) Bash(kubectl get *) Bash(kubectl describe *) Bash(cub * get) Bash(cub * list) Bash(cub unit get *) Bash(cub link list *) Bash(argocd app get *) Bash(flux get *)

---

# pilot-cd-gate

The pre-deploy CD-gate scenario from **Pilot's** side. A release pipeline asks Pilot: *should this deploy go?* Pilot consumes cub-scout's structured evidence — specifically `compare source-truth --strategy <s>` plus (optionally) a typed `cub-scout receipt verify --strategy <s>` — and renders a verdict that the pipeline gates on.

cub-scout is the **witness**. Pilot is the **judge**. ConfigHub (via `cub`) is the authority. cub-scout never decides PASS / BLOCK — it produces evidence with documented `omissions[]` where evidence is missing, and Pilot makes the call. This skill is about how Pilot reads that evidence; it is NOT about how Pilot itself mutates.

## When to use

Explicit phrasings the user brings to Pilot (which Pilot then satisfies by calling cub-scout):

- "Should this deploy go?"
- "Pilot, gate the apply"
- "Is `payments-api@v1.4.3` safe to promote to prod?"
- "Block the deploy if source-truth disagrees"
- "Run the pre-deploy gate against `git-argo`"
- "PASS/WATCH/ASK/BLOCK on the release ticket"
- "Should we promote this PR's branch?"

Implicit intents:

- The user is **about to deploy** (pre-deploy, not post-deploy)
- The evidence needs to feed a **gate** — a decision that turns into a CD pipeline exit code, a PR check, or a release-ticket field
- The decision should be **strategy-anchored** — the user has declared what the source of truth is (one of the 9 source-truth strategies), and Pilot is checking conformance against that
- The result needs to be **auditable** later — Pilot may want a fingerprinted receipt to attach to the release record

## Do not load for

- A **post-deploy** verification (release just shipped; did it land per Git?) — `pilot-release-verification` (batch B)
- A **real-time** event-driven response (Pilot subscribing to `cub-scout watch`) — [`pilot-watch-alert-response`](../pilot-watch-alert-response/SKILL.md)
- A **multi-cluster periodic conformance audit** (not tied to one release event) — [`pilot-fleet-conformance`](../pilot-fleet-conformance/SKILL.md)
- A **drift-classification** question after the gate already passed — [`pilot-patch-and-drift`](../pilot-patch-and-drift/SKILL.md)
- **Postmortem** evidence assembly — [`pilot-incident-evidence`](../pilot-incident-evidence/SKILL.md)
- Anything that **mutates** the cluster or ConfigHub. The gate decision is Pilot's; the mutation is `cub` / Argo / Flux's. cub-scout never mutates.

## The Pilot ↔ cub-scout loop

```
   release pipeline
        │
        ▼
   Pilot (acceptance judge)
        │  "should this deploy go?"
        │
        ▼
   cub-scout compare source-truth <subject> --strategy <s> --format json
        │
        │  ──────────────────────────────────────────────────────────
        │  status:       PASS | WATCH | BLOCK | ASK
        │  sourceTruth:  AGREED | MISMATCH | INCOMPLETE | BLOCKED | UNKNOWN
        │  proofGaps:    [] (or list of structured missing-evidence entries)
        │  attribution:  cause, managerHint, gitSource per field
        │  ──────────────────────────────────────────────────────────
        │
        ▼
   (optional) cub-scout receipt verify <subject> --strategy <s>
                                --save --out release.receipt.json
        │
        │  fingerprinted in-toto Statement v1 artifact for the release record
        │
        ▼
   Pilot's verdict: PASS / WATCH / ASK / BLOCK
        │
        ▼
   release pipeline reads the verdict
        │  PASS  → continue
        │  WATCH → continue, attach the receipt to the release, alert
        │  ASK   → block, ask a human
        │  BLOCK → halt, alert, halt the promotion
        └──────────────────────────────────────────────────────────
```

## Strategy required upfront

cub-scout **never infers** the source-truth strategy. Pilot's gate must declare one of the 9 — pass it explicitly via `--strategy`:

| Strategy | What it asserts |
|----------|-----------------|
| `git-argo`        | Argo CD application reading from a Git source; cluster matches the git tree |
| `git-flux`        | Flux reading from a Git source; cluster matches the git tree |
| `git-helm`        | Helm release from a Git-hosted chart; cluster matches the rendered chart |
| `oci-argo`        | Argo CD reading from an OCI registry; cluster matches the OCI image |
| `oci-flux`        | Flux reading from an OCI registry; cluster matches the OCI image |
| `oci-helm`        | Helm chart published to OCI; cluster matches the rendered chart |
| `kustomize-flux`  | Flux Kustomization; cluster matches the rendered kustomize tree |
| `helm-flux`       | Flux HelmRelease; cluster matches the rendered chart |
| `confighub-oci-argo` | ConfigHub authoring + OCI publish + Argo CD apply; cluster matches the unit revision via OCI |

See [`source-truth-strategies`](../references/source-truth-strategies.md) for the full reference. If the user can't name a strategy, the gate is premature — Pilot should ask before evaluating.

## Two-tier evidence: `compare source-truth` vs. `receipt verify`

Pilot has two tiers of evidence available, and the choice is a quality / durability trade-off:

| Tier | Verb | Output | When to use |
|------|------|--------|-------------|
| **Live evidence** | `cub-scout compare source-truth <kind>/<name> -n <ns> --strategy <s> --format json` | Ephemeral JSON the pipeline reads once | Most gate decisions. Pilot reads `status` + `sourceTruth` + `proofGaps`, renders a verdict, and the JSON is discarded after the decision. |
| **Typed evidence** | `cub-scout receipt verify <kind>/<name> -n <ns> --strategy <s> --save --out release.receipt.json --fail-on any-non-pass` | Persistent in-toto Statement v1 envelope with SHA-256 fingerprint | When the release record needs an immutable receipt attached (audit, compliance, postmortem). `--fail-on any-non-pass` doubles as the exit-2 CI gate. |

The receipt path is **preferred for production gates** because:

1. The artifact is durable — the release record carries the evidence the gate decision was made on, fingerprint-verifiable later.
2. `--fail-on any-non-pass` gives Pilot a native CI exit code (0/2/1) without per-pipeline glue.
3. The receipt is preserved **even if the gate fires** — exit-2 happens AFTER stdout/`--out`/`--save`, so the failure case still produces evidence. (See `examples/receipts/ci-gate/README.md` for the full walkthrough.)
4. The receipt can be re-validated later: `cub-scout receipt validate release.receipt.json` exits 0 (OK) / 1 (mismatch) / 2 (I/O).

## Verdict mapping (cub-scout status → Pilot verdict)

The source-truth contract has **two** enums:

- **`status`** — evidence-quality verdict: `PASS` / `WATCH` / `BLOCK` / `ASK`
- **`sourceTruth`** — cross-surface agreement: `AGREED` / `MISMATCH` / `INCOMPLETE` / `BLOCKED` / `UNKNOWN`

Pilot's gate verdict typically tracks `status`, with `sourceTruth` available for drill-down:

| cub-scout `status` | cub-scout `sourceTruth` (typical) | Pilot gate verdict | Pipeline action |
|--------------------|-----------------------------------|--------------------|-----------------|
| PASS  | AGREED      | **PASS** | continue |
| WATCH | INCOMPLETE  | **WATCH** | continue but attach receipt; alert release channel |
| ASK   | UNKNOWN     | **ASK** | block; ask human (proof gaps the user can fill) |
| BLOCK | MISMATCH    | **BLOCK** | halt; alert |
| BLOCK | BLOCKED     | **BLOCK** | halt; the evidence itself was unreachable (cluster down, ConfigHub down) |

When wrapped in a receipt, `ASK` maps to receipt verdict `WATCH` per the locked synthesis in `docs/proposals/receipts-way-forward.md`. The receipt-level `INCONCLUSIVE` verdict is reserved for receipts that themselves couldn't be built (missing predicate inputs, e.g., source-truth-pass without `--strategy`); it does NOT appear in `compare source-truth`'s own `status`.

## Worked example

A release pipeline is gating on `payments-api@v1.4.3` going to `prod`. The team's declared strategy is `git-argo` (Argo CD reading from the org's Git repo).

```bash
# Step 1 — Pilot asks cub-scout for live evidence
$ cub-scout compare source-truth deploy/payments-api -n prod \
    --strategy git-argo \
    --format json
```

```json
{
  "scope": "resource:Deployment/payments-api/prod",
  "strategy": "git-argo",
  "status": "WATCH",
  "sourceTruth": "INCOMPLETE",
  "proofGaps": [
    {
      "missing": "git-source-anchor",
      "reason": "argocd CLI not installed; tracer fell back to label-only ownership",
      "severity": "warning"
    }
  ],
  "attribution": {
    "Deployment/payments-api": {
      "cause": "controller-drift",
      "managerHint": "argocd-controller"
    }
  }
}
```

Pilot reads this:
- `status: WATCH` → not a hard PASS; gate fires WATCH (continue but flag).
- `proofGaps[0].missing: git-source-anchor` → the operator can fix this by installing argocd CLI in the pipeline container.
- `attribution.cause: controller-drift` → no manual-edit signal; Argo is in control.

Pilot's CD pipeline policy says WATCH is non-blocking but must attach a receipt to the release ticket. So Pilot escalates the call to:

```bash
# Step 2 — Pilot generates a typed receipt to attach to the release
$ cub-scout receipt verify deploy/payments-api -n prod \
    --strategy git-argo \
    --save \
    --out release.receipt.json \
    --fail-on BLOCK
# exit 0 because verdict is WATCH (not in --fail-on BLOCK set)
# stdout shows the JSON; release.receipt.json is on disk; the local store has the artifact too
```

The release ticket gets the WATCH verdict + the `release.receipt.json` artifact attached. Later, an audit query can:

```bash
$ cub-scout receipt validate ./release.receipt.json
# exit 0 — fingerprint still matches
$ cub-scout receipt show ./release.receipt.json --format ascii
# renders the same evidence Pilot read
```

The receipt is **fingerprint-stable** — the same release.receipt.json can be archived for years and validated indefinitely (subject to the underlying evidence sources still existing for cross-checks).

## Standalone vs connected

Pilot's CD gate runs in two flavors:

| Flavor | Verbs available | Notes |
|--------|-----------------|-------|
| **Standalone** (cluster only, no ConfigHub auth) | `receipt verify --predicate applied-matches-spec`, `receipt verify --predicate no-manual-edits-since` | Limited — cannot use `source-truth-pass`. Useful for non-ConfigHub-managed deployments. |
| **Connected** (cluster + `cub auth login`) | All of the above + `receipt verify --strategy <s>` (source-truth-pass), `compare source-truth`, `compare three-way`, `confighub-unit://` subject in receipt | Full surface. Recommended for production gates. |

A standalone gate is still valuable but produces an INCOMPLETE receipt (missing `confighub-unit://` subject) with a documented `OmissionConfigHubUnitSubject` entry. Pilot can still consume it, but the verdict ceiling is WATCH (the receipt is honest about what's missing).

Run `cub-scout status` in the pipeline preamble to confirm the mode; if standalone and the gate requires a strategy, Pilot's policy should escalate to ASK rather than proceed.

## CI integration shapes

```yaml
# GitHub Actions — Pilot calls cub-scout, Pilot decides
- name: cub-scout evidence
  id: evidence
  run: |
    cub-scout compare source-truth deploy/payments-api -n prod \
      --strategy git-argo \
      --format json > evidence.json
- name: Pilot verdict
  id: verdict
  run: |
    # Pilot's policy engine consumes evidence.json
    verdict=$(pilot decide --evidence ./evidence.json --release ${{ env.RELEASE }})
    echo "verdict=$verdict" >> $GITHUB_OUTPUT
- name: Halt on BLOCK or ASK
  if: steps.verdict.outputs.verdict == 'BLOCK' || steps.verdict.outputs.verdict == 'ASK'
  run: exit 1
```

```yaml
# Simpler: skip Pilot, use the receipt --fail-on path directly
- name: cub-scout receipt gate
  run: |
    cub-scout receipt verify deploy/payments-api -n prod \
      --strategy git-argo \
      --fail-on any-non-pass \
      --save \
      --out release.receipt.json
    # exit 0 → continue; exit 2 → gate fired (release.receipt.json is still on disk)
- name: Upload receipt (always)
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: release-receipt
    path: release.receipt.json
```

See [`examples/receipts/ci-gate/README.md`](../../examples/receipts/ci-gate/README.md) for the full GitHub Actions / GitLab CI / Jenkins walkthrough.

## Tool boundary

- **Allowed (cub-scout side):** `compare source-truth`, `compare three-way`, `receipt verify` / `show` / `validate`, `explain`, `trace`, `doctor`. All read-only.
- **Allowed (cluster side):** `kubectl get/describe`. **NOT** `kubectl apply/edit/patch/delete`.
- **Allowed (ConfigHub side, connected):** `cub * get / list`, `cub unit get`, `cub link list`. **NOT** any `cub * create / update / delete`.
- **Allowed (controller side):** `argocd app get`, `flux get`. **NOT** `argocd app sync`, `flux reconcile`.
- **Pilot itself may mutate** — but through `cub` / Argo / Flux / whatever, not through cub-scout. This skill's allowed-tools list is what cub-scout authorizes for Pilot's evidence-gathering calls.

## References

- [`scout-compare`](../scout-compare/SKILL.md) — the four Compare verbs (cub-scout side)
- [`scout-verify`](../scout-verify/SKILL.md) — the receipt verbs (cub-scout side)
- [`confighub-source-truth`](../confighub-source-truth/SKILL.md) — strategy-typed verdict workflow
- [`pilot-watch-alert-response`](../pilot-watch-alert-response/SKILL.md) — real-time companion (event-driven, this skill is query-driven)
- [`pilot-patch-and-drift`](../pilot-patch-and-drift/SKILL.md) — drift classification after the gate passes
- [`pilot-incident-evidence`](../pilot-incident-evidence/SKILL.md) — evidence assembly when the gate didn't catch it
- Source-truth contract: `#393`, `#418` (Phase 2 strategies), `#409` (Phase 3 deferred)
- Receipts: `#446` (v1), `#463` (v2: `--fail-on`, chained, watch emit)
- Worked CI walkthrough: [`examples/receipts/ci-gate/README.md`](../../examples/receipts/ci-gate/README.md)

## Constraints

- Connected mode (`cub auth login`) is **required** for `--strategy source-truth-pass`. Standalone gates fall back to `applied-matches-spec` or `no-manual-edits-since`, which are narrower.
- The strategy is **caller-declared**, never inferred. If the team can't name one, the gate is premature.
- The gate decision is Pilot's, but cub-scout's `--fail-on` flag implements the same shape (exit 2 = non-PASS) for gateways that don't want a Pilot dependency.
- Receipts are **immutable** — once persisted to the store, `SaveStatement` uses `O_EXCL` and refuses to overwrite. Re-running the gate produces a new receipt with a new fingerprint.
- The receipt-level verdict `INCONCLUSIVE` is distinct from `compare source-truth`'s `status: ASK`. INCONCLUSIVE means the receipt itself couldn't be built; ASK means the receipt was built but the evidence quality couldn't reach PASS.