<!-- task=m-iop-agent-cli-runtime plan=8 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API -->

# Plan - Reject shell metacharacters anywhere in validation declaration prose

## For the Implementing Agent

Filling the implementation-owned sections in `CODE_REVIEW-*-G??.md` is mandatory. Implement the checklist, run every verification command, paste actual notes and stdout/stderr into the active review file, leave the active Plan/Review files in place, and report ready for review. Final verdicts, log renames, `complete.log`, and archive moves belong only to the code-review skill. If blocked, record only 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 eighth review confirmed that the structural Markdown parser and the prescribed safe regression suites pass, but the classifier still accepts shell metacharacters when prose separates them from the exact dispatcher command span. A fresh reviewer probe showed four variants in which the exact command is erased before ownership scanning. This follow-up closes that fail-closed boundary without changing product runtime behavior.

## Archive Evidence Snapshot

- `agent-task/m-iop-agent-cli-runtime/plan_cloud_G04_7.log` and `agent-task/m-iop-agent-cli-runtime/code_review_cloud_G04_7.log`: eighth loop, FAIL with 1 Required, 0 Suggested, and 0 Nit findings.
- Remaining Required finding: `classifyExactValidationDocumentation` checks single-character shell operators only at the immediate prefix/suffix boundary, so non-adjacent `;`, `>`, and `|` compositions remain classified as declarations and are removed before caller scanning.
- Fresh reviewer evidence: the prescribed focused ownership suite, safe full `apps/agent/...` suite with only `TestBuiltBinaryHeadlessS10Transcript` skipped, and the race suite passed. A temporary four-case reviewer test proved that a non-adjacent prefix semicolon plus suffix semicolon, output redirection, and pipeline forms all return the exact declaration identity; the probe file was removed immediately. No product `iop-agent` binary or real provider CLI was executed.
- Roadmap carryover: `cli-surface` remains the only unchecked Milestone Task and requires SDD S10 headless evidence plus `Roadmap Completion` after the supporting S13 ownership guard passes.

## 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:
  - `cli-surface`: headless binary, split config, Milestone discovery/selection/preview, lifecycle, and per-work overlay/integration/blocker observation
- Completion mode: check-on-pass

## Analysis

### Files Read

- Active loop: `agent-task/m-iop-agent-cli-runtime/PLAN-cloud-G04.md` and `agent-task/m-iop-agent-cli-runtime/CODE_REVIEW-cloud-G04.md`.
- Changed test oracle, read in full: `apps/agent/internal/taskloop/cutover_test.go`.
- Contract and design: `agent-contract/index.md`, `agent-contract/inner/iop-agent-cli-runtime.md`, `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`, `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`, and `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`.
- Rules and verification: `agent-ops/rules/project/rules.md`, `agent-ops/rules/project/domain/agent/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-test/local/rules.md`, and `agent-test/local/testing-smoke.md`.
- Workflow: `agent-ops/skills/common/router.md`, `agent-ops/skills/common/code-review/SKILL.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`, and `agent-ops/skills/common/plan/templates/review-stub-template.md`.
- Agent spec: `agent-ops/rules/common/rules-agent-spec.md` and `agent-spec/index.md`; no standalone `iop-agent` CLI spec matches this work.

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`; status `[approved]`, lock released, and no unresolved `USER_REVIEW.md`.
- Primary target: S10 / `cli-surface`; its Evidence Map requires binary plus split-config command integration and a headless operation transcript.
- Supporting boundary: S13 requires zero product Python fallback/callers while the transitional dispatcher validation preflight is restricted to declared Agent-Ops documents and exact command forms.
- The checklist retains accepted S10 evidence and requires every shell metacharacter in Markdown prose outside the exact command span to remain visible to the caller scanner, with deterministic positive and negative fixtures, before emitting `cli-surface` Roadmap Completion.

### Verification Context

- Supplied handoff: eighth-review source inspection, active pair, current diff, claimed implementation output, and predicted archive identities.
- Local preflight: `/config/workspace/iop-s0`; Go `/config/.local/bin/go`, `go version go1.26.2 linux/arm64`, and `GOROOT=/config/opt/go`; the worktree contains task-owned carryover and unrelated provider-usage task changes.
- Fresh reviewer commands passed: the prescribed focused ownership tests, safe full Agent tests with only the prohibited compiled-binary test skipped, and the internal race suite.
- Fresh reviewer reproducer: a temporary table test called `classifyExactValidationDocumentation` with four non-adjacent operator variants. All four were incorrectly declared; the file was removed immediately after execution.
- Preconditions: retain the structural span parser, exact candidate/written identity accounting, restored compiled transcript, canonical slug behavior, aligned S13 contract, and prior task-owned files. Use deterministic temporary fixtures and no real provider process.
- Constraints: do not edit Agent-Ops common/project rules or skills, production CLI/runtime behavior, roadmap, SDD, contract, or agent-spec. Official worker/review/final-verification paths must not execute the product `iop-agent` binary.
- External Verification Preflight: not applicable. Darwin is a local cross-build, and logged-in macOS/provider execution remains prior S14 evidence.
- Gap and confidence: the remaining gap is deterministic and isolated to validation of parsed prose segments in one test file; confidence is high.

### Test Coverage Gaps

- Existing negative cases cover operators immediately adjacent to the exact span, command substitution, repeated occurrences, malformed spans, and double-backtick delimiters.
- Missing coverage allows the same `;`, `&`, `|`, `<`, or `>` token when ordinary prose appears between the exact span and the operator, or between a prefix operator and the span.
- Existing accepted-form coverage must remain unchanged, including raw, whole single-backtick, list-inline candidate/written identities, and an unrelated preserved code span.

### Symbol References

- No production symbol is renamed or removed.
- `classifyExactValidationDocumentation`, `parseMarkdownCodeSpans`, and the new prose-safety helper remain test-local in `apps/agent/internal/taskloop/cutover_test.go`; `noDispatcherCallerViolation` is the sole classifier caller.

### Split Judgment

Keep one compact plan. Prose safety, exact command removal, preserved non-exempt spans, and their positive/negative fixtures are one fail-closed ownership invariant in one test file.

### Scope Rationale

- Change only `apps/agent/internal/taskloop/cutover_test.go` and the active review evidence.
- Retain task-owned contract, compiled transcript, canonical slug, workflow, and adapter changes unchanged.
- Exclude product runtime behavior, Agent-Ops rules/skills, roadmap, SDD, agent-spec, real provider execution, and product-binary execution.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer `finalize-task-policy.sh`, mode `pair`.
- Build closures: scope/context/verification/evidence/ownership/decision all `true`; grade scores `1/0/1/1/1` = `G04`; base `local-fit`, final `recovery-boundary`, lane `cloud`.
- Review closures: scope/context/verification/evidence/ownership/decision all `true`; grade scores `1/0/1/1/1` = `G04`; route `official-review`, lane `cloud`, adapter `codex`, model `gpt-5.6-sol`, reasoning `xhigh`.
- `large_indivisible_context=false`; positive loop risks: `boundary_contract`, `structured_interpretation` (`loop_risk_count=2`).
- Recovery signals: `review_rework_count=8`, `evidence_integrity_failure=false`; recovery boundary matched.
- Capability gap: none.
- Canonical files: `PLAN-cloud-G04.md`, `CODE_REVIEW-cloud-G04.md`.

## Implementation Checklist

- [ ] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1] Reject shell metacharacters anywhere in parsed Markdown prose outside the exact validation command span and add the complete non-adjacent regression matrix.
- [ ] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] Run the fresh no-product-binary S10/S13 closure matrix and record exact output.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1] Make parsed prose fail closed

**Problem**

`apps/agent/internal/taskloop/cutover_test.go:563-575` rejects a single-character shell operator only at the end of `prefixText` or the beginning of `suffixText`. Lines such as `- check <exact-span> then ; echo done` and `- preface ; then <exact-span>` therefore erase the dispatcher call before ownership patterns run, contrary to the plan and S13 exact-declaration boundary.

**Solution**

- Derive the prose segments outside all parsed code spans from `markdownCodeSpan.prefixProse` plus the final `suffixProse`.
- Apply one test-local helper to every prose segment. Reject `$(`, `&&`, `||`, and every occurrence of `;`, `&`, `|`, `<`, or `>` regardless of distance from the exact command span. Apply the existing shell execution/control-word check consistently to both sides.
- Preserve non-exempt parsed code spans byte-for-byte in the returned scanner text. Malformed backticks, non-single delimiters around the exact command, repeated exact spans, and multiple raw occurrences remain fail-closed.
- Extend the negative table with non-adjacent prefix and suffix rows covering the complete single-character operator set, and assert the classifier returns the original line with no declared identity before the end-to-end exact path/line check.

Before (`apps/agent/internal/taskloop/cutover_test.go:563`):

```go
if regexp.MustCompile(`([><|;&])\s*$`).MatchString(prefixText) {
	return line, nil
}
if regexp.MustCompile(`^\s*([><|;&])`).MatchString(suffixText) {
	return line, nil
}
```

After:

```go
for _, prose := range validationProseSegments(spans) {
	if containsUnsafeValidationProse(prose) {
		return line, nil
	}
}
```

The helper names are illustrative. Keep the implementation test-local, deterministic, and fail closed.

**Modified Files and Checklist**

- [ ] `apps/agent/internal/taskloop/cutover_test.go`: validate all parsed prose segments and add direct plus end-to-end non-adjacent operator fixtures.

**Test Strategy**

- Extend `TestCutoverProductionOwnershipRejectsNonExactValidationVariants` with prefix/suffix cases for `;`, `&`, `|`, `<`, and `>` separated from the exact span by prose.
- Add direct classifier assertions for the reviewer-observed prefix semicolon and suffix semicolon/redirection/pipeline variants so a future caller-pattern change cannot hide the declaration error.
- Retain `TestClassifyExactValidationDocumentationAcceptsDeclaredForms` unchanged as the positive contract, including preserved non-exempt code spans.

**Verification**

```bash
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/cache go test -count=1 -v ./apps/agent/internal/taskloop -run '^(TestClassifyExactValidationDocumentationAcceptsDeclaredForms|TestCutoverProductionOwnershipHasNoReferenceCallerOrStaticRouteTable|TestCutoverProductionOwnershipRejectsInjectedPythonCaller|TestCutoverProductionOwnershipRejectsUnexpectedCallerInAllowedDocument|TestCutoverProductionOwnershipRejectsNonExactValidationVariants|TestCutoverProductionOwnershipRejectsMissingDeclaredValidationCommand)$'
```

Expected: PASS; all accepted declarations retain exact identities, and every immediate or non-adjacent shell composition remains visible and fails with exact path/line evidence.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] Fresh safe S10/S13 closure

**Problem**

The final ownership guard controls whether retained S10 transcript and S13 cutover evidence can support `cli-surface` Roadmap Completion. Final verification must cover the current checkout without executing the product binary or a real provider CLI in the active task loop.

**Solution**

- Run the focused parser matrix, safe full Agent suite, internal race matrix, static analysis, builds, and logged-smoke preflight with fresh external caches.
- Preserve prior compiled transcript evidence and do not execute a product binary or real provider CLI.
- Record every command's actual stdout/stderr and any deviation in the active review file.

**Modified Files and Checklist**

- [ ] `agent-task/m-iop-agent-cli-runtime/CODE_REVIEW-cloud-G04.md`: fill implementation evidence and exact command output.

**Test Strategy**

- Item 1 owns the structural parser regression matrix.
- Use `-count=1` and task-specific external caches. The safe full suite skips exactly `TestBuiltBinaryHeadlessS10Transcript`; no real provider CLI is allowed.
- Re-run the internal race suite because a PASS emits the final S10 roadmap evidence.

**Verification**

```bash
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/cache go test -count=1 -skip '^TestBuiltBinaryHeadlessS10Transcript$' ./apps/agent/...
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/race-cache go test -count=1 -race ./apps/agent/internal/taskloop ./apps/agent/internal/command ./apps/agent/internal/bootstrap ./packages/go/agenttask ./packages/go/agentstate
```

Expected: PASS across the safe Agent packages and internal race matrix without product-binary or real-provider execution.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/taskloop/cutover_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1 |
| `agent-task/m-iop-agent-cli-runtime/CODE_REVIEW-cloud-G04.md` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2 |

## Final Verification

Fresh execution is required; cached Go test output is not acceptable. Do not execute the product `iop-agent` binary or a real provider CLI.

```bash
install -d -m 700 /var/tmp/i /var/tmp/iop-cli-cutover-final/cache /var/tmp/iop-cli-cutover-final/race-cache /var/tmp/iop-cli-cutover-final/darwin-cache
gofmt -w apps/agent/internal/taskloop/cutover_test.go
```

Expected: temporary directories are ready outside the repository and formatting completes without error.

```bash
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/cache go test -count=1 -v ./apps/agent/internal/taskloop -run '^(TestClassifyExactValidationDocumentationAcceptsDeclaredForms|TestCutoverProductionOwnershipHasNoReferenceCallerOrStaticRouteTable|TestCutoverProductionOwnershipRejectsInjectedPythonCaller|TestCutoverProductionOwnershipRejectsUnexpectedCallerInAllowedDocument|TestCutoverProductionOwnershipRejectsNonExactValidationVariants|TestCutoverProductionOwnershipRejectsMissingDeclaredValidationCommand)$'
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/cache go test -count=1 -skip '^TestBuiltBinaryHeadlessS10Transcript$' ./apps/agent/...
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/race-cache go test -count=1 -race ./apps/agent/internal/taskloop ./apps/agent/internal/command ./apps/agent/internal/bootstrap ./packages/go/agenttask ./packages/go/agentstate
```

Expected: focused structural parser, safe full Agent, and internal race suites PASS without executing the product binary.

```bash
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/cache go vet ./apps/agent/internal/taskloop ./apps/agent/cmd/agent ./apps/agent/internal/bootstrap ./packages/go/...
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/cache make build-agent
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/darwin-cache GOOS=darwin GOARCH=arm64 go build -trimpath -o /var/tmp/iop-cli-cutover-final/iop-agent-darwin-arm64 ./apps/agent/cmd/agent
TMPDIR=/var/tmp/i GOTMPDIR=/var/tmp/i GOCACHE=/var/tmp/iop-cli-cutover-final/cache make test-iop-agent-logged-smoke-preflight
```

Expected: vet, current-host build, Darwin arm64 cross-build, and non-provider logged-smoke preflight PASS.

```bash
rg --sort path -n '\b(TestCutoverProductionOwnership|TestClassifyExactValidationDocumentationAcceptsDeclaredForms|classifyExactValidationDocumentation|parseMarkdownCodeSpans|validationProse|declaredDispatcherValidationCommand|dispatch\.py)\b' apps/agent/internal/taskloop/cutover_test.go agent-contract/inner/iop-agent-cli-runtime.md agent-ops/skills/common/plan/SKILL.md agent-ops/skills/common/code-review/SKILL.md agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md
git diff --check
git status --short
```

Expected: ownership references are intentional, prose validation is test-local, no whitespace errors exist, and only task-owned changes plus unrelated pre-existing worktree changes remain.

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