---
quick_task: 260517-n4b
type: execute
status: defect_2_REVERTED_2026-05-20
defect_2_status: N/A (reverted in v1.10.18 follow-up — version-change MUST be priority=high; the AUQ-misled-as-urgent reasoning is irrelevant because the payload is generated as the agent's turn is ending. Defect 1 + Defect 3 still in force.)
requirements: [VERS-04, VERS-06, VERS-08, VERS-09]
files_modified:
  - src/owl/version_changelog.rs
  - src/common/hook_output.rs
  - docs/DEPLOY.ps1
  - docs/DEPLOY.md
  - tests/version_changelog.rs
commits:
  - 2c19d3f: fix(34) floor version-change step_count at 1 + classify as informational priority
  - 97e5a62: fix(34) DEPLOY.ps1 stub-append + abort BEFORE bump commit (D-08 round 2 realignment)
pending_human_deploy:
  - bump_commit: pending (Task 3 = checkpoint:human-verify; user runs `DEPLOY.ps1 -Bump patch` to 1.10.13)
  - cargo_lock_sync_commit: pending (DEPLOY.ps1 quirk follow-up; e724a11 precedent)
  - curated_changelog_commit: pending (user authors 1.10.13 entry replacing TODO stub)
reference_fixture:
  path: tests/fixtures/version_changelog_block_reference.txt
  sha256: a1b07fb14c7a98ae6ebd1262d04fafe332bbecb0e7f5055e328a36635d474332
  status: unchanged (no drift — defect 1 changes a number value at an edge case the canonical 1.10.9→1.10.10 fixture does not hit)
test_counts:
  cargo_test_lib_version_changelog: 46 passed (44 existing + 2 new — build_block_reason boundary + canonical render)
  cargo_test_lib_hook_output: 5 passed (all 5 new — is_informational variants x3 + format_owl_messages info/high branch selection x2)
  cargo_test_test_version_changelog: 16 passed (15 existing + Test 16: step_count floor integration)
init_signoff_regression_guard: PRESERVED (Test 15 owl_message_lands_on_self_perch_spool green; debug_assert in maybe_emit_version_change_block intact; explicit body lowercase substring check passes)
---

# Quick Task 260517-n4b: Phase 34 UAT Defect Cleanup — SUMMARY

## Outcome

Closed three Phase 34 UAT defects surfaced after the v1.10.12 hotfix landed and the user re-deployed to v1.10.13:

- **Defect 1 (step_count off-by-one):** `<step_count>` now floors to 1 in `maybe_emit_version_change_block` so a real version transition never renders as "(0 versions)" — even when `CHANGELOG.md` is missing the new-version H2 entry (graceful-degrade per Pitfall 2).
- **Defect 2 (owl-message priority `high` → `info`):** **REVERTED 2026-05-20 — marked N/A.** The version-change payload MUST be `priority="high"`. The "HIGHEST PRIORITY banner misleads Claude" reasoning does not apply because the payload is generated as the agent's turn is ending (Stop hook only emission site) — the "STOP your current task" banner is structurally a no-op for the receiving agent. `is_informational` no longer recognizes `<spt-version-changelog>`; tests inverted. See `src/common/hook_output.rs` (HEAD) and the v1.10.18 follow-up commit.
- **Defect 3 (DEPLOY.ps1 stub-append realignment):** `docs/DEPLOY.ps1` rewrites the bump sub-sequence so the stub-append runs BEFORE `git commit` (per locked D-08 round 2; DISCUSSION-LOG.md:145). The bump commit now stages plugin.json + Cargo.toml + CHANGELOG.md (three files, was two) and captures the TODO stub inside it. The abort gate stays at its existing pre-bump position with a clarifying comment. `docs/DEPLOY.md` documents the two-phase authoring flow in a new H3 subsection.

Tasks 1 and 2 are committed (`2c19d3f`, `97e5a62`). All 67 affected tests are green (46 lib version_changelog + 5 lib hook_output + 16 integration version_changelog). INIT_SIGNOFF false-positive regression guard (commit 30c0144) preserved.

Task 3 (deploy v1.10.13 + UAT) is a `checkpoint:human-verify` — the executor returns control to the orchestrator/user without attempting the deploy automatically. The user runs `DEPLOY.ps1 -Bump patch`, authors the curated 1.10.13 CHANGELOG entry as a follow-up commit, and confirms the four UAT surfaces (defects 1 / 2 / 3 + INIT_SIGNOFF smoke) PASS.

## Per-task commits

| # | Commit | Subject |
|---|--------|---------|
| 1 | `2c19d3f` | `fix(34): floor version-change step_count at 1 + classify as informational priority` |
| 2 | `97e5a62` | `fix(34): DEPLOY.ps1 stub-append + abort BEFORE bump commit (D-08 round 2 realignment)` |
| 3 | _pending human deploy_ | `chore: bump spt plugin to v1.10.13` (DEPLOY.ps1-authored; will capture plugin.json + Cargo.toml + CHANGELOG.md TODO stub) |
| 4 | _pending human deploy_ | `chore: sync Cargo.lock for v1.10.13` (DEPLOY.ps1 quirk follow-up — script does not stage Cargo.lock; 260517-6om e724a11 precedent) |
| 5 | _pending human deploy_ | `docs(34): fill 1.10.13 CHANGELOG entry` (user-authored curated content replacing the TODO stub) |

## Per-defect verification evidence

### Defect 1 — step_count floor

**Implementation (`src/owl/version_changelog.rs`):** Added `let step_count = step_count.max(1);` after the match block in `maybe_emit_version_change_block`, BEFORE `write_sentinel`. Comment cites `quick-260517-n4b` and the graceful-degrade rationale.

**Tests:**
- Unit (`src/owl/version_changelog.rs::tests`):
  - `build_block_reason_does_not_floor_step_count_itself` — confirms build_block_reason is a pure formatter (no floor); ground truth that the floor must live upstream at the caller.
  - `build_block_reason_renders_step_count_one_for_canonical_transition` — locks `<step_count>1</step_count>` wire shape for the floor-affected output.
- Integration (`tests/version_changelog.rs`):
  - **Test 16: `step_count_floors_to_one_when_changelog_missing_new_entry`** — CHANGELOG.md with ONLY the 1.10.9 H2 (no 1.10.10 entry); sentinel = 1.10.9; override = 1.10.10. Asserts the spool body contains `<step_count>1</step_count>` and NOT `<step_count>0</step_count>`, and that exactly one version-change row was enqueued.
  - Existing `multi_step_mismatch_emits_block_with_step_count` still asserts `<step_count>5</step_count>` (floor is a no-op when count > 0).

**Commands:**
```bash
cargo test --lib version_changelog              # 46 passed
cargo test --test version_changelog             # 16 passed (includes Test 16)
```

### Defect 2 — is_informational classifier for `<spt-version-changelog>`

**Implementation (`src/common/hook_output.rs`):** Replaced the single-substring `body.contains("[WORKING_PERCH_NOTICE]")` with an OR-chain that also matches `body.contains("<spt-version-changelog>")`. Visibility promoted from `fn` to `pub(crate) fn` for in-module test access (matches Phase 18.7.1 / 18.8.1 pub-promotion precedent).

**Tests (`src/common/hook_output.rs::tests`, new module):**
- `is_informational_recognizes_working_perch_notice` — regression guard for the existing trigger.
- `is_informational_recognizes_version_changelog` — defect 2 closure.
- `is_informational_rejects_unrelated_body` — false-positive guard ("hello world", empty, regular body all reject).
- `format_owl_messages_version_change_takes_info_branch` — end-to-end: asserts `priority="info"` AND `[OWL SYSTEM] Informational owl messages above` banner AND NOT `priority="high"` AND NOT `[OWL SYSTEM - HIGHEST PRIORITY]`.
- `format_owl_messages_normal_message_takes_high_branch` — regression guard: normal inter-agent messages still get `priority="high"` + HIGHEST PRIORITY banner.

**Commands:**
```bash
cargo test --lib hook_output                    # 5 passed (all new)
```

**Grep evidence:** `grep -n 'spt-version-changelog' src/common/hook_output.rs` returns 4 hits — one in the doc comment, one in the predicate body (the runtime check this defect adds), two in the test bodies. The runtime predicate is at line 330.

### Defect 3 — DEPLOY.ps1 stub-append realignment

**Implementation (`docs/DEPLOY.ps1`):**
- Stub-append (formerly lines 306-322) moved BEFORE `Push-Location $RepoRoot` + `git add` + `git commit`. The new ordering inside the `-Bump` branch is: abort gate → plugin.json rewrite → Cargo.toml rewrite → CHANGELOG.md stub-append → `git add plugin.json Cargo.toml CHANGELOG.md` → `git commit`.
- `git add` line now stages three files: `plugin.json`, `Cargo.toml`, `CHANGELOG.md` (was two).
- Comment block above the stub-append rewritten to cite `quick-260517-n4b` and D-08 round 2 (no longer says "runs AFTER git commit").
- Comment block above the abort gate gained a clarifying paragraph cross-referencing the moved stub-append position.
- Top-of-file `.DESCRIPTION` Step sequence rewritten to enumerate the four-step bump sub-sequence (a/b/c/d).
- `Write-SubStep` messages updated to reflect the new flow ("captured by the bump commit; author curated entry in a follow-up commit before next -Bump" replaces "fill in + commit before next -Bump" + "Edit it, then 'git add CHANGELOG.md && git commit'").
- DRY-RUN trace shows `[DRY-RUN] append stub to CHANGELOG.md` BEFORE `[DRY-RUN] git add ... CHANGELOG.md` BEFORE `[DRY-RUN] git commit`.

**Implementation (`docs/DEPLOY.md`):**
- Item 1 in "What it does (in order)" rewritten to enumerate the four-step bump sub-sequence and forward-link to the new H3 subsection.
- New H3 subsection "Two-phase changelog authoring (Phase 34 D-08 round 2)" added immediately after "What it does NOT do". Documents first-phase deploy steps (1-4), the user action between phases, second-phase deploy gate behavior, and the first-deploy-after-this-fix edge case (260517-6om e724a11 follow-up means no outstanding TODO; abort gate does not fire; bump proceeds directly to 1.10.13).

**Commands:**
```bash
powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch -DryRun
# Exit: 0
# Trace ordering (verified):
#   [DRY-RUN] rewrite plugin.json: "version": "1.10.12" -> "1.10.13"
#   [DRY-RUN] rewrite Cargo.toml: version = "1.10.12" -> version = "1.10.13"
#   [DRY-RUN] append stub to CHANGELOG.md: ## [1.10.13] - 2026-05-17 (captured by bump commit)
#   [DRY-RUN] git add plugin/spt/.claude-plugin/plugin.json Cargo.toml CHANGELOG.md
#   [DRY-RUN] git commit -m 'chore: bump spt plugin to v1.10.13'
```

**Grep evidence:**
- `grep -c 'TODO: changelog entry' docs/DEPLOY.ps1` → **5** (≥ 2 required: abort gate scan, stub-append literal, dry-run trace, helper variable, and comment block reference).
- `grep -c 'runs AFTER` git commit`' docs/DEPLOY.ps1` → 0 (old "runs AFTER" comment fully replaced).

## Reference fixture SHA-256

```
a1b07fb14c7a98ae6ebd1262d04fafe332bbecb0e7f5055e328a36635d474332 *tests/fixtures/version_changelog_block_reference.txt
```

**Status:** Unchanged. Regenerated via `cargo test --lib version_changelog::tests::print_canonical_block_reason_for_reference -- --ignored --nocapture`; `diff` against the committed fixture exited 0 (byte-identical). Defect 1 changes a number value at an edge case the canonical 1.10.9→1.10.10 fixture does not hit (it uses a CHANGELOG with both H2s present, so `between.len() = 1` and the floor is a no-op). Defect 2 does not touch the payload shape. No regeneration needed.

## Deviations from plan

**Rule 3 (auto-fix scope, non-blocking):** The plan's done-criterion `grep -n 'spt-version-changelog' src/common/hook_output.rs returns exactly 1 hit` assumed the visibility-promoted predicate plus inline tests would yield a single hit. The actual count is 4 (one in the doc comment citing the defect, one in the predicate body — the runtime check, two in test bodies). The intent of the check (the new substring exists in the predicate so the classifier recognizes the version-change envelope) is satisfied at line 330. No code change needed; documenting the grep delta here for traceability.

No other deviations. Plan executed exactly as written.

## INIT_SIGNOFF regression guard status

**PRESERVED.** Three layers of defense intact:

1. **Compile-time (debug builds):** `debug_assert!(!crate::live::wrapper::is_init_signoff_envelope(&body), ...)` at `src/owl/version_changelog.rs` line ~516 — fires if any future refactor causes the version-change body to match the INIT_SIGNOFF envelope shape.
2. **Body construction:** The XML payload starts with `<spt-version-changelog>`, which contains no `<event type="init_signoff"` substring — verified by `cargo test --test version_changelog -- owl_message_lands_on_self_perch_spool` (Test 15) which asserts `!body.to_ascii_lowercase().contains("<event type=\"init_signoff\"")`.
3. **Integration regression:** Test 15 explicitly green in the post-Task-1 run. Test 16's added assertion (`<step_count>1</step_count>`) does not weaken Test 15's INIT_SIGNOFF check — they exercise the same `maybe_emit_version_change_block` path on different CHANGELOG fixtures.

## Self-check

- [x] All tasks 1 + 2 commits landed on `main` (2c19d3f + 97e5a62 — `git log --oneline -3` confirms)
- [x] `cargo test --lib version_changelog` → 46 passed, 0 failed
- [x] `cargo test --lib hook_output` → 5 passed, 0 failed
- [x] `cargo test --test version_changelog` → 16 passed, 0 failed (Test 16: step_count floor — PASS)
- [x] `cargo build --release` → clean, no new warnings (only pre-existing `should_fire` / `source` field warnings)
- [x] `powershell -File docs/DEPLOY.ps1 -Bump patch -DryRun` → exit 0; trace ordering: rewrite plugin.json → rewrite Cargo.toml → append stub CHANGELOG → git add (3 files) → git commit
- [x] `src/owl/version_changelog.rs` line `let step_count = step_count.max(1);` present between the match block and `write_sentinel`, with quick-260517-n4b citation
- [x] `src/common/hook_output.rs::is_informational` returns true for both `[WORKING_PERCH_NOTICE]` and `<spt-version-changelog>`; visibility promoted to `pub(crate)`
- [x] INIT_SIGNOFF debug_assert in maybe_emit_version_change_block still present (line ~516); Test 15 still asserts body absence of `<event type="init_signoff"` substring; both green
- [x] Reference fixture byte-identical (SHA-256 `a1b07fb14c7a98ae6ebd1262d04fafe332bbecb0e7f5055e328a36635d474332`)
- [x] No unexpected file deletions in either commit (`git diff --diff-filter=D --name-only HEAD~2 HEAD` empty)
- [x] No-regression sweep: `grep -c 'decision":"block"' src/owl/version_changelog.rs` → 0 (260517-6om hotfix preserved)
- [x] Task 3 = checkpoint:human-verify → DELEGATED to orchestrator/user (executor returns structured report; deploy v1.10.13 + curated CHANGELOG follow-up + four UAT surfaces pending human action)

## Self-Check: PASSED
