---
phase: 25.4-perch-path-single-source-of-truth-finish-d-01-nested-migrati
plan: 07
subsystem: testing
tags: [perch-path, nested-migration, integration-tests, smoke-probe, operator-verify, perch_path-resolver]

# Dependency graph
requires:
  - phase: 25.4
    provides: "Plans 25.4-01..06 — central perch_path resolver + writer migrations (poll.rs, spool.rs, inbox.rs, hook_check.rs, hook_idle.rs, wrapper-state) + 8 Class-E reader migrations"
provides:
  - "tests/perch_path_no_flat_psyche.rs — CI regression guard pinning nested-only psyche perch side effects (2 tests)"
  - "tests/perch_path_no_flat_worker.rs — CI regression guard pinning nested-only worker perch side effects via both `_at` and legacy id-routes (4 tests)"
  - "25.4-SMOKE-PROBE.md — operator-captured pre/post-deploy probe with PASS verdict + in-checkpoint finishing-fix RCA"
  - "Phase 25.4 GOAL closure: zero flat psyche/worker dirs created by Phase 25.4 binary on operator's live machine"
  - "RESEARCH Open Q1 (worker-perch asymmetry) carried forward as separate orthogonal investigation"
affects: [Phase 35, Phase 25 future cleanup-phase, hook_idle.rs gate-predicate audits]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "Integration test pattern: spawn built owl.exe via env!(CARGO_BIN_EXE_owl) against TempDir SPT_HOME — Command::env per-spawn (parallel-safe, no global ENV_LOCK needed for subprocess-style tests)"
    - "Integration test pattern: helper fn returning std::process::Output with stdout+stderr captured; assertion failures print captured streams for CI diagnosis"
    - "Pre/post-deploy operator smoke probe: pair an automated probe.ps1 with a structured SMOKE-PROBE.md report so before/after diffs are auditable and orphan-vs-fresh distinctions are explicit"
    - "Checkpoint-loop finishing-fix protocol: when an operator-verify reprobe surfaces in-scope seam bugs, fix inline + recommit + redeploy + record root-cause analysis in the SMOKE-PROBE.md report (rather than spawning a follow-up plan)"

key-files:
  created:
    - "tests/perch_path_no_flat_psyche.rs"
    - "tests/perch_path_no_flat_worker.rs"
    - ".planning/phases/25.4-perch-path-single-source-of-truth-finish-d-01-nested-migrati/25.4-SMOKE-PROBE.md"
    - ".planning/phases/25.4-perch-path-single-source-of-truth-finish-d-01-nested-migrati/post-probe.txt"
  modified:
    - "src/owl/hook_idle.rs (Rule 1 finishing fix: .more-done gate predicate migrated to nested resolver; Stop-hook version-change early-return dropped)"

key-decisions:
  - "Operator-verify checkpoint resolved as PASS with two in-scope finishing fixes applied during the checkpoint loop rather than deferred to a gap-closure plan — both fixes were small, scoped, and directly traced to Phase 25.4 / orthogonal-coupling seams discoverable only by the live probe"
  - "Worker-perch asymmetry (RESEARCH Open Q1) explicitly DECOUPLED from Phase 25.4 GOAL — phase asserts NO flat side-effect dirs, which holds; whether spawn_worker_perch fires reliably for one-shot subagents is a registration question carried forward as a separate investigation"
  - "Operator accepted close without third reprobe — gate-predicate fix has positive proof (.more-done observed at nested path on v1.11.11), version-change early-return fix has reasoned justification + downstream test coverage intact"
  - "Plugin version bumped twice in checkpoint loop (v1.11.10 → v1.11.11 → v1.11.12) per patch-bump convention; CHANGELOG entries land alongside each bump"

patterns-established:
  - "Pattern: writer-migration-must-grep-readers — when migrating a path-composition writer, grep the SAME file for sibling readers/predicates against the same path family (e.g. `owlery::*_file(`, `owlery::perch_dir(`, `owlery::info_file(`, `.exists()` on composed paths). The Phase 25.4-04 vs 25.4-06 split missed `hook_idle.rs:137` `.more-done` gate predicate exactly because the audit was call-site-scoped, not file-scoped"
  - "Pattern: positive-invariant-smoke alongside negative-invariant-CI — integration tests asserting `no flat dirs created` (negative) do NOT catch `expected sentinel never written` (positive). Pair them when migrating gated side effects"
  - "Pattern: operator-driven smoke probe scaffolding — pre-populate the report with RESEARCH-era evidence + filled checkboxes for expected/forbidden outcomes; operator only has to paste post-output and tick boxes"

requirements-completed:
  - PERCH-PATH-SMOKE-PROBE-01
  - PERCH-PATH-WORKER-MYSTERY-01
  - PERCH-PATH-GOAL-VERIFY-01

# Metrics
duration: ~60min (Tasks 1+2+4 ~20min execution; Task 3 operator checkpoint + 2 finishing-fix deploy cycles ~40min)
completed: 2026-05-23
---

# Phase 25.4 Plan 07: Smoke-Probe + Integration-Test GOAL Verification Summary

**Phase 25.4 GOAL closure: live-machine probe confirms zero flat psyche dirs, zero flat worker dirs, all probetest writes nested-only. Two automated integration tests (6 cases total) pin the invariant in CI; two in-scope seam bugs (.more-done gate predicate + Stop-hook version-change early-return) caught and fixed in-checkpoint at v1.11.11 + v1.11.12.**

## Performance

- **Duration:** ~60 min
- **Started:** 2026-05-23
- **Completed:** 2026-05-23
- **Tasks:** 4 (3 auto + 1 operator-verify checkpoint)
- **Files modified:** 4 (2 created tests + 1 SMOKE-PROBE.md + 1 hook_idle.rs for finishing fixes)
- **Finishing-fix deploy cycles:** 2 (v1.11.10 → v1.11.11 → v1.11.12)

## Accomplishments

- `tests/perch_path_no_flat_psyche.rs` — 2/2 tests pass: psyche poll subprocess with `--parent <self>` writes nested-only; Self listener without `--parent` stays flat (rule pinned)
- `tests/perch_path_no_flat_worker.rs` — 4/4 tests pass: `spool::open_spool_at` + legacy `spool::spool_message` + `inbox::set_idle_ready_at` all route worker ids to nested; Self id stays flat under legacy call shape (CONTEXT D-04 + D-05 pinned)
- `25.4-SMOKE-PROBE.md` scaffold pre-populated from RESEARCH §3, post-deploy section filled by operator with verbatim `post-probe.txt` + diff annotations + PASS verdict + finishing-fix RCA
- Live operator-machine probe: post-deploy `=== FLAT PSYCHE DIRS ===` empty, `=== FLAT WORKER DIRS (-w[N]) ===` empty, all `probetest/*` writes nested-only — Phase 25.4 GOAL achieved
- Two in-checkpoint finishing fixes: `.more-done` gate-predicate (Phase 25.4 miss; Plan 04 vs Plan 06 seam) + Stop-hook version-change early-return (Phase 34 unrelated coupling caught during reprobe)

## Task Commits

1. **Task 1: tests/perch_path_no_flat_psyche.rs** — `c6588bd` (test)
2. **Task 2: tests/perch_path_no_flat_worker.rs** — `95f0467` (test)
3. **Task 4: 25.4-SMOKE-PROBE.md scaffold** — `cf321fe` (docs) [Task 4 executed before Task 3 checkpoint per plan instruction]
4. **Task 3: operator smoke-probe PASS + diff annotations + RCA** — `38619a0` (test/docs)

**In-checkpoint finishing fixes:**

5. **Rule 1 fix: `.more-done` gate predicate → nested resolver** — `48b61be` (fix) + deploy v1.11.11 chore-bump `3830b63` + CHANGELOG `e505df4`
6. **Rule 1 fix: Stop hook version-change early-return dropped** — `72b4263` (fix) + deploy v1.11.12 chore-bump `f27a3c7` + CHANGELOG `252250c`

**Plan metadata commit:** (this commit — `docs(25.4-07): complete plan`)

## Files Created/Modified

- `tests/perch_path_no_flat_psyche.rs` (NEW, 253 lines) — psyche-spawn integration test, 2 cases
- `tests/perch_path_no_flat_worker.rs` (NEW, 266 lines) — worker spool/inbox integration test, 4 cases
- `.planning/phases/25.4-.../25.4-SMOKE-PROBE.md` (NEW) — pre/post-deploy probe report + diff annotations + RCA + PASS verdict
- `.planning/phases/25.4-.../post-probe.txt` (NEW) — captured probe.ps1 output post-v1.11.10 deploy
- `src/owl/hook_idle.rs` (MODIFIED, finishing fixes) — gate predicate at line 137 migrated to `perch_path::resolve_ready_file` with `ParentHint::Explicit(owl_id)`; `VersionPrompt::Emitted => return` short-circuit dropped from Stop hook run() so `set_idle_ready` + `spawn_echo_commune_if_live` fire on version transitions

## Decisions Made

- **Finishing fixes applied in-checkpoint vs deferred to gap-closure plan:** Both fixes were small (single-file, ≤10-line scoped diffs), directly traced to Phase 25.4 seams (gate predicate) or orthogonal coupling discoverable only via the live probe (Stop hook early-return). Routing them as deviation Rule 1 fixes inside the checkpoint loop and recording the RCA in 25.4-SMOKE-PROBE.md was cleaner than a follow-up 25.4-08 gap-closure plan
- **Worker-perch asymmetry decoupled from Phase 25.4 GOAL:** Phase 25.4 asserts the NEGATIVE invariant "no flat side-effect dirs from writers." This holds. RESEARCH Open Q1 ("does `spawn_worker_perch` fire reliably for one-shot subagents") is an orthogonal POSITIVE invariant about subagent-perch registration — carried forward as a separate investigation
- **Operator accepted close without third reprobe** after v1.11.12 — gate-predicate fix has positive proof on v1.11.11 (sentinel observed nested), early-return fix has reasoned justification + downstream tests intact

## Deviations from Plan

### Auto-fixed Issues (Rule 1 — bugs caught by operator-verify smoke probe)

**1. [Rule 1 — Bug] `.more-done` gate predicate still composing flat path (Phase 25.4 miss)**
- **Found during:** Task 3 operator smoke probe — `.more-done` sentinel never appeared at nested path even though Stop hook should have fired
- **Issue:** `src/owl/hook_idle.rs:137` gate predicate was `owlery::ready_file(psyche_id).exists()` — a flat composition. Post-Phase-25.4 the ready file is written nested, so the flat gate always returned `false` → sentinel never written → wrapper echo-commune fired against a non-existent sentinel
- **Why missed:** Plan 25.4-04 listed `hook_idle.rs` in `files_modified` and migrated the `.more-done` WRITER at line 153. The READER predicate at line 137 went unmigrated because the audit was call-site-scoped: RESEARCH Class-E enumerated direct path-composition reader calls but did not require "for every file we touch the writer in, sweep all sibling readers in the same function." The gate predicate sat inside Plan 04's file but was conceptually Plan 06 work. Neither plan claimed it. Tests pinned the broken contract via the pre-fix `touch_psyche_ready` helper writing the flat path
- **Fix:** Flipped to `perch_path::resolve_ready_file(psyche_id, ParentHint::Explicit(owl_id))`
- **Files modified:** `src/owl/hook_idle.rs`
- **Verification:** Reprobe on v1.11.11 confirmed `.more-done` sentinel landed at `probetest/nested/probetest-psyche/.more-done` after Stop hook fired
- **Committed in:** `48b61be` (fix); deploy v1.11.11 `3830b63` + CHANGELOG `e505df4`

**2. [Rule 1 — Bug] Stop hook `VersionPrompt::Emitted => return` short-circuited filesystem sentinel writers (Phase 34 unrelated coupling)**
- **Found during:** Task 3 reprobe-of-reprobe — version-change emission was blocking both `set_idle_ready` and `spawn_echo_commune_if_live` on the version-transition fire
- **Issue:** Phase 34 over-broad ordering invariant — Stop hook returned early on any version-prompt emission, skipping pure filesystem sentinel writers that are orthogonal to spool/stdout. Result: `.more-done` dropped for one fire per version transition
- **Why missed:** Pre-Phase-25.4, the only side effects gated behind the version-prompt return were spool/stdout writes; the sentinel writers were added later (Phase 18.3 / 25.4-04 era) and the original ordering invariant was never revisited. Not a Phase 25.4 planner miss — pure orthogonal coupling caught during 25.4 reprobe
- **Fix:** Dropped the `VersionPrompt::Emitted => return` short-circuit from `hook_idle::run`; sentinel writers now always execute
- **Files modified:** `src/owl/hook_idle.rs`
- **Verification:** Reasoned justification (pure filesystem writes, orthogonal to version-prompt mechanism). Operator accepted without third reprobe; `.more-done` already present from prior v1.11.11 fire confirmed gate-side fix works regardless
- **Committed in:** `72b4263` (fix); deploy v1.11.12 `f27a3c7` + CHANGELOG `252250c`

---

**Total deviations:** 2 auto-fixed (both Rule 1 — bugs)
**Impact on plan:** Both fixes essential for Phase 25.4 GOAL claim to hold post-deploy. No scope creep — fixes scoped to a single file (`src/owl/hook_idle.rs`). The Phase 25.4-04 vs Plan 25.4-06 seam (writer migrated by 04, gate-predicate reader sat in 04's file but conceptually belonged to 06) is documented in 25.4-SMOKE-PROBE.md "Why was this missed by the Phase 25.4 planner?" as a process lesson for future migrations.

## Issues Encountered

- **Worker-mystery (RESEARCH Open Q1) low-signal observation:** One-shot subagent spawned during probe did not produce either a flat OR a nested worker perch dir. This is consistent with Phase 25.4 GOAL (no flat side-effect dirs) but means `spawn_worker_perch` evidently does not fire reliably for one-shot subagents. **Decoupled** from Phase 25.4 — carried forward as a separate investigation per CONTEXT D-07. Will file under `.planning/debug/<DATE>-worker-perch-asymmetry-persists.md` when prioritized

## User Setup Required

None — Phase 25.4 binary deployed cleanly (v1.11.10 → 11 → 12 patch bumps via `DEPLOY.ps1 -Bump patch`). `/reload-plugins` was run manually by operator per CLAUDE.md convention.

## Next Phase Readiness

- **Phase 25.4 GOAL closed:** zero flat psyche dirs, zero flat worker dirs, all writers route through `perch_path::resolve_*`. Wave 1+2+3 migration end-to-end-verified
- **RESEARCH Open Q1 (worker-perch asymmetry)** carried forward as separate investigation — not blocking on v1.8 milestone progress
- **Reader-side dual-path tear-down** (CONTEXT D-03/D-08 — `wrapper_state_path_resolved`, `pick_fresher_more_done`) still pending one more deploy cycle per CONTEXT discipline; tracking for future cleanup phase
- **Phase 25.4 is the seventh and final plan** of Phase 25.4; phase complete pending milestone-close audit

## Self-Check: PASSED

- `tests/perch_path_no_flat_psyche.rs` exists (commit `c6588bd` confirmed in `git log --oneline`)
- `tests/perch_path_no_flat_worker.rs` exists (commit `95f0467` confirmed)
- `.planning/phases/25.4-.../25.4-SMOKE-PROBE.md` exists with PASS verdict + finishing-fix RCA (commit `38619a0`)
- `src/owl/hook_idle.rs` gate predicate + early-return fixes confirmed (commits `48b61be`, `72b4263`)
- Deploys v1.11.11 + v1.11.12 confirmed via `git log --oneline` chore-bump commits `3830b63`, `f27a3c7`

---
*Phase: 25.4-perch-path-single-source-of-truth-finish-d-01-nested-migrati*
*Plan: 07*
*Completed: 2026-05-23*
