# Tool / Skill / Runbook Alignment — Rationale & Enforcement

Lifted from `.gzkit/rules/tool-skill-runbook-alignment.md` (rule version 0.2.0, GHI #327).

## Canonical violation examples

### Invariant 1 — orphaned tool (2026-04-13)

`gz adr status` is a live CLI verb (handler at `src/gzkit/commands/status.py:385`, registered at `src/gzkit/cli/parser_artifacts.py:84-104`, correctly prescribed by `docs/user/runbook.md:21-202` at multiple line locations, and directly invoked by `gz-adr-audit`, `gz-adr-recon`, `gz-adr-verification`, and `gz-obpi-pipeline`) — yet no skill routes operators to it via skill discovery. The `gz-adr-status` skill routes to `gz adr report` instead (see GHI #141). The verb is live and load-bearing but unreachable through the operator-facing skill layer.

### Invariant 2 — skill/runbook mismatch (2026-04-13)

The skill `.gzkit/skills/gz-adr-status/SKILL.md` has frontmatter `gz_command: adr report` (line 11) and body invoking `uv run gz adr report` (lines 33-34, 48, 51). The skill name says "status," the runbook prescribes `gz adr status` for single-ADR drilldown (`docs/user/runbook.md:23, 130, 192, 202`), but the skill routes to `gz adr report`. The operator gets tabular output from `report` when the skill name and runbook both imply they should get the single-ADR prose output from `status`.

### Invariant 3 — output form drift (2026-04-14, post-#141)

The skill `.gzkit/skills/gz-adr-status/SKILL.md` declared in its Output Contract *"Inspect all ADRs for pending gates and lifecycle at a glance"* and *"consistent table format"*, plus the explicit instruction *"Do not paraphrase, condense, or replace the table with prose unless the user explicitly asks for analysis."* After GHI #141's repair routed the single-ADR case to `uv run gz adr status <ID>` — satisfying Invariant 2 — the destination verb's `adr_status_cmd` at `src/gzkit/commands/status.py:385` rendered ad-hoc indented `console.print` lines rather than a Rich `Table`. The skill's declared form (table) and the verb's observed form (prose) disagreed. Invariant 2 passed; Invariant 3 was violated. The drift went undetected for two days until an operator saw raw prose in a tool call and asked *"do we not have a standardization of that output?"* See GHI #149 for the invariant addition, GHI #150 for the one-time audit sweep, and GHI #151 for the commit-authoring discipline that would have prevented the #141 follow-up class of failure.

### Invariant 3 — string-shape assertions

**String-shape assertions belong in dedicated output-form fixtures, not REQ-derived unit tests.** The test surface for this invariant is a fixture-style test (one per skill whose Output Contract declares a form) that invokes the declared `gz_command` target against a non-empty fixture and asserts the observed default output contains the form's markers (box-drawing characters for "table", JSON-parseable payload for "JSON", one-record-per-line for "plain text", etc.). These fixtures MUST live in a separate test class or module from the REQ-derived unit tests that cover the verb's behavior, per `.gzkit/rules/tests.md` section "Tests assert semantics, not strings" (Invariant 6f). The separation prevents the two contracts from colliding: a REQ-derived behavior test pins operator-facing semantics and must not drift into byte-level shape assertions; an output-form fixture pins the skill-to-verb rendering contract and is allowed — indeed required — to make string-shape assertions. Blending them in one test forces every semantic refactor to rewrite string shapes and every rendering change to rewrite semantic assertions, which is exactly the collision GHI #270 surfaced.

## Rationale

The layered hierarchy exists because acute focus meanders. Skills get renamed during absorption work; CLI verbs get refactored for size-cap compliance; runbooks get edited to reflect operator reality. Each layer holds a different kind of truth:

- **Tools** hold *capability* — what the system can actually do
- **Skills** hold *operator value chains* — how capability gets composed toward an intent
- **Runbooks** hold *preserved intent* — the canonical answer to "what should I run to accomplish X"

When the layers agree, the cascade works: operator invokes a skill, the skill wields the right tool, the runbook documents the sequence, and future operators can trace the value chain. When the layers drift, operators get silent misrouting, dependent skills call orphaned verbs directly, and the only thing that preserves intent across the drift is the runbook — which is exactly what happened with GHI #141: the runbook held `gz adr status` correctly across months of absorption work while the skill layer drifted, and the audit surfaced the drift by comparing runtime reality against the runbook's prescribed commands.

## Enforcement

These invariants are currently **advisory** — they govern authoring discipline but are not yet mechanically checked. The long-term home for enforcement is `gz validate --surfaces`, which should grow checks that:

1. Enumerate every CLI verb registered in `src/gzkit/cli/**` and confirm at least one skill under `.gzkit/skills/**` references it (either via `gz_command:` frontmatter or in the skill body)
2. Enumerate every skill's declared CLI target and confirm the runbook prescribes that same verb for the operator moment the skill's name describes
3. For every skill whose Output Contract (or body prose) declares a rendering form, invoke the declared `gz_command` target against a non-empty fixture and assert the observed default output contains markers consistent with the declared form (table box-drawing characters for "table", JSON-parseable output for "JSON", etc.)

Until those checks exist, apply the invariants by hand whenever you author or modify a CLI verb, a skill, or a runbook entry. Short-term test-level enforcement for Invariant 3 lives in dedicated **output-form fixtures** — a test class or module separate from the command's REQ-derived unit tests — that invoke the `gz_command` target against a non-empty fixture and assert the observed default output contains the form's markers (e.g. a `TestAdrStatusOutputForm` fixture class holding the `test_adr_status_renders_shared_table_via_deterministic_renderer` assertion in `tests/commands/test_status.py`). The separation is binding per `.gzkit/rules/tests.md` section "Output-form fixture carve-out": REQ-derived unit tests pin semantics (Invariant 6f) and must not assert byte-level shape; output-form fixtures pin the skill-to-verb rendering contract (Invariant 3) and are the correct home for string-shape assertions. Do not blend them in one test.

## Commit-message discipline for skill-routing changes

Any commit that modifies a skill's `gz_command` frontmatter field, changes which verb a skill's body invokes, or edits a CLI verb's default human-readable rendering MUST include one of the following in the commit body:

1. **Observed-output evidence.** The exact command(s) the author ran to verify the destination verb's observed output form, followed by a 3-5 line representative excerpt of what was observed. Example:
   ```
   Verified via `uv run gz adr status ADR-0.1.0`:

     +----------- +------+-----------+------+
     | ADR        | Lane | Lifecycle | OBPI |
     +------------+------+-----------+------+
     | ADR-0.1.0  | lite | Pending   | 0/1  |
     +------------+------+-----------+------+
   ```

2. **Test reference.** An explicit citation of a test (file path and test name) that asserts the destination verb's default human-readable output contains the form markers the routing skill's Output Contract declares. Example:
   ```
   Output form locked by tests/commands/test_status.py::TestLifecycleStatusSemantics::test_adr_status_renders_shared_table_via_deterministic_renderer
   ```

A commit body that asserts the destination's output form without either of the above — e.g. "the verb's default output is tabular" as a bare claim — does not satisfy this rule. Such claims are post-hoc reasoning pathways, not verification pathways, and are the class of failure that produced the GHI #141 follow-up drift in commit `3a75822b`. The cost of running the command once and pasting the observed output into the commit body is far lower than the cost of a silently-broken skill contract sitting in production until an operator catches it in a raw tool-call output.

**Current enforcement:** advisory. Reviewers should reject PRs whose commit bodies make unverified output-form claims about changed skill routing or changed CLI rendering. Mechanical enforcement via a commit-message hook is tracked under GHI #151.

## Anti-patterns

- Authoring a CLI verb without a wielding skill, and deferring the skill to "later"
- Routing a skill to a different CLI verb than its name implies, without renaming the skill to match
- Editing the runbook to prescribe a verb no skill exposes, and relying on direct CLI invocation
- Treating skill-to-CLI drift as a cosmetic naming issue instead of a defect signal
- Using the runbook as the only surface that preserves intent, without mirroring the intent in skill routing
- Re-routing a skill's `gz_command` without running the new target once to verify observed output matches the skill's declared form — "it should be tabular" is not verification
- Writing a test that asserts the currently-observed string in a command's output, without checking whether the currently-observed form matches the routing skill's declared Output Contract — this codifies drift rather than catching it

## Related

- `.gzkit/rules/cli.md` — CLI contract doctrine (Heavy-lane trigger for subcommand changes)
- `.gzkit/rules/gate5-runbook-code-covenant.md` — runbook as first-class deliverable tracking code
- `.gzkit/rules/skill-surface-sync.md` — skill version discipline and mirror-drift prevention
- `AGENTS.md` section Attestation — precedent for requiring observed evidence over narrative reconstruction (the ARB receipt requirement is the same shape as the commit-message verification rule above)
- GHI #141 — status-adjacent CLI/skill routing drift, the audit that surfaced Invariants 1 and 2
- GHI #149 — Invariant 3 addition (this section), the forward-looking enforcement
- GHI #150 — one-time audit sweep of every skill's Output Contract claims against observed runtime output (`artifacts/audits/skill-output-contract-audit-2026-04-14.md`)
- GHI #151 — commit-message discipline for skill-routing changes (the section above), the reasoning-pathway prevention
- GHI #270 — fixture vs unit-test separation for Invariant 3 (resolves the collision between this rule and `.gzkit/rules/tests.md` section "Tests assert semantics, not strings")
- 2026-04-14 post-mortem (in session transcript) — root-cause analysis of the #141 follow-up drift that produced the three ameliorations above
