<!-- task=dispatcher_workspace_ownership plan=0 tag=REFACTOR -->

# Dispatcher Workspace Ownership and Runtime Write Claims

## For the Implementing Agent

Filling every implementation-owned section in `CODE_REVIEW-*-G??.md` is mandatory. Run the specified verification, record actual implementation notes and output, keep the active task files 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 only 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

Persisted dispatcher locators can currently make one physical checkout monitor a session created from another checkout because liveness recovery trusts process and stream evidence without first validating workspace ownership. The dispatcher also parses each PLAN's modified-file table but deliberately ignores it during admission, allowing independent tasks that target the same file to run concurrently. Workspace-bound liveness and persistent per-workspace write claims are required to make isolated worktrees parallel-safe while serializing conflicting work inside one checkout.

## Analysis

### Files Read

- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`

### SDD Criteria

Not applicable. This is a non-roadmap dispatcher runtime correction and has no Milestone-linked SDD gate.

### Test Environment Rules

`test_env=local`. `agent-test/local/rules.md` and the matched `agent-test/local/testing-smoke.md` profile were present and read. The profile's Edge/Node product-runtime commands do not target this Python dispatcher, so deterministic fallback verification uses the existing dispatcher `unittest` suite plus Python bytecode compilation. No verification leaves the current checkout, no real provider invocation is permitted, and test-rule maintenance is not needed.

### Test Coverage Gaps

- Existing tests cover locator PID/marker/stream liveness, but do not reject a live locator owned by another physical workspace.
- Existing write-set tests cover extraction and canonicalization, but do not require a valid non-empty table for admission.
- Existing scheduler tests explicitly expect overlapping write sets to run concurrently; they must be revised to prove collision waiting, claim persistence, follow-up retention, verified-completion release, and continued parallelism for disjoint paths.
- Existing convergence simulation must be updated so disjoint tasks overlap while tasks targeting the same file serialize through worker, self-check, and review.

### Symbol References

No public symbols are renamed or removed. Internal call sites of `external_active_is_live` are `orchestration_live_agent_processes` and the main scheduler's persisted-active-stage recovery path. Internal call sites of `select_dispatch_candidates` are in the scheduler and unit tests.

### Split Judgment

Use one plan. Workspace identity validation, claim acquisition, claim retention across stages/restarts, and claim release after verified completion form one indivisible execution-ownership invariant; landing only part of it would either permit duplicate monitoring or leave unsafe write concurrency.

### Scope Rationale

Modify only the project dispatcher script, its project policy skill, and its tests. Do not modify centrally managed common rules/skills, roadmap files, product Edge/Node runtime, or establish a repository-global lock shared by separate worktrees/clones. Claims are intentionally scoped to one canonical physical workspace so isolated checkouts may execute independently.

### Final Routing

- `evaluation_mode`: `pair`
- `finalizer`: `local-fit`
- Build target: closure `risk-boundary`, grade `G08`, route `cloud-G08`
- Review target: closure `official-review`, grade `G08`, route `cloud-G08`
- `large_indivisible_context`: `false`
- Positive loop risks: `temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation` (4)
- Recovery signals: `review_rework_count=0`, `evidence_integrity_failure=false`
- Capability-gap evidence: none
- Canonical files: `PLAN-cloud-G08.md`, `CODE_REVIEW-cloud-G08.md`

## Implementation Checklist

- [ ] Bind dispatcher state, locator records, process markers, and liveness recovery to canonical physical workspace identity.
- [ ] Enforce persistent Modified Files Summary claims before worker/selfcheck/review admission, wait on collisions, retain across follow-ups/restarts, and release only after verified completion.
- [ ] Update project dispatcher policy and mocked regression/convergence tests for workspace isolation, fail-closed write sets, claim lifecycle, and safe parallelism.
- [ ] Run deterministic syntax and full dispatcher unit verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REFACTOR-1] Bind runtime liveness to physical workspace identity

#### Problem

At `dispatch.py:439-510`, `StateStore` derives a local hash only for a fallback directory and does not persist or expose canonical workspace ownership. At `dispatch.py:2925-2995`, `external_active_is_live` accepts a locator's PID, process marker, and stream evidence before proving that the locator belongs to the current checkout. A stale or copied state record can therefore make this dispatcher monitor a session launched from another worktree or clone.

#### Solution

Persist and validate canonical workspace root/id metadata in dispatcher state, include the id in locators, and namespace attempt process markers by workspace id. Require matching locator workspace identity at production liveness call sites before accepting any process or stream evidence; legacy locators are accepted only when their locator path is physically under this store's `runs` root.

Before (`dispatch.py:2925`):

```python
def external_active_is_live(state: dict[str, Any]) -> tuple[bool, str]:
    raw_locator = state.get("active_locator")
```

After:

```python
def external_active_is_live(
    state: dict[str, Any],
    *,
    expected_workspace: Path | None = None,
    expected_workspace_id: str | None = None,
    expected_runs_root: Path | None = None,
) -> tuple[bool, str]:
    raw_locator = state.get("active_locator")
    # Reject foreign workspace identity before PID/marker/stream checks.
```

#### Modified Files and Checklist

- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: persist workspace identity, namespace locators/markers, and validate liveness ownership.
- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: add matching, foreign, and legacy-locator ownership regressions.

#### Test Strategy

Write tests in `test_dispatch.py` proving that a foreign locator with a live current PID is rejected, a matching locator is retained, a legacy locator is accepted only below the current store's runs root, and generated locators/markers contain the current workspace id. All process evidence is mocked or uses the current test process; no provider is started.

#### Verification

Run the full dispatcher unit command in `Final Verification`; all workspace ownership tests and pre-existing liveness tests must pass.

### [REFACTOR-2] Enforce persistent per-workspace write claims

#### Problem

At `dispatch.py:950-991`, PLAN write sets are parsed but missing, malformed, wildcard, empty, directory, or outside-workspace targets only result in `write_set_known=False`. At `dispatch.py:5159-5167`, every dependency-ready task is admitted and the implementation explicitly states that write sets never create a cross-task barrier. This permits two stages in the same physical checkout to edit the same canonical file concurrently.

#### Solution

Treat an active/recovery PLAN without one valid, non-empty `Modified Files Summary` as a task-local fail-closed error. Persist a workspace-scoped write-claim ledger keyed by task generation; atomically admit ordered ready candidates only when their canonical file set has no intersection with another task claim. Keep the claim across worker, self-check, official review, retries, dispatcher restarts, and follow-up generations; replace/expand a task's own claim only when the new set does not conflict. Report collisions as waiting rather than logical dependency blockers, adopt conservatively exclusive claims for externally active legacy tasks with unknown write sets, and release only after a verified completed archive.

Before (`dispatch.py:5159`):

```python
def select_dispatch_candidates(
    ready: list[tuple[Task, str]],
) -> tuple[list[tuple[Task, str]], list[tuple[Task, str]], str]:
    ready_reviews = [(task, stage) for task, stage in ready if stage == "review"]
    ready_workers = [(task, stage) for task, stage in ready if stage in {"worker", "selfcheck"}]
    return ready_reviews + ready_workers, [], ""
```

After:

```python
def select_dispatch_candidates(
    store: StateStore,
    ready: list[tuple[Task, str]],
    *,
    persist: bool,
) -> tuple[list[tuple[Task, str]], list[tuple[Task, str, str]], str]:
    # Simulate or atomically commit ordered workspace write claims.
```

#### Modified Files and Checklist

- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: validate write-set inputs, persist claims, gate admission, adopt active claims, and release verified completions.
- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: cover conflict/disjoint admission, persistence, follow-up replacement, task-group scope, and release.

#### Test Strategy

Write direct state-store and scheduler tests with temporary repositories. Assert exact canonical-path collision diagnostics, unchanged ownership while a conflicting follow-up waits, persisted claims after reopening the store, cross-task-group collision enforcement in the same workspace, conservative adoption for legacy active tasks, and release only from verified completion paths. Revise the convergence simulation so two tasks with the same target do not overlap while disjoint work still overlaps.

#### Verification

Run the full dispatcher unit command in `Final Verification`; all claim lifecycle, scheduler, and convergence assertions must pass.

### [REFACTOR-3] Align the project dispatcher contract

#### Problem

At `SKILL.md:85-89`, the project contract requires parallel dispatch regardless of write set and classifies the modified-file table only as review/stagnation evidence. At `SKILL.md:193-216`, restart and review procedure text repeats that another task's write set must never delay execution. This contradicts the required runtime file-lock policy and leaves workspace ownership of locators unspecified.

#### Solution

Document that each valid PLAN claims its canonical targets per physical workspace, collisions wait without becoming predecessor dependencies, claims survive all stages/follow-ups/restarts and release only on verified completion, malformed write sets fail closed, and separate clones/worktrees have independent ledgers. Document that liveness recovery accepts locator/process evidence only for the current workspace identity.

Before (`SKILL.md:85`):

```markdown
- Run worker/self-check and official review in parallel when they belong to different dependency-ready tasks, regardless of write-set.
```

After:

```markdown
- Run dependency-ready stages in parallel only after atomically claiming their canonical PLAN write sets in the current physical workspace.
```

#### Modified Files and Checklist

- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md`: replace unsafe concurrency language with workspace claim and locator-ownership contracts.
- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: retain the English-policy and deterministic verification checks.

#### Test Strategy

Use the existing project-skill narrative test and add precise policy assertions only where needed. Keep artifact prose in English except for exact Korean runtime protocol literals already allowed by the suite.

#### Verification

Run the full dispatcher unit command in `Final Verification`; policy-language checks must pass.

## Modified Files Summary

| File | Items |
|------|-------|
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py` | REFACTOR-1, REFACTOR-2 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` | REFACTOR-1, REFACTOR-2, REFACTOR-3 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md` | REFACTOR-3 |

## Final Verification

Cached output is not applicable to Python `unittest`.

```bash
python3 -m py_compile agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py
```

Expected: exit code `0` with no output.

```bash
python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_dispatch.py'
```

Expected: every dispatcher test passes, no real provider command is invoked, and the final result is `OK`.

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