[object Object]

← back to Cli Printing Press

fix(cli): verify-mode HTTP-verb short-circuit + N1 envelope + doctor + read-only POST bypass (#1398)

75e4d1b9245ad8fd7b9684b5e75bc910a66d17d1 · 2026-05-19 17:12:32 -0500 · JustSpellJ

Closes plan 2026-05-13-001 (U1-U10), the N1 envelope follow-up surfaced in the petstore agent-readiness review, and the greptile P1 read-only-POST finding.

- **Transport-layer gate** (`client.do()`): mutating verbs (DELETE/POST/PUT/PATCH) under `PRINTING_PRESS_VERIFY=1` short-circuit with a synthetic `{"__pp_verify_synthetic__":true,"status":"noop","reason":"verify_short_circuit",...}` envelope. `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` opts back into the real wire for the verify pipeline's mock-mode subprocesses.
- **Read-only POST bypass** (greptile P1): `client.do()` is now a thin wrapper around `doInternal(..., readOnlyIntent bool)`; a new `doRead()` passes `readOnlyIntent=true`. New `PostQueryWithParams` / `PostQueryWithParamsAndHeaders` route through `doRead`. The endpoint template emits `PostQuery*` for `.IsReadOnly` POST commands so GraphQL queries, JSON-RPC reads, and POST-based search dial through the real transport even under verify mode.
- **N1 envelope propagation**: outer command envelope reports `verify_noop: true` and `success: false` when the synthetic sentinel is detected. Naive validators no longer read the noop as a real mutation.
- **Doctor diagnosis line** (U9): `<cli> doctor` surfaces verify state ("normal operation" / "ACTIVE — short-circuit" / "ACTIVE — live HTTP opt-in") so an operator inheriting `PRINTING_PRESS_VERIFY=1` detects the foot-gun without inspecting a response body.
- **Live-verifier env strip** (U10): `live_dogfood` and `workflow_verify` strip both verify env vars from subprocess env via `filterVerifyEnv` so they cannot inherit verify-mode short-circuiting.
- **Per-CLI emitted test** (U5): every regenerated CLI ships `internal/client/client_verify_short_circuit_test.go` covering all four gate states (mutating + verify, LIVE_HTTP opt-in, no env, GET control) plus a `_ReadOnlyPOST` sub-test asserting `doRead` dials through. A template edit that drops the gate fails downstream `go test` rather than silently re-opening the readiness gap.
- **Docs** (U6): AGENTS.md "Side-effect commands" section + `cliutil_verifyenv.go.tmpl` docstring document the transport-layer gate, the LIVE_HTTP opt-in, the `doRead` read-only bypass, the live-verifier strip, and the doctor diagnosis anchor.
- **Regen-merge fixture** (U7): pins `TEMPLATED-WITH-ADDITIONS` classification for the canonical scenario where an operator has hand-added a top-level helper to `client.go` and the fresh template introduces the short-circuit.
- **Rollout playbook** (U8): `docs/solutions/best-practices/verify-mode-short-circuit-rollout-2026-05-13.md` covers the tier:official-first sweep order and known foot-guns.

Verified live on the petstore smoke pipeline: `PRINTING_PRESS_VERIFY=1 petstore-pp-cli pet delete 42 --json` returns the synthetic envelope with no network call.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

Files touched

Diff

commit 75e4d1b9245ad8fd7b9684b5e75bc910a66d17d1
Author: JustSpellJ <lebowski81@gmail.com>
Date:   Tue May 19 17:12:32 2026 -0500

    fix(cli): verify-mode HTTP-verb short-circuit + N1 envelope + doctor + read-only POST bypass (#1398)
    
    Closes plan 2026-05-13-001 (U1-U10), the N1 envelope follow-up surfaced in the petstore agent-readiness review, and the greptile P1 read-only-POST finding.
    
    - **Transport-layer gate** (`client.do()`): mutating verbs (DELETE/POST/PUT/PATCH) under `PRINTING_PRESS_VERIFY=1` short-circuit with a synthetic `{"__pp_verify_synthetic__":true,"status":"noop","reason":"verify_short_circuit",...}` envelope. `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` opts back into the real wire for the verify pipeline's mock-mode subprocesses.
    - **Read-only POST bypass** (greptile P1): `client.do()` is now a thin wrapper around `doInternal(..., readOnlyIntent bool)`; a new `doRead()` passes `readOnlyIntent=true`. New `PostQueryWithParams` / `PostQueryWithParamsAndHeaders` route through `doRead`. The endpoint template emits `PostQuery*` for `.IsReadOnly` POST commands so GraphQL queries, JSON-RPC reads, and POST-based search dial through the real transport even under verify mode.
    - **N1 envelope propagation**: outer command envelope reports `verify_noop: true` and `success: false` when the synthetic sentinel is detected. Naive validators no longer read the noop as a real mutation.
    - **Doctor diagnosis line** (U9): `<cli> doctor` surfaces verify state ("normal operation" / "ACTIVE — short-circuit" / "ACTIVE — live HTTP opt-in") so an operator inheriting `PRINTING_PRESS_VERIFY=1` detects the foot-gun without inspecting a response body.
    - **Live-verifier env strip** (U10): `live_dogfood` and `workflow_verify` strip both verify env vars from subprocess env via `filterVerifyEnv` so they cannot inherit verify-mode short-circuiting.
    - **Per-CLI emitted test** (U5): every regenerated CLI ships `internal/client/client_verify_short_circuit_test.go` covering all four gate states (mutating + verify, LIVE_HTTP opt-in, no env, GET control) plus a `_ReadOnlyPOST` sub-test asserting `doRead` dials through. A template edit that drops the gate fails downstream `go test` rather than silently re-opening the readiness gap.
    - **Docs** (U6): AGENTS.md "Side-effect commands" section + `cliutil_verifyenv.go.tmpl` docstring document the transport-layer gate, the LIVE_HTTP opt-in, the `doRead` read-only bypass, the live-verifier strip, and the doctor diagnosis anchor.
    - **Regen-merge fixture** (U7): pins `TEMPLATED-WITH-ADDITIONS` classification for the canonical scenario where an operator has hand-added a top-level helper to `client.go` and the fresh template introduces the short-circuit.
    - **Rollout playbook** (U8): `docs/solutions/best-practices/verify-mode-short-circuit-rollout-2026-05-13.md` covers the tier:official-first sweep order and known foot-guns.
    
    Verified live on the petstore smoke pipeline: `PRINTING_PRESS_VERIFY=1 petstore-pp-cli pet delete 42 --json` returns the synthetic envelope with no network call.
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
---
 AGENTS.md                                          |   3 +
 ...verify-mode-short-circuit-mutating-http-plan.md | 731 +++++++++++++++++++++
 ...verify-mode-short-circuit-rollout-2026-05-13.md | 147 +++++
 internal/generator/client_invalidate_cache_test.go |  49 +-
 .../client_verify_short_circuit_emit_test.go       |  76 +++
 .../generator/client_verify_short_circuit_test.go  | 102 +++
 internal/generator/cliutil_verifyenv_test.go       |  72 ++
 internal/generator/doctor_verify_mode_test.go      |  63 ++
 internal/generator/endpoint_verify_noop_test.go    |  83 +++
 internal/generator/generator.go                    |  77 +--
 internal/generator/generator_test.go               |   7 +-
 internal/generator/templates/client.go.tmpl        |  84 +++
 .../client_verify_short_circuit_test.go.tmpl       | 168 +++++
 .../generator/templates/cliutil_verifyenv.go.tmpl  |  38 ++
 .../generator/templates/command_endpoint.go.tmpl   |  45 +-
 internal/generator/templates/doctor.go.tmpl        |  17 +
 internal/narrativecheck/narrativecheck.go          |   7 +-
 internal/pipeline/live_dogfood.go                  |   5 +
 internal/pipeline/regenmerge/classify_test.go      |  50 ++
 .../testdata/verify-short-circuit/fresh/go.mod     |   3 +
 .../fresh/internal/client/client.go                |  42 ++
 .../testdata/verify-short-circuit/published/go.mod |   3 +
 .../published/internal/client/client.go            |  25 +
 internal/pipeline/runtime.go                       |   9 +
 internal/pipeline/verify_env_filter.go             |  38 ++
 internal/pipeline/verify_env_filter_test.go        |  74 +++
 internal/pipeline/workflow_verify.go               |   4 +
 scripts/golden.sh                                  |  23 +-
 scripts/golden_normalize_windows.py                |  81 +++
 .../internal/client/client.go                      |  84 +++
 .../internal/cli/doctor.go                         |  17 +
 .../printing-press-golden/internal/cli/doctor.go   |  17 +
 .../internal/cli/projects_avatar_upload-project.go |  37 +-
 .../internal/cli/projects_create.go                |  37 +-
 .../internal/cli/projects_tasks_update-project.go  |  37 +-
 .../internal/client/client.go                      |  84 +++
 .../internal/cliutil/verifyenv.go                  |  38 ++
 .../internal/cli/things_get.go                     |  39 +-
 .../internal/cli/things_list.go                    |  39 +-
 .../internal/cli/stores_create.go                  |  37 +-
 .../tier-routing-golden/internal/cli/doctor.go     |  17 +
 .../tier-routing-golden/internal/client/client.go  |  84 +++
 42 files changed, 2560 insertions(+), 133 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 92ec219e..e5368586 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -41,6 +41,9 @@ Hand-written novel commands that perform visible actions (open browser tabs, sen
 1. Print by default; require explicit opt-in (`--launch`, `--send`, `--play`, etc.) to actually act.
 2. Short-circuit when `cliutil.IsVerifyEnv()` is true. The verifier sets `PRINTING_PRESS_VERIFY=1` in every mock-mode subprocess; this env-var check is the floor that catches any side-effect command the verifier's heuristic classifier misses.
 
+Generated endpoint-mirror commands also gate mutating HTTP verbs (DELETE/POST/PUT/PATCH) at the transport layer in `internal/client/client.go`. Under `PRINTING_PRESS_VERIFY=1` such requests short-circuit with a synthetic `{"__pp_verify_synthetic__":true,"status":"noop","reason":"verify_short_circuit",...}` envelope and never dial. Read-only operations that ride a mutating verb on the wire (GraphQL queries, JSON-RPC reads, POST-based search; codegen-marked `mcp:read-only`) route through `doRead()` and bypass the gate, so verify-mode does not silently break reads on shared-endpoint APIs. The outer command envelope reports `verify_noop: true` and `success: false` so naive validators do not read the noop as a real mutation. `printing-press verify` opts its mock-mode subprocesses back in via `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` so the httptest mock server still receives mutating requests; agents, narrative full-example runs, and ad-hoc operators leave `LIVE_HTTP` unset so mutating requests no-op. Live verifiers (`live_dogfood`, `workflow_verify`) strip both env vars from subprocess env so they cannot inherit verify-mode short-circuiting. `<cli> doctor` surfaces the active verify state as a defense-in-depth diagnosis anchor.
+
+
 ### Long-running commands under live-dogfood
 Hand-written novel commands whose happy path is an expensive network operation (full sync loops, content crawlers, bulk archive walks) MUST curtail work when `cliutil.IsDogfoodEnv()` returns true. The `printing-press dogfood --live` runner sets `PRINTING_PRESS_DOGFOOD=1` in every subprocess and applies a flat 30s per-command timeout; without a short-circuit, the happy-path test trips the timeout and the matrix verdict flips to FAIL even when the command itself is healthy. Unlike `IsVerifyEnv`, this does NOT mean "don't hit the network" — dogfood is a real-API matrix. Use it to bound work (paginate once, fetch a bounded sample, honor a smaller `--limit` default), never to substitute mock data for real calls.
 
diff --git a/docs/plans/2026-05-13-001-fix-verify-mode-short-circuit-mutating-http-plan.md b/docs/plans/2026-05-13-001-fix-verify-mode-short-circuit-mutating-http-plan.md
new file mode 100644
index 00000000..b953a27d
--- /dev/null
+++ b/docs/plans/2026-05-13-001-fix-verify-mode-short-circuit-mutating-http-plan.md
@@ -0,0 +1,731 @@
+---
+title: "fix(cli): gate mutating HTTP verbs on PRINTING_PRESS_VERIFY in generated clients"
+type: fix
+status: active
+date: 2026-05-13
+deepened: 2026-05-13
+depth: deep
+---
+
+# Plan: Verify-Mode Short-Circuit on Mutating HTTP Verbs
+
+## Summary
+
+Add a verb-gated short-circuit in `internal/generator/templates/client.go.tmpl` so generated CLIs return a synthetic `{"status":"noop","reason":"verify_short_circuit",...}` envelope for DELETE/POST/PUT/PATCH whenever `PRINTING_PRESS_VERIFY=1` is set, unless a new `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` opt-in is also set. `pipeline.RunVerify` sets both envs in mock mode so its existing httptest flow keeps exercising the real wire path; every other consumer (agents, narrative verification, ad-hoc operators) gets a safe no-op.
+
+---
+
+## Problem Frame
+
+Agent-readiness review of the matrix-22cc2fd9 smoke pipeline (`.runstate/matrix-22cc2fd9/runs/20260513T220622Z-49578526/pipeline/agent-readiness.md`, finding B1) confirmed that `PRINTING_PRESS_VERIFY=1 ./petstore-pp-cli.exe pet delete 42` still attempts a real network call. Only handwritten novel commands (`auth setup --launch`) honor `cliutil.IsVerifyEnv()` today; generated endpoint-mirror handlers route through `Client.do()` (`internal/generator/templates/client.go.tmpl` around line 713), which has no verify-mode gate.
+
+The AGENTS.md "Side-effect commands" rule (line 39) names this as the floor: defense-in-depth that catches anything the verifier's heuristic classifier misses. The cliutil docstring scopes it narrowly to "open browser tabs, send notifications, dial out to OS handlers" — that scoping is the source of ambiguity. Mutating HTTP verbs are arguably the canonical visible action on a remote system, and any consumer applying AGENTS.md strictly (agent-readiness reviewers, security audits, downstream skill authors) will flag the gap.
+
+This plan extends the rule down to the transport layer so the contract is unambiguous: under verify mode, generated CLIs do not issue mutating HTTP verbs (DELETE/POST/PUT/PATCH) unless the verifier explicitly opts in. Non-HTTP mutation surfaces (local store writes, file outputs, future RPC) remain governed by their existing rules — see Scope Boundaries for the explicit non-goals.
+
+---
+
+## Requirements
+
+| R-ID | Requirement |
+|---|---|
+| R1 | Generated CLIs short-circuit DELETE/POST/PUT/PATCH when `PRINTING_PRESS_VERIFY=1` is set and `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` is not set. Short-circuit returns a synthetic structured envelope; no network call is issued. |
+| R2 | `printing-press verify` (mock mode) continues to validate request paths, headers, dry-run output, exit codes, and command surface at 100% pass rate against the httptest server. The mock server still receives mutating requests because verify opts back in via `PRINTING_PRESS_VERIFY_LIVE_HTTP=1`. |
+| R3 | `printing-press dogfood --live`, `scorecard --live-check`, and any other live verification path must NOT short-circuit. These paths do not set `PRINTING_PRESS_VERIFY=1`. |
+| R4 | The `cliutil.IsVerifyEnv()` contract (existing) is unchanged. The new `cliutil.IsVerifyLiveHTTPEnv()` helper is added in the generator-reserved namespace. |
+| R5 | AGENTS.md "Side-effect commands" section and the `cliutil_verifyenv.go.tmpl` docstring are updated to reflect that the transport-layer gate is part of the rule (pointer-rot rule). |
+| R6 | Goldens regenerate cleanly; the diff is intentional and explained. |
+| R7 | An emitted printed-CLI test (template) verifies the short-circuit in every regenerated CLI's own test suite. |
+| R8 | A generator-level test pins the template contract so a future template edit cannot silently regress the short-circuit. |
+| R9 | regen-merge classifies the new template block correctly for users with hand-edited `client.go`; no TEMPLATED-VALUE-DRIFT confusion that silently drops hand-edits. |
+| R10 | Every published library CLI receives the short-circuit gate via a documented, reproducible regen-merge path. (U8 captures the HOW; this R-ID captures the WHAT — that the rollout actually reaches every CLI.) |
+
+---
+
+## Key Technical Decisions
+
+### KTD1. Resolution choice: verb-gated short-circuit + opt-out env (Option B)
+
+The brief listed four candidates. Decision: **B** (verb-gated short-circuit with `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` opt-out).
+
+| Option | Why rejected (or chosen) |
+|---|---|
+| A — base-URL heuristic ("short-circuit only when BaseURL doesn't look like localhost httptest") | Fragile. APIs with legitimate localhost endpoints exist; verify's httptest URL shape isn't a public contract. Leaks an implementation detail of verify (mock-server URL pattern) into every generated client. Reject. |
+| **B — verb-gated + explicit opt-in env** (chosen) | Smallest blast radius (one template change + one runtime line). Clearest semantics: "mutating verbs are special in verify mode; verify itself opts in to dial." Mirrors the [dry-run-default-for-mutator-probes-in-test-harnesses](../solutions/design-patterns/dry-run-default-for-mutator-probes-in-test-harnesses-2026-05-05.md) two-pronged gate pattern. Easy to test (set both envs). Composes cleanly with `--dry-run`, with the existing client_credentials short-circuit, and with future verify-mode escape valves. |
+| C — handler-level short-circuit | High blast radius — every endpoint handler template gets a check. More verbose generated code per CLI. Doesn't compose with the existing client-level mutation discriminator (the cache-invalidation block at `client.go.tmpl`). Reject. |
+| D — tighten AGENTS.md only (no code change) | Resolves the rule-interpretation ambiguity but leaves the actual readiness gap unsolved. Any external auditor applying the rule strictly still flags the same thing. Doesn't compound (per AGENTS.md "Default to machine changes"). Reject. |
+
+### KTD2. New helper in `cliutil`, not inline
+
+Add `cliutil.IsVerifyLiveHTTPEnv()` symmetric with `IsVerifyEnv()`. AGENTS.md "Generator-reserved namespaces" mandates `cliutil` for cross-template helpers, and centralizing the env-var literal in one place avoids drift if the name ever changes. The new helper lives in `cliutil_verifyenv.go.tmpl`.
+
+**Asymmetric semantics, intentionally.** `IsVerifyLiveHTTPEnv()` in isolation has no behavioral meaning — the gate condition only checks it when `IsVerifyEnv()` is also true. `LIVE_HTTP=1` alone (with `VERIFY` unset) produces normal operation, not a sandbox. This asymmetry is intentional: `LIVE_HTTP` is the opt-out from the verify-mode gate, not an independent activation. Documenting it here so an external consumer who sees `LIVE_HTTP=1` in an environment does not infer sandbox semantics.
+
+### KTD3. Synthetic envelope shape
+
+Return a structured noop envelope, not an empty `{}` or raw 2xx. The envelope leads with a namespace-reserved boolean sentinel that no real API would emit, so downstream consumers (`validate-narrative --full-examples`, agent inspections, future verify-mode assertions) key on one obvious field instead of trying to interpret common low-entropy literals like `status:"noop"` (which legitimate APIs sometimes emit — Stripe idempotency replays, Kubernetes NoChange, queue-dedup APIs):
+
+```json
+{
+  "__pp_verify_synthetic__": true,
+  "status": "noop",
+  "reason": "verify_short_circuit",
+  "method": "DELETE",
+  "path": "/pet/42"
+}
+```
+
+The double-underscore prefix is a reserved-namespace convention — `__pp_*` belongs to Printing Press and no upstream API spec is expected to use it. Downstream consumers should key on `__pp_verify_synthetic__ == true`. The remaining fields (`status`, `reason`, `method`, `path`) are diagnostic prose, useful for human / agent inspection but not the primary classifier. Borrowing the envelope-around-no-op shape from [generated-cli-idempotent-noops-and-export-validation](../solutions/logic-errors/generated-cli-idempotent-noops-and-export-validation-2026-05-05.md). Status code returned alongside the body: 200 (a synthetic 204 for DELETE would force callers to special-case empty-body parsing).
+
+**Outer envelope wrapping note.** The generated endpoint handler in `internal/generator/templates/command_endpoint.go.tmpl` (around lines 650-707) wraps this synthetic body as the `data` field inside its own `{action, resource, path, status:200, success:true, data:{...}}` presentation envelope. The outer envelope reports `success:true`. Operator diagnosis under short-circuit therefore keys on the **inner** `reason:"verify_short_circuit"` literal, not on the outer success flag. This is an accepted tradeoff (the inner envelope is the diagnostic anchor) — but it informs the U9 `doctor` line that surfaces verify-env state explicitly so operators don't have to read a response body to detect they're in verify mode.
+
+### KTD4. Short-circuit location: collocated with existing mutation discriminator in `Client.do()`
+
+The cache-invalidation block already gates on `method != http.MethodGet && !c.DryRun` (per [http-client-cache-invalidate-on-mutation](../solutions/design-patterns/http-client-cache-invalidate-on-mutation-2026-05-05.md)). Add the short-circuit as a sibling check at the top of `do()` so all mutation-aware logic lives in one place. The short-circuit fires BEFORE the request is constructed, so no URL building, no auth header minting, no cache key generation runs.
+
+**Explicit cache-state guarantee.** Because the short-circuit returns BEFORE the success branch in `do()` reaches the `invalidateCache()` call (`client.go.tmpl` around line 996), the cache is **correctly NOT invalidated** under short-circuit. Cache state stays consistent with the un-mutated remote: a subsequent GET sees the still-present resource. The retry middleware (around lines 850-1040) sits below the short-circuit too, so no retry fires on the synthetic envelope. Inline code comment in U2's Approach makes this explicit so a future reader doesn't try to "fix" the missing cache call.
+
+### KTD5. MCP tool annotations unchanged
+
+`destructiveHint: true` on a DELETE tool stays true. Per [mcp-sql-search-readonly-bypass](../solutions/security-issues/mcp-sql-search-readonly-bypass-2026-05-08.md), wrong annotations are worse than missing ones. The short-circuit changes runtime dial-out, not the tool's advertised semantics — when `LIVE_HTTP=1` it really is destructive.
+
+### KTD6. AGENTS.md update lands in the same PR (pointer-rot rule)
+
+The AGENTS.md "Side-effect commands" inline trigger sentence at line 42 will be updated to add the transport-layer gate. The `cliutil_verifyenv.go.tmpl` docstring widens to include "mutating HTTP verbs."
+
+### KTD7. Public library rollout is a tier-prioritized per-CLI sweep
+
+There is no single library-wide regen command (`regen-merge` is per-CLI). Per [cross-repo-coordination-with-printing-press-library](../solutions/best-practices/cross-repo-coordination-with-printing-press-library-2026-05-06.md), coordinate landing order: this repo's change first, then a sweep PR in `printing-press-library` that regens tier:official CLIs first, then tier:community.
+
+---
+
+## High-Level Technical Design
+
+The change touches three surfaces. Directional sketch — not implementation specification.
+
+```mermaid
+flowchart TD
+    A[Agent or operator invokes] -->|PRINTING_PRESS_VERIFY=1| B{Client.do method?}
+    B -->|GET| C[Real request path<br/>unchanged]
+    B -->|DELETE/POST/PUT/PATCH| D{LIVE_HTTP=1 set?}
+    D -->|Yes - verify's mock-mode opt-in| E[Real request path<br/>hits httptest]
+    D -->|No - default safe| F[Short-circuit:<br/>synthetic noop envelope]
+    F --> G[Return 200 +<br/>{status:noop, reason:verify_short_circuit}]
+    E --> H[Real do path:<br/>buildURL, authHeader, retry, cache]
+    C --> H
+```
+
+Behavior matrix (inputs to `Client.do()`):
+
+| `PRINTING_PRESS_VERIFY` | `PRINTING_PRESS_VERIFY_LIVE_HTTP` | Verb | Behavior |
+|---|---|---|---|
+| unset | unset | (any) | normal — real request |
+| unset | `1` | (any) | normal — real request (LIVE_HTTP alone has no behavioral effect; sandbox semantics require BOTH vars) |
+| `1` | unset | GET | normal — real request |
+| `1` | unset | DELETE/POST/PUT/PATCH | **short-circuit** — synthetic envelope |
+| `1` | `1` | (any) | normal — real request (verify mock-mode path) |
+
+Sketch of the short-circuit shape (directional — not implementation):
+
+```go
+// Inside Client.do(), as the first block after parameter normalization:
+if isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+    // Return synthetic envelope; do not dial, do not mint auth, do not touch cache.
+    return verifyShortCircuitEnvelope(method, path), http.StatusOK, nil
+}
+```
+
+`isMutatingVerb` is a small helper in `client.go.tmpl`'s emitted code (one-line switch over the four verbs). `verifyShortCircuitEnvelope` returns a `json.RawMessage` shaped per KTD3. Both helpers can be local to `client.go` to keep `cliutil` minimal.
+
+---
+
+## Implementation Units
+
+### U1. Add `cliutil.IsVerifyLiveHTTPEnv()` helper
+
+**Goal:** Introduce the new env-var literal in the generator-reserved namespace, symmetric with `IsVerifyEnv()`.
+
+**Requirements:** R4
+
+**Dependencies:** none
+
+**Files:**
+- `internal/generator/templates/cliutil_verifyenv.go.tmpl` (modify — add constant + function; widen docstring)
+- `internal/generator/cliutil_verifyenv_test.go` (modify or create — unit test for the new helper)
+
+**Approach:**
+- Add `VerifyLiveHTTPEnvVar = "PRINTING_PRESS_VERIFY_LIVE_HTTP"` constant.
+- Add `IsVerifyLiveHTTPEnv() bool` returning `os.Getenv(VerifyLiveHTTPEnvVar) == "1"`.
+- Widen the file-level docstring to mention "mutating HTTP verbs in generated clients" as a use case for the verify-env contract. The body of `IsVerifyEnv()`'s docstring stays unchanged.
+
+**Patterns to follow:** existing `IsVerifyEnv()` shape in the same file.
+
+**Test scenarios:**
+- Helper returns true when env is exactly `"1"`.
+- Helper returns false when env is unset, empty, `"0"`, `"true"`, or any other non-`"1"` value.
+- Asymmetry pin: when `PRINTING_PRESS_VERIFY` is unset but `PRINTING_PRESS_VERIFY_LIVE_HTTP=1`, the gate condition (`isMutatingVerb && IsVerifyEnv() && !IsVerifyLiveHTTPEnv()`) evaluates false; no short-circuit fires for any verb. (Asserts the LIVE_HTTP-alone case produces normal operation, per the KTD2 asymmetric-semantics note.)
+
+**Verification:** `go test ./internal/generator/...` for the new unit test; existing tests still pass.
+
+---
+
+### U2. Add verb-gated short-circuit in `Client.do()`
+
+**Goal:** Generated CLIs short-circuit mutating verbs under verify mode.
+
+**Requirements:** R1, R3, R4
+
+**Dependencies:** U1
+
+**Files:**
+- `internal/generator/templates/client.go.tmpl` (modify — add short-circuit block at top of `do()`, plus `isMutatingVerb` and `verifyShortCircuitEnvelope` helpers)
+- `internal/generator/templates/client_test.go.tmpl` (modify — emitted test, see U5)
+
+**Approach:**
+- At the top of `do()` (before EndpointTemplateVars resolution), check `isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv()`. When true, build and return the synthetic envelope.
+- `isMutatingVerb`: switch over `"DELETE","POST","PUT","PATCH"`. Case-sensitive (`Client.do` already receives uppercase methods from the generated wrappers).
+- `verifyShortCircuitEnvelope(method, path string) json.RawMessage`: marshal the `{"status":"noop","reason":"verify_short_circuit","method":<METHOD>,"path":<PATH>}` shape. Return value documented via a code comment as obviously synthetic.
+- Both helpers live in the template's package-level region (file body) alongside other small utilities.
+- Place the check BEFORE auth header minting so the existing `authHeader()` client_credentials short-circuit doesn't fire unnecessarily during verify-mode mutations.
+- Place the check BEFORE the success-branch cache-invalidation block at `client.go.tmpl:~996`. Add an inline code comment at the short-circuit: `// No cache invalidation — no remote state changed.` Code-as-documentation per AGENTS.md hygiene rules; prevents a future reader from "fixing" the missing `invalidateCache()` call.
+
+**Execution note:** Test-first for the short-circuit branch. Add the generator test (U4) before the template change so the failing-test signal is recorded before the green pass.
+
+**Technical design** (directional):
+
+```go
+// Helper, file-scoped:
+func isMutatingVerb(method string) bool {
+    switch method {
+    case "DELETE", "POST", "PUT", "PATCH":
+        return true
+    }
+    return false
+}
+
+// Inside Client.do, as the first non-trivial check:
+if isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+    body := verifyShortCircuitEnvelope(method, path)
+    return body, http.StatusOK, nil
+}
+```
+
+**Patterns to follow:**
+- The existing client_credentials short-circuit at `client.go.tmpl` line 1307 (inside `authHeader()`) — synthetic-value-injection precedent.
+- The cache-invalidation `method != http.MethodGet && !c.DryRun` block — collocate the new mutation-aware check nearby.
+
+**Test scenarios:**
+- *Covers R1.* `PRINTING_PRESS_VERIFY=1`, no `LIVE_HTTP`, method `DELETE`: returns synthetic envelope with `__pp_verify_synthetic__: true`, `status:"noop"`, `reason:"verify_short_circuit"`, and the request method/path echoed back; no HTTP request is issued (verify via injected mock transport).
+- *Covers R1.* Same shape for `POST`, `PUT`, `PATCH`.
+- `PRINTING_PRESS_VERIFY=1`, no `LIVE_HTTP`, method `GET`: real request path is exercised; no short-circuit.
+- `PRINTING_PRESS_VERIFY=1` AND `PRINTING_PRESS_VERIFY_LIVE_HTTP=1`, method `DELETE`: real request path is exercised (the verify mock-mode contract).
+- `PRINTING_PRESS_VERIFY` unset, method `DELETE`: real request path is exercised (the operator's live path).
+- Envelope is valid JSON with the five expected keys (`__pp_verify_synthetic__`, `status`, `reason`, `method`, `path`) and the exact literal values for the sentinel (`true`) and `reason` (`"verify_short_circuit"`).
+- Short-circuit returns before `authHeader()` runs (assert via side-channel: a deliberately broken auth config does NOT produce an auth error in short-circuit mode).
+
+**Verification:** Generator-level tests pass; emitted-CLI tests (U5) pass; goldens regenerate with the expected diff (see Verification Strategy).
+
+---
+
+### U3. Make `pipeline.RunVerify` set the live-HTTP opt-in
+
+**Goal:** U3 sets `LIVE_HTTP=1` in mock mode; `narrativecheck.go:238` does the same parallel to U3. Verify mock-mode and narrative full-example runners both keep exercising the real wire path.
+
+**Requirements:** R2
+
+**Dependencies:** U1
+
+**Files:**
+- `internal/pipeline/runtime.go` (modify — add `env = append(env, "PRINTING_PRESS_VERIFY_LIVE_HTTP=1")` in `buildEnv()` next to line 265)
+- `internal/pipeline/runtime_test.go` (modify — test that mock-mode env contains both verify vars)
+- `internal/narrativecheck/narrativecheck.go` (modify — line 238: append `LIVE_HTTP=1` alongside the existing `PRINTING_PRESS_VERIFY=1`)
+
+**Approach:**
+- Inside the `if report.Mode == "mock"` branch of `buildEnv()`, after the existing `PRINTING_PRESS_VERIFY=1` append, add the `LIVE_HTTP=1` append. Add a comment explaining why: verify owns the mock httptest server and needs the real wire path to assert against.
+- Parallel one-line change in `narrativecheck.go:238`: the runner currently does `cmd.Env = append(os.Environ(), "PRINTING_PRESS_VERIFY=1")`; append `"PRINTING_PRESS_VERIFY_LIVE_HTTP=1"` alongside so narrative full-example subprocesses also continue to hit the real wire path. Without this, every narrative recipe invoking a mutating verb receives the synthetic envelope and any body-shape assertion silently breaks.
+- All three changes are one-line additions with one-line comments. The rest of the surrounding logic is unchanged.
+
+**Patterns to follow:** existing `env = append(env, ...)` calls at runtime.go lines 246-266; the parallel `cmd.Env = append(os.Environ(), "PRINTING_PRESS_VERIFY=1")` shape at narrativecheck.go:238.
+
+**Test scenarios:**
+- *Covers R2.* `pipeline.RunVerify` mock-mode subprocess env contains both `PRINTING_PRESS_VERIFY=1` and `PRINTING_PRESS_VERIFY_LIVE_HTTP=1`.
+- *Covers R3.* `pipeline.RunVerify` live-mode subprocess env contains NEITHER var.
+- *Covers R2.* `narrativecheck` full-example subprocess env contains both `PRINTING_PRESS_VERIFY=1` and `PRINTING_PRESS_VERIFY_LIVE_HTTP=1`.
+- **Credential-substitution integration leg:** with `pipeline.RunVerify` in mock mode and the CLI's auth config seeded with a real-looking bearer token (e.g., `production-token-do-not-leak`), the httptest mock server records that the incoming `Authorization` header is `Bearer mock-token-for-testing`, NOT the production literal. Pins the existing `buildEnv` substitution contract at the integration boundary so a future refactor of mock-mode env construction can't silently leak credentials into mock-server logs.
+- End-to-end: running the existing `TestRunVerify_MockMode_AllPassPercent` (or equivalent) against a CLI built from the updated template still produces 100% pass rate. (This guards against accidentally breaking the mock-mode contract.)
+
+**Verification:** `go test ./internal/pipeline/... ./internal/narrativecheck/...` passes; manual run of `printing-press verify --dir <smoke> --spec <spec> --fix --json` still reports `mode=mock` and 100% pass rate; manual run of `printing-press validate-narrative --full-examples` still passes against existing narrative fixtures.
+
+---
+
+### U4. Generator-level test pinning the template contract
+
+**Goal:** Future edits to `client.go.tmpl` cannot silently regress the short-circuit.
+
+**Requirements:** R8
+
+**Dependencies:** U2
+
+**Files:**
+- `internal/generator/client_verify_short_circuit_test.go` (create)
+
+**Approach:**
+- Test renders `client.go.tmpl` against a small fixture APISpec and asserts the emitted Go source contains:
+  - The `isMutatingVerb` helper.
+  - The short-circuit check using both `cliutil.IsVerifyEnv()` and `cliutil.IsVerifyLiveHTTPEnv()`.
+  - The synthetic envelope construction.
+- Plain text-content assertions (no need to compile or execute the emitted code; that's covered by U5 and the goldens).
+
+**Patterns to follow:** existing generator tests in `internal/generator/*_test.go` that assert on template output.
+
+**Test scenarios:**
+- *Covers R8.* Emitted `client.go` contains `isMutatingVerb` definition.
+- Emitted `client.go` contains both env-var helper calls in the `do()` body.
+- Emitted `client.go` contains the literal `"verify_short_circuit"` reason string and the `__pp_verify_synthetic__` sentinel field name.
+- Test fails if the short-circuit block is deleted or any of the three conditions is removed.
+
+**Verification:** `go test ./internal/generator/ -run TestClient_VerifyShortCircuit` passes.
+
+---
+
+### U5. Emitted printed-CLI test (template)
+
+**Goal:** Every regenerated CLI's own test suite verifies the short-circuit in its own client.
+
+**Requirements:** R7
+
+**Dependencies:** U2
+
+**Files:**
+- `internal/generator/templates/client_verify_short_circuit_test.go.tmpl` (create — emits into `<cli>/internal/client/client_verify_short_circuit_test.go`)
+- `internal/generator/generator.go` (modify — register in the `singleFiles` map at line ~1417, adjacent to `client_test.go.tmpl`. Do NOT register in the `mcpFiles` map at line ~1683; this test is part of the printed CLI's client package, not the MCP surface.)
+
+**Approach:**
+- The emitted test injects a recording-mock `http.RoundTripper` into the `Client`, calls `client.do("DELETE", "/test", nil, nil, nil)` with `PRINTING_PRESS_VERIFY=1` set via `t.Setenv`, and asserts:
+  - The RoundTripper was never called (no network attempt).
+  - The returned `json.RawMessage` parses to a struct with `__pp_verify_synthetic__: true`, `status:"noop"`, and `reason:"verify_short_circuit"`.
+- Second subtest with `t.Setenv("PRINTING_PRESS_VERIFY_LIVE_HTTP", "1")`: assert the RoundTripper WAS called (verify mock-mode path).
+- Third subtest with no env: assert real path.
+
+**Patterns to follow:** existing emitted test templates (e.g., `internal/generator/templates/mcp_tools_test.go.tmpl` — same shape, same emission flow).
+
+**Test scenarios** (the emitted test, not this template's own test):
+- *Covers R1.* DELETE under verify-only → no network call, synthetic envelope returned.
+- *Covers R2.* DELETE under verify + LIVE_HTTP → network call attempted.
+- DELETE with no env → network call attempted.
+- GET under verify-only → network call attempted (control case proving the gate is verb-specific).
+
+**Verification:** After regen, every printed CLI's `go test ./internal/client/...` includes and passes the new test.
+
+---
+
+### U6. Update AGENTS.md and `cliutil_verifyenv.go.tmpl` docstring
+
+**Goal:** Documentation matches the new transport-layer gate (pointer-rot rule).
+
+**Requirements:** R5
+
+**Dependencies:** U1, U2
+
+**Files:**
+- `AGENTS.md` (modify — "Side-effect commands" section, inline trigger sentence around line 42)
+- `internal/generator/templates/cliutil_verifyenv.go.tmpl` (modify — widen file-level docstring; partially done in U1)
+
+**Approach:**
+- AGENTS.md: extend the side-effect-commands bullet list to explicitly mention "mutating HTTP verbs in generated endpoint-mirror commands are gated at the transport layer; `printing-press verify` opts back in via `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` for its mock-mode httptest flow."
+- Docstring widening: add a paragraph naming HTTP DELETE/POST/PUT/PATCH as in-scope for the short-circuit, with a forward reference to `IsVerifyLiveHTTPEnv()` for the verifier opt-in.
+- Both files keep their existing prose; the changes are additive.
+
+**Execution note:** Land docs in the same commit as U1+U2+U3 (per AGENTS.md pointer-rot rule).
+
+**Test scenarios:** None (docs change). Verification is by review.
+
+**Verification:** A reviewer reading AGENTS.md "Side-effect commands" understands that mutating HTTP verbs are gated at the transport layer without having to read the template source.
+
+---
+
+### U7. regen-merge classification fixture for the new template block
+
+**Goal:** Users with hand-edited `client.go` don't get TEMPLATED-VALUE-DRIFT confusion when they `--force` regen.
+
+**Requirements:** R9
+
+**Dependencies:** U2
+
+**Files:**
+- `internal/pipeline/regenmerge/testdata/verify_short_circuit_fixture/published_client.go` (create — fixture with hand-edits AROUND the do() function but not in it)
+- `internal/pipeline/regenmerge/testdata/verify_short_circuit_fixture/fresh_client.go` (create — fresh template output with the new short-circuit block)
+- `internal/pipeline/regenmerge/classify_test.go` (modify — add test case using the new fixture)
+
+**Approach:**
+- Fixture mimics a real-world hand-edit: e.g., a user added a custom header in `do()` between the URL building and the request dispatch. The regen-merge classifier should preserve that hand-edit and ALSO apply the new short-circuit block.
+- The test asserts the classifier verdict is `TEMPLATED-WITH-ADDITIONS` (preserved-and-merged), not `TEMPLATED-VALUE-DRIFT` (which forces manual review).
+- If the verdict comes out wrong, the fix isn't this plan's job — open a follow-up retro for the classifier. But the fixture must exist so future regressions are caught.
+
+**Patterns to follow:** existing regenmerge fixtures under `internal/pipeline/regenmerge/testdata/`.
+
+**Test scenarios:**
+- *Covers R9.* Hand-edited `do()` + fresh template with short-circuit → classifier returns TEMPLATED-WITH-ADDITIONS, merged file contains both the hand-edit and the new short-circuit.
+- Unmodified published `do()` + fresh template → classifier returns TEMPLATED, clean overwrite.
+
+**Verification:** `go test ./internal/pipeline/regenmerge/...` passes.
+
+---
+
+### U8. Public library regen-sweep playbook
+
+**Goal:** A documented, tier-prioritized rollout path so every published CLI gets the short-circuit.
+
+**Requirements:** R10
+
+**Dependencies:** U1–U7 and U9 merged in this repo
+
+**Files:**
+- `docs/solutions/best-practices/verify-mode-short-circuit-rollout-2026-05-13.md` (create — playbook)
+- `scripts/library-regen-verify-mode-rollout.sh` (optional — helper script that loops `regen-merge --apply` across the public library's local checkout, tier-prioritized)
+
+**Approach:**
+- Playbook documents the order: tier:official CLIs first (smaller blast radius, higher review attention), tier:community after. For each CLI: clone or pull, run `printing-press regen-merge <cli-dir> --fresh <tmp-fresh> --apply`, run the CLI's `go test ./...`, commit, batch-PR.
+- Script is optional sugar: parses `registry.json` for tier, loops by tier with a pause between CLIs. Skips on test failure, prints a summary at the end.
+- Playbook calls out the macOS/Linux limitation of `regen-merge` (Windows is not supported per its --help) and the implication that the Windows-based author of this plan will need a macOS/Linux box (or VM/WSL) to run the sweep.
+- Playbook also calls out the latent risk that `live_dogfood` and `workflow_verify` inherit parent env: operators must NOT have `PRINTING_PRESS_VERIFY=1` set in their shell when running those.
+
+**Patterns to follow:** existing playbooks under `docs/solutions/best-practices/`, especially `cross-repo-coordination-with-printing-press-library-2026-05-06.md`.
+
+**Test scenarios:** None for the playbook (prose). The optional script gets a smoke test against a 2-CLI fixture if it ships.
+
+**Verification:** A reviewer reading the playbook can execute the sweep without back-channel questions.
+
+---
+
+### U10. Strip verify-env vars from live-verifier subprocess env
+
+**Goal:** `pipeline.RunLiveDogfood` and `pipeline.RunWorkflowVerification` (and any other near-destructive runner) must NOT honor a verify-mode short-circuit inherited from the caller's shell. The destructive-side silent-success path closes.
+
+**Requirements:** R3
+
+**Dependencies:** U1
+
+**Files:**
+- `internal/pipeline/live_dogfood.go` (modify — at line 845, assign `cmd.Env` to a filtered copy of `os.Environ()` that drops `PRINTING_PRESS_VERIFY=*` and `PRINTING_PRESS_VERIFY_LIVE_HTTP=*` entries)
+- `internal/pipeline/workflow_verify.go` (modify — at line 147, same filtered env assignment)
+- `internal/pipeline/live_dogfood_test.go` (modify or add — assert subprocess env does NOT contain verify vars even when parent process has them set via `t.Setenv`)
+- `internal/pipeline/workflow_verify_test.go` (modify or add — same assertion)
+
+**Approach:**
+- Add a small filter helper (e.g., `filterVerifyEnv(env []string) []string`) co-located with the runners or in an internal helper file. The filter drops any `key=value` entry whose key matches the two verify env vars.
+- At each `exec.CommandContext` site, set `cmd.Env = filterVerifyEnv(os.Environ())`. Inline code comment explains why: these runners exercise real / near-real destructive behavior and must not honor verify-mode short-circuits inherited from the caller's shell.
+
+**Patterns to follow:** existing `cmd.Env = ...` patterns elsewhere in `internal/pipeline/*.go`; the env-filtering shape parallels `buildEnv` in runtime.go (which adds env vars; this helper drops them).
+
+**Test scenarios:**
+- *Covers R3.* `t.Setenv("PRINTING_PRESS_VERIFY", "1")` followed by invoking the runner: subprocess env (captured via a recording exec.Cmd substitute or via a process that re-emits its own env) does NOT contain `PRINTING_PRESS_VERIFY` or `PRINTING_PRESS_VERIFY_LIVE_HTTP`.
+- Same scenario for `PRINTING_PRESS_VERIFY_LIVE_HTTP`.
+- All non-verify env vars survive the filter (`PATH`, `HOME`, API tokens, etc.).
+
+**Verification:** `go test ./internal/pipeline/...` passes; manual smoke: `PRINTING_PRESS_VERIFY=1 printing-press dogfood --dir <smoke> --live --allow-destructive` actually performs destructive ops (does not silently noop).
+
+---
+
+### U9. Surface verify-env state in `doctor`
+
+**Goal:** Operators who unintentionally inherit `PRINTING_PRESS_VERIFY=1` (parent shell, CI runner, container image) can detect the foot-gun without reading response bodies. Closes the "silent inheritance" gap that U8's playbook only flags as a caution.
+
+**Requirements:** R1 (operator legibility of the short-circuit), supports R3 indirectly
+
+**Dependencies:** U1
+
+**Files:**
+- `internal/generator/templates/doctor.go.tmpl` (modify — add an "Env" section line that reports `PRINTING_PRESS_VERIFY`/`PRINTING_PRESS_VERIFY_LIVE_HTTP` state alongside the existing env-driven flag readouts)
+- `internal/generator/templates/doctor_test.go.tmpl` (modify or create — emitted test asserting the lines appear when env is set)
+- `internal/generator/generator.go` (modify if `doctor_test.go.tmpl` is created — register in the `singleFiles` map, parallel to where `doctor.go.tmpl` emits)
+
+**Approach:**
+- After the existing `Env Vars` block in `doctor`'s output, add a `Verify Mode` line:
+  - When neither env is set: `Verify Mode: not active (normal operation)`
+  - When `PRINTING_PRESS_VERIFY=1`, no `LIVE_HTTP`: `Verify Mode: ACTIVE — mutating HTTP verbs short-circuit (no network calls for DELETE/POST/PUT/PATCH)` — flagged as INFO with a hint pointing at the env var.
+  - When both envs set: `Verify Mode: ACTIVE — live HTTP opt-in (mutating verbs dial out)` — INFO without warning.
+- The point: an operator running `<cli> doctor` after an unexpected `success:true / status:noop` sees the diagnostic context in one place. Pairs with the inner-envelope `reason` literal as the two-anchor diagnosis story.
+
+**Patterns to follow:** existing `doctor` env-readout lines (Config, Auth, Env Vars, API, Cache sections).
+
+**Test scenarios:**
+- *Covers R1.* Doctor output with no env vars set: line reports "not active."
+- Doctor output with `PRINTING_PRESS_VERIFY=1` only: line reports the short-circuit state.
+- Doctor output with both envs: line reports the live-HTTP state.
+- Existing doctor checks (Config / Auth / Env / API / Cache) unaffected.
+
+**Verification:** Emitted `doctor_test.go` passes; petstore smoke retest shows the new line in `./petstore-pp-cli.exe doctor`.
+
+---
+
+## Verification Strategy
+
+End-to-end verification per requirement, in landing order:
+
+1. **U1–U3 land + goldens regen.**
+   - Run `scripts/golden.sh verify`. Expect diffs in 3 fixtures: `testdata/golden/expected/generate-golden-api/printing-press-golden/internal/client/client.go`, `testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go`, `testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/client/client.go`.
+   - Inspect each diff. Each should show: new `isMutatingVerb` helper, new short-circuit block at top of `do()`, new envelope helper. No incidental changes elsewhere.
+   - Run `scripts/golden.sh update` on a clean worktree.
+   - Commit the golden diffs in the same PR as the template change.
+
+2. **R1 (short-circuit fires) — Repo-level.** Generator test `TestClient_VerifyShortCircuit` (U4) passes.
+
+3. **R1 + R3 + R7 (short-circuit fires in printed CLI; live path unchanged) — Smoke retest.**
+   - Regenerate the matrix-22cc2fd9 petstore smoke CLI (in the local matrix output dir — e.g., `<matrix-output-root>/_smoke/petstore` — substitute your matrix checkout path).
+   - With `PRINTING_PRESS_VERIFY=1` and no `LIVE_HTTP`: `./petstore-pp-cli.exe pet delete 42 --dry-run=false` returns `{"status":"noop","reason":"verify_short_circuit",...}`; no network call (verify via OS-level packet capture or by setting an unroutable base URL — see deferred notes).
+   - With both envs set: same command attempts the network call (control case).
+   - With no envs: same command attempts the network call (operator path).
+   - **LIVE_HTTP plumbing assertion** (R2 integration leg): re-run U5's emitted printed-CLI test against a regenerated smoke CLI binary with `PRINTING_PRESS_VERIFY=1 PRINTING_PRESS_VERIFY_LIVE_HTTP=1` set. The recording-mock `http.RoundTripper` the test injects must record at least one outbound mutating-verb request — proving the `LIVE_HTTP=1` opt-in actually plumbs through and short-circuits do not fire when it's set. If the recording transport sees zero requests for mutating verbs, the verify mock-mode contract is silently broken even when pass-rate stays at 100. (This uses the in-process recording RoundTripper U5 already builds; no new mock-server-side counter infrastructure required.)
+
+4. **R2 (verify mock-mode integrity) — `printing-press verify`.** Re-run `printing-press verify --dir <regenerated-smoke> --spec <merged-spec> --fix --json` on a regenerated CLI. Expect `mode=mock`, `verdict=PASS`, `pass_rate=100`. Confirm non-zero `total` (at least one test ran). Any drop in `pass_rate` below 100% is a regression.
+
+5. **R3 (live paths unchanged) — Live verifiers.** Confirm `pipeline.RunLiveDogfood` and `pipeline.RunLiveCheck` do not set `PRINTING_PRESS_VERIFY=1` (existing behavior, confirmed by code read). No regression test needed; preserve by code review.
+
+6. **R6 (golden diffs explained).** PR description includes a section explaining the three golden diffs with the reasoning above.
+
+7. **R7 (emitted printed-CLI test).** After regen, every printed CLI runs `go test ./internal/client/...` and the new test passes. Verify on at least: petstore smoke CLI, plus one tier:official library CLI (e.g., notion, stripe, GitHub — whichever is fastest to regen locally) before the broader sweep.
+
+8. **R8 (generator-level test).** Already covered by U4's verification.
+
+9. **R9 (regen-merge fixture).** Covered by U7's verification.
+
+10. **R10 (rollout playbook).** Playbook reviewed; first sweep batch (3-5 tier:official CLIs) verifies the recipe end-to-end before broader rollout.
+
+11. **Agent-readiness re-test.** After regen, re-run the agent-readiness reviewer (or its substitute — see Risks) on the petstore smoke CLI. B1 should no longer fire.
+
+---
+
+## Scope Boundaries
+
+### In scope
+- Verb-gated transport-layer short-circuit
+- `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` opt-in env
+- Generator-level and emitted-printed-CLI test coverage
+- AGENTS.md + cliutil docstring update (same PR)
+- regen-merge classification fixture
+- Public library rollout playbook
+
+### Out of scope (machine deferrals already captured in `morning-report.md`)
+- Generator CRLF emission on Windows
+- verify-skill Python resolution (Windows `py` vs working `python`)
+- Cobra flag aliases invisible in `--help`
+- Spec-author test-instruction debris in descriptions
+- `DeriveRunIDFromResearchDir` not matching Matrix run-id format
+- `verify-skill`'s `likely_false_positive` heuristic that mislabeled the `auth set-token` ghost recipe
+- Misclassified exit code on missing `base_url` (separate friction from agent-readiness review)
+- Array-flag JSON-only without help-text indication (separate friction)
+- `doctor` API line lacking inline `config_path` (polish-tier finding)
+
+### Deferred to Follow-Up Work
+<!-- Removed: live_dogfood and workflow_verify env-inheritance hardening is now covered by U10 (strips verify env vars from subprocess env). -->
+<!-- Removed: narrative validation handling is now covered by U3 expansion (sets LIVE_HTTP=1 in narrativecheck.go:238). -->
+- **New `mcp:verify-allow-live` annotation taxonomy.** Currently env-var gating is sufficient. If future CLIs need finer-grained control (e.g., "this DELETE is safe to actually run in verify"), introduce annotation then. Not now.
+
+### Outside this product's identity
+- Changing the meaning of `PRINTING_PRESS_VERIFY=1` for non-HTTP side effects. The browser-launch / notification / OS-handler scope of the existing rule is preserved; the new gate is additive.
+- Adding any client-level mocking that competes with `printing-press verify`'s httptest flow. Verify owns mock-mode; this short-circuit is the floor underneath verify, not a replacement for it.
+- **Gating non-HTTP mutation surfaces under verify mode.** This plan does NOT extend `IsVerifyEnv()` short-circuiting to:
+  - Local SQLite store writes (sync, import, workflow, analytics commands writing to `internal/store/`)
+  - File outputs from `--deliver file:<path>`, `export`, manuscript writes, and similar
+  - Future RPC / gRPC / SSE / WebSocket / IPC surfaces that bypass `Client.do`
+  - In-process state mutations inside handlers (local counters, in-flight retry state)
+
+  These surfaces remain gated only by the existing side-effect-command rule and any hand-written `cliutil.IsVerifyEnv()` checks. If broader verify-mode safety becomes a requirement, address in a separate plan with its own scope analysis.
+
+---
+
+## Risks & Mitigations
+
+| Risk | Likelihood | Impact | Mitigation |
+|---|---|---|---|
+| Anti-reimplementation rule flags the synthetic envelope as a banned "endpoint stub" | Medium | High (PR review block) | Frame the change explicitly as transport-layer infrastructure in PR description. Confirm `reimplementation_check` scans handlers (root.go, etc.) not `client.go` — research confirms this. Add a code comment in `do()` referencing AGENTS.md "Side-effect commands" so future readers see the rationale. |
+| Verify mock-mode regresses because `LIVE_HTTP=1` isn't honored everywhere | Low | High | U3 covers the verify runtime. The dependency chain (U1 → U3 + U2) ensures the helper exists before verify uses it. Verification step 4 catches regressions. |
+| `live_dogfood` / `workflow_verify` operators have `PRINTING_PRESS_VERIFY=1` in their shell and silently get no-ops on destructive tests | Low | Low (resolved by U10) | Resolved: U10 strips `PRINTING_PRESS_VERIFY` and `PRINTING_PRESS_VERIFY_LIVE_HTTP` from `live_dogfood.go:845` and `workflow_verify.go:147` subprocess env. Operators with the env set in their shell get real destructive behavior; the verify-mode short-circuit cannot be inherited. |
+| Hand-edited `client.go` users lose customizations on `--force` regen | Low | High (data loss equivalent) | U7 fixture pins regen-merge classification. If verdict is wrong, follow-up retro before the sweep starts. |
+| Goldens diff includes unintended drift beyond the short-circuit | Low | Low (caught in review) | Diff inspection during verification step 1. Three fixtures total — easy to eyeball. |
+| `validate-narrative --full-examples` breaks because a recipe asserts on mutating-verb response body | Low | Low (resolved by U3 expansion) | Resolved: U3 now expands to set `LIVE_HTTP=1` in `narrativecheck.go:238` alongside the existing `PRINTING_PRESS_VERIFY=1`, parallel to `pipeline.RunVerify`'s opt-in. Narrative full-example subprocesses continue to hit the real wire path; no fixture audit required. |
+| Agent-readiness reviewer (canonical, `cli-agent-readiness-reviewer`) isn't installed when the re-test runs; substitute reviewer disagrees on whether B1 is fixed | Medium | Low | Use the same substitute (`ce-agent-native-reviewer`) for the re-test; require both manual `pet delete 42` test and reviewer's nod. |
+| Cross-platform: `regen-merge` is macOS/Linux-only; primary author is on Windows | High | Medium (rollout friction) | Document in U8 playbook. Plan author either uses WSL/VM or hands off the sweep to a Linux-capable maintainer. |
+
+---
+
+## System-Wide Impact
+
+The short-circuit fires at the transport layer (`Client.do`), below every surface in a printed CLI. Interfaces crossed and their expected behavior:
+
+| Surface | File | Behavior under short-circuit | Notes |
+|---|---|---|---|
+| Endpoint handler envelope | `internal/generator/templates/command_endpoint.go.tmpl` (around lines 650-707) | Wraps synthetic body as `data` inside `{action, resource, path, status:200, success:true, data:{status:"noop",...}}`. `--select`/`--compact` pass through unchanged. | Operator-visible distinguishability: rely on the inner `status:"noop"` + `reason:"verify_short_circuit"` — the OUTER envelope reports `success:true`. U2 adds an inline code-comment in the envelope assembly noting the synthetic case; U9's `doctor` line is the second diagnosis anchor. |
+| Cache layer | `Client.invalidateCache()` at `client.go.tmpl:473`, called at `:996` | NOT invoked — short-circuit returns BEFORE the success branch reaches the `method != http.MethodGet` block. Cache state stays consistent: a subsequent GET sees the still-present resource. | KTD4 states this explicitly. |
+| Retry middleware | Retry/backoff loop at `client.go.tmpl` (around lines 850-1040) | NOT invoked — sits below the short-circuit. No retry on the synthetic 200. | Correct. |
+| MCP shellout walker | `internal/generator/templates/cobratree/shellout.go.tmpl` (`exec.CommandContext` around line 37) | Spawns the same binary; inherits parent env including `PRINTING_PRESS_VERIFY=1`. Mutating tools advertise `destructiveHint:true` and return synthetic envelopes at runtime under verify. Annotation matches advertised semantics when `LIVE_HTTP=1` is also set (verify's contract). | Confirms KTD5: no annotation drift; the tool IS destructive when actually dialed. |
+| MCP typed endpoint tools | Endpoint-mirror tools registered via `pp:endpoint` annotation | Same `do()` path — same short-circuit, same envelope. | No special handling needed. |
+| Narrative full-example runner | `internal/narrativecheck/narrativecheck.go:238` | Already sets `PRINTING_PRESS_VERIFY=1` without the new opt-in. Mutating-verb recipes will receive synthetic envelopes after this lands. | Promoted to a Medium/Medium risk; scan recipe fixtures BEFORE landing — pre-land gate, not follow-up. |
+| Cache invalidation contract | [`http-client-cache-invalidate-on-mutation`](../solutions/design-patterns/http-client-cache-invalidate-on-mutation-2026-05-05.md) | Contract is "invalidate ONLY on successful real mutation." Short-circuit returns success but is not a real mutation; correctly skips invalidation by virtue of placement. | U2 Approach adds an inline comment at the short-circuit explicitly noting "no cache invalidation — no state changed." |
+
+**Failure-mode legibility.** If `PRINTING_PRESS_VERIFY=1` is set unintentionally (inherited from a parent shell, CI runner, container image), a confused operator sees `success:true` (outer envelope) plus an inner `__pp_verify_synthetic__:true` and `reason:"verify_short_circuit"`. The `__pp_verify_synthetic__` sentinel is the primary diagnostic anchor — namespace-reserved and unambiguous; the `reason` literal is the secondary prose anchor; U9's new `doctor` line is the third anchor that surfaces the same context without requiring the operator to inspect a response body.
+
+**Cross-boundary env contract.** `PRINTING_PRESS_VERIFY_LIVE_HTTP=1` is a new contract spanning the generator (template), the runtime (`pipeline.RunVerify`), and every published CLI. The cliutil docstring (U6) is the canonical home; external consumers (downstream skill authors, security auditors, library CI maintainers) find the contract there without reading template source. Future deeper documentation can land in a dedicated `docs/VERIFY-ENV.md` if more contracts accumulate.
+
+---
+
+## Rollout Plan
+
+### Phase R1: This repo (cli-printing-press)
+1. PR with U1–U7, U9, and U10 in one commit set. Conventional commit: `fix(cli): gate mutating HTTP verbs on PRINTING_PRESS_VERIFY in generated clients`. U8 (playbook + optional script) can land in the same PR or as a follow-up before the sweep begins — playbook prose doesn't gate the binary release.
+2. CI gate: `go test ./...`, `scripts/golden.sh verify` (the goldens are now correct).
+3. Mergify queue with `ready-to-merge` label.
+4. Release-please picks up the `fix(cli):` commit on next release PR.
+
+### Phase R2: Public library sweep (printing-press-library)
+1. Wait for the cli-printing-press release that includes U1–U7 to ship a new printing-press binary (or use the unreleased binary from a tagged commit; document either path).
+2. Tier:official CLIs first. Per CLI: `printing-press regen-merge <cli> --fresh <tmp> --apply`, `go test ./...`, commit, batch-PR or one-PR-per-CLI per the public library's convention.
+3. Tier:community CLIs second. Same flow.
+4. Each PR's description references this plan and U8 playbook.
+
+### Phase R3: Smoke re-verification (matrix-22cc2fd9 or equivalent)
+1. Re-run the petstore smoke pipeline end-to-end with the regenerated binary.
+2. Confirm agent-readiness B1 no longer fires.
+3. Update the matrix run's `agent-readiness.md` to reflect the verdict change (optional housekeeping).
+
+---
+
+## Documentation Plan
+
+Inline with U6 (AGENTS.md + cliutil docstring). No separate docs PR.
+
+`docs/SPEC-EXTENSIONS.md` — no change (no new `x-*` extensions).
+`docs/PIPELINE.md` — no change (no new pipeline phase).
+`docs/PATTERNS.md` — optional: add the verb-gated short-circuit pattern as a cross-cutting design pattern entry referencing this plan. Defer unless reviewer requests.
+`docs/solutions/best-practices/verify-mode-short-circuit-rollout-2026-05-13.md` — created in U8.
+
+---
+
+## Deferred to Implementation
+
+These are knowable from code but cheaper to resolve when the implementer has the editor open:
+
+- Exact placement of the short-circuit block in `do()` — before or after the proxy-envelope vs standard-path fork. Implementer chooses based on whichever reads cleaner; the contract (no network call when conditions match) holds either way.
+- Whether `isMutatingVerb` lives in the template body or as a generated helper. Implementer judgment.
+- Whether U7's fixture goes under existing testdata or a new subdir. Match nearest existing pattern.
+- Whether the U8 helper script ships or stays as documented manual steps. Lean toward shipping if the per-CLI loop is more than ~5 steps; otherwise keep it as a checklist.
+- Exact naming of the regen-merge test case in U7.
+
+These are NOT planning-time questions; they're 30-second decisions during implementation that don't affect plan shape.
+
+---
+
+## Deferred / Open Questions
+
+### From 2026-05-13 review
+
+**Q1. Premise validation: should the canonical `cli-agent-readiness-reviewer` confirm B1 before committing to Option B?** (P1, root; deferred from doc-review round 1)
+
+The agent-readiness finding B1 that triggered this plan was flagged by a substitute reviewer (`ce-agent-native-reviewer`), not the canonical `cli-agent-readiness-reviewer` (which isn't installed in this session). AGENTS.md line 40 scopes the side-effect rule to "Hand-written novel commands that perform visible actions," and the cliutil docstring limits to OS-visible side effects. On a strict reading, generated transport code may be out of scope for the rule — in which case B1 is a misapplication and Option D (docs-only) closes the gap without committing to a permanent env-var contract, three golden diffs, a 200+ CLI sweep, and a narrativecheck fixture audit.
+
+Before Rollout Phase R1 lands, get one canonical-reviewer validation pass (or an explicit maintainer ruling on whether AGENTS.md's side-effect rule reaches generated transport code). If the canonical reviewer agrees with the substitute, proceed with Option B as planned. If it disagrees, scope-shrink to Option D and defer Option B until evidence accumulates.
+
+The following 6 sub-questions are dependents of Q1 — they auto-resolve if Q1 is answered "Option D suffices; cancel Option B work":
+
+- **Q1a.** Should we consider a typed `cliutil.VerifyMode` struct (intent named at one place, parsed once from env) instead of accumulating per-question env vars? (product-lens P3)
+- **Q1b.** What's the realistic public-library sweep cost in maintainer-hours vs. the 9 deferred frictions in `morning-report.md`'s out-of-scope list? Is this sweep the highest-leverage use of that time? (product-lens P4)
+- **Q1c.** Is `PRINTING_PRESS_VERIFY_LIVE_HTTP` a long-term contract or scaffolding tied to verify's current httptest mock-server architecture? If verify pivots to request-replay/contract-testing, does the opt-in become dead code in 200+ CLIs? (product-lens P6)
+- **Q1d.** Is U9 (doctor verify-env line) genuinely required by R1, or post-deepening scope creep? No R-ID actually mandates operator-legibility output. (scope-guardian SG1)
+- **Q1e.** Is U8 (rollout playbook) in scope for this PR or a follow-up? Rollout Plan Phase R1 explicitly permits deferral but U8 remains in active scope. (scope-guardian SG2)
+- **Q1f.** Should the conventional commit type be `feat(cli):` rather than `fix(cli):`? The new env-var contract is a public-API addition, not a behavior correction — semver implications favor `feat`. (adversarial A1)
+
+These items remain in the plan as Q1 + sub-questions until a maintainer resolves Q1; ce-work should not begin implementation of Phase R1 until the canonical-reviewer ruling is recorded here.
+
+---
+
+## Grep Receipts
+
+Evidence gathered during planning that grounds the file/symbol references above. All commands were run from the cli-printing-press repo root unless noted.
+
+```text
+$ grep -rln "PRINTING_PRESS_VERIFY\|IsVerifyEnv" internal/ AGENTS.md
+testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cliutil/verifyenv.go
+testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go
+testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/cli/auth.go
+skills/printing-press/SKILL.md
+internal/pipeline/runtime_commands.go
+internal/pipeline/runtime.go
+internal/narrativecheck/narrativecheck.go
+internal/generator/templates/config.go.tmpl
+internal/generator/templates/cliutil_verifyenv.go.tmpl
+internal/generator/templates/client.go.tmpl
+internal/generator/templates/auth_client_credentials.go.tmpl
+internal/generator/templates/auth_simple.go.tmpl
+internal/generator/templates/auth.go.tmpl
+internal/generator/generator_test.go
+internal/generator/auth_env_precedence_test.go
+internal/cli/validate_narrative.go
+AGENTS.md
+```
+
+Confirms the surface this plan must touch: `cliutil_verifyenv.go.tmpl`, `client.go.tmpl`, `runtime.go`, `AGENTS.md`. Plus three golden trees that will diff.
+
+```text
+$ grep -n "^func (c \*Client) do(" internal/generator/templates/client.go.tmpl
+713:func (c *Client) do(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+```
+
+Anchors KTD4 and U2: the `do()` function is the single dispatch point, line 713 in the template.
+
+```text
+$ grep -n "IsVerifyEnv\|PRINTING_PRESS_VERIFY" internal/generator/templates/client.go.tmpl
+1306:{{- if eq .Auth.EffectiveOAuth2Grant "client_credentials"}}
+1307:    if c.Config.AccessToken == "" && cliutil.IsVerifyEnv() {
+1308:        c.Config.AccessToken = "mock-token-for-testing"
+1309:        return c.Config.AuthHeader(), nil
+1310:    }
+```
+
+Anchors KTD2 (synthetic-value precedent at line 1307) and U2's placement choice (short-circuit must fire BEFORE this so client_credentials minting doesn't run unnecessarily).
+
+```text
+$ grep -n "PRINTING_PRESS_VERIFY\|IsVerifyEnv" internal/pipeline/runtime.go
+260:			// cliutil.IsVerifyEnv() and short-circuit when set, so even
+265:			env = append(env, "PRINTING_PRESS_VERIFY=1")
+275:		// PRINTING_PRESS_VERIFY env var alone may not gate, skip its
+411:	// PRINTING_PRESS_VERIFY=1 is already set in env, so well-behaved
+430:	// that during verify even with PRINTING_PRESS_VERIFY=1 set, because
+```
+
+Anchors U3: line 265 is the exact insertion point for the new `LIVE_HTTP=1` env append.
+
+```text
+$ grep -n "Env\b\|Setenv\|exec.Command" internal/pipeline/live_dogfood.go internal/pipeline/live_check.go internal/pipeline/workflow_verify.go
+internal/pipeline/live_dogfood.go:845:    cmd := exec.CommandContext(ctx, binaryPath, args...)
+internal/pipeline/workflow_verify.go:147:    cmd := exec.CommandContext(ctx, binary, args...)
+```
+
+Confirms R3: live_dogfood, live_check, and workflow_verify do NOT explicitly set `PRINTING_PRESS_VERIFY=1`. They inherit parent env (Risks table calls this out as a latent inheritance risk for operators).
+
+```text
+$ grep -rln "client.go" testdata/golden/cases/
+testdata/golden/cases/generate-golden-api/artifacts.txt
+testdata/golden/cases/generate-golden-api-oauth2-cc/artifacts.txt
+testdata/golden/cases/generate-tier-routing-api/artifacts.txt
+```
+
+Confirms three golden fixtures touch `client.go` — Verification Strategy step 1 enumerates these three.
+
+```text
+$ grep -n "Side-effect\|IsVerifyEnv\|Generator-reserved\|Anti-reimplementation\|Generator Output Stability" AGENTS.md | head
+3:## Machine vs Printed CLI
+13:### Anti-reimplementation
+39:### Side-effect commands
+44:### Generator-reserved namespaces
+61:## Generator Output Stability
+```
+
+Anchors the AGENTS.md sections cited in Problem Frame, KTD6, and Risks. Line 39 is the section that U6 updates; line 44's "Generator-reserved namespaces" rule mandates the cliutil placement in KTD2.
+
+```text
+$ grep -rn "client.go\b" internal/pipeline/reimplementation_check.go
+(no matches)
+```
+
+Confirms the Risk-table mitigation: `reimplementation_check` does not scan `client.go`, so the synthetic envelope in `do()` won't trip the anti-reimplementation heuristic (which targets handlers and `root.go`).
diff --git a/docs/solutions/best-practices/verify-mode-short-circuit-rollout-2026-05-13.md b/docs/solutions/best-practices/verify-mode-short-circuit-rollout-2026-05-13.md
new file mode 100644
index 00000000..a5f2b1e9
--- /dev/null
+++ b/docs/solutions/best-practices/verify-mode-short-circuit-rollout-2026-05-13.md
@@ -0,0 +1,147 @@
+---
+title: "Verify-mode HTTP-verb short-circuit: rolling out to printing-press-library"
+date: 2026-05-13
+category: best-practices
+module: printing-press-library-sweep
+problem_type: best_practice
+component: tooling
+severity: medium
+applies_when:
+  - "Rolling out docs/plans/2026-05-13-001 (verify-mode short-circuit) to printed CLIs in printing-press-library"
+  - "Performing any cross-CLI sweep that depends on regen-merge"
+tags:
+  - cross-repo
+  - regen-merge
+  - rollout
+  - verify-mode
+  - tier-prioritization
+related_components:
+  - tooling
+  - documentation
+  - templates
+---
+
+# Verify-mode HTTP-verb short-circuit: rolling out to printing-press-library
+
+## Context
+
+`docs/plans/2026-05-13-001-fix-verify-mode-short-circuit-mutating-http-plan.md` introduces a transport-layer gate that short-circuits mutating HTTP verbs (DELETE/POST/PUT/PATCH) under `PRINTING_PRESS_VERIFY=1`. The gate lives in the generator template `internal/generator/templates/client.go.tmpl` plus a new helper in `cliutil_verifyenv.go.tmpl`. Every printed CLI inherits the gate at its next regen, but `printing-press-library` has no library-wide regen command — each CLI must be regenerated and PR'd individually via `printing-press regen-merge`.
+
+This playbook captures the order, mechanics, and known foot-guns so a maintainer can execute the sweep without back-channel questions.
+
+## Prerequisites
+
+1. **A new `printing-press` binary that includes commit `5701f692` (U1+U2+U4), `7b9bbbf1` (U3), and the rest of the verify-mode-plan chain.** Easiest path: pull `cli-printing-press` `fix/verify-mode-http-gate` post-merge, then `go install ./cmd/printing-press@<tag>`.
+2. **macOS or Linux.** `printing-press regen-merge --help` currently warns that Windows is not supported (signed-attribute path semantics + the `os.Symlink` calls in the merge layer). A Windows-based maintainer will need WSL, a Linux VM, or to hand the sweep to a Linux-capable colleague.
+3. **A local clone of `mvanhorn/printing-press-library` with `main` up to date.** The sweep runs per-CLI from this clone's root.
+4. **Verify env CLEAN.** Before running the sweep, confirm `echo "$PRINTING_PRESS_VERIFY $PRINTING_PRESS_VERIFY_LIVE_HTTP"` prints two empty strings. The live verifiers landed in U10 strip both vars from subprocess env, so an inherited value cannot silently noop the destructive paths — but the operator-visible behavior of `printing-press verify` and `printing-press regen-merge` still depends on these being unset.
+
+## Order: tier:official first
+
+The sweep is tier-prioritized for two reasons:
+
+- **Tier:official CLIs see more review attention.** Bugs surface fast on these; you want them in the bag before working through the long tail.
+- **Smaller blast radius.** Tier:official is a fixed-name set (~12 CLIs); tier:community is a churning set. Landing tier:official first lets you stabilize the recipe before the volume hits.
+
+Read the tier per CLI from `library/<cat>/<api>/.printing-press.json` or the per-CLI `printing-press.toml`. Sweep tier:official first, tier:community second.
+
+## Per-CLI recipe
+
+For each CLI directory under `printing-press-library/library/<category>/<api>/`:
+
+1. **Materialize a fresh template tree to a temp dir.**
+
+   ```bash
+   tmp=$(mktemp -d)
+   printing-press print "$api" \
+       --spec library/<cat>/<api>/spec.yaml \
+       --output "$tmp"
+   ```
+
+2. **Run regen-merge with `--apply`.**
+
+   ```bash
+   printing-press regen-merge \
+       library/<cat>/<api> \
+       --fresh "$tmp" \
+       --apply
+   ```
+
+   Watch the verdict table. Every file should classify as `TEMPLATED-CLEAN`, `TEMPLATED-WITH-ADDITIONS`, or `NEW-TEMPLATE-EMISSION`. The fixture pinned in `internal/pipeline/regenmerge/testdata/verify-short-circuit/` (U7) confirms `internal/client/client.go` resolves to `TEMPLATED-WITH-ADDITIONS` when the operator has hand-edited it; a `TEMPLATED-VALUE-DRIFT` verdict on `client.go` is a regression — stop the sweep and investigate.
+
+3. **Run the CLI's tests.**
+
+   ```bash
+   cd library/<cat>/<api>
+   go test ./...
+   ```
+
+   The U5 emitted-test template adds `internal/client/client_verify_short_circuit_test.go` to every regenerated CLI; this is where verify-mode behavior is pinned at the printed-CLI level. A failure here is a real regression, not a sweep glitch.
+
+4. **Commit + PR.**
+
+   ```bash
+   git checkout -b sweep/verify-mode-<api>-2026-05-13
+   git add library/<cat>/<api> cli-skills/pp-<api>  # mirror parity, see below
+   git commit -m "regen(cli): apply verify-mode short-circuit to <api>"
+   gh pr create --base main --title "regen(<api>): apply verify-mode HTTP-verb short-circuit"
+   ```
+
+   PR body should reference `docs/plans/2026-05-13-001` so reviewers can find context.
+
+## Mirror parity (don't forget cli-skills/)
+
+The library has a `cli-skills` parity check that runs on every PR — see `docs/solutions/best-practices/cross-repo-coordination-with-printing-press-library-2026-05-06.md`. When the SKILL.md changes between regen passes (uncommon for this plan, but possible if a CLI's verify-related help text moves), run the mirror generator and stage the diff:
+
+```bash
+go run ./tools/generate-skills/main.go
+git add cli-skills/pp-<api>
+```
+
+## Batch vs one-PR-per-CLI
+
+Two viable strategies:
+
+- **One PR per CLI** — clean review, easy revert, but ~20+ PRs. Use when CLIs differ enough that reviewers want each diff in isolation.
+- **One PR per tier** — fewer PRs, but the diff is large. Use when the regen-merge diffs look mechanical and reviewers are comfortable scanning bulk.
+
+For this rollout: lean toward **one PR per tier**. The verify-mode gate change emits an identical block into every CLI's `client.go`, so reviewing 12 nearly-identical diffs in 12 PRs is busywork. The exception is any CLI whose regen-merge surfaces a `TEMPLATED-WITH-ADDITIONS` verdict — those deserve their own PR so the preserved-additions delta gets explicit review.
+
+## Known foot-guns
+
+### `PRINTING_PRESS_VERIFY=1` in the operator's shell
+
+If the maintainer running the sweep has `PRINTING_PRESS_VERIFY=1` set in their shell (parent process, CI runner, container image), the U10 env-strip in `live_dogfood` and `workflow_verify` neutralizes the silent-noop risk for live verification — but `printing-press verify` itself and any ad-hoc `go test ./...` runs against the regenerated CLI will see the inherited var. Mutating-verb tests under the inherited env will short-circuit through the synthetic envelope and pass — but they won't be exercising the real wire path. **Unset both env vars before running the sweep.**
+
+### Hand-edited `client.go`
+
+The U7 fixture pins `TEMPLATED-WITH-ADDITIONS` for the canonical hand-edit case (operator added a top-level helper method on `*Client`). Any other shape of hand-edit — particularly INSIDE the existing `do()` method body — will currently classify as `TEMPLATED-CLEAN` because the decl-set comparison doesn't see function-body changes. The merge layer will overwrite the operator's in-function edit silently.
+
+Mitigation: before running `regen-merge --apply`, grep each CLI's `client.go` for non-template additions:
+
+```bash
+diff -u \
+    <(printing-press print "$api" --spec library/<cat>/<api>/spec.yaml --output - 2>/dev/null | head -n 500) \
+    library/<cat>/<api>/internal/client/client.go
+```
+
+Any diff that isn't the new short-circuit block deserves a manual review before `--apply`.
+
+### tier:community sprawl
+
+`printing-press-library/library/community/` has CLIs with widely varying maturity. Some have no tests; some have a single happy-path smoke. The emitted U5 test will compile and run on all of them (it uses stdlib + the `client` package only), but a CLI that ships pre-existing test compilation failures will surface those failures on first regen. Triage:
+
+1. If the failure is pre-existing (compile error, missing test file, etc.) — open a separate cleanup PR for that CLI; do not block the verify-mode rollout on it.
+2. If the failure is the new U5 test itself — the gate has regressed for that CLI's config; investigate (likely a generator-template variable mismatch).
+
+## Phase R3: smoke re-verification
+
+After the printing-press-library sweep, re-run the matrix `petstore` smoke pipeline (the original B1 surface) against a regenerated CLI:
+
+```bash
+PRINTING_PRESS_VERIFY=1 petstore-pp-cli pet delete 42 --json
+```
+
+Expected output: outer envelope with `verify_noop: true` and `success: false`, inner `data` carrying `__pp_verify_synthetic__: true` and `reason: "verify_short_circuit"`. No network call attempted. This is the agent-readiness re-test that confirms B1 stays closed end-to-end.
+
+If the re-test passes, the rollout is complete.
diff --git a/internal/generator/client_invalidate_cache_test.go b/internal/generator/client_invalidate_cache_test.go
index 9caffa03..904be62b 100644
--- a/internal/generator/client_invalidate_cache_test.go
+++ b/internal/generator/client_invalidate_cache_test.go
@@ -14,12 +14,18 @@ import (
 
 // TestGenerateEmitsInvalidateCacheSymmetry guards #603's two-prong fix:
 // the generated client.go must contain BOTH the invalidateCache method
-// definition AND a c.invalidateCache() call inside do()'s body.
-// Method-presence alone is not enough — a future refactor that drops
-// the call but keeps the method would silently re-introduce the
-// stale-list-after-mutation bug. See
+// definition AND a c.invalidateCache() call inside the do-family
+// implementation's body. Method-presence alone is not enough — a future
+// refactor that drops the call but keeps the method would silently
+// re-introduce the stale-list-after-mutation bug. See
 // docs/solutions/design-patterns/http-client-cache-invalidate-on-mutation-2026-05-05.md
 // for full rationale.
+//
+// After the verify-mode read-only-POST work, do() is a thin wrapper
+// around doInternal(...readOnlyIntent bool), so the cache-invalidation
+// call lives in doInternal(). The assertion follows the single
+// implementation site: a call site OUTSIDE doInternal() would not
+// protect against the stale-list-after-mutation regression.
 func TestGenerateEmitsInvalidateCacheSymmetry(t *testing.T) {
 	t.Parallel()
 
@@ -38,22 +44,29 @@ func TestGenerateEmitsInvalidateCacheSymmetry(t *testing.T) {
 	assert.Contains(t, clientGo, "func (c *Client) invalidateCache()",
 		"client.go must define invalidateCache method (R1)")
 
-	// Prong 2: do() must call invalidateCache. Locate do() and verify the
-	// call is in its body — not just anywhere in the file. The do
-	// function spans from its declaration to the next package-level
-	// `func ` or end of file. A call site OUTSIDE do() would not protect
-	// against the stale-list-after-mutation regression.
-	doStart := strings.Index(clientGo, "func (c *Client) do(")
-	require.NotEqual(t, -1, doStart, "client.go must contain Client.do function")
-	doRest := clientGo[doStart:]
-	// Find the next top-level func declaration to bound do()'s body.
-	nextFunc := strings.Index(doRest[1:], "\nfunc ")
-	doBody := doRest
+	// Prong 2: doInternal() must call invalidateCache. Bound the search
+	// to doInternal()'s body so a call site emitted at file scope (or in
+	// an unrelated helper) does not pass. doInternal() spans from its
+	// declaration to the next package-level `func ` or end of file.
+	implStart := strings.Index(clientGo, "func (c *Client) doInternal(")
+	require.NotEqual(t, -1, implStart, "client.go must contain Client.doInternal function")
+	implRest := clientGo[implStart:]
+	nextFunc := strings.Index(implRest[1:], "\nfunc ")
+	implBody := implRest
 	if nextFunc != -1 {
-		doBody = doRest[:nextFunc+1]
+		implBody = implRest[:nextFunc+1]
 	}
-	assert.Contains(t, doBody, "c.invalidateCache()",
-		"Client.do must call c.invalidateCache() in its success branch (R2)")
+	assert.Contains(t, implBody, "c.invalidateCache()",
+		"Client.doInternal must call c.invalidateCache() in its success branch (R2)")
+
+	// do() and doRead() must remain thin wrappers around doInternal so
+	// the cache-invalidation call site stays single. A future edit that
+	// inlines do()'s implementation back into do() would silently move
+	// the call out of doInternal — pin the wrapper shape here too.
+	assert.Contains(t, clientGo, "func (c *Client) do(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {\n\treturn c.doInternal(method, path, params, body, headerOverrides, false)\n}",
+		"Client.do must be a thin wrapper delegating to doInternal(..., false)")
+	assert.Contains(t, clientGo, "func (c *Client) doRead(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {\n\treturn c.doInternal(method, path, params, body, headerOverrides, true)\n}",
+		"Client.doRead must be a thin wrapper delegating to doInternal(..., true)")
 
 	// Prong 3: writeCache must still be present (asymmetry diagnostic
 	// from the design-pattern doc — writeCache without invalidateCache
diff --git a/internal/generator/client_verify_short_circuit_emit_test.go b/internal/generator/client_verify_short_circuit_emit_test.go
new file mode 100644
index 00000000..4e497e00
--- /dev/null
+++ b/internal/generator/client_verify_short_circuit_emit_test.go
@@ -0,0 +1,76 @@
+package generator
+
+import (
+	"go/parser"
+	"go/token"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestEmitted_ClientVerifyShortCircuitTest pins the contract that every
+// regenerated CLI ships with a client_verify_short_circuit_test.go in
+// its internal/client package, exercising the transport-layer verify
+// short-circuit at the printed-CLI level. Without this pin, a future
+// template edit that silently drops the emitted file (or weakens its
+// assertions) would let each downstream CLI's go-test pass even when
+// verify-mode behavior regresses.
+func TestEmitted_ClientVerifyShortCircuitTest(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := minimalSpec("emit-verify-test")
+	outputDir := filepath.Join(t.TempDir(), "emit-verify-test-pp-cli")
+	require.NoError(t, New(apiSpec, outputDir).Generate())
+
+	src, err := os.ReadFile(filepath.Join(outputDir, "internal", "client", "client_verify_short_circuit_test.go"))
+	require.NoError(t, err, "client_verify_short_circuit_test.go must be emitted into every printed CLI")
+	emitted := string(src)
+
+	// Package declaration must match the host package so the test can
+	// call the unexported do() method.
+	assert.Contains(t, emitted, "package client",
+		"emitted test file must be in package client")
+
+	// Four canonical test functions cover the gate's four states:
+	// mutating-verb short-circuit, LIVE_HTTP opt-in, no-env operator path,
+	// and GET control case.
+	for _, fn := range []string{
+		"func TestClient_VerifyShortCircuit_MutatingVerbs",
+		"func TestClient_VerifyShortCircuit_LiveHTTPOptIn",
+		"func TestClient_VerifyShortCircuit_NoEnv",
+		"func TestClient_VerifyShortCircuit_GETControl",
+	} {
+		assert.Contains(t, emitted, fn,
+			"emitted test must define %s to cover its gate state", fn)
+	}
+
+	// Recording-mock infrastructure must be present so the gate's
+	// "no-network-call" assertion is observable.
+	assert.Contains(t, emitted, "type recordingRoundTripper struct",
+		"emitted test must define the recording RoundTripper helper")
+	assert.Contains(t, emitted, "func (r *recordingRoundTripper) RoundTrip(req *http.Request)",
+		"recordingRoundTripper must implement http.RoundTripper")
+
+	// All four mutating verbs are enumerated in the verbs-loop test.
+	for _, verb := range []string{`"DELETE"`, `"POST"`, `"PUT"`, `"PATCH"`} {
+		assert.Contains(t, emitted, verb,
+			"mutating-verbs subtest must enumerate %s", verb)
+	}
+
+	// Envelope-shape assertions are present: the synthetic sentinel field
+	// plus the reason literal. Drift on either is a contract regression.
+	assert.Contains(t, emitted, `"__pp_verify_synthetic__"`,
+		"emitted test must assert on the __pp_verify_synthetic__ sentinel")
+	assert.Contains(t, emitted, `"verify_short_circuit"`,
+		"emitted test must assert on the verify_short_circuit reason literal")
+
+	// Final guard: the emitted file must be syntactically valid Go.
+	// Strings-only assertions above can pass against malformed source;
+	// parsing the file catches template-syntax errors that survive the
+	// content checks.
+	_, parseErr := parser.ParseFile(token.NewFileSet(), "client_verify_short_circuit_test.go", emitted, parser.AllErrors)
+	require.NoError(t, parseErr, "emitted test file must be syntactically valid Go")
+}
diff --git a/internal/generator/client_verify_short_circuit_test.go b/internal/generator/client_verify_short_circuit_test.go
new file mode 100644
index 00000000..f6932ed5
--- /dev/null
+++ b/internal/generator/client_verify_short_circuit_test.go
@@ -0,0 +1,102 @@
+package generator
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestClient_VerifyShortCircuit pins the verify-mode HTTP-verb
+// short-circuit contract emitted into every generated client.go.
+//
+// The contract exists because the AGENTS.md "Side-effect commands" rule
+// requires a defense-in-depth gate that catches anything the verifier's
+// heuristic classifier misses. Mutating HTTP verbs (DELETE/POST/PUT/
+// PATCH) under PRINTING_PRESS_VERIFY=1 must short-circuit with a
+// synthetic envelope unless PRINTING_PRESS_VERIFY_LIVE_HTTP=1 opts back
+// in. A future template edit that drops the gate, narrows it to fewer
+// verbs, or removes either env-var check would silently re-open the
+// readiness gap; this test fails on any of those drifts.
+func TestClient_VerifyShortCircuit(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := minimalSpec("verify-short-circuit")
+	outputDir := filepath.Join(t.TempDir(), "verify-short-circuit-pp-cli")
+	require.NoError(t, New(apiSpec, outputDir).Generate())
+
+	src, err := os.ReadFile(filepath.Join(outputDir, "internal", "client", "client.go"))
+	require.NoError(t, err)
+	emitted := string(src)
+
+	// Helper: isMutatingVerb covers exactly the four mutating verbs.
+	assert.Contains(t, emitted, "func isMutatingVerb(method string) bool",
+		"client.go should define an isMutatingVerb helper at file scope")
+	for _, verb := range []string{`"DELETE"`, `"POST"`, `"PUT"`, `"PATCH"`} {
+		assert.Contains(t, emitted, verb,
+			"isMutatingVerb should enumerate %s as a mutating verb", verb)
+	}
+
+	// The short-circuit gate inside doInternal() must consult both helpers,
+	// in the order isMutatingVerb -> IsVerifyEnv -> !IsVerifyLiveHTTPEnv.
+	// Order matters for short-circuit evaluation: cheapest check first, and
+	// the env-var lookups skip when the verb is GET. The leading
+	// !readOnlyIntent suppresses the gate for read-only mutating-verb
+	// operations (GraphQL queries, JSON-RPC reads, POST-based search).
+	gate := "!readOnlyIntent && isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv()"
+	assert.Contains(t, emitted, gate,
+		"doInternal() should gate the short-circuit on !readOnlyIntent && isMutatingVerb && IsVerifyEnv && !IsVerifyLiveHTTPEnv")
+
+	// Synthetic envelope sentinel: downstream consumers (validate-narrative,
+	// agent inspections, future verify-mode assertions) key on the namespace
+	// -reserved boolean so a real API's "status:noop" cannot be confused
+	// for the synthetic short-circuit.
+	assert.Contains(t, emitted, `"__pp_verify_synthetic__"`,
+		"synthetic envelope should include the namespace-reserved sentinel field")
+	assert.Contains(t, emitted, `"verify_short_circuit"`,
+		"synthetic envelope's reason field should be the literal verify_short_circuit")
+
+	// Diagnostic prose fields echo back method/path so an operator who
+	// inspects the envelope sees exactly which call no-op'd.
+	assert.Contains(t, emitted, `"method"`,
+		"synthetic envelope should echo the request method")
+	assert.Contains(t, emitted, `"path"`,
+		"synthetic envelope should echo the request path")
+
+	// Bound the gate inside doInternal(): verify the gate appears between
+	// the doInternal() declaration and the next top-level func. A check
+	// living outside doInternal() would silently fail to short-circuit
+	// real requests.
+	doStart := strings.Index(emitted, "func (c *Client) doInternal(")
+	require.NotEqual(t, -1, doStart, "client.go must declare Client.doInternal")
+	rest := emitted[doStart:]
+	nextFunc := strings.Index(rest[1:], "\nfunc ")
+	require.NotEqual(t, -1, nextFunc, "client.go should have at least one func after doInternal()")
+	doBody := rest[:nextFunc+1]
+	assert.Contains(t, doBody, gate,
+		"the gate must live INSIDE doInternal(), not in a sibling helper or at file scope")
+
+	// do() and doRead() must both be thin wrappers around doInternal() so
+	// the gate-check site stays single. A future edit that inlines the
+	// gate into do() would split the source of truth and could drift.
+	assert.Contains(t, emitted, "func (c *Client) do(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {\n\treturn c.doInternal(method, path, params, body, headerOverrides, false)\n}",
+		"do() must be a thin wrapper passing readOnlyIntent=false to doInternal()")
+	assert.Contains(t, emitted, "func (c *Client) doRead(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {\n\treturn c.doInternal(method, path, params, body, headerOverrides, true)\n}",
+		"doRead() must be a thin wrapper passing readOnlyIntent=true to doInternal()")
+
+	// Read-only POST surface (GraphQL queries, RPC reads, search-by-POST)
+	// must route through doRead so the verify-mode gate does not fire.
+	// Without these, any read-only mutating-verb command silently returns
+	// a synthetic noop envelope under PRINTING_PRESS_VERIFY=1.
+	assert.Contains(t, emitted, "func (c *Client) PostQueryWithParams(",
+		"client.go should expose PostQueryWithParams for read-only POST operations")
+	assert.Contains(t, emitted, "func (c *Client) PostQueryWithParamsAndHeaders(",
+		"client.go should expose PostQueryWithParamsAndHeaders for read-only POST operations")
+	assert.Contains(t, emitted, `return c.doRead("POST", path, params, body, nil)`,
+		"PostQueryWithParams must delegate to doRead so the verify-mode gate is bypassed")
+	assert.Contains(t, emitted, `return c.doRead("POST", path, params, body, headers)`,
+		"PostQueryWithParamsAndHeaders must delegate to doRead so the verify-mode gate is bypassed")
+}
diff --git a/internal/generator/cliutil_verifyenv_test.go b/internal/generator/cliutil_verifyenv_test.go
new file mode 100644
index 00000000..2e3c6efd
--- /dev/null
+++ b/internal/generator/cliutil_verifyenv_test.go
@@ -0,0 +1,72 @@
+package generator
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestCliutilVerifyEnvTemplateEmitsBothHelpers asserts the rendered
+// cliutil package exposes the original IsVerifyEnv plus the new
+// IsVerifyLiveHTTPEnv with their canonical env-var names. This pins the
+// generator's contract so a future template edit cannot silently drop
+// or rename either helper, which the transport-layer short-circuit and
+// the verify pipeline both depend on.
+func TestCliutilVerifyEnvTemplateEmitsBothHelpers(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := minimalSpec("verifyenv-helpers")
+	outputDir := filepath.Join(t.TempDir(), "verifyenv-helpers-pp-cli")
+	require.NoError(t, New(apiSpec, outputDir).Generate())
+
+	src, err := os.ReadFile(filepath.Join(outputDir, "internal", "cliutil", "verifyenv.go"))
+	require.NoError(t, err)
+	emitted := string(src)
+
+	assert.Contains(t, emitted, `const VerifyEnvVar = "PRINTING_PRESS_VERIFY"`,
+		"existing VerifyEnvVar constant should still be emitted")
+	assert.Contains(t, emitted, `const VerifyLiveHTTPEnvVar = "PRINTING_PRESS_VERIFY_LIVE_HTTP"`,
+		"new VerifyLiveHTTPEnvVar constant should be emitted with its canonical name")
+	assert.Contains(t, emitted, "func IsVerifyEnv() bool",
+		"existing IsVerifyEnv function should still be emitted")
+	assert.Contains(t, emitted, "func IsVerifyLiveHTTPEnv() bool",
+		"new IsVerifyLiveHTTPEnv function should be emitted")
+	assert.Contains(t, emitted, `os.Getenv(VerifyLiveHTTPEnvVar) == "1"`,
+		"helper should treat only the literal string \"1\" as truthy, matching IsVerifyEnv's contract")
+
+	// Docstring widening: the file-level comment block should mention the
+	// transport-layer use case so an external reader who hits this file
+	// understands both gates without having to read client.go.
+	assert.True(t,
+		strings.Contains(emitted, "DELETE/POST/PUT/PATCH") ||
+			strings.Contains(emitted, "mutating HTTP verbs"),
+		"docstring should document the new transport-layer scope (DELETE/POST/PUT/PATCH or 'mutating HTTP verbs')")
+}
+
+// TestIsVerifyLiveHTTPEnv_OnlyOneIsTruthy mirrors the IsVerifyEnv
+// truthiness contract: the helper returns true ONLY for the literal
+// string "1". Common alternative truthy values (true, yes, 2, empty)
+// must return false so the gate behavior is unambiguous across shells
+// and CI runners that interpret env-var truthiness differently.
+func TestIsVerifyLiveHTTPEnv_OnlyOneIsTruthy(t *testing.T) {
+	apiSpec := minimalSpec("verifyenv-truthiness")
+	outputDir := filepath.Join(t.TempDir(), "verifyenv-truthiness-pp-cli")
+	require.NoError(t, New(apiSpec, outputDir).Generate())
+
+	src, err := os.ReadFile(filepath.Join(outputDir, "internal", "cliutil", "verifyenv.go"))
+	require.NoError(t, err)
+	emitted := string(src)
+
+	// The helper body is a one-liner. Asserting on its exact shape is
+	// the simplest way to pin the truthiness contract without compiling
+	// and executing the emitted package from this generator-side test.
+	assert.Contains(t, emitted,
+		`func IsVerifyLiveHTTPEnv() bool {
+	return os.Getenv(VerifyLiveHTTPEnvVar) == "1"
+}`,
+		"IsVerifyLiveHTTPEnv body should be the canonical one-liner that treats only \"1\" as truthy")
+}
diff --git a/internal/generator/doctor_verify_mode_test.go b/internal/generator/doctor_verify_mode_test.go
new file mode 100644
index 00000000..4eccc7fc
--- /dev/null
+++ b/internal/generator/doctor_verify_mode_test.go
@@ -0,0 +1,63 @@
+package generator
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestDoctor_VerifyModeLine pins that the emitted doctor command surfaces
+// verify-env state alongside the other env-driven readouts. An operator
+// who unintentionally inherits PRINTING_PRESS_VERIFY=1 (parent shell, CI
+// runner, container image) must detect the foot-gun by running
+// `<cli> doctor`, without having to read a synthetic response body. This
+// pairs with the synthetic envelope's verify_noop top-level signal as a
+// second diagnosis anchor.
+func TestDoctor_VerifyModeLine(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := minimalSpec("doctor-verify-mode")
+	outputDir := filepath.Join(t.TempDir(), "doctor-verify-mode-pp-cli")
+	require.NoError(t, New(apiSpec, outputDir).Generate())
+
+	src, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "doctor.go"))
+	require.NoError(t, err)
+	emitted := string(src)
+
+	// cliutil import must be present — the verify-env helpers live there.
+	// Module path varies per generated CLI, so match on the suffix only.
+	assert.Contains(t, emitted, `/internal/cliutil"`,
+		"doctor.go should import cliutil to call IsVerifyEnv / IsVerifyLiveHTTPEnv")
+
+	// Both helpers must be called: IsVerifyEnv gates the active/inactive
+	// branch; IsVerifyLiveHTTPEnv distinguishes short-circuit vs dial-out
+	// modes inside the active branch.
+	assert.Contains(t, emitted, "cliutil.IsVerifyEnv()",
+		"doctor should call cliutil.IsVerifyEnv() to detect verify mode")
+	assert.Contains(t, emitted, "cliutil.IsVerifyLiveHTTPEnv()",
+		"doctor should call cliutil.IsVerifyLiveHTTPEnv() to distinguish dial-out mode")
+
+	// All three branches populate report["verify_mode"] with content that
+	// reads cleanly when prefixed by the OK / INFO indicator.
+	assert.Contains(t, emitted, `report["verify_mode"] = "normal operation"`,
+		"inactive branch should report normal operation")
+	assert.Contains(t, emitted, `report["verify_mode"] = "INFO ACTIVE — mutating HTTP verbs short-circuit`,
+		"verify-only branch should report the short-circuit state")
+	assert.Contains(t, emitted, `report["verify_mode"] = "INFO ACTIVE — live HTTP opt-in (mutating verbs dial out)"`,
+		"verify+live-http branch should report the dial-out state")
+
+	// checkKeys must include the verify_mode entry so the line renders
+	// in the human-readable output alongside the other env-driven rows.
+	envVars := strings.Index(emitted, `{"env_vars", "Env Vars"}`)
+	verifyMode := strings.Index(emitted, `{"verify_mode", "Verify Mode"}`)
+	apiKey := strings.Index(emitted, `{"api", "API"}`)
+	require.NotEqual(t, -1, envVars, `checkKeys must contain {"env_vars", "Env Vars"}`)
+	require.NotEqual(t, -1, verifyMode, `checkKeys must contain {"verify_mode", "Verify Mode"}`)
+	require.NotEqual(t, -1, apiKey, `checkKeys must contain {"api", "API"}`)
+	assert.True(t, envVars < verifyMode && verifyMode < apiKey,
+		"verify_mode line must render between Env Vars and API rows")
+}
diff --git a/internal/generator/endpoint_verify_noop_test.go b/internal/generator/endpoint_verify_noop_test.go
new file mode 100644
index 00000000..b96d6027
--- /dev/null
+++ b/internal/generator/endpoint_verify_noop_test.go
@@ -0,0 +1,83 @@
+package generator
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/mvanhorn/cli-printing-press/v4/internal/spec"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestEndpoint_VerifyNoopPropagation pins that when Client.do()
+// short-circuits a mutating verb under PRINTING_PRESS_VERIFY mode and the
+// inner data payload carries the reserved __pp_verify_synthetic__
+// sentinel, the emitted endpoint handler surfaces that signal at the
+// OUTER envelope level via verify_noop: true and flips success: false.
+// Without this, naive validators keying on .success or .status read a
+// noop as a real mutation because the synthetic envelope returns HTTP 200.
+func TestEndpoint_VerifyNoopPropagation(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := minimalSpec("verify-noop")
+	apiSpec.Resources["items"] = spec.Resource{
+		Description: "Manage items",
+		Endpoints: map[string]spec.Endpoint{
+			"list":   {Method: "GET", Path: "/items", Description: "List items"},
+			"delete": {Method: "DELETE", Path: "/items/{id}", Description: "Delete one item"},
+		},
+	}
+
+	outputDir := filepath.Join(t.TempDir(), "verify-noop-pp-cli")
+	require.NoError(t, New(apiSpec, outputDir).Generate())
+
+	src, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "items_delete.go"))
+	require.NoError(t, err)
+	emitted := string(src)
+
+	// Sentinel-driven detection: handler must check parsed data for the
+	// reserved __pp_verify_synthetic__ field name.
+	assert.Contains(t, emitted, `"__pp_verify_synthetic__"`,
+		"endpoint handler should detect the synthetic envelope via __pp_verify_synthetic__")
+
+	// Top-level signal: when the inner sentinel is present, the OUTER
+	// envelope carries verify_noop: true.
+	assert.Contains(t, emitted, `envelope["verify_noop"] = true`,
+		"endpoint handler should set top-level verify_noop=true on synthetic envelope detection")
+
+	// Success-flip: a noop did not actually mutate. Mirrors the dry_run
+	// shape that lives in the same envelope block.
+	assert.Contains(t, emitted, `envelope["success"] = false`,
+		"endpoint handler should flip success=false on synthetic envelope detection")
+
+	// Ordering invariant: detection MUST run against RAW data (before the
+	// `filtered := data` assignment) so the sentinel field cannot be
+	// stripped by --compact / --select before detection sees it. The
+	// verify_noop assignment must appear BEFORE the `filtered := data`
+	// line, and the envelope marshal must follow.
+	verifyNoop := strings.Index(emitted, `envelope["verify_noop"] = true`)
+	filteredAssign := strings.Index(emitted, `filtered := data`)
+	marshal := strings.Index(emitted, `envelopeJSON, err := json.Marshal(envelope)`)
+	require.NotEqual(t, -1, verifyNoop, "envelope[\"verify_noop\"] = true must exist")
+	require.NotEqual(t, -1, filteredAssign, "filtered := data assignment must exist")
+	require.NotEqual(t, -1, marshal, "envelopeJSON marshal call must exist")
+	assert.True(t, verifyNoop < filteredAssign,
+		"verify_noop detection must precede filtered := data so --compact/--select cannot strip the sentinel before detection")
+	assert.True(t, filteredAssign < marshal,
+		"filtered assignment must precede envelope marshal so populated data field makes it into output")
+
+	// Detection runs against RAW bytes — unmarshal target should be `data`
+	// (not `filtered`). A regression that swaps these silently re-opens
+	// the --compact bypass.
+	assert.Contains(t, emitted, "json.Unmarshal(data, &rawParsed)",
+		"sentinel detection must unmarshal RAW data, not filtered")
+
+	// GET handlers must NOT carry the mutating-verb envelope block at all
+	// — verify the list handler is unaffected.
+	listSrc, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "items_list.go"))
+	require.NoError(t, err)
+	assert.NotContains(t, string(listSrc), `envelope["verify_noop"] = true`,
+		"GET handlers should not emit the verify_noop propagation block")
+}
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index f6d54469..14115ffe 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -1443,44 +1443,45 @@ func (g *Generator) prepareOutput() error {
 
 func (g *Generator) renderSingleFiles() error {
 	singleFiles := map[string]string{
-		"main.go.tmpl":                       filepath.Join("cmd", naming.CLI(g.Spec.Name), "main.go"),
-		"helpers.go.tmpl":                    filepath.Join("internal", "cli", "helpers.go"),
-		"root_test.go.tmpl":                  filepath.Join("internal", "cli", "root_test.go"),
-		"doctor.go.tmpl":                     filepath.Join("internal", "cli", "doctor.go"),
-		"agent_context.go.tmpl":              filepath.Join("internal", "cli", "agent_context.go"),
-		"profile.go.tmpl":                    filepath.Join("internal", "cli", "profile.go"),
-		"deliver.go.tmpl":                    filepath.Join("internal", "cli", "deliver.go"),
-		"feedback.go.tmpl":                   filepath.Join("internal", "cli", "feedback.go"),
-		"which.go.tmpl":                      filepath.Join("internal", "cli", "which.go"),
-		"which_test.go.tmpl":                 filepath.Join("internal", "cli", "which_test.go"),
-		"config.go.tmpl":                     filepath.Join("internal", "config", "config.go"),
-		"cache.go.tmpl":                      filepath.Join("internal", "cache", "cache.go"),
-		"client.go.tmpl":                     filepath.Join("internal", "client", "client.go"),
-		"client_test.go.tmpl":                filepath.Join("internal", "client", "client_test.go"),
-		"cliutil_fanout.go.tmpl":             filepath.Join("internal", "cliutil", "fanout.go"),
-		"cliutil_text.go.tmpl":               filepath.Join("internal", "cliutil", "text.go"),
-		"cliutil_probe.go.tmpl":              filepath.Join("internal", "cliutil", "probe.go"),
-		"cliutil_ratelimit.go.tmpl":          filepath.Join("internal", "cliutil", "ratelimit.go"),
-		"cliutil_verifyenv.go.tmpl":          filepath.Join("internal", "cliutil", "verifyenv.go"),
-		"cliutil_extractnumber.go.tmpl":      filepath.Join("internal", "cliutil", "extractnumber.go"),
-		"cliutil_extractnumber_test.go.tmpl": filepath.Join("internal", "cliutil", "extractnumber_test.go"),
-		"cliutil_jwtshape.go.tmpl":           filepath.Join("internal", "cliutil", "jwtshape.go"),
-		"cliutil_jwtshape_test.go.tmpl":      filepath.Join("internal", "cliutil", "jwtshape_test.go"),
-		"cliutil_test.go.tmpl":               filepath.Join("internal", "cliutil", "cliutil_test.go"),
-		"cobratree/walker.go.tmpl":           filepath.Join("internal", "mcp", "cobratree", "walker.go"),
-		"cobratree/classify.go.tmpl":         filepath.Join("internal", "mcp", "cobratree", "classify.go"),
-		"cobratree/typemap.go.tmpl":          filepath.Join("internal", "mcp", "cobratree", "typemap.go"),
-		"cobratree/shellout.go.tmpl":         filepath.Join("internal", "mcp", "cobratree", "shellout.go"),
-		"cobratree/shellout_test.go.tmpl":    filepath.Join("internal", "mcp", "cobratree", "shellout_test.go"),
-		"cobratree/cli_path.go.tmpl":         filepath.Join("internal", "mcp", "cobratree", "cli_path.go"),
-		"cobratree/names.go.tmpl":            filepath.Join("internal", "mcp", "cobratree", "names.go"),
-		"types.go.tmpl":                      filepath.Join("internal", "types", "types.go"),
-		"golangci.yml.tmpl":                  ".golangci.yml",
-		"readme.md.tmpl":                     "README.md",
-		"agents.md.tmpl":                     "AGENTS.md",
-		"skill.md.tmpl":                      "SKILL.md",
-		"LICENSE.tmpl":                       "LICENSE",
-		"NOTICE.tmpl":                        "NOTICE",
+		"main.go.tmpl":                             filepath.Join("cmd", naming.CLI(g.Spec.Name), "main.go"),
+		"helpers.go.tmpl":                          filepath.Join("internal", "cli", "helpers.go"),
+		"root_test.go.tmpl":                        filepath.Join("internal", "cli", "root_test.go"),
+		"doctor.go.tmpl":                           filepath.Join("internal", "cli", "doctor.go"),
+		"agent_context.go.tmpl":                    filepath.Join("internal", "cli", "agent_context.go"),
+		"profile.go.tmpl":                          filepath.Join("internal", "cli", "profile.go"),
+		"deliver.go.tmpl":                          filepath.Join("internal", "cli", "deliver.go"),
+		"feedback.go.tmpl":                         filepath.Join("internal", "cli", "feedback.go"),
+		"which.go.tmpl":                            filepath.Join("internal", "cli", "which.go"),
+		"which_test.go.tmpl":                       filepath.Join("internal", "cli", "which_test.go"),
+		"config.go.tmpl":                           filepath.Join("internal", "config", "config.go"),
+		"cache.go.tmpl":                            filepath.Join("internal", "cache", "cache.go"),
+		"client.go.tmpl":                           filepath.Join("internal", "client", "client.go"),
+		"client_test.go.tmpl":                      filepath.Join("internal", "client", "client_test.go"),
+		"client_verify_short_circuit_test.go.tmpl": filepath.Join("internal", "client", "client_verify_short_circuit_test.go"),
+		"cliutil_fanout.go.tmpl":                   filepath.Join("internal", "cliutil", "fanout.go"),
+		"cliutil_text.go.tmpl":                     filepath.Join("internal", "cliutil", "text.go"),
+		"cliutil_probe.go.tmpl":                    filepath.Join("internal", "cliutil", "probe.go"),
+		"cliutil_ratelimit.go.tmpl":                filepath.Join("internal", "cliutil", "ratelimit.go"),
+		"cliutil_verifyenv.go.tmpl":                filepath.Join("internal", "cliutil", "verifyenv.go"),
+		"cliutil_extractnumber.go.tmpl":            filepath.Join("internal", "cliutil", "extractnumber.go"),
+		"cliutil_extractnumber_test.go.tmpl":       filepath.Join("internal", "cliutil", "extractnumber_test.go"),
+		"cliutil_jwtshape.go.tmpl":                 filepath.Join("internal", "cliutil", "jwtshape.go"),
+		"cliutil_jwtshape_test.go.tmpl":            filepath.Join("internal", "cliutil", "jwtshape_test.go"),
+		"cliutil_test.go.tmpl":                     filepath.Join("internal", "cliutil", "cliutil_test.go"),
+		"cobratree/walker.go.tmpl":                 filepath.Join("internal", "mcp", "cobratree", "walker.go"),
+		"cobratree/classify.go.tmpl":               filepath.Join("internal", "mcp", "cobratree", "classify.go"),
+		"cobratree/typemap.go.tmpl":                filepath.Join("internal", "mcp", "cobratree", "typemap.go"),
+		"cobratree/shellout.go.tmpl":               filepath.Join("internal", "mcp", "cobratree", "shellout.go"),
+		"cobratree/shellout_test.go.tmpl":          filepath.Join("internal", "mcp", "cobratree", "shellout_test.go"),
+		"cobratree/cli_path.go.tmpl":               filepath.Join("internal", "mcp", "cobratree", "cli_path.go"),
+		"cobratree/names.go.tmpl":                  filepath.Join("internal", "mcp", "cobratree", "names.go"),
+		"types.go.tmpl":                            filepath.Join("internal", "types", "types.go"),
+		"golangci.yml.tmpl":                        ".golangci.yml",
+		"readme.md.tmpl":                           "README.md",
+		"agents.md.tmpl":                           "AGENTS.md",
+		"skill.md.tmpl":                            "SKILL.md",
+		"LICENSE.tmpl":                             "LICENSE",
+		"NOTICE.tmpl":                              "NOTICE",
 	}
 
 	for tmplName, outPath := range singleFiles {
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 162f48ea..37330043 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -64,6 +64,7 @@ func TestGenerateProjectsCompile(t *testing.T) {
 		"internal/cliutil/cliutil_test.go",
 		"internal/client/client.go",
 		"internal/client/client_test.go",
+		"internal/client/client_verify_short_circuit_test.go",
 		"internal/config/config.go",
 		"internal/mcp/cobratree/walker.go",
 		"internal/mcp/cobratree/classify.go",
@@ -83,9 +84,9 @@ func TestGenerateProjectsCompile(t *testing.T) {
 		// Bump it AND add to mustInclude above when adding always-emitted
 		// templates. Per-spec dynamic files (per-resource command files,
 		// generated tests) account for the difference between fixtures.
-		{name: "stytch", specPath: filepath.Join("..", "..", "testdata", "stytch.yaml"), expectedFiles: 62},
-		{name: "clerk", specPath: filepath.Join("..", "..", "testdata", "clerk.yaml"), expectedFiles: 67},
-		{name: "loops", specPath: filepath.Join("..", "..", "testdata", "loops.yaml"), expectedFiles: 64},
+		{name: "stytch", specPath: filepath.Join("..", "..", "testdata", "stytch.yaml"), expectedFiles: 63},
+		{name: "clerk", specPath: filepath.Join("..", "..", "testdata", "clerk.yaml"), expectedFiles: 68},
+		{name: "loops", specPath: filepath.Join("..", "..", "testdata", "loops.yaml"), expectedFiles: 65},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/generator/templates/client.go.tmpl b/internal/generator/templates/client.go.tmpl
index 497bb71d..5c909ca8 100644
--- a/internal/generator/templates/client.go.tmpl
+++ b/internal/generator/templates/client.go.tmpl
@@ -569,6 +569,21 @@ func (c *Client) PostWithParamsAndHeaders(path string, params map[string]string,
 	return c.do("POST", path, params, body, headers)
 }
 
+// PostQueryWithParams is a POST that does not mutate remote state — used
+// by read-only operations that ride a mutating verb on the wire (GraphQL
+// queries, JSON-RPC reads, POST-based search endpoints). The verify-mode
+// short-circuit does not fire for these calls; the request reaches the
+// real transport even under PRINTING_PRESS_VERIFY=1 without LIVE_HTTP=1.
+func (c *Client) PostQueryWithParams(path string, params map[string]string, body any) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, nil)
+}
+
+// PostQueryWithParamsAndHeaders is the headers-aware counterpart to
+// PostQueryWithParams. See PostQueryWithParams for the verify-mode rationale.
+func (c *Client) PostQueryWithParamsAndHeaders(path string, params map[string]string, body any, headers map[string]string) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, headers)
+}
+
 {{- if .HasMultipartRequest }}
 func (c *Client) PostMultipart(path string, fields map[string]string, fileFields map[string]string) (json.RawMessage, int, error) {
 	return c.do("POST", path, nil, multipartRequestBody{Fields: fields, FileFields: fileFields}, nil)
@@ -784,9 +799,78 @@ func encodeFormBody(body formRequestBody) ([]byte, string, error) {
 
 {{ end }}
 
+// isMutatingVerb reports whether the HTTP method writes server state.
+// Used by do()'s verify-mode short-circuit to gate dial-out: under
+// PRINTING_PRESS_VERIFY=1 (without LIVE_HTTP=1 opt-in), generated
+// commands must not actually issue mutating requests, even if a
+// handler-level dry-run check was missed.
+func isMutatingVerb(method string) bool {
+	switch method {
+	case "DELETE", "POST", "PUT", "PATCH":
+		return true
+	}
+	return false
+}
+
+// verifyShortCircuitEnvelope returns the synthetic JSON body that
+// stands in for a real mutating response when do() short-circuits in
+// verify mode. The __pp_verify_synthetic__ sentinel is namespace-
+// reserved (no real API uses __pp_*) so downstream consumers
+// (validate-narrative, agent inspections) can key on one obvious field
+// instead of trying to disambiguate common literals like status:"noop".
+// method and path are echoed back as diagnostic prose for human/agent
+// inspection.
+func verifyShortCircuitEnvelope(method, path string) json.RawMessage {
+	body, _ := json.Marshal(map[string]any{
+		"__pp_verify_synthetic__": true,
+		"status":                  "noop",
+		"reason":                  "verify_short_circuit",
+		"method":                  method,
+		"path":                    path,
+	})
+	return json.RawMessage(body)
+}
+
 // do executes an HTTP request. headerOverrides, when non-nil, override global
 // RequiredHeaders for this specific request (used for per-endpoint API versioning).
 func (c *Client) do(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, false)
+}
+
+// doRead is do() minus the verify-mode mutating-verb gate. Used by the
+// PostQuery* family for read-only operations that ride a mutating verb on
+// the wire (GraphQL queries, JSON-RPC reads, POST-based search endpoints).
+// The wire verb is still POST/PUT/PATCH so the server sees a real request,
+// but the verify-mode short-circuit does not fire because the operation
+// does not mutate remote state.
+func (c *Client) doRead(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, true)
+}
+
+// doInternal is the shared implementation behind do() and doRead(). The
+// readOnlyIntent flag is set by doRead() callers (read-only POST/PUT/PATCH
+// operations like GraphQL queries) to skip the mutating-verb verify-mode
+// gate. Plain do() callers leave it false and get the usual short-circuit.
+func (c *Client) doInternal(method, path string, params map[string]string, body any, headerOverrides map[string]string, readOnlyIntent bool) (json.RawMessage, int, error) {
+	// Verify-mode transport-layer gate. When the verifier (or any consumer
+	// that sets PRINTING_PRESS_VERIFY=1) drives a mutating verb without
+	// the LIVE_HTTP=1 opt-in, return a synthetic envelope without dialing,
+	// minting auth, or touching the cache. The verify pipeline itself
+	// sets both env vars in mock mode so its httptest server still sees
+	// real requests; every other consumer gets a safe no-op.
+	//
+	// readOnlyIntent suppresses the gate for read-only operations that
+	// happen to ride a mutating verb on the wire (GraphQL queries, JSON-RPC
+	// reads, POST-based search endpoints). The handler-level annotation
+	// `mcp:read-only: true` drives the codegen choice of doRead() vs do().
+	//
+	// Placement note: this fires BEFORE URL building, auth header
+	// minting, and the success-branch invalidateCache() call below — so
+	// no cache invalidation runs (no remote state changed) and no
+	// client_credentials mint happens unnecessarily.
+	if !readOnlyIntent && isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+		return verifyShortCircuitEnvelope(method, path), http.StatusOK, nil
+	}
 {{- if .EndpointTemplateVars}}
 	// EndpointTemplateVars are declared in the spec — resolve {placeholder}
 	// markers in BaseURL (and, for non-proxy clients, the request path)
diff --git a/internal/generator/templates/client_verify_short_circuit_test.go.tmpl b/internal/generator/templates/client_verify_short_circuit_test.go.tmpl
new file mode 100644
index 00000000..094f4c50
--- /dev/null
+++ b/internal/generator/templates/client_verify_short_circuit_test.go.tmpl
@@ -0,0 +1,168 @@
+// Copyright {{currentYear}} {{.Owner}}. Licensed under Apache-2.0. See LICENSE.
+// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
+
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"net/http"
+	"testing"
+	"time"
+
+	"{{modulePath}}/internal/config"
+)
+
+// recordingRoundTripper counts how many times its RoundTrip method is
+// invoked and returns an empty 200 response. Used by the verify-mode
+// short-circuit tests to assert that the transport layer never dials
+// when the gate fires.
+type recordingRoundTripper struct {
+	calls int
+}
+
+func (r *recordingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	r.calls++
+	return &http.Response{
+		StatusCode: 200,
+		Body:       io.NopCloser(bytes.NewReader([]byte("{}"))),
+		Header:     http.Header{},
+	}, nil
+}
+
+// newClientWithRecorder builds a minimal *Client wired to a recording
+// transport. The Client is constructed through New() so the unexported
+// limiter and cacheDir fields are initialized, then HTTPClient is
+// swapped out for one whose Transport records every call.
+func newClientWithRecorder(t *testing.T) (*Client, *recordingRoundTripper) {
+	t.Helper()
+	rec := &recordingRoundTripper{}
+	cfg := &config.Config{BaseURL: "http://example.test"}
+	c := New(cfg, time.Second, 0)
+	c.HTTPClient = &http.Client{Transport: rec}
+	c.NoCache = true
+	return c, rec
+}
+
+// TestClient_VerifyShortCircuit_MutatingVerbs pins the transport-layer
+// short-circuit emitted by client.go.tmpl. Under PRINTING_PRESS_VERIFY=1
+// with no LIVE_HTTP opt-in, every mutating verb (DELETE/POST/PUT/PATCH)
+// must return a synthetic envelope without dialing.
+//
+// A future template edit that drops the gate, narrows the verb list, or
+// removes either env-var check would silently re-open the agent-readiness
+// gap the gate was added to close. This test fails on any of those
+// drifts as part of every printed CLI's own go-test pass.
+func TestClient_VerifyShortCircuit_MutatingVerbs(t *testing.T) {
+	for _, verb := range []string{"DELETE", "POST", "PUT", "PATCH"} {
+		verb := verb
+		t.Run(verb, func(t *testing.T) {
+			t.Setenv("PRINTING_PRESS_VERIFY", "1")
+			// LIVE_HTTP must NOT be set for the short-circuit branch.
+			// Explicit unset defends against a stale value in the parent env.
+			t.Setenv("PRINTING_PRESS_VERIFY_LIVE_HTTP", "")
+			c, rec := newClientWithRecorder(t)
+
+			body, status, err := c.do(verb, "/test", nil, nil, nil)
+			if err != nil {
+				t.Fatalf("do(%s) returned error: %v", verb, err)
+			}
+			if status != http.StatusOK {
+				t.Fatalf("do(%s) status = %d, want %d", verb, status, http.StatusOK)
+			}
+			if rec.calls != 0 {
+				t.Fatalf("do(%s) attempted %d HTTP calls; want 0 (short-circuit)", verb, rec.calls)
+			}
+
+			var env map[string]any
+			if err := json.Unmarshal(body, &env); err != nil {
+				t.Fatalf("envelope is not valid JSON: %v", err)
+			}
+			if got, _ := env["__pp_verify_synthetic__"].(bool); !got {
+				t.Fatalf("envelope must include __pp_verify_synthetic__: true; got %v", env)
+			}
+			if got, _ := env["reason"].(string); got != "verify_short_circuit" {
+				t.Fatalf("envelope reason = %q, want %q", got, "verify_short_circuit")
+			}
+			if got, _ := env["method"].(string); got != verb {
+				t.Fatalf("envelope method = %q, want %q", got, verb)
+			}
+			if got, _ := env["path"].(string); got != "/test" {
+				t.Fatalf("envelope path = %q, want %q", got, "/test")
+			}
+		})
+	}
+}
+
+// TestClient_VerifyShortCircuit_LiveHTTPOptIn pins the verify mock-mode
+// contract: when LIVE_HTTP=1 is set alongside VERIFY=1, the short-circuit
+// does NOT fire and the transport dials. The verifier's httptest mock
+// server depends on this opt-in path to receive mutating requests so its
+// pass/fail assertions can run against real wire-format responses.
+func TestClient_VerifyShortCircuit_LiveHTTPOptIn(t *testing.T) {
+	t.Setenv("PRINTING_PRESS_VERIFY", "1")
+	t.Setenv("PRINTING_PRESS_VERIFY_LIVE_HTTP", "1")
+	c, rec := newClientWithRecorder(t)
+
+	_, _, _ = c.do("DELETE", "/test", nil, nil, nil)
+
+	if rec.calls < 1 {
+		t.Fatalf("LIVE_HTTP=1 should opt back in to real dial; recorder saw %d calls", rec.calls)
+	}
+}
+
+// TestClient_VerifyShortCircuit_NoEnv pins the operator path: with no
+// verify env vars set, mutating verbs dial normally.
+func TestClient_VerifyShortCircuit_NoEnv(t *testing.T) {
+	// Explicitly unset to defend against test-runner inherited values.
+	t.Setenv("PRINTING_PRESS_VERIFY", "")
+	t.Setenv("PRINTING_PRESS_VERIFY_LIVE_HTTP", "")
+	c, rec := newClientWithRecorder(t)
+
+	_, _, _ = c.do("DELETE", "/test", nil, nil, nil)
+
+	if rec.calls < 1 {
+		t.Fatalf("no verify env should dial normally; recorder saw %d calls", rec.calls)
+	}
+}
+
+// TestClient_VerifyShortCircuit_GETControl pins that the gate is
+// verb-specific: GET requests are never short-circuited, even under
+// PRINTING_PRESS_VERIFY=1, because they cannot mutate remote state.
+// A regression that broadens isMutatingVerb to include GET would break
+// every CLI's cached-fallback and list/show paths under verify mode.
+func TestClient_VerifyShortCircuit_GETControl(t *testing.T) {
+	t.Setenv("PRINTING_PRESS_VERIFY", "1")
+	t.Setenv("PRINTING_PRESS_VERIFY_LIVE_HTTP", "")
+	c, rec := newClientWithRecorder(t)
+
+	_, _, _ = c.do("GET", "/test", nil, nil, nil)
+
+	if rec.calls < 1 {
+		t.Fatalf("GET must never short-circuit; recorder saw %d calls", rec.calls)
+	}
+}
+
+// TestClient_VerifyShortCircuit_ReadOnlyPOST pins the doRead bypass: a
+// POST routed through doRead (the path PostQuery* takes for GraphQL
+// queries, JSON-RPC reads, and POST-based search) must NOT short-circuit
+// under PRINTING_PRESS_VERIFY=1, because the operation does not mutate
+// remote state. Without this, an inherited verify env silently breaks
+// every read on a shared-endpoint API.
+func TestClient_VerifyShortCircuit_ReadOnlyPOST(t *testing.T) {
+	for _, verb := range []string{"POST", "PUT", "PATCH"} {
+		verb := verb
+		t.Run(verb, func(t *testing.T) {
+			t.Setenv("PRINTING_PRESS_VERIFY", "1")
+			t.Setenv("PRINTING_PRESS_VERIFY_LIVE_HTTP", "")
+			c, rec := newClientWithRecorder(t)
+
+			_, _, _ = c.doRead(verb, "/test", nil, nil, nil)
+
+			if rec.calls < 1 {
+				t.Fatalf("doRead(%s) must dial through; recorder saw %d calls", verb, rec.calls)
+			}
+		})
+	}
+}
diff --git a/internal/generator/templates/cliutil_verifyenv.go.tmpl b/internal/generator/templates/cliutil_verifyenv.go.tmpl
index b05f0d20..36a1165d 100644
--- a/internal/generator/templates/cliutil_verifyenv.go.tmpl
+++ b/internal/generator/templates/cliutil_verifyenv.go.tmpl
@@ -10,8 +10,27 @@ import "os"
 // effects (open browser tabs, send notifications, dial out to OS
 // handlers) MUST short-circuit when this env var is "1" to avoid
 // spamming the user's environment during verify runs.
+//
+// The transport layer in internal/client also gates mutating HTTP verbs
+// (DELETE/POST/PUT/PATCH) on this var: under verify mode such requests
+// short-circuit with a synthetic envelope and never dial. The verifier
+// itself opts back in to the real wire path via VerifyLiveHTTPEnvVar
+// so its httptest mock-server flow keeps exercising the real client.
 const VerifyEnvVar = "PRINTING_PRESS_VERIFY"
 
+// VerifyLiveHTTPEnvVar opts a verify-mode subprocess back in to the
+// real HTTP wire path for mutating verbs. It is intentionally
+// asymmetric with VerifyEnvVar: setting LIVE_HTTP=1 alone (with VERIFY
+// unset) has no behavioral effect, because the gate only consults this
+// var when IsVerifyEnv() is also true. The verify pipeline and
+// narrative full-example runner both set BOTH vars in their mock-mode
+// subprocesses (so the httptest server keeps exercising the real wire
+// path); agents and ad-hoc operators leave LIVE_HTTP unset so mutating
+// requests no-op. Live verifiers (live_dogfood, workflow_verify) strip
+// both vars from subprocess env entirely so they cannot inherit a
+// verify-mode short-circuit from the operator's shell.
+const VerifyLiveHTTPEnvVar = "PRINTING_PRESS_VERIFY_LIVE_HTTP"
+
 // DogfoodEnvVar is the env var the printing-press live-dogfood runner
 // sets in every subprocess. Distinct from VerifyEnvVar because dogfood
 // is a real-network matrix: commands may still perform actual API
@@ -36,6 +55,25 @@ func IsVerifyEnv() bool {
 	return os.Getenv(VerifyEnvVar) == "1"
 }
 
+// IsVerifyLiveHTTPEnv reports whether the current process has opted
+// back in to the real HTTP wire path while running under the verifier.
+// Only meaningful when IsVerifyEnv() is also true; on its own this
+// returns true does NOT enable any sandbox behavior — see
+// VerifyLiveHTTPEnvVar's docstring for the asymmetric semantics.
+//
+// The generated client uses this gate as:
+//
+//	if !readOnlyIntent && isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+//	    // synthetic envelope, no network call
+//	}
+//
+// readOnlyIntent is set by Client.doRead() callers (the PostQuery*
+// family used by codegen-marked `mcp:read-only` operations on mutating
+// verbs — GraphQL queries, JSON-RPC reads, POST-based search).
+func IsVerifyLiveHTTPEnv() bool {
+	return os.Getenv(VerifyLiveHTTPEnvVar) == "1"
+}
+
 // IsDogfoodEnv reports whether the current process is running under
 // the printing-press live-dogfood matrix. Long-running commands (full
 // sync loops, content crawlers, bulk archive walks) should use this
diff --git a/internal/generator/templates/command_endpoint.go.tmpl b/internal/generator/templates/command_endpoint.go.tmpl
index 17ea512b..6d2e091d 100644
--- a/internal/generator/templates/command_endpoint.go.tmpl
+++ b/internal/generator/templates/command_endpoint.go.tmpl
@@ -301,11 +301,19 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
 				body = map[string]any{}
 {{bodyMapForEndpoint .Endpoint "\t\t\t\t"}}			}
 {{- if or .Endpoint.HeaderOverrides .Endpoint.UsesBinaryResponse}}
+{{- if .IsReadOnly}}
+			data, statusCode, err := c.PostQueryWithParamsAndHeaders(path, params, body, headerOverrides)
+{{- else}}
 			data, statusCode, err := c.PostWithParamsAndHeaders(path, params, body, headerOverrides)
+{{- end}}
+{{- else}}
+{{- if .IsReadOnly}}
+			data, statusCode, err := c.PostQueryWithParams(path, params, body)
 {{- else}}
 			data, statusCode, err := c.PostWithParams(path, params, body)
 {{- end}}
 {{- end}}
+{{- end}}
 {{- else if eq .Endpoint.Method "DELETE"}}
 			params := map[string]string{}
 {{- range .Endpoint.Params}}
@@ -574,16 +582,6 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
 					}
 					return nil
 				}
-				// Apply --compact and --select to the API response before wrapping.
-				// --select wins when both are set: explicit field choice trumps the
-				// generic high-gravity allow-list. Otherwise --compact still applies
-				// when --agent is on but the user did not name fields.
-				filtered := data
-				if flags.selectFields != "" {
-					filtered = filterFields(filtered, flags.selectFields)
-				} else if flags.compact {
-					filtered = compactFields(filtered)
-				}
 				envelope := map[string]any{
 					"action":   "{{lower .Endpoint.Method}}",
 					"resource": "{{.ResourceName}}",
@@ -599,6 +597,33 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
 					envelope["status"] = 0
 					envelope["success"] = false
 				}
+				// Verify-mode synthetic envelope detection runs against RAW data
+				// (before --compact/--select filtering) so the sentinel field is
+				// guaranteed to be visible even if the operator passes a filter
+				// flag that would otherwise strip it. Surfaces a top-level
+				// verify_noop signal + flips success to false. Mirrors the dry_run
+				// shape above.
+				if len(data) > 0 {
+					var rawParsed any
+					if err := json.Unmarshal(data, &rawParsed); err == nil {
+						if m, ok := rawParsed.(map[string]any); ok {
+							if v, ok := m["__pp_verify_synthetic__"].(bool); ok && v {
+								envelope["verify_noop"] = true
+								envelope["success"] = false
+							}
+						}
+					}
+				}
+				// Apply --compact and --select to the API response before wrapping.
+				// --select wins when both are set: explicit field choice trumps the
+				// generic high-gravity allow-list. Otherwise --compact still applies
+				// when --agent is on but the user did not name fields.
+				filtered := data
+				if flags.selectFields != "" {
+					filtered = filterFields(filtered, flags.selectFields)
+				} else if flags.compact {
+					filtered = compactFields(filtered)
+				}
 				if len(filtered) > 0 {
 					var parsed any
 					if err := json.Unmarshal(filtered, &parsed); err == nil {
diff --git a/internal/generator/templates/doctor.go.tmpl b/internal/generator/templates/doctor.go.tmpl
index f0024ac1..2f11b088 100644
--- a/internal/generator/templates/doctor.go.tmpl
+++ b/internal/generator/templates/doctor.go.tmpl
@@ -46,6 +46,7 @@ import (
 {{- end}}
 
 	"{{modulePath}}/internal/client"
+	"{{modulePath}}/internal/cliutil"
 	"{{modulePath}}/internal/config"
 {{- if .HasStore}}
 	"{{modulePath}}/internal/store"
@@ -481,6 +482,21 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 			report["cache"] = collectCacheReport(cmd.Context(), {{if .Cache.StaleAfter}}"{{.Cache.StaleAfter}}"{{else}}""{{end}})
 {{- end}}
 
+			// Verify mode state. Surfaced so an operator who unintentionally
+			// inherits PRINTING_PRESS_VERIFY=1 (parent shell, CI runner, container
+			// image) detects the foot-gun without inspecting a response body.
+			// Pairs with the synthetic envelope's verify_noop / reason literals
+			// as a second diagnosis anchor.
+			if cliutil.IsVerifyEnv() {
+				if cliutil.IsVerifyLiveHTTPEnv() {
+					report["verify_mode"] = "INFO ACTIVE — live HTTP opt-in (mutating verbs dial out)"
+				} else {
+					report["verify_mode"] = "INFO ACTIVE — mutating HTTP verbs short-circuit (PRINTING_PRESS_VERIFY=1; no network calls for DELETE/POST/PUT/PATCH)"
+				}
+			} else {
+				report["verify_mode"] = "normal operation"
+			}
+
 			report["version"] = version
 
 			if flags.asJSON {
@@ -496,6 +512,7 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 				{"config", "Config"},
 				{"auth", "Auth"},
 				{"env_vars", "Env Vars"},
+				{"verify_mode", "Verify Mode"},
 {{- if .Auth.RequiresBrowserSession}}
 				{"browser_session_proof", "Browser Session Proof"},
 {{- end}}
diff --git a/internal/narrativecheck/narrativecheck.go b/internal/narrativecheck/narrativecheck.go
index 213b6af1..de4d5bb0 100644
--- a/internal/narrativecheck/narrativecheck.go
+++ b/internal/narrativecheck/narrativecheck.go
@@ -309,7 +309,12 @@ func classifyFullExample(ctx context.Context, binaryPath, command string, helpOu
 	}
 
 	cmd := exec.CommandContext(ctx, binaryPath, args...)
-	cmd.Env = append(os.Environ(), "PRINTING_PRESS_VERIFY=1")
+	// Mirror the verify pipeline's mock-mode contract: VERIFY=1 lets
+	// generated commands short-circuit visible side effects, and
+	// VERIFY_LIVE_HTTP=1 opts back in to the real wire path so the
+	// transport-layer mutating-verb gate doesn't collapse narrative
+	// full-example assertions to a synthetic envelope.
+	cmd.Env = append(os.Environ(), "PRINTING_PRESS_VERIFY=1", "PRINTING_PRESS_VERIFY_LIVE_HTTP=1")
 	out, err := cmd.CombinedOutput()
 	if err != nil {
 		r.Status = StatusExampleFailed
diff --git a/internal/pipeline/live_dogfood.go b/internal/pipeline/live_dogfood.go
index 79908103..6146dfd7 100644
--- a/internal/pipeline/live_dogfood.go
+++ b/internal/pipeline/live_dogfood.go
@@ -872,6 +872,11 @@ func runLiveDogfoodProcess(binaryPath, cliDir string, args []string, timeout tim
 	cmd := exec.CommandContext(ctx, binaryPath, args...)
 	cmd.Dir = cliDir
 	applyDefaultSubprocessEnv(cmd)
+	// Strip PRINTING_PRESS_VERIFY{,_LIVE_HTTP} from the subprocess env so an
+	// operator who inherited them from a parent shell, CI runner, or
+	// container image cannot silently noop the destructive live path.
+	// The transport-layer short-circuit is for verify mock-mode only.
+	cmd.Env = filterVerifyEnv(cmd.Env)
 	cmd.Env = append(cmd.Env, dogfoodEnvVar+"=1")
 	stdout := &bytes.Buffer{}
 	stderr := &bytes.Buffer{}
diff --git a/internal/pipeline/regenmerge/classify_test.go b/internal/pipeline/regenmerge/classify_test.go
index 04436027..b2d2f5ec 100644
--- a/internal/pipeline/regenmerge/classify_test.go
+++ b/internal/pipeline/regenmerge/classify_test.go
@@ -120,6 +120,47 @@ type Alias = string
 	assert.Contains(t, decls, "Alias")
 }
 
+// TestClassifyVerifyShortCircuitFixture pins regen-merge classification
+// for the verify-mode HTTP-verb gate. Scenario: an operator has hand-added
+// a custom helper method to internal/client/client.go; the fresh template
+// now includes the new short-circuit helpers (isMutatingVerb,
+// verifyShortCircuitEnvelope) plus the gate inside do(). Because the
+// published file carries a top-level decl absent from fresh, the verdict
+// is TEMPLATED-WITH-ADDITIONS — the merge path that preserves the
+// operator's edit AND applies the new short-circuit. A future classifier
+// regression that re-routed this case through TEMPLATED-VALUE-DRIFT would
+// force manual review on every downstream CLI rolling out the verify-mode
+// change; this fixture catches that drift.
+func TestClassifyVerifyShortCircuitFixture(t *testing.T) {
+	t.Parallel()
+
+	pubDir, freshDir := verifyShortCircuitFixture(t)
+
+	report, err := Classify(pubDir, freshDir, Options{Force: true})
+	require.NoError(t, err)
+	require.NotNil(t, report)
+
+	verdicts := verdictMap(report)
+	got, ok := verdicts["internal/client/client.go"]
+	require.True(t, ok, "client.go must appear in the report; got verdicts: %v", verdicts)
+	assert.Equal(t, VerdictTemplatedWithAdditions, got,
+		"published has the hand-added customAnnotateRequest method; classifier must preserve it via TEMPLATED-WITH-ADDITIONS, not silently overwrite via TEMPLATED-VALUE-DRIFT")
+
+	// The delta should explicitly list the operator's added method so the
+	// merge layer knows which decls to carry forward into the merged file.
+	var clientFC *FileClassification
+	for i := range report.Files {
+		if report.Files[i].Path == "internal/client/client.go" {
+			clientFC = &report.Files[i]
+			break
+		}
+	}
+	require.NotNil(t, clientFC, "client.go must appear in the report")
+	require.NotNil(t, clientFC.DeclSetDelta, "TEMPLATED-WITH-ADDITIONS must populate DeclSetDelta")
+	assert.Contains(t, clientFC.DeclSetDelta.InPublishedNotFresh, "(*Client).customAnnotateRequest",
+		"delta must surface the operator's hand-added method so merge can preserve it")
+}
+
 // TestClassifyRejectsTraversal exercises path-validation per
 // docs/solutions/security-issues/filepath-join-traversal-with-user-input-2026-03-29.md.
 func TestClassifyRejectsTraversal(t *testing.T) {
@@ -202,6 +243,15 @@ func ebayAuthFixture(t *testing.T) (string, string) {
 	return abs, freshAbs
 }
 
+func verifyShortCircuitFixture(t *testing.T) (string, string) {
+	t.Helper()
+	abs, err := filepath.Abs("testdata/verify-short-circuit/published")
+	require.NoError(t, err)
+	freshAbs, err := filepath.Abs("testdata/verify-short-circuit/fresh")
+	require.NoError(t, err)
+	return abs, freshAbs
+}
+
 func verdictMap(r *MergeReport) map[string]Verdict {
 	out := make(map[string]Verdict, len(r.Files))
 	for _, fc := range r.Files {
diff --git a/internal/pipeline/regenmerge/testdata/verify-short-circuit/fresh/go.mod b/internal/pipeline/regenmerge/testdata/verify-short-circuit/fresh/go.mod
new file mode 100644
index 00000000..9488e398
--- /dev/null
+++ b/internal/pipeline/regenmerge/testdata/verify-short-circuit/fresh/go.mod
@@ -0,0 +1,3 @@
+module github.com/example/verify-fixture
+
+go 1.23.0
diff --git a/internal/pipeline/regenmerge/testdata/verify-short-circuit/fresh/internal/client/client.go b/internal/pipeline/regenmerge/testdata/verify-short-circuit/fresh/internal/client/client.go
new file mode 100644
index 00000000..28f5aced
--- /dev/null
+++ b/internal/pipeline/regenmerge/testdata/verify-short-circuit/fresh/internal/client/client.go
@@ -0,0 +1,42 @@
+// Copyright 2026 test-org. Licensed under Apache-2.0. See LICENSE.
+// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
+
+package client
+
+import (
+	"encoding/json"
+	"net/http"
+)
+
+type Client struct {
+	BaseURL    string
+	HTTPClient *http.Client
+}
+
+// isMutatingVerb covers the four mutating HTTP verbs, used by the
+// verify-mode short-circuit gate.
+func isMutatingVerb(method string) bool {
+	switch method {
+	case "DELETE", "POST", "PUT", "PATCH":
+		return true
+	}
+	return false
+}
+
+// verifyShortCircuitEnvelope mints the synthetic verify-mode response.
+func verifyShortCircuitEnvelope(method, path string) json.RawMessage {
+	body := map[string]any{
+		"__pp_verify_synthetic__": true,
+		"status":                  "noop",
+		"reason":                  "verify_short_circuit",
+		"method":                  method,
+		"path":                    path,
+	}
+	b, _ := json.Marshal(body)
+	return b
+}
+
+func (c *Client) do(method, path string) (json.RawMessage, int, error) {
+	// New short-circuit block under verify mode.
+	return nil, 0, nil
+}
diff --git a/internal/pipeline/regenmerge/testdata/verify-short-circuit/published/go.mod b/internal/pipeline/regenmerge/testdata/verify-short-circuit/published/go.mod
new file mode 100644
index 00000000..9488e398
--- /dev/null
+++ b/internal/pipeline/regenmerge/testdata/verify-short-circuit/published/go.mod
@@ -0,0 +1,3 @@
+module github.com/example/verify-fixture
+
+go 1.23.0
diff --git a/internal/pipeline/regenmerge/testdata/verify-short-circuit/published/internal/client/client.go b/internal/pipeline/regenmerge/testdata/verify-short-circuit/published/internal/client/client.go
new file mode 100644
index 00000000..ea986af5
--- /dev/null
+++ b/internal/pipeline/regenmerge/testdata/verify-short-circuit/published/internal/client/client.go
@@ -0,0 +1,25 @@
+// Copyright 2026 test-org. Licensed under Apache-2.0. See LICENSE.
+// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
+
+package client
+
+import (
+	"encoding/json"
+	"net/http"
+)
+
+type Client struct {
+	BaseURL    string
+	HTTPClient *http.Client
+}
+
+// Hand-added by an operator: custom trace-header injector. This represents
+// the kind of pre-existing user edit that regen-merge must preserve when
+// it applies the new short-circuit block in fresh.
+func (c *Client) customAnnotateRequest(req *http.Request) {
+	req.Header.Set("X-Custom-Trace", "operator-edit-2026")
+}
+
+func (c *Client) do(method, path string) (json.RawMessage, int, error) {
+	return nil, 0, nil
+}
diff --git a/internal/pipeline/runtime.go b/internal/pipeline/runtime.go
index 58f54375..a90f8c09 100644
--- a/internal/pipeline/runtime.go
+++ b/internal/pipeline/runtime.go
@@ -269,6 +269,15 @@ func RunVerify(cfg VerifyConfig) (*VerifyReport, error) {
 			// verify. Documented in skills/printing-press/SKILL.md and
 			// AGENTS.md.
 			env = append(env, "PRINTING_PRESS_VERIFY=1")
+			// Verify owns its httptest mock-server and needs the real
+			// wire path to assert against, so it opts back in to the
+			// transport-layer mutating-verb gate that every other
+			// consumer leaves engaged. Without this var, the gate in
+			// generated Client.do() returns a synthetic envelope for
+			// DELETE/POST/PUT/PATCH and the mock server never sees the
+			// request — collapsing verify's pass-rate signal to zero
+			// for those verbs.
+			env = append(env, "PRINTING_PRESS_VERIFY_LIVE_HTTP=1")
 		}
 		return env
 	}
diff --git a/internal/pipeline/verify_env_filter.go b/internal/pipeline/verify_env_filter.go
new file mode 100644
index 00000000..14f6053d
--- /dev/null
+++ b/internal/pipeline/verify_env_filter.go
@@ -0,0 +1,38 @@
+package pipeline
+
+import (
+	"strings"
+)
+
+// filterVerifyEnv returns a copy of env with every entry whose key matches a
+// verify-mode env var dropped. Live verification runners (live_dogfood,
+// workflow_verify) use this to construct subprocess envs that cannot be
+// short-circuited by PRINTING_PRESS_VERIFY=1 inherited from the operator's
+// shell.
+//
+// The transport-layer short-circuit added in `client.go.tmpl` short-circuits
+// mutating HTTP verbs when PRINTING_PRESS_VERIFY=1 is set. That is the
+// correct behavior for verify-mode mock-mode runs. But the live verifiers
+// exercise real (or near-real) destructive behavior and must not silently
+// noop when the operator happens to have PRINTING_PRESS_VERIFY=1 in their
+// shell (parent process, CI runner, container image). Stripping both vars
+// at the exec boundary is defense in depth.
+//
+// Filter is exact-key match on the bytes before the first '=' to avoid
+// accidentally dropping unrelated env vars whose values contain the literal
+// string.
+func filterVerifyEnv(env []string) []string {
+	out := make([]string, 0, len(env))
+	for _, entry := range env {
+		key, _, found := strings.Cut(entry, "=")
+		if !found {
+			out = append(out, entry)
+			continue
+		}
+		if key == "PRINTING_PRESS_VERIFY" || key == "PRINTING_PRESS_VERIFY_LIVE_HTTP" {
+			continue
+		}
+		out = append(out, entry)
+	}
+	return out
+}
diff --git a/internal/pipeline/verify_env_filter_test.go b/internal/pipeline/verify_env_filter_test.go
new file mode 100644
index 00000000..f59854b3
--- /dev/null
+++ b/internal/pipeline/verify_env_filter_test.go
@@ -0,0 +1,74 @@
+package pipeline
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// TestFilterVerifyEnv pins the contract that the two verify env vars
+// (PRINTING_PRESS_VERIFY and PRINTING_PRESS_VERIFY_LIVE_HTTP) are stripped
+// from subprocess env, and every other env var survives. Failure mode if
+// dropped: live verifiers silently noop on destructive paths when an
+// operator has PRINTING_PRESS_VERIFY=1 inherited.
+func TestFilterVerifyEnv(t *testing.T) {
+	t.Parallel()
+
+	t.Run("strips both verify vars", func(t *testing.T) {
+		in := []string{
+			"PATH=/usr/bin:/bin",
+			"PRINTING_PRESS_VERIFY=1",
+			"HOME=/home/op",
+			"PRINTING_PRESS_VERIFY_LIVE_HTTP=1",
+			"PETSTORE_TOKEN=abc123",
+		}
+		out := filterVerifyEnv(in)
+		assert.NotContains(t, out, "PRINTING_PRESS_VERIFY=1")
+		assert.NotContains(t, out, "PRINTING_PRESS_VERIFY_LIVE_HTTP=1")
+		assert.Contains(t, out, "PATH=/usr/bin:/bin")
+		assert.Contains(t, out, "HOME=/home/op")
+		assert.Contains(t, out, "PETSTORE_TOKEN=abc123")
+		assert.Len(t, out, 3, "exactly the 2 verify vars should be dropped, leaving 3 of 5")
+	})
+
+	t.Run("strips verify vars regardless of value", func(t *testing.T) {
+		// Any value, not just "1" — operator might have stale ="0" or
+		// "true" or empty. Strip on key match.
+		in := []string{
+			"PRINTING_PRESS_VERIFY=",
+			"PRINTING_PRESS_VERIFY=true",
+			"PRINTING_PRESS_VERIFY_LIVE_HTTP=0",
+		}
+		out := filterVerifyEnv(in)
+		assert.Empty(t, out, "all verify-keyed entries should be stripped regardless of value")
+	})
+
+	t.Run("does not strip entries whose values contain the var names", func(t *testing.T) {
+		// Defense against substring-match bugs: a real env var like
+		// CUSTOM_NOTE="see PRINTING_PRESS_VERIFY docs" must survive.
+		in := []string{
+			"CUSTOM_NOTE=see PRINTING_PRESS_VERIFY docs",
+			"PRINTING_PRESS_VERIFY_OTHER=should-survive",
+			"OTHER=PRINTING_PRESS_VERIFY_LIVE_HTTP=1",
+		}
+		out := filterVerifyEnv(in)
+		assert.Len(t, out, 3, "exact-key match must not drop entries whose values mention the var names")
+		assert.Contains(t, out, "CUSTOM_NOTE=see PRINTING_PRESS_VERIFY docs")
+		assert.Contains(t, out, "PRINTING_PRESS_VERIFY_OTHER=should-survive")
+		assert.Contains(t, out, "OTHER=PRINTING_PRESS_VERIFY_LIVE_HTTP=1")
+	})
+
+	t.Run("handles malformed entries gracefully", func(t *testing.T) {
+		// Entries without '=' are unusual but should pass through, not crash.
+		in := []string{"PATH=/usr/bin", "MALFORMED_NO_EQUALS", "PRINTING_PRESS_VERIFY=1"}
+		out := filterVerifyEnv(in)
+		assert.Contains(t, out, "PATH=/usr/bin")
+		assert.Contains(t, out, "MALFORMED_NO_EQUALS")
+		assert.NotContains(t, out, "PRINTING_PRESS_VERIFY=1")
+	})
+
+	t.Run("empty input", func(t *testing.T) {
+		out := filterVerifyEnv(nil)
+		assert.Empty(t, out)
+	})
+}
diff --git a/internal/pipeline/workflow_verify.go b/internal/pipeline/workflow_verify.go
index c6d56664..2eea291d 100644
--- a/internal/pipeline/workflow_verify.go
+++ b/internal/pipeline/workflow_verify.go
@@ -153,6 +153,10 @@ func executeStep(binary string, step WorkflowStep, cmdExpanded string, dir strin
 		cmd := exec.CommandContext(ctx, binary, args...)
 		cmd.Dir = dir
 		applyDefaultSubprocessEnv(cmd)
+		// Strip verify-mode env from the subprocess so an inherited
+		// PRINTING_PRESS_VERIFY=1 cannot short-circuit the live workflow
+		// path. See internal/pipeline/verify_env_filter.go for rationale.
+		cmd.Env = filterVerifyEnv(cmd.Env)
 		out, err := cmd.CombinedOutput()
 		cancel()
 
diff --git a/scripts/golden.sh b/scripts/golden.sh
index f5606720..3d859d8b 100755
--- a/scripts/golden.sh
+++ b/scripts/golden.sh
@@ -10,6 +10,17 @@ fi
 repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
 cd "$repo_root"
 
+# Resolve a Python 3 interpreter. Linux/macOS contributors typically have
+# `python3` on PATH; Windows / Git Bash contributors typically have `python`.
+if command -v python3 >/dev/null 2>&1; then
+  PYTHON=python3
+elif command -v python >/dev/null 2>&1; then
+  PYTHON=python
+else
+  echo "scripts/golden.sh requires python (python3 or python) on PATH" >&2
+  exit 1
+fi
+
 binary="./printing-press"
 cases_root="testdata/golden/cases"
 expected_root="testdata/golden/expected"
@@ -28,12 +39,22 @@ actual_abs_pattern="$(escape_sed "$actual_abs")"
 
 # Keep normalization intentionally narrow. These substitutions remove
 # machine-specific paths while preserving behaviorally meaningful output.
+#
+# The sed step handles POSIX-form paths (forward slashes). When the generator
+# runs on Windows it emits absolute paths with backslashes (and JSON files
+# contain the double-escaped form). The Python post-pass adds Windows-form
+# substitutions + normalizes trailing backslashes within tokenized segments
+# to forward slashes + strips the .exe suffix so goldens stay portable across
+# Linux/macOS/Windows contributors. See scripts/golden_normalize_windows.py
+# for the rationale.
 normalize_text() {
   sed \
     -e "s|$actual_abs_pattern|<ARTIFACT_DIR>|g" \
     -e "s|$actual_root_pattern|<ARTIFACT_DIR>|g" \
     -e "s|$repo_root_pattern|<REPO>|g" \
-    -e "s|$home_pattern|<HOME>|g"
+    -e "s|$home_pattern|<HOME>|g" |
+    "$PYTHON" "$repo_root/scripts/golden_normalize_windows.py" \
+      "$actual_abs" "$actual_root" "$repo_root" "$HOME"
 }
 
 normalize_json() {
diff --git a/scripts/golden_normalize_windows.py b/scripts/golden_normalize_windows.py
new file mode 100644
index 00000000..2f826481
--- /dev/null
+++ b/scripts/golden_normalize_windows.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+"""Post-pass for scripts/golden.sh normalize_text on Windows.
+
+The sed-based normalize_text in golden.sh only handles POSIX-form paths
+(forward slashes). When the generator runs on Windows it emits absolute
+paths with backslashes, and the same path appears two ways in captured
+output:
+
+  - stderr.txt: `C:\\Users\\name\\repo\\.gotmp\\golden\\actual\\...`
+    (single backslashes between segments; this is the literal byte
+    sequence in the file)
+  - JSON files: `C:\\\\Users\\\\name\\\\repo\\\\.gotmp\\\\golden\\\\actual\\\\...`
+    (json.dumps doubles each backslash, so the literal bytes contain
+    two backslashes per separator)
+
+Both forms slip past the sed substitution and leave Windows absolute
+paths in expected/ goldens, which then fail byte-for-byte comparison
+on Linux CI.
+
+This script reads stdin, takes four path arguments (actual_abs,
+actual_root, repo_root, HOME) as the script does in bash, and emits
+stdout with the Windows-form variants of those paths replaced by the
+same <ARTIFACT_DIR> / <REPO> / <HOME> tokens. It also normalizes
+backslash separators inside tokenized path segments to forward slash
+so goldens are portable, and strips the .exe suffix on tokenized
+binary paths so Linux CI (which builds ELF binaries with no extension)
+matches.
+
+POSIX-form substitution still happens in the sed pipeline upstream;
+this is purely additive.
+"""
+import os
+import re
+import sys
+
+
+def windows_variants(p: str) -> list[str]:
+    """Backslash-form variants of a POSIX-style path."""
+    if not p:
+        return []
+    win = p.replace("/", "\\")
+    win_json = win.replace("\\", "\\\\")
+    return [win_json, win]  # JSON-escaped first (longer prefix wins)
+
+
+def main() -> int:
+    if len(sys.argv) != 5:
+        sys.stderr.write(
+            "usage: golden_normalize_windows.py <actual_abs> <actual_root> <repo_root> <home>\n"
+        )
+        return 2
+
+    actual_abs, actual_root, repo_root, home = sys.argv[1:]
+    text = sys.stdin.read()
+
+    for variant in windows_variants(actual_abs):
+        text = text.replace(variant, "<ARTIFACT_DIR>")
+    for variant in windows_variants(actual_root):
+        text = text.replace(variant, "<ARTIFACT_DIR>")
+    for variant in windows_variants(repo_root):
+        text = text.replace(variant, "<REPO>")
+    for variant in windows_variants(home):
+        text = text.replace(variant, "<HOME>")
+
+    # Collapse any run of backslashes to a single forward slash. JSON-string
+    # literals encode each path separator as two backslash chars (`\\`); a
+    # naive char-by-char replace would emit `//` instead of `/` (Greptile P1
+    # on PR #1398 — Linux CI compares against single-slash tokens).
+    text = re.sub(
+        r"(<(?:ARTIFACT_DIR|REPO|HOME)>)((?:\\+[A-Za-z0-9._\-]+)+)",
+        lambda m: m.group(1) + re.sub(r"\\+", "/", m.group(2)),
+        text,
+    )
+    text = re.sub(r"(<(?:ARTIFACT_DIR|REPO)>/[A-Za-z0-9._\-/]+)\.exe", r"\1", text)
+
+    sys.stdout.write(text)
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go b/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go
index 41e42318..3884fc83 100644
--- a/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go
+++ b/testdata/golden/expected/generate-golden-api-oauth2-cc/printing-press-oauth2-cc/internal/client/client.go
@@ -220,6 +220,21 @@ func (c *Client) PostWithParamsAndHeaders(path string, params map[string]string,
 	return c.do("POST", path, params, body, headers)
 }
 
+// PostQueryWithParams is a POST that does not mutate remote state — used
+// by read-only operations that ride a mutating verb on the wire (GraphQL
+// queries, JSON-RPC reads, POST-based search endpoints). The verify-mode
+// short-circuit does not fire for these calls; the request reaches the
+// real transport even under PRINTING_PRESS_VERIFY=1 without LIVE_HTTP=1.
+func (c *Client) PostQueryWithParams(path string, params map[string]string, body any) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, nil)
+}
+
+// PostQueryWithParamsAndHeaders is the headers-aware counterpart to
+// PostQueryWithParams. See PostQueryWithParams for the verify-mode rationale.
+func (c *Client) PostQueryWithParamsAndHeaders(path string, params map[string]string, body any, headers map[string]string) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, headers)
+}
+
 func (c *Client) Delete(path string) (json.RawMessage, int, error) {
 	return c.do("DELETE", path, nil, nil, nil)
 }
@@ -268,9 +283,78 @@ func (c *Client) PatchWithParamsAndHeaders(path string, params map[string]string
 	return c.do("PATCH", path, params, body, headers)
 }
 
+// isMutatingVerb reports whether the HTTP method writes server state.
+// Used by do()'s verify-mode short-circuit to gate dial-out: under
+// PRINTING_PRESS_VERIFY=1 (without LIVE_HTTP=1 opt-in), generated
+// commands must not actually issue mutating requests, even if a
+// handler-level dry-run check was missed.
+func isMutatingVerb(method string) bool {
+	switch method {
+	case "DELETE", "POST", "PUT", "PATCH":
+		return true
+	}
+	return false
+}
+
+// verifyShortCircuitEnvelope returns the synthetic JSON body that
+// stands in for a real mutating response when do() short-circuits in
+// verify mode. The __pp_verify_synthetic__ sentinel is namespace-
+// reserved (no real API uses __pp_*) so downstream consumers
+// (validate-narrative, agent inspections) can key on one obvious field
+// instead of trying to disambiguate common literals like status:"noop".
+// method and path are echoed back as diagnostic prose for human/agent
+// inspection.
+func verifyShortCircuitEnvelope(method, path string) json.RawMessage {
+	body, _ := json.Marshal(map[string]any{
+		"__pp_verify_synthetic__": true,
+		"status":                  "noop",
+		"reason":                  "verify_short_circuit",
+		"method":                  method,
+		"path":                    path,
+	})
+	return json.RawMessage(body)
+}
+
 // do executes an HTTP request. headerOverrides, when non-nil, override global
 // RequiredHeaders for this specific request (used for per-endpoint API versioning).
 func (c *Client) do(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, false)
+}
+
+// doRead is do() minus the verify-mode mutating-verb gate. Used by the
+// PostQuery* family for read-only operations that ride a mutating verb on
+// the wire (GraphQL queries, JSON-RPC reads, POST-based search endpoints).
+// The wire verb is still POST/PUT/PATCH so the server sees a real request,
+// but the verify-mode short-circuit does not fire because the operation
+// does not mutate remote state.
+func (c *Client) doRead(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, true)
+}
+
+// doInternal is the shared implementation behind do() and doRead(). The
+// readOnlyIntent flag is set by doRead() callers (read-only POST/PUT/PATCH
+// operations like GraphQL queries) to skip the mutating-verb verify-mode
+// gate. Plain do() callers leave it false and get the usual short-circuit.
+func (c *Client) doInternal(method, path string, params map[string]string, body any, headerOverrides map[string]string, readOnlyIntent bool) (json.RawMessage, int, error) {
+	// Verify-mode transport-layer gate. When the verifier (or any consumer
+	// that sets PRINTING_PRESS_VERIFY=1) drives a mutating verb without
+	// the LIVE_HTTP=1 opt-in, return a synthetic envelope without dialing,
+	// minting auth, or touching the cache. The verify pipeline itself
+	// sets both env vars in mock mode so its httptest server still sees
+	// real requests; every other consumer gets a safe no-op.
+	//
+	// readOnlyIntent suppresses the gate for read-only operations that
+	// happen to ride a mutating verb on the wire (GraphQL queries, JSON-RPC
+	// reads, POST-based search endpoints). The handler-level annotation
+	// `mcp:read-only: true` drives the codegen choice of doRead() vs do().
+	//
+	// Placement note: this fires BEFORE URL building, auth header
+	// minting, and the success-branch invalidateCache() call below — so
+	// no cache invalidation runs (no remote state changed) and no
+	// client_credentials mint happens unnecessarily.
+	if !readOnlyIntent && isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+		return verifyShortCircuitEnvelope(method, path), http.StatusOK, nil
+	}
 	targetURL := c.BaseURL + path
 
 	var bodyBytes []byte
diff --git a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/doctor.go b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/doctor.go
index 4698fc28..dd5b2e38 100644
--- a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/doctor.go
+++ b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/internal/cli/doctor.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"printing-press-rich-pp-cli/internal/client"
+	"printing-press-rich-pp-cli/internal/cliutil"
 	"printing-press-rich-pp-cli/internal/config"
 	"printing-press-rich-pp-cli/internal/store"
 )
@@ -246,6 +247,21 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 			// whether to trust the cached data before issuing queries.
 			report["cache"] = collectCacheReport(cmd.Context(), "")
 
+			// Verify mode state. Surfaced so an operator who unintentionally
+			// inherits PRINTING_PRESS_VERIFY=1 (parent shell, CI runner, container
+			// image) detects the foot-gun without inspecting a response body.
+			// Pairs with the synthetic envelope's verify_noop / reason literals
+			// as a second diagnosis anchor.
+			if cliutil.IsVerifyEnv() {
+				if cliutil.IsVerifyLiveHTTPEnv() {
+					report["verify_mode"] = "INFO ACTIVE — live HTTP opt-in (mutating verbs dial out)"
+				} else {
+					report["verify_mode"] = "INFO ACTIVE — mutating HTTP verbs short-circuit (PRINTING_PRESS_VERIFY=1; no network calls for DELETE/POST/PUT/PATCH)"
+				}
+			} else {
+				report["verify_mode"] = "normal operation"
+			}
+
 			report["version"] = version
 
 			if flags.asJSON {
@@ -261,6 +277,7 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 				{"config", "Config"},
 				{"auth", "Auth"},
 				{"env_vars", "Env Vars"},
+				{"verify_mode", "Verify Mode"},
 				{"api", "API"},
 				{"credentials", "Credentials"},
 			}
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go
index 065df457..7d2cd071 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"printing-press-golden-pp-cli/internal/client"
+	"printing-press-golden-pp-cli/internal/cliutil"
 	"printing-press-golden-pp-cli/internal/config"
 	"printing-press-golden-pp-cli/internal/store"
 )
@@ -194,6 +195,21 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 			// whether to trust the cached data before issuing queries.
 			report["cache"] = collectCacheReport(cmd.Context(), "")
 
+			// Verify mode state. Surfaced so an operator who unintentionally
+			// inherits PRINTING_PRESS_VERIFY=1 (parent shell, CI runner, container
+			// image) detects the foot-gun without inspecting a response body.
+			// Pairs with the synthetic envelope's verify_noop / reason literals
+			// as a second diagnosis anchor.
+			if cliutil.IsVerifyEnv() {
+				if cliutil.IsVerifyLiveHTTPEnv() {
+					report["verify_mode"] = "INFO ACTIVE — live HTTP opt-in (mutating verbs dial out)"
+				} else {
+					report["verify_mode"] = "INFO ACTIVE — mutating HTTP verbs short-circuit (PRINTING_PRESS_VERIFY=1; no network calls for DELETE/POST/PUT/PATCH)"
+				}
+			} else {
+				report["verify_mode"] = "normal operation"
+			}
+
 			report["version"] = version
 
 			if flags.asJSON {
@@ -209,6 +225,7 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 				{"config", "Config"},
 				{"auth", "Auth"},
 				{"env_vars", "Env Vars"},
+				{"verify_mode", "Verify Mode"},
 				{"api", "API"},
 				{"credentials", "Credentials"},
 			}
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_avatar_upload-project.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_avatar_upload-project.go
index 7727b02d..6f8bf94e 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_avatar_upload-project.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_avatar_upload-project.go
@@ -103,16 +103,6 @@ func newProjectsAvatarUploadProjectCmd(flags *rootFlags) *cobra.Command {
 					}
 					return nil
 				}
-				// Apply --compact and --select to the API response before wrapping.
-				// --select wins when both are set: explicit field choice trumps the
-				// generic high-gravity allow-list. Otherwise --compact still applies
-				// when --agent is on but the user did not name fields.
-				filtered := data
-				if flags.selectFields != "" {
-					filtered = filterFields(filtered, flags.selectFields)
-				} else if flags.compact {
-					filtered = compactFields(filtered)
-				}
 				envelope := map[string]any{
 					"action":   "put",
 					"resource": "avatar",
@@ -128,6 +118,33 @@ func newProjectsAvatarUploadProjectCmd(flags *rootFlags) *cobra.Command {
 					envelope["status"] = 0
 					envelope["success"] = false
 				}
+				// Verify-mode synthetic envelope detection runs against RAW data
+				// (before --compact/--select filtering) so the sentinel field is
+				// guaranteed to be visible even if the operator passes a filter
+				// flag that would otherwise strip it. Surfaces a top-level
+				// verify_noop signal + flips success to false. Mirrors the dry_run
+				// shape above.
+				if len(data) > 0 {
+					var rawParsed any
+					if err := json.Unmarshal(data, &rawParsed); err == nil {
+						if m, ok := rawParsed.(map[string]any); ok {
+							if v, ok := m["__pp_verify_synthetic__"].(bool); ok && v {
+								envelope["verify_noop"] = true
+								envelope["success"] = false
+							}
+						}
+					}
+				}
+				// Apply --compact and --select to the API response before wrapping.
+				// --select wins when both are set: explicit field choice trumps the
+				// generic high-gravity allow-list. Otherwise --compact still applies
+				// when --agent is on but the user did not name fields.
+				filtered := data
+				if flags.selectFields != "" {
+					filtered = filterFields(filtered, flags.selectFields)
+				} else if flags.compact {
+					filtered = compactFields(filtered)
+				}
 				if len(filtered) > 0 {
 					var parsed any
 					if err := json.Unmarshal(filtered, &parsed); err == nil {
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_create.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_create.go
index fd0ca326..72dfb1ba 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_create.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_create.go
@@ -119,16 +119,6 @@ func newProjectsCreateCmd(flags *rootFlags) *cobra.Command {
 					}
 					return nil
 				}
-				// Apply --compact and --select to the API response before wrapping.
-				// --select wins when both are set: explicit field choice trumps the
-				// generic high-gravity allow-list. Otherwise --compact still applies
-				// when --agent is on but the user did not name fields.
-				filtered := data
-				if flags.selectFields != "" {
-					filtered = filterFields(filtered, flags.selectFields)
-				} else if flags.compact {
-					filtered = compactFields(filtered)
-				}
 				envelope := map[string]any{
 					"action":   "post",
 					"resource": "projects",
@@ -144,6 +134,33 @@ func newProjectsCreateCmd(flags *rootFlags) *cobra.Command {
 					envelope["status"] = 0
 					envelope["success"] = false
 				}
+				// Verify-mode synthetic envelope detection runs against RAW data
+				// (before --compact/--select filtering) so the sentinel field is
+				// guaranteed to be visible even if the operator passes a filter
+				// flag that would otherwise strip it. Surfaces a top-level
+				// verify_noop signal + flips success to false. Mirrors the dry_run
+				// shape above.
+				if len(data) > 0 {
+					var rawParsed any
+					if err := json.Unmarshal(data, &rawParsed); err == nil {
+						if m, ok := rawParsed.(map[string]any); ok {
+							if v, ok := m["__pp_verify_synthetic__"].(bool); ok && v {
+								envelope["verify_noop"] = true
+								envelope["success"] = false
+							}
+						}
+					}
+				}
+				// Apply --compact and --select to the API response before wrapping.
+				// --select wins when both are set: explicit field choice trumps the
+				// generic high-gravity allow-list. Otherwise --compact still applies
+				// when --agent is on but the user did not name fields.
+				filtered := data
+				if flags.selectFields != "" {
+					filtered = filterFields(filtered, flags.selectFields)
+				} else if flags.compact {
+					filtered = compactFields(filtered)
+				}
 				if len(filtered) > 0 {
 					var parsed any
 					if err := json.Unmarshal(filtered, &parsed); err == nil {
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_update-project.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_update-project.go
index fadac499..036a5689 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_update-project.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_update-project.go
@@ -126,16 +126,6 @@ func newProjectsTasksUpdateProjectCmd(flags *rootFlags) *cobra.Command {
 					}
 					return nil
 				}
-				// Apply --compact and --select to the API response before wrapping.
-				// --select wins when both are set: explicit field choice trumps the
-				// generic high-gravity allow-list. Otherwise --compact still applies
-				// when --agent is on but the user did not name fields.
-				filtered := data
-				if flags.selectFields != "" {
-					filtered = filterFields(filtered, flags.selectFields)
-				} else if flags.compact {
-					filtered = compactFields(filtered)
-				}
 				envelope := map[string]any{
 					"action":   "patch",
 					"resource": "tasks",
@@ -151,6 +141,33 @@ func newProjectsTasksUpdateProjectCmd(flags *rootFlags) *cobra.Command {
 					envelope["status"] = 0
 					envelope["success"] = false
 				}
+				// Verify-mode synthetic envelope detection runs against RAW data
+				// (before --compact/--select filtering) so the sentinel field is
+				// guaranteed to be visible even if the operator passes a filter
+				// flag that would otherwise strip it. Surfaces a top-level
+				// verify_noop signal + flips success to false. Mirrors the dry_run
+				// shape above.
+				if len(data) > 0 {
+					var rawParsed any
+					if err := json.Unmarshal(data, &rawParsed); err == nil {
+						if m, ok := rawParsed.(map[string]any); ok {
+							if v, ok := m["__pp_verify_synthetic__"].(bool); ok && v {
+								envelope["verify_noop"] = true
+								envelope["success"] = false
+							}
+						}
+					}
+				}
+				// Apply --compact and --select to the API response before wrapping.
+				// --select wins when both are set: explicit field choice trumps the
+				// generic high-gravity allow-list. Otherwise --compact still applies
+				// when --agent is on but the user did not name fields.
+				filtered := data
+				if flags.selectFields != "" {
+					filtered = filterFields(filtered, flags.selectFields)
+				} else if flags.compact {
+					filtered = compactFields(filtered)
+				}
 				if len(filtered) > 0 {
 					var parsed any
 					if err := json.Unmarshal(filtered, &parsed); err == nil {
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/client/client.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/client/client.go
index 29132e66..9d260005 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/client/client.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/client/client.go
@@ -213,6 +213,21 @@ func (c *Client) PostWithHeaders(path string, body any, headers map[string]strin
 func (c *Client) PostWithParamsAndHeaders(path string, params map[string]string, body any, headers map[string]string) (json.RawMessage, int, error) {
 	return c.do("POST", path, params, body, headers)
 }
+
+// PostQueryWithParams is a POST that does not mutate remote state — used
+// by read-only operations that ride a mutating verb on the wire (GraphQL
+// queries, JSON-RPC reads, POST-based search endpoints). The verify-mode
+// short-circuit does not fire for these calls; the request reaches the
+// real transport even under PRINTING_PRESS_VERIFY=1 without LIVE_HTTP=1.
+func (c *Client) PostQueryWithParams(path string, params map[string]string, body any) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, nil)
+}
+
+// PostQueryWithParamsAndHeaders is the headers-aware counterpart to
+// PostQueryWithParams. See PostQueryWithParams for the verify-mode rationale.
+func (c *Client) PostQueryWithParamsAndHeaders(path string, params map[string]string, body any, headers map[string]string) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, headers)
+}
 func (c *Client) PostMultipart(path string, fields map[string]string, fileFields map[string]string) (json.RawMessage, int, error) {
 	return c.do("POST", path, nil, multipartRequestBody{Fields: fields, FileFields: fileFields}, nil)
 }
@@ -349,9 +364,78 @@ func encodeMultipartBody(body multipartRequestBody) ([]byte, string, error) {
 	return buf.Bytes(), writer.FormDataContentType(), nil
 }
 
+// isMutatingVerb reports whether the HTTP method writes server state.
+// Used by do()'s verify-mode short-circuit to gate dial-out: under
+// PRINTING_PRESS_VERIFY=1 (without LIVE_HTTP=1 opt-in), generated
+// commands must not actually issue mutating requests, even if a
+// handler-level dry-run check was missed.
+func isMutatingVerb(method string) bool {
+	switch method {
+	case "DELETE", "POST", "PUT", "PATCH":
+		return true
+	}
+	return false
+}
+
+// verifyShortCircuitEnvelope returns the synthetic JSON body that
+// stands in for a real mutating response when do() short-circuits in
+// verify mode. The __pp_verify_synthetic__ sentinel is namespace-
+// reserved (no real API uses __pp_*) so downstream consumers
+// (validate-narrative, agent inspections) can key on one obvious field
+// instead of trying to disambiguate common literals like status:"noop".
+// method and path are echoed back as diagnostic prose for human/agent
+// inspection.
+func verifyShortCircuitEnvelope(method, path string) json.RawMessage {
+	body, _ := json.Marshal(map[string]any{
+		"__pp_verify_synthetic__": true,
+		"status":                  "noop",
+		"reason":                  "verify_short_circuit",
+		"method":                  method,
+		"path":                    path,
+	})
+	return json.RawMessage(body)
+}
+
 // do executes an HTTP request. headerOverrides, when non-nil, override global
 // RequiredHeaders for this specific request (used for per-endpoint API versioning).
 func (c *Client) do(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, false)
+}
+
+// doRead is do() minus the verify-mode mutating-verb gate. Used by the
+// PostQuery* family for read-only operations that ride a mutating verb on
+// the wire (GraphQL queries, JSON-RPC reads, POST-based search endpoints).
+// The wire verb is still POST/PUT/PATCH so the server sees a real request,
+// but the verify-mode short-circuit does not fire because the operation
+// does not mutate remote state.
+func (c *Client) doRead(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, true)
+}
+
+// doInternal is the shared implementation behind do() and doRead(). The
+// readOnlyIntent flag is set by doRead() callers (read-only POST/PUT/PATCH
+// operations like GraphQL queries) to skip the mutating-verb verify-mode
+// gate. Plain do() callers leave it false and get the usual short-circuit.
+func (c *Client) doInternal(method, path string, params map[string]string, body any, headerOverrides map[string]string, readOnlyIntent bool) (json.RawMessage, int, error) {
+	// Verify-mode transport-layer gate. When the verifier (or any consumer
+	// that sets PRINTING_PRESS_VERIFY=1) drives a mutating verb without
+	// the LIVE_HTTP=1 opt-in, return a synthetic envelope without dialing,
+	// minting auth, or touching the cache. The verify pipeline itself
+	// sets both env vars in mock mode so its httptest server still sees
+	// real requests; every other consumer gets a safe no-op.
+	//
+	// readOnlyIntent suppresses the gate for read-only operations that
+	// happen to ride a mutating verb on the wire (GraphQL queries, JSON-RPC
+	// reads, POST-based search endpoints). The handler-level annotation
+	// `mcp:read-only: true` drives the codegen choice of doRead() vs do().
+	//
+	// Placement note: this fires BEFORE URL building, auth header
+	// minting, and the success-branch invalidateCache() call below — so
+	// no cache invalidation runs (no remote state changed) and no
+	// client_credentials mint happens unnecessarily.
+	if !readOnlyIntent && isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+		return verifyShortCircuitEnvelope(method, path), http.StatusOK, nil
+	}
 	targetURL := c.BaseURL + path
 
 	var bodyBytes []byte
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cliutil/verifyenv.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cliutil/verifyenv.go
index f18bb240..3443bd7c 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cliutil/verifyenv.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cliutil/verifyenv.go
@@ -10,8 +10,27 @@ import "os"
 // effects (open browser tabs, send notifications, dial out to OS
 // handlers) MUST short-circuit when this env var is "1" to avoid
 // spamming the user's environment during verify runs.
+//
+// The transport layer in internal/client also gates mutating HTTP verbs
+// (DELETE/POST/PUT/PATCH) on this var: under verify mode such requests
+// short-circuit with a synthetic envelope and never dial. The verifier
+// itself opts back in to the real wire path via VerifyLiveHTTPEnvVar
+// so its httptest mock-server flow keeps exercising the real client.
 const VerifyEnvVar = "PRINTING_PRESS_VERIFY"
 
+// VerifyLiveHTTPEnvVar opts a verify-mode subprocess back in to the
+// real HTTP wire path for mutating verbs. It is intentionally
+// asymmetric with VerifyEnvVar: setting LIVE_HTTP=1 alone (with VERIFY
+// unset) has no behavioral effect, because the gate only consults this
+// var when IsVerifyEnv() is also true. The verify pipeline and
+// narrative full-example runner both set BOTH vars in their mock-mode
+// subprocesses (so the httptest server keeps exercising the real wire
+// path); agents and ad-hoc operators leave LIVE_HTTP unset so mutating
+// requests no-op. Live verifiers (live_dogfood, workflow_verify) strip
+// both vars from subprocess env entirely so they cannot inherit a
+// verify-mode short-circuit from the operator's shell.
+const VerifyLiveHTTPEnvVar = "PRINTING_PRESS_VERIFY_LIVE_HTTP"
+
 // DogfoodEnvVar is the env var the printing-press live-dogfood runner
 // sets in every subprocess. Distinct from VerifyEnvVar because dogfood
 // is a real-network matrix: commands may still perform actual API
@@ -36,6 +55,25 @@ func IsVerifyEnv() bool {
 	return os.Getenv(VerifyEnvVar) == "1"
 }
 
+// IsVerifyLiveHTTPEnv reports whether the current process has opted
+// back in to the real HTTP wire path while running under the verifier.
+// Only meaningful when IsVerifyEnv() is also true; on its own this
+// returns true does NOT enable any sandbox behavior — see
+// VerifyLiveHTTPEnvVar's docstring for the asymmetric semantics.
+//
+// The generated client uses this gate as:
+//
+//	if !readOnlyIntent && isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+//	    // synthetic envelope, no network call
+//	}
+//
+// readOnlyIntent is set by Client.doRead() callers (the PostQuery*
+// family used by codegen-marked `mcp:read-only` operations on mutating
+// verbs — GraphQL queries, JSON-RPC reads, POST-based search).
+func IsVerifyLiveHTTPEnv() bool {
+	return os.Getenv(VerifyLiveHTTPEnvVar) == "1"
+}
+
 // IsDogfoodEnv reports whether the current process is running under
 // the printing-press live-dogfood matrix. Long-running commands (full
 // sync loops, content crawlers, bulk archive walks) should use this
diff --git a/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_get.go b/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_get.go
index a19d2443..2a783e4f 100644
--- a/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_get.go
+++ b/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_get.go
@@ -49,7 +49,7 @@ func newThingsGetCmd(flags *rootFlags) *cobra.Command {
 			} else {
 				body = map[string]any{}
 			}
-			data, statusCode, err := c.PostWithParams(path, params, body)
+			data, statusCode, err := c.PostQueryWithParams(path, params, body)
 			if err != nil {
 				return classifyAPIError(err, flags)
 			}
@@ -106,16 +106,6 @@ func newThingsGetCmd(flags *rootFlags) *cobra.Command {
 					}
 					return nil
 				}
-				// Apply --compact and --select to the API response before wrapping.
-				// --select wins when both are set: explicit field choice trumps the
-				// generic high-gravity allow-list. Otherwise --compact still applies
-				// when --agent is on but the user did not name fields.
-				filtered := data
-				if flags.selectFields != "" {
-					filtered = filterFields(filtered, flags.selectFields)
-				} else if flags.compact {
-					filtered = compactFields(filtered)
-				}
 				envelope := map[string]any{
 					"action":   "post",
 					"resource": "things",
@@ -131,6 +121,33 @@ func newThingsGetCmd(flags *rootFlags) *cobra.Command {
 					envelope["status"] = 0
 					envelope["success"] = false
 				}
+				// Verify-mode synthetic envelope detection runs against RAW data
+				// (before --compact/--select filtering) so the sentinel field is
+				// guaranteed to be visible even if the operator passes a filter
+				// flag that would otherwise strip it. Surfaces a top-level
+				// verify_noop signal + flips success to false. Mirrors the dry_run
+				// shape above.
+				if len(data) > 0 {
+					var rawParsed any
+					if err := json.Unmarshal(data, &rawParsed); err == nil {
+						if m, ok := rawParsed.(map[string]any); ok {
+							if v, ok := m["__pp_verify_synthetic__"].(bool); ok && v {
+								envelope["verify_noop"] = true
+								envelope["success"] = false
+							}
+						}
+					}
+				}
+				// Apply --compact and --select to the API response before wrapping.
+				// --select wins when both are set: explicit field choice trumps the
+				// generic high-gravity allow-list. Otherwise --compact still applies
+				// when --agent is on but the user did not name fields.
+				filtered := data
+				if flags.selectFields != "" {
+					filtered = filterFields(filtered, flags.selectFields)
+				} else if flags.compact {
+					filtered = compactFields(filtered)
+				}
 				if len(filtered) > 0 {
 					var parsed any
 					if err := json.Unmarshal(filtered, &parsed); err == nil {
diff --git a/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_list.go b/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_list.go
index 15c1f5b6..72cd94f0 100644
--- a/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_list.go
+++ b/testdata/golden/expected/generate-graphql-shared-endpoint/graphql-shared-golden/internal/cli/things_list.go
@@ -45,7 +45,7 @@ func newThingsListCmd(flags *rootFlags) *cobra.Command {
 			} else {
 				body = map[string]any{}
 			}
-			data, statusCode, err := c.PostWithParams(path, params, body)
+			data, statusCode, err := c.PostQueryWithParams(path, params, body)
 			if err != nil {
 				return classifyAPIError(err, flags)
 			}
@@ -102,16 +102,6 @@ func newThingsListCmd(flags *rootFlags) *cobra.Command {
 					}
 					return nil
 				}
-				// Apply --compact and --select to the API response before wrapping.
-				// --select wins when both are set: explicit field choice trumps the
-				// generic high-gravity allow-list. Otherwise --compact still applies
-				// when --agent is on but the user did not name fields.
-				filtered := data
-				if flags.selectFields != "" {
-					filtered = filterFields(filtered, flags.selectFields)
-				} else if flags.compact {
-					filtered = compactFields(filtered)
-				}
 				envelope := map[string]any{
 					"action":   "post",
 					"resource": "things",
@@ -127,6 +117,33 @@ func newThingsListCmd(flags *rootFlags) *cobra.Command {
 					envelope["status"] = 0
 					envelope["success"] = false
 				}
+				// Verify-mode synthetic envelope detection runs against RAW data
+				// (before --compact/--select filtering) so the sentinel field is
+				// guaranteed to be visible even if the operator passes a filter
+				// flag that would otherwise strip it. Surfaces a top-level
+				// verify_noop signal + flips success to false. Mirrors the dry_run
+				// shape above.
+				if len(data) > 0 {
+					var rawParsed any
+					if err := json.Unmarshal(data, &rawParsed); err == nil {
+						if m, ok := rawParsed.(map[string]any); ok {
+							if v, ok := m["__pp_verify_synthetic__"].(bool); ok && v {
+								envelope["verify_noop"] = true
+								envelope["success"] = false
+							}
+						}
+					}
+				}
+				// Apply --compact and --select to the API response before wrapping.
+				// --select wins when both are set: explicit field choice trumps the
+				// generic high-gravity allow-list. Otherwise --compact still applies
+				// when --agent is on but the user did not name fields.
+				filtered := data
+				if flags.selectFields != "" {
+					filtered = filterFields(filtered, flags.selectFields)
+				} else if flags.compact {
+					filtered = compactFields(filtered)
+				}
 				if len(filtered) > 0 {
 					var parsed any
 					if err := json.Unmarshal(filtered, &parsed); err == nil {
diff --git a/testdata/golden/expected/generate-public-param-names/public-param-golden/internal/cli/stores_create.go b/testdata/golden/expected/generate-public-param-names/public-param-golden/internal/cli/stores_create.go
index 23fdc7c1..661b3060 100644
--- a/testdata/golden/expected/generate-public-param-names/public-param-golden/internal/cli/stores_create.go
+++ b/testdata/golden/expected/generate-public-param-names/public-param-golden/internal/cli/stores_create.go
@@ -108,16 +108,6 @@ func newStoresCreateCmd(flags *rootFlags) *cobra.Command {
 					}
 					return nil
 				}
-				// Apply --compact and --select to the API response before wrapping.
-				// --select wins when both are set: explicit field choice trumps the
-				// generic high-gravity allow-list. Otherwise --compact still applies
-				// when --agent is on but the user did not name fields.
-				filtered := data
-				if flags.selectFields != "" {
-					filtered = filterFields(filtered, flags.selectFields)
-				} else if flags.compact {
-					filtered = compactFields(filtered)
-				}
 				envelope := map[string]any{
 					"action":   "post",
 					"resource": "stores",
@@ -133,6 +123,33 @@ func newStoresCreateCmd(flags *rootFlags) *cobra.Command {
 					envelope["status"] = 0
 					envelope["success"] = false
 				}
+				// Verify-mode synthetic envelope detection runs against RAW data
+				// (before --compact/--select filtering) so the sentinel field is
+				// guaranteed to be visible even if the operator passes a filter
+				// flag that would otherwise strip it. Surfaces a top-level
+				// verify_noop signal + flips success to false. Mirrors the dry_run
+				// shape above.
+				if len(data) > 0 {
+					var rawParsed any
+					if err := json.Unmarshal(data, &rawParsed); err == nil {
+						if m, ok := rawParsed.(map[string]any); ok {
+							if v, ok := m["__pp_verify_synthetic__"].(bool); ok && v {
+								envelope["verify_noop"] = true
+								envelope["success"] = false
+							}
+						}
+					}
+				}
+				// Apply --compact and --select to the API response before wrapping.
+				// --select wins when both are set: explicit field choice trumps the
+				// generic high-gravity allow-list. Otherwise --compact still applies
+				// when --agent is on but the user did not name fields.
+				filtered := data
+				if flags.selectFields != "" {
+					filtered = filterFields(filtered, flags.selectFields)
+				} else if flags.compact {
+					filtered = compactFields(filtered)
+				}
 				if len(filtered) > 0 {
 					var parsed any
 					if err := json.Unmarshal(filtered, &parsed); err == nil {
diff --git a/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/cli/doctor.go b/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/cli/doctor.go
index c76d5c70..8a4b0bcf 100644
--- a/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/cli/doctor.go
+++ b/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/cli/doctor.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"tier-routing-golden-pp-cli/internal/client"
+	"tier-routing-golden-pp-cli/internal/cliutil"
 	"tier-routing-golden-pp-cli/internal/config"
 	"tier-routing-golden-pp-cli/internal/store"
 )
@@ -216,6 +217,21 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 			// whether to trust the cached data before issuing queries.
 			report["cache"] = collectCacheReport(cmd.Context(), "")
 
+			// Verify mode state. Surfaced so an operator who unintentionally
+			// inherits PRINTING_PRESS_VERIFY=1 (parent shell, CI runner, container
+			// image) detects the foot-gun without inspecting a response body.
+			// Pairs with the synthetic envelope's verify_noop / reason literals
+			// as a second diagnosis anchor.
+			if cliutil.IsVerifyEnv() {
+				if cliutil.IsVerifyLiveHTTPEnv() {
+					report["verify_mode"] = "INFO ACTIVE — live HTTP opt-in (mutating verbs dial out)"
+				} else {
+					report["verify_mode"] = "INFO ACTIVE — mutating HTTP verbs short-circuit (PRINTING_PRESS_VERIFY=1; no network calls for DELETE/POST/PUT/PATCH)"
+				}
+			} else {
+				report["verify_mode"] = "normal operation"
+			}
+
 			report["version"] = version
 
 			if flags.asJSON {
@@ -231,6 +247,7 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
 				{"config", "Config"},
 				{"auth", "Auth"},
 				{"env_vars", "Env Vars"},
+				{"verify_mode", "Verify Mode"},
 				{"api", "API"},
 				{"credentials", "Credentials"},
 			}
diff --git a/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/client/client.go b/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/client/client.go
index e12ed4b7..57bd9595 100644
--- a/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/client/client.go
+++ b/testdata/golden/expected/generate-tier-routing-api/tier-routing-golden/internal/client/client.go
@@ -303,6 +303,21 @@ func (c *Client) PostWithParamsAndHeaders(path string, params map[string]string,
 	return c.do("POST", path, params, body, headers)
 }
 
+// PostQueryWithParams is a POST that does not mutate remote state — used
+// by read-only operations that ride a mutating verb on the wire (GraphQL
+// queries, JSON-RPC reads, POST-based search endpoints). The verify-mode
+// short-circuit does not fire for these calls; the request reaches the
+// real transport even under PRINTING_PRESS_VERIFY=1 without LIVE_HTTP=1.
+func (c *Client) PostQueryWithParams(path string, params map[string]string, body any) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, nil)
+}
+
+// PostQueryWithParamsAndHeaders is the headers-aware counterpart to
+// PostQueryWithParams. See PostQueryWithParams for the verify-mode rationale.
+func (c *Client) PostQueryWithParamsAndHeaders(path string, params map[string]string, body any, headers map[string]string) (json.RawMessage, int, error) {
+	return c.doRead("POST", path, params, body, headers)
+}
+
 func (c *Client) Delete(path string) (json.RawMessage, int, error) {
 	return c.do("DELETE", path, nil, nil, nil)
 }
@@ -351,9 +366,78 @@ func (c *Client) PatchWithParamsAndHeaders(path string, params map[string]string
 	return c.do("PATCH", path, params, body, headers)
 }
 
+// isMutatingVerb reports whether the HTTP method writes server state.
+// Used by do()'s verify-mode short-circuit to gate dial-out: under
+// PRINTING_PRESS_VERIFY=1 (without LIVE_HTTP=1 opt-in), generated
+// commands must not actually issue mutating requests, even if a
+// handler-level dry-run check was missed.
+func isMutatingVerb(method string) bool {
+	switch method {
+	case "DELETE", "POST", "PUT", "PATCH":
+		return true
+	}
+	return false
+}
+
+// verifyShortCircuitEnvelope returns the synthetic JSON body that
+// stands in for a real mutating response when do() short-circuits in
+// verify mode. The __pp_verify_synthetic__ sentinel is namespace-
+// reserved (no real API uses __pp_*) so downstream consumers
+// (validate-narrative, agent inspections) can key on one obvious field
+// instead of trying to disambiguate common literals like status:"noop".
+// method and path are echoed back as diagnostic prose for human/agent
+// inspection.
+func verifyShortCircuitEnvelope(method, path string) json.RawMessage {
+	body, _ := json.Marshal(map[string]any{
+		"__pp_verify_synthetic__": true,
+		"status":                  "noop",
+		"reason":                  "verify_short_circuit",
+		"method":                  method,
+		"path":                    path,
+	})
+	return json.RawMessage(body)
+}
+
 // do executes an HTTP request. headerOverrides, when non-nil, override global
 // RequiredHeaders for this specific request (used for per-endpoint API versioning).
 func (c *Client) do(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, false)
+}
+
+// doRead is do() minus the verify-mode mutating-verb gate. Used by the
+// PostQuery* family for read-only operations that ride a mutating verb on
+// the wire (GraphQL queries, JSON-RPC reads, POST-based search endpoints).
+// The wire verb is still POST/PUT/PATCH so the server sees a real request,
+// but the verify-mode short-circuit does not fire because the operation
+// does not mutate remote state.
+func (c *Client) doRead(method, path string, params map[string]string, body any, headerOverrides map[string]string) (json.RawMessage, int, error) {
+	return c.doInternal(method, path, params, body, headerOverrides, true)
+}
+
+// doInternal is the shared implementation behind do() and doRead(). The
+// readOnlyIntent flag is set by doRead() callers (read-only POST/PUT/PATCH
+// operations like GraphQL queries) to skip the mutating-verb verify-mode
+// gate. Plain do() callers leave it false and get the usual short-circuit.
+func (c *Client) doInternal(method, path string, params map[string]string, body any, headerOverrides map[string]string, readOnlyIntent bool) (json.RawMessage, int, error) {
+	// Verify-mode transport-layer gate. When the verifier (or any consumer
+	// that sets PRINTING_PRESS_VERIFY=1) drives a mutating verb without
+	// the LIVE_HTTP=1 opt-in, return a synthetic envelope without dialing,
+	// minting auth, or touching the cache. The verify pipeline itself
+	// sets both env vars in mock mode so its httptest server still sees
+	// real requests; every other consumer gets a safe no-op.
+	//
+	// readOnlyIntent suppresses the gate for read-only operations that
+	// happen to ride a mutating verb on the wire (GraphQL queries, JSON-RPC
+	// reads, POST-based search endpoints). The handler-level annotation
+	// `mcp:read-only: true` drives the codegen choice of doRead() vs do().
+	//
+	// Placement note: this fires BEFORE URL building, auth header
+	// minting, and the success-branch invalidateCache() call below — so
+	// no cache invalidation runs (no remote state changed) and no
+	// client_credentials mint happens unnecessarily.
+	if !readOnlyIntent && isMutatingVerb(method) && cliutil.IsVerifyEnv() && !cliutil.IsVerifyLiveHTTPEnv() {
+		return verifyShortCircuitEnvelope(method, path), http.StatusOK, nil
+	}
 	requestBaseURL := c.baseURLForRequest()
 	targetURL := requestBaseURL + path
 

← c5d86825 fix(cli): create llm prompt temp files privately (#1674)  ·  back to Cli Printing Press  ·  feat(cli): detect Auth0 SPA in-memory auth + emit CDP extrac 015478bf →