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

# Close PLAN Containment, Caller Discovery, and Disposal Transition

## 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 second S13 follow-up closed the original Python caller and terminal-exit defects, but review found three fail-closed evidence gaps. The Go PLAN validator accepts an external broken-symlink leaf, the cutover guard enumerates five documents instead of discovering active production files, and the documented deletion transition cannot pass once the retained Python fixtures are absent. This follow-up closes those three variants without deleting the retained fixtures before the Milestone-completion gate.

## 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_2.log`; Required=3, Suggested=0, Nit=0.
- Prior plan: `plan_cloud_G08_2.log`.
- Required finding 1: `canonicalizeCandidate` ignored a broken symlink leaf after `EvalSymlinks` returned `ENOENT`; a reviewer probe for an external target exited 0.
- Required finding 2: `cutoverProductionDocuments` and its injected-caller test supplied a fixed path list, so a new active common/project/rules document could call the retained dispatcher without failing the production guard.
- Required finding 3: retained-mode validation requires every reference path to exist, while the recorded transition requires deletion followed by the same parity suite; no disposed state can produce the promised S13 evidence.
- Fresh reviewer evidence: focused, race, vet, parity, build, built validator/parity/dry-run, broad zero-match, and full Go suites passed with `/tmp` cache roots. Fresh `gofmt -d` contradicted the recorded formatting claim; the reviewer repaired only that non-behavioral drift.
- Roadmap carryover: approved SDD S13 and its Evidence Map still require symlink-resolved write-set containment, trustworthy zero Python production callers, and executable 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_G07_1.log`
- `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/code_review_cloud_G07_1.log`
- `agent-task/m-iop-agent-cli-runtime/26+19,21,22,23,25_parity_cutover/code_review_cloud_G10_0.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-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/service.go`
- `apps/agent/internal/command/task_loop.go`
- `apps/agent/internal/command/task_loop_test.go`
- `apps/agent/internal/command/root_test.go`
- `apps/agent/cmd/agent/main.go`
- `apps/agent/cmd/agent/main_test.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 Python runtime dependency/fallback and physical Python disposal at the Milestone-completion transition; the shared `PlanWriteSet` contract also requires symlink-resolved physical containment.
- Evidence Map row S13 requires a disposition-complete parity matrix, stale-dependency/Python-fallback searches, and Python disposal evidence.
- The checklist therefore requires a symlink-complete candidate canonicalizer, a discovered production caller scope, and an explicit retained-to-disposed verifier that can pass before and after the deletion gate without deleting live fixtures now.

### Verification Context

- No neutral verification handoff was supplied.
- Repository-native sources are the approved SDD, standalone runtime contract, agent/testing domain rules, current Go workflow/parity/cutover code, 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`.
- The inherited `GOCACHE` remains inaccessible. Use repository-external `GOCACHE=/tmp/iop-s0-parity-third-gocache` and `TMPDIR=/tmp/t GOTMPDIR=/tmp/t`.
- Reviewer verification passed for focused, race, vet, parity, build, built command, broad zero-match, and full Go suites. The broken-symlink probe alone contradicted validator correctness, and fresh `gofmt -d` established `evidence_integrity_failure=true`.
- No external provider, credential, remote runner, device, network service, or real provider process is required. Disposed-state coverage uses temporary fixture roots only.
- Confidence: high. Each defect has an exact source branch and deterministic negative oracle.

### Test Coverage Gaps

- `workflow_test.go` has no contained or external broken-symlink leaf, symlink loop, or nested nonexistent-path case.
- `cutover_test.go` proves pattern rejection only after the caller path is supplied; it does not prove discovery of a new active production document.
- `parity_test.go` covers retained completeness and unrecorded fixtures only; it has no retained-to-disposed transition, mixed-state rejection, or post-deletion success case.

### Symbol References

- `canonicalizeCandidate` is private and called only by `canonicalWriteClaim` in `workflow.go`.
- `validateCutoverProductionOwnership` and `cutoverProductionDocuments` are test-only symbols in `cutover_test.go`.
- `ParityDisposal` is decoded from embedded/test manifests and consumed by `ValidateParityManifest` and `FormatParityReport`; update every fixture constructor and report assertion when adding lifecycle state.

### Split Judgment

Keep one follow-up in the existing started subtask. The path may not be renamed or split outside `refine-plans`, and all three fixes are required to close the same S13 evidence claim: literal workspace containment, zero active Python callers, and an executable disposal transition. They share the final parity/cutover and built-command verification boundary.

### Scope Rationale

- Do not delete or edit the nine retained Python fixtures in this packet.
- Do not change task-loop scheduling, terminal exit status, provider construction, shared manager semantics, Node runtime, or common plan/code-review caller text.
- Do not broaden static provider/model/capacity bans into unrelated deployment skills; discover Python callers across production roots, but keep static-route checks scoped to S13-owned orchestration documents.
- Do not modify roadmap state; PASS metadata remains a runtime responsibility.
- `agent-spec/index.md` has no matching standalone `iop-agent` spec, so no living-spec change is required.

### 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: `temporal_state`, `boundary_contract`, `structured_interpretation`, `variant_product` (count `4`)
- recovery signals: `review_rework_count=2`, `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

- [ ] Make PLAN write-claim canonicalization resolve nonexistent paths through every symlink component, normalize contained broken-symlink targets, reject external or looping aliases, and add deterministic regression coverage.
- [ ] Replace fixed production document enumeration with deterministic active-root discovery for Python callers while retaining bounded static-route and exact Go-validator ownership checks, with an unlisted injected-caller regression.
- [ ] Add an explicit retained/disposed disposal lifecycle that validates exact hashes before deletion, exact absence after transition, mixed-state rejection, and stable operator/contract evidence without deleting 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] Enforce Symlink-Complete PLAN Containment

#### Problem

`apps/agent/internal/taskloop/workflow.go:523-581` falls back from `EvalSymlinks(candidate)` to resolving only `filepath.Dir(candidate)`. `os.Lstat` still sees a broken leaf symlink, but the fallback never reads it, so a link to a nonexistent external file is normalized as the in-workspace alias and accepted. `workflow_test.go:148-197` has no symlink case.

#### Solution

Resolve path components with symlink awareness even when the final target does not exist. Follow each existing symlink with a bounded loop check, carry remaining nonexistent suffix components over the resolved physical target, normalize a contained broken symlink to its physical target, and apply workspace containment only after that resolution.

```text
Before: EvalSymlinks(candidate) ENOENT -> EvalSymlinks(parent) + unchecked leaf
After:  resolve existing components/symlinks -> append safe nonexistent suffix -> containment
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/taskloop/workflow.go` — replace the broken-leaf fallback with bounded symlink-complete canonicalization that also supports nested new files.
- [ ] `apps/agent/internal/taskloop/workflow_test.go` — add contained/external broken symlink, loop, and nested nonexistent target matrix cases.

#### Test Strategy

Extend `TestValidatePlanWriteSetMatrix` or add `TestValidatePlanWriteSetSymlinkContainment`. Assert a contained broken link returns its physical workspace-relative target, an external broken link and loop fail, and a normal nested new file remains valid. No provider/runtime is constructed.

#### Verification

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

Expected: all literal-path cases pass and every symlink escape/loop fails before mutation.

### [REVIEW_REFACTOR-2] Discover the Full Production Caller Scope

#### Problem

`apps/agent/internal/taskloop/cutover_test.go:15-40,90-115` checks a fixed five-file slice. Its negative test calls the validator with the injected path, so it cannot prove that an unlisted new active production document is discovered. The broad reviewer search currently has no Python caller, but that point-in-time output does not make the guard fail-closed.

#### Solution

Walk the declared production roots in deterministic repository-relative order. Exclude test directories and the exact checksum-bound retained reference script subtree, scan every remaining regular production file for Python dispatcher calls, and report exact path/line evidence. Keep static provider/model/capacity patterns on their bounded S13 orchestration documents so unrelated deployment documentation remains valid. Continue requiring the exact Go validator command in common plan/code-review skills.

```text
Before: validateCutoverProductionOwnership(root, fiveExplicitPaths)
After:  discoverProductionCallerFiles(root, roots, exactExclusions) -> validate all discovered files
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/taskloop/cutover_test.go` — split discovered Python-caller scope from bounded static ownership checks and add deterministic path/line diagnostics.
- [ ] `apps/agent/internal/taskloop/cutover_test.go` — add an unlisted active-document fixture whose caller is found without passing its path.

#### Test Strategy

Add a temporary `agent-ops/skills/common/other/SKILL.md` caller below a declared root and invoke only the root-discovery validator. Assert exact relative path and line. Also prove test fixtures and the exact retained reference subtree are excluded while a production script outside that subtree remains guarded.

#### Verification

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

Expected: current production roots pass, and every unlisted production caller fixture fails with deterministic evidence.

### [REVIEW_REFACTOR-3] Make the Disposal Gate Executable

#### Problem

`apps/agent/internal/taskloop/parity.go:131-193` requires every reference and disposal path to exist, while `testdata/parity.yaml:266-272` and `agent-contract/inner/iop-agent-cli-runtime.md:50` require deleting those files and rerunning parity/cutover. The first deletion therefore yields a stale-path error and no post-disposal evidence can pass.

#### Solution

Add a strict disposal lifecycle such as `state: retained | disposed`. Retained mode preserves current exact discovery and checksum rules. Disposed mode preserves the recorded inventory/checksums as historical evidence, requires every recorded reference path to be absent, requires discovery to find zero retained Python fixtures, and rejects present, partial, unknown, or mixed states. Keep the embedded manifest in retained state now; the Milestone-completion transition first runs retained verification, deletes only the recorded files, changes the state to disposed, and reruns the same suite.

```text
retained -> verify exact inventory and hashes -> delete recorded fixtures
         -> set disposed -> verify exact absence and zero production callers
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/taskloop/parity.go` — validate strict retained/disposed semantics and include lifecycle state in the stable report.
- [ ] `apps/agent/internal/taskloop/parity_test.go` — add retained-to-disposed, partial deletion, unexpected survivor, unknown state, and stable report cases.
- [ ] `apps/agent/internal/taskloop/testdata/parity.yaml` — declare the current `retained` state and an executable transition procedure without changing fixture contents.
- [ ] `agent-contract/inner/iop-agent-cli-runtime.md` — align S13 evidence with the two-phase retained/disposed verification contract.

#### Test Strategy

Use only temporary fixture roots. Validate retained state, delete the exact temporary fixture inventory, switch to disposed, and require success. Assert deletion without a state transition, a disposed state with one survivor, a retained state with one missing file, and an unknown state all fail.

#### Verification

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

Expected: retained and fully disposed states pass their own exact invariants; every partial or ambiguous state fails.

## 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/cutover_test.go` | REVIEW_REFACTOR-2 |
| `apps/agent/internal/taskloop/parity.go` | REVIEW_REFACTOR-3 |
| `apps/agent/internal/taskloop/parity_test.go` | REVIEW_REFACTOR-3 |
| `apps/agent/internal/taskloop/testdata/parity.yaml` | REVIEW_REFACTOR-3 |
| `agent-contract/inner/iop-agent-cli-runtime.md` | REVIEW_REFACTOR-3 |
| `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, REVIEW_REFACTOR-3 |

## Final Verification

```bash
mkdir -p /tmp/iop-s0-parity-third-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 apps/agent/internal/taskloop/cutover_test.go
test -z "$(gofmt -d 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)"
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-third-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-third-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-third-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-third-gocache GOTMPDIR=/tmp/t make test-iop-agent-parity
TMPDIR=/tmp/t GOCACHE=/tmp/iop-s0-parity-third-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-third-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. Temporary disposed-state fixtures prove the post-deletion gate; the nine live retained fixtures remain unchanged. No real provider launches.

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