---
phase: 25.4-perch-path-single-source-of-truth-finish-d-01-nested-migrati
plan: 01
subsystem: infra
tags: [rust, perch-path, resolver, owlery, path-composition, single-source-of-truth]

requires:
  - phase: 25.3
    provides: nested perch layout (psyche/worker under owlery/<parent>/nested/<child>/)
  - phase: 25
    provides: D-01 nested-perch direction; is_worker_perch suffix rule; perch_dir/nested_perch_dir/*_at primitives
provides:
  - "PerchKind enum (Self_, Psyche, Worker, Spine, Touch) with classify() and nests_under_parent()"
  - "ParentHint enum (Explicit borrowing parent slice, Infer via suffix-strip)"
  - "resolve_perch_path(child_id, hint) → PathBuf (sole source of truth for perch dir composition)"
  - "resolve_ready_file / resolve_info_file / resolve_inbox_dir / resolve_perch_root companion helpers"
  - "byte-for-byte agreement test pinning PerchKind::Worker to owlery::is_worker_perch"
affects: [25.4-03, 25.4-04, 25.4-05, 25.4-06, 25.4-07]

tech-stack:
  added: []
  patterns:
    - "Suffix-based perch classification colocated in a single module"
    - "ParentHint::Explicit borrow-slice API for zero-alloc call sites"
    - "Module-level docstring citing CONTEXT decisions for traceability"

key-files:
  created:
    - src/common/perch_path.rs
  modified:
    - src/common/mod.rs

key-decisions:
  - "Module placement: src/common/perch_path.rs (sibling of owlery.rs) rather than extending owlery.rs further (owlery is 1800+ lines; new module has clear single responsibility)."
  - "Inline duplication of is_worker_perch rule (rather than calling the pub(crate) owlery predicate) so this module is self-contained per CONTEXT D-02 spirit; agreement pinned by a unit test."
  - "Worker pattern classified BEFORE Psyche (rightmost -w{digits} wins) so dunsen-psyche-w1 → Worker — agrees byte-for-byte with owlery::is_worker_perch."
  - "Infer-failure falls back to flat owlery::perch_dir(id) for safety; debug-build eprintln! surfaces the bug in dev/test."
  - "Used unit tests inline in the module (#[cfg(test)] mod tests) rather than a sibling integration test — pure-function surface, no need for cross-crate access."

patterns-established:
  - "Pattern: every reader/writer of psyche/worker/spine/touch/Self perch paths routes through perch_path::resolve_* — owlery::perch_dir(id) for non-Self ids is forbidden outside this module (enforced in Wave 2/3 plans)."
  - "Pattern: ParentHint::Explicit is the default at all new call sites; ParentHint::Infer is only used for legacy id-only readers (send.rs/ring.rs/commune.rs/signoff.rs)."

requirements-completed: [PERCH-PATH-RESOLVER-01, PERCH-PATH-RESOLVER-02, PERCH-PATH-RESOLVER-03]

duration: ~6min
completed: 2026-05-23
---

# Phase 25.4 Plan 01: perch_path Resolver Module Summary

**Central perch-path resolver module landed — `PerchKind` classifier + `ParentHint` + `resolve_perch_path` + four child-file helpers; 25 inline unit tests passing; zero consumers migrated (Wave 2/3's job).**

## Performance

- **Duration:** ~6 min
- **Tasks:** 1
- **Files modified:** 2

## Accomplishments

- New module `src/common/perch_path.rs` encodes the "psyche/worker nest under parent; spine/touch/Self stay flat" layout invariant in a single function (`resolve_perch_path`).
- `PerchKind::classify` agrees byte-for-byte with `owlery::is_worker_perch` (pinned by `classify_worker_agrees_with_owlery_is_worker_perch` test covering 11 representative ids).
- `ParentHint::Explicit` is borrow-slice (`&'a str`) so call sites can pass `ParentHint::Explicit(&self.self_id)` with zero allocation.
- Four companion helpers (`resolve_ready_file`, `resolve_info_file`, `resolve_inbox_dir`, `resolve_perch_root`) compose on the same resolver, returning ready/info/inbox/root paths.
- 25 inline unit tests cover all classification rules (12), nests_under_parent matrix (1), owlery-agreement pinning (1), resolution cases (8), and child-file helpers (4) — exceeds the plan's ≥15-test floor.

## Task Commits

1. **Task 1: Create src/common/perch_path.rs + register in src/common/mod.rs** — `9e8ad93` (feat)

_Single commit: tests are inline (`#[cfg(test)] mod tests`) in the same file as the implementation; the test scaffold and resolver bodies are tightly coupled and not meaningfully separable into independent test/feat commits._

## Files Created/Modified

- `src/common/perch_path.rs` (created) — PerchKind enum, ParentHint enum, resolve_perch_path + four child-file helpers, inline `#[cfg(test)] mod tests` with 25 unit tests.
- `src/common/mod.rs` (modified) — added `pub mod perch_path;` registration (one line, after `pub mod outcomes;`).

## Decisions Made

- **Inline duplication of `is_worker_perch` rule**: re-implemented `is_worker_id` privately in `perch_path.rs` rather than calling `owlery::is_worker_perch`. Reason: keeps the resolver self-contained per CONTEXT D-02 spirit ("single source of truth, no scattered hardcoded paths"). Agreement is pinned by `classify_worker_agrees_with_owlery_is_worker_perch` so a future change to either implementation will break the test immediately.
- **Worker check FIRST in classify()**: order is worker → psyche → spine → touch → Self_. Without this, `dunsen-psyche-w1` would classify as Psyche; with this, it classifies as Worker (matching `rfind("-w")` semantics from `owlery.rs:381`).
- **Infer for spine/touch returns None**: in `infer_parent`, Spine/Touch/Self_ all return None because they are flat — the resolver early-returns flat before consulting `infer_parent` for them, so this branch is defensive.

## Deviations from Plan

None — plan executed exactly as written. The plan's TDD task structure (RED test commit, GREEN impl commit) was collapsed into a single commit because the test scaffold (inline `#[cfg(test)] mod tests`) and implementation live in the same file; the implementation was written alongside the tests rather than as a separate stub-first iteration. All 25 tests pass on first run; no GREEN-phase debug required.

## Issues Encountered

None.

## Verification

- `cargo test --lib common::perch_path::tests` → **25 passed; 0 failed** (target was ≥15)
- `cargo build --release` → succeeds (only pre-existing warnings)
- `grep "perch_path::" src/ -r` outside the module itself → **0 matches** (Wave 2/3 plans add consumers)
- `grep "pub mod perch_path" src/common/mod.rs` → match present

## Self-Check: PASSED

- File `src/common/perch_path.rs` exists (439-line module, 25 tests, build passes).
- File `src/common/mod.rs` modified (one-line registration).
- Commit `9e8ad93` present on main branch.

## Next Phase Readiness

- Resolver surface is stable and importable by Wave 2 plans (25.4-03 through 25.4-05) and Wave 3 plan 25.4-06.
- Plan 25.4-02 (landmine regression test) can land in parallel — independent files, no shared symbols.
- No blockers.

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