<!-- task=m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover plan=4 tag=REVIEW_REFACTOR -->

# Preserve Deep PLAN Paths and Disposal Inventory

## For the Implementing Agent

Run every verification command, fill implementation-owned sections in `CODE_REVIEW-cloud-G08.md` with actual notes and stdout/stderr, keep the active pair in place, and report ready for review. Finalization belongs only to the code-review skill. If blocked, record the exact blocker, attempted commands/output, and resume condition in implementation-owned evidence fields. Do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The third S13 follow-up closes the original broken-symlink, production-caller, and executable-disposal findings, but review found two remaining fail-closed gaps. The path canonicalizer counts ordinary nonexistent components as symlink resolutions, and disposed validation trusts a truncated historical inventory. This follow-up preserves both the literal-file admission contract and checksum-bound disposal evidence without deleting the retained fixtures.

## Archive Evidence Snapshot

- Prior task: `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover`.
- Prior verdict: FAIL in `code_review_cloud_G08_3.log`; Required=2, Suggested=0, Nit=0.
- Prior plan: `plan_cloud_G08_3.log`.
- Required finding 1: `canonicalizeCandidateSeen` rejects a valid contained new-file path after 255 nonexistent parent components because its depth counter is not limited to symlink traversals.
- Required finding 2: disposed validation accepts a shortened `Disposal.ReferenceFiles` list after deletion and therefore loses checksum-bound historical evidence.
- Fresh reviewer evidence: formatting, focused, race, vet, parity, build, built validator/parity/dry-run, broad zero-match, and full Go suites passed with `/tmp` cache roots. One temporary reviewer reproducer deterministically failed both omitted variants and was removed.
- Roadmap carryover: approved SDD S13 and its Evidence Map still require symlink-resolved literal write-set containment plus disposition-complete, checksum-bound physical-disposal evidence.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- Milestone link: [Milestone document](agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
- Task ids:
  - `parity-cutover`: classify Python/Node behavior and cut production task-loop ownership to Go with zero unclassified/runtime dependency/duplicate
- Completion mode: check-on-pass

## Analysis

### Files Read

- `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/PLAN-cloud-G08.md`
- `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/CODE_REVIEW-cloud-G08.md`
- `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/plan_cloud_G08_2.log`
- `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/code_review_cloud_G08_2.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/complete.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/21+13,17,20_project_log_sink/complete.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/22+16_local_control/complete.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/complete.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/25+19,21,22,23,24_logged_smoke_closure/complete.log`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`
- `agent-contract/index.md`
- `agent-contract/inner/agent-runtime.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `agent-spec/index.md`
- `agent-ops/rules/project/domain/agent/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- `apps/agent/internal/taskloop/workflow.go`
- `apps/agent/internal/taskloop/workflow_test.go`
- `apps/agent/internal/taskloop/parity.go`
- `apps/agent/internal/taskloop/parity_test.go`
- `apps/agent/internal/taskloop/cutover_test.go`
- `apps/agent/internal/taskloop/testdata/parity.yaml`
- `apps/agent/internal/command/task_loop.go`
- `apps/agent/cmd/agent/main.go`

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`; status `[승인됨]`, lock released.
- Target scenario: S13, mapped to Milestone Task `parity-cutover`.
- S13 requires zero unclassified/Python-runtime ownership and physical Python disposal at the Milestone-completion transition. The shared `PlanWriteSet` interface also requires contained literal file claims with symlink-resolved physical keys.
- Evidence Map row S13 requires a disposition-complete parity matrix, stale-dependency/Python-fallback searches, and Python disposal evidence.
- The checklist therefore binds the historical disposal list to the classified reference inventory in both states and preserves every contained literal new-file path independently of the symlink-loop budget.

### Verification Context

- No neutral verification handoff was supplied.
- Repository-native sources are the approved SDD, runtime contracts, Agent/testing domain rules, current workflow/parity code, tests, and the archived FAIL findings named above.
- Local preflight: `command -v go` resolved `/config/.local/bin/go`; `go version` reported `go1.26.2 linux/arm64`; `go env GOROOT` reported `/config/opt/go`.
- Use repository-external `GOCACHE=/tmp/iop-s0-parity-fourth-gocache` and `TMPDIR=/tmp/t GOTMPDIR=/tmp/t`.
- The worktree contains intentional Milestone-wide uncommitted changes. Verification is scoped first to the four claimed source/test files and then to fresh Agent/full Go suites.
- No external provider, credential, remote runner, device, network service, or real provider process is required. Disposed-state coverage uses temporary fixture roots only.
- Reviewer reproduction proved both defects: a 256-component contained nonexistent target returned `too many symlink resolutions`, and a fully deleted disposed fixture set validated after its historical list was truncated.
- Confidence: high. Both failures have direct source branches and deterministic negative oracles.

### Test Coverage Gaps

- `workflow_test.go` covers a three-component nonexistent target but not a valid literal target deeper than the symlink-resolution budget.
- `parity_test.go` preserves the complete historical list when testing disposed state and therefore does not reject inventory loss after deletion.

### Symbol References

- `canonicalizeCandidateSeen` is private and called only by `canonicalizeCandidate`; its `depth` parameter and `maxCandidateSymlinkResolutions` are internal to `workflow.go`.
- `ValidateParityManifest` is called by `ValidateEmbeddedParityManifest` and the parity tests. No public signature change is required.
- No dependency manifest or external package change is required.

### Split Judgment

Keep one compact follow-up in the existing started subtask. The directory identity cannot be renamed or split outside `refine-plans`, and both fixes close the same S13 completion evidence claim with one taskloop verification boundary.

The declared predecessors are satisfied:

- `19`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/complete.log`
- `21`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/21+13,17,20_project_log_sink/complete.log`
- `22`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/22+16_local_control/complete.log`
- `23`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/complete.log`
- `25`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/25+19,21,22,23,24_logged_smoke_closure/complete.log`

### Scope Rationale

- Do not change production caller discovery, command exit mapping, Node, provider, selector, quota, review, integration, config, proto, or logged-smoke behavior; fresh evidence already passes those boundaries.
- Do not delete or edit the nine retained Python fixtures or change the current manifest state.
- Do not change the SDD, roadmap, or runtime contracts; their current text already requires the two missing invariants.
- Do not broaden PLAN claim syntax or change public command signatures.

### Final Routing

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh pair`
- build closures: scope/context/verification/evidence/ownership/decision all true
- review closures: scope/context/verification/evidence/ownership/decision all true
- capability-gap evidence: none
- build grade scores: scope=2, state=1, blast=2, evidence=2, verification=1; grade `G08`
- review grade scores: scope=2, state=1, blast=2, evidence=2, verification=1; grade `G08`
- build base route: `local-fit`
- large_indivisible_context: `false`
- positive loop risks: `boundary_contract`, `structured_interpretation`, `variant_product` (count `3`)
- recovery signals: `review_rework_count=3`, `evidence_integrity_failure=true`
- final build route: `recovery-boundary`, `cloud`, `PLAN-cloud-G08.md`
- final review route: `official-review`, `cloud`, `CODE_REVIEW-cloud-G08.md`

## Implementation Checklist

- [ ] Decouple nonexistent suffix reconstruction from the symlink traversal budget and add deterministic deep-target regression coverage without weakening escape or loop rejection.
- [ ] Require exact classified-reference/disposal inventory equality in retained and disposed states and add a disposed-state missing-history regression while preserving all nine live fixtures.
- [ ] Run fresh formatting, focused, race, vet, parity, built-command, zero-match, and full Go verification with repository-external cache/tmp roots.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REFACTOR-1] Separate Path Depth from Symlink Traversal

#### Problem

`apps/agent/internal/taskloop/workflow.go:581-615` increments one `depth` value for both actual symlink following and each missing parent traversed while reconstructing a new suffix. A contained 256-component nonexistent target therefore fails with `too many symlink resolutions`, contrary to the literal contained-file contract. `workflow_test.go:199-242` exercises only three missing components.

#### Solution

Preserve the bounded symlink-loop guard but do not charge ordinary path components against it. Resolve the nearest existing ancestor and actual symlinks, then append the cleaned missing suffix in order.

```text
Before: missing parent recursion -> depth++ -> symlink limit failure
After:  collect missing suffix -> resolve physical ancestor/symlinks -> append suffix
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/taskloop/workflow.go` — count only actual symlink traversals or use an iterative nearest-existing-ancestor reconstruction.
- [ ] `apps/agent/internal/taskloop/workflow_test.go` — add `TestValidatePlanWriteSetDeepNonexistentTarget` and retain contained/external broken-link and loop assertions.

#### Test Strategy

Add a deterministic path whose component count exceeds `maxCandidateSymlinkResolutions` while remaining below platform path-length limits. Require canonical workspace-relative success. Keep the existing external broken link and loop failures to prove the fix does not weaken containment.

#### Verification

```bash
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t \
  go test -count=1 ./apps/agent/internal/taskloop -run 'Test.*ValidatePlan'
```

Expected: deep literal new-file and contained-link cases pass; external and looping symlinks fail.

### [REVIEW_REFACTOR-2] Preserve Checksum-Bound Disposal History

#### Problem

`apps/agent/internal/taskloop/parity.go:166-198` checks absence only for entries that remain in `Disposal.ReferenceFiles`. After all fixtures are deleted, removing one historical entry still returns success because disposed mode never compares the disposal keys with the classified `ReferenceSources` union. `parity_test.go:122-153` keeps the complete list throughout its transition.

#### Solution

Build deterministic sets for classified Python reference sources and disposal entries, require exact equality before the retained/disposed state branch, and then apply the existing checksum-or-absence rules. This keeps historical inventory and coverage anchored without requiring deleted files to exist.

```text
Before: disposed -> discovered empty + listed entries absent -> pass
After:  classified inventory == disposal inventory -> disposed absence -> pass
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/taskloop/parity.go` — enforce exact bidirectional inventory equality before state-specific validation.
- [ ] `apps/agent/internal/taskloop/parity_test.go` — add `TestParityManifestDisposedRejectsMissingHistoricalInventory` after deleting the full temporary fixture set.

#### Test Strategy

Use the existing temporary parity fixture root. Verify retained success, full delete plus unchanged inventory disposed success, and full delete plus one removed historical entry failure. Keep current missing/survivor/unknown-state assertions.

#### Verification

```bash
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t \
  go test -count=1 ./apps/agent/internal/taskloop -run 'Test.*(Parity|Disposition|Disposal)'
```

Expected: exact retained and disposed inventories pass; truncated history, partial deletion, survivors, and unknown state fail.

## Dependencies and Execution Order

Predecessors 19, 21, 22, 23, and 25 are satisfied by the exact archived `complete.log` paths in `Analysis > Split Judgment`. Implement REVIEW_REFACTOR-1 and REVIEW_REFACTOR-2 independently, then run the combined verification. Do not delete retained fixtures.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/taskloop/workflow.go` | REVIEW_REFACTOR-1 |
| `apps/agent/internal/taskloop/workflow_test.go` | REVIEW_REFACTOR-1 |
| `apps/agent/internal/taskloop/parity.go` | REVIEW_REFACTOR-2 |
| `apps/agent/internal/taskloop/parity_test.go` | REVIEW_REFACTOR-2 |
| `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/CODE_REVIEW-cloud-G08.md` | REVIEW_REFACTOR-1, REVIEW_REFACTOR-2 |

## Final Verification

```bash
mkdir -p /tmp/iop-s0-parity-fourth-gocache /tmp/t
gofmt -w apps/agent/internal/taskloop/workflow.go apps/agent/internal/taskloop/workflow_test.go apps/agent/internal/taskloop/parity.go apps/agent/internal/taskloop/parity_test.go
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t go test -count=1 ./apps/agent/internal/taskloop -run 'TestValidatePlanWriteSetDeepNonexistentTarget|TestParityManifestDisposedRejectsMissingHistoricalInventory'
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t go test -count=1 ./apps/agent/internal/taskloop ./apps/agent/internal/command ./apps/agent/cmd/agent
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t go test -count=1 -race ./apps/agent/internal/taskloop ./apps/agent/internal/command ./apps/agent/cmd/agent
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t go vet ./apps/agent/internal/taskloop ./apps/agent/internal/command ./apps/agent/cmd/agent
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t make test-iop-agent-parity
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t make build-agent
build/bin/iop-agent task-loop validate-plan --workspace /config/workspace/iop-s0 agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/PLAN-cloud-G08.md
build/bin/iop-agent task-loop parity --disposal-manifest
build/bin/iop-agent task-loop --dry-run --task-group m-iop-agent-cli-runtime
rg -n --sort path 'python3 .*orchestrate-agent-task-loop|dispatch\.py --workspace|dispatch\.py' agent-ops/skills/common agent-ops/skills/project agent-ops/rules --glob '!**/tests/**' --glob '!agent-ops/skills/project/orchestrate-agent-task-loop/scripts/**'
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-fourth-gocache GOTMPDIR=/tmp/t go test -count=1 ./packages/go/... ./apps/agent/...
git diff --check
```

Expected: formatting, focused, race, vet, parity, build, Go PLAN validation, retained disposal, dry-run, and full Go regression checks pass. The production caller search exits 1 with no matches. No real provider launches, and all nine retained fixtures remain unchanged.

After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
