<!-- task=m-iop-agent-chronos-extraction-decoupling/05+03_state_fixture plan=1 tag=REVIEW_REFACTOR milestone-task=transfer -->

# Enforce the read-only state-schema contract

## For the Implementing Agent

Filling the implementation-owned sections in `CODE_REVIEW-cloud-G04.md` is mandatory. Reconfirm the encoded predecessor PASS, implement only this schema-contract follow-up, run every verification command exactly, paste actual output, keep the active pair in place, and report ready for review. 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`; finalization belongs to the code-review skill.

## Background

The v1 state fixture is complete and sanitized, but its published JSON Schema accepts transfer records that `validateStateExport` rejects. The schema must encode the read-only and quarantine/resumability rules, and a tracked deterministic regression must prove the canonical fixture passes while the observed invalid variants fail.

## Archive Evidence Snapshot

- The current failed pair will archive as `agent-task/m-iop-agent-chronos-extraction-decoupling/05+03_state_fixture/plan_local_G05_0.log` and `agent-task/m-iop-agent-chronos-extraction-decoupling/05+03_state_fixture/code_review_cloud_G05_0.log`; verdict: FAIL.
- Required finding: `scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json` permits `mode: "read-write"`, non-quarantine `resumable: false`, and resumable quarantine records, although `cmd/iop-chronos-transfer/main.go:812-831` rejects those values.
- The canonical 12-record fixture, exact ten original manifest IDs plus two D04 addendum IDs, JSON parsing, provenance checks, and sensitive-text scan passed. Fresh planning preflight showed the current schema-shape oracle returns `false`, while the intended in-memory constraints and four positive/negative cases return `STATE_SCHEMA_CONTRACT_PASS`.
- Roadmap carryover remains `milestone-task=transfer`, SDD scenario S02, with no roadmap mutation or completion claim in this follow-up.

## Analysis

### Files Read

- `scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json`
- `scripts/fixtures/iop-agent-chronos-transfer-state-v1.json`
- `scripts/fixtures/iop-agent-chronos-ownership-manifest.tsv`
- `cmd/iop-chronos-transfer/main.go`
- `cmd/iop-chronos-transfer/main_test.go`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-chronos-extraction-decoupling.md`
- `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-chronos-extraction-decoupling/SDD.md`
- `agent-spec/runtime/iop-agent-cli-runtime.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/03+02_inventory_closure/complete.log`

### SDD Criteria

- `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-chronos-extraction-decoupling/SDD.md` is `[approved]` with its lock released. The unchanged first-line scope is `milestone-task=transfer`.
- Acceptance Scenario S02 requires a versioned full-state transfer with provenance and digest evidence. Its Evidence Map requires the complete state-transfer fixture as input to the later isolated bundle verification.
- D01 and the S02 state-transfer contract require read-only transfer and prohibit presenting broken residual state as resumable. Those rules directly drive the schema conditionals and the positive/negative regression oracle below.

### Verification Context

- No external verification handoff was supplied. Repository-native evidence came from the current schema and fixture, the ten manifest `state` rows, `validateStateExport` at `cmd/iop-chronos-transfer/main.go:782-838`, and the existing quarantine regression at `cmd/iop-chronos-transfer/main_test.go:185-217`.
- Local preflight: `/bin/jq`, `/bin/python3`, Go `go1.26.2 linux/arm64`, module `/config/workspace/iop-s1/go.mod`; no JSON Schema CLI or Python `jsonschema` module is available. The regression therefore uses the repository-required `jq` and reads the exact constraints from the tracked schema instead of downloading a validator.
- The current schema-shape probe returned `false`. The same jq contract function against the intended in-memory schema returned `STATE_SCHEMA_CONTRACT_PASS` for the canonical fixture and rejected `read-write`, non-quarantine non-resumable, quarantine resumable, and non-quarantine quarantine-reason mutations.
- Preconditions: the exact index-03 predecessor PASS remains at `agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/03+02_inventory_closure/complete.log`; verification stays inside the current checkout and needs no service, credential, or external host.
- Constraint: do not modify the exporter, fixture values, ownership manifest, roadmap, contract, or spec. Confidence is high because the defect is a direct mismatch between explicit schema clauses and production validation branches.

### Test Coverage Gaps

- Existing exporter tests reject a resumable quarantine record but do not execute the published JSON Schema or cover the other observed schema-only variants.
- Add one tracked jq regression that verifies the schema clause shape, accepts the canonical fixture, and rejects four contract mutations. No Go test change is needed because production validation already rejects these values and the defect is confined to the schema artifact.

### Symbol References

- None. No Go symbol or JSON field is renamed or removed.

### Split Judgment

- Keep one compact follow-up. The `mode`, `resumable`, and `quarantine_reason` clauses plus their regression oracle form one schema-admission invariant and independently PASS without changing exporter behavior.
- Encoded predecessor index `03` is satisfied by the single archived PASS at `agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/03+02_inventory_closure/complete.log`.

### Scope Rationale

- Exclude `cmd/iop-chronos-transfer/**`: production already rejects all targeted values, so changing it would overlap the active exporter child and expand beyond the Required finding.
- Exclude the canonical fixture and ownership manifest: their 12-ID content, provenance, and redaction already passed and remain read-only verification inputs.
- Exclude roadmap/spec/contract updates: this follow-up restores conformance to the existing SDD and runtime behavior; it does not change the product contract. Spec update not needed for the same reason.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer=`finalize-task-policy.sh`, mode=`pair`.
- Build closures are all true. Scores are `scope=1,state=0,blast=1,evidence=1,verification=1`, grade `G04`, base/final route `local-fit`, lane `local`.
- Review closures are all true. Scores are `scope=1,state=0,blast=1,evidence=1,verification=1`, route `official-review`, lane `cloud`, grade `G04`.
- `large_indivisible_context=false`; positive loop risks are `boundary_contract` and `structured_interpretation` (2); `review_rework_count=1`; `evidence_integrity_failure=false`; no capability gap.
- Canonical files: `PLAN-local-G04.md`, `CODE_REVIEW-cloud-G04.md`.

## Dependencies and Execution Order

1. Reconfirm the exact PASS in `agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/03+02_inventory_closure/complete.log`.
2. Tighten the schema, add its contract regression, then run all final verification against the unchanged canonical fixture and manifest.

## Implementation Checklist

- [ ] Reconfirm that predecessor index 03 resolves to exactly one PASS `complete.log` before changing the schema.
- [ ] Require read-only mode and enforce non-resumable quarantine with a reason versus resumable non-quarantine without a quarantine reason in the v1 JSON Schema.
- [ ] Add and run a deterministic jq schema-contract regression that accepts the canonical fixture and rejects all four read-only/resumability/reason counterexamples.
- [ ] Re-run JSON parsing, exact manifest/addendum ID provenance, sensitive-text, and diff checks without changing the canonical fixture or manifest.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REFACTOR-1] Close the state-schema acceptance gap

**Problem**

`scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json:73-76` allows both `read-only` and `read-write`. Its only conditional at lines 88-97 requires a quarantine reason but does not constrain quarantine resumability or the inverse non-quarantine rules, while `cmd/iop-chronos-transfer/main.go:812-831` rejects those cases.

```json
// before: scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json:73-97
"mode": { "enum": ["read-only", "read-write"] },
"allOf": [{
  "if": { "properties": { "category": { "const": "quarantine" } } },
  "then": { "required": ["quarantine_reason"] }
}]
```

**Solution**

Make `mode` a `const: "read-only"`. Extend the existing category conditional so quarantine records require a reason and `resumable: false`; the `else` branch requires `resumable: true` and forbids `quarantine_reason`. Add a jq regression that reads these exact schema clauses, verifies the canonical fixture, and proves all four counterexamples fail.

```json
// after: scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json
"mode": { "type": "string", "const": "read-only" },
"allOf": [{
  "if": { "properties": { "category": { "const": "quarantine" } }, "required": ["category"] },
  "then": { "properties": { "resumable": { "const": false } }, "required": ["quarantine_reason"] },
  "else": { "properties": { "resumable": { "const": true } }, "not": { "required": ["quarantine_reason"] } }
}]
```

**Modified Files and Checklist**

- [ ] `scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json` — encode the read-only and bidirectional quarantine/resumability contract.
- [ ] `scripts/fixtures/iop-agent-chronos-transfer-state-v1.contract-test.jq` — add the canonical positive case and four deterministic negative mutations.
- [ ] `agent-task/m-iop-agent-chronos-extraction-decoupling/05+03_state_fixture/CODE_REVIEW-cloud-G04.md` — record implementation notes and actual command output.

**Test Strategy**

- Add `scripts/fixtures/iop-agent-chronos-transfer-state-v1.contract-test.jq`. It must inspect the schema's `mode.const`, quarantine `then`, and non-quarantine `else`, accept the unchanged canonical fixture, and reject: `mode=read-write`, non-quarantine `resumable=false`, quarantine `resumable=true`, and a non-quarantine `quarantine_reason`.
- Keep the existing exact-ID and sensitive scan commands as regression coverage. Do not add a Go dependency or repository-local downloaded validator.

**Verification**

- `jq -e . scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json scripts/fixtures/iop-agent-chronos-transfer-state-v1.json >/dev/null && printf 'JSON_PARSE_PASS\n'` — both JSON documents parse.
- `jq -nr --slurpfile schema scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json --slurpfile fixture scripts/fixtures/iop-agent-chronos-transfer-state-v1.json -f scripts/fixtures/iop-agent-chronos-transfer-state-v1.contract-test.jq` — prints exactly `STATE_SCHEMA_CONTRACT_PASS`.

## Modified Files Summary

| File | Item |
|------|------|
| `scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json` | REVIEW_REFACTOR-1 |
| `scripts/fixtures/iop-agent-chronos-transfer-state-v1.contract-test.jq` | REVIEW_REFACTOR-1 |
| `agent-task/m-iop-agent-chronos-extraction-decoupling/05+03_state_fixture/CODE_REVIEW-cloud-G04.md` | REVIEW_REFACTOR-1 |

## Final Verification

1. `bash -lc 'set -euo pipefail; mapfile -t predecessor_logs < <({ test ! -f agent-task/m-iop-agent-chronos-extraction-decoupling/03+02_inventory_closure/complete.log || printf "%s\n" agent-task/m-iop-agent-chronos-extraction-decoupling/03+02_inventory_closure/complete.log; find agent-task/archive -path "*/m-iop-agent-chronos-extraction-decoupling/03+02_inventory_closure/complete.log" -type f -print 2>/dev/null; } | sort -u); test "${#predecessor_logs[@]}" -eq 1; awk "BEGIN {in_summary=0; found=0} /^## Summary$/ {in_summary=1; next} in_summary && /^## / {in_summary=0} in_summary && /final verdict: PASS/ {found++} END {exit found==1?0:1}" "${predecessor_logs[0]}"; printf "%s\n" "${predecessor_logs[0]}"'` — exactly one predecessor PASS.
2. `jq -e . scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json scripts/fixtures/iop-agent-chronos-transfer-state-v1.json >/dev/null && printf 'JSON_PARSE_PASS\n'` — both JSON documents parse.
3. `jq -nr --slurpfile schema scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json --slurpfile fixture scripts/fixtures/iop-agent-chronos-transfer-state-v1.json -f scripts/fixtures/iop-agent-chronos-transfer-state-v1.contract-test.jq` — prints exactly `STATE_SCHEMA_CONTRACT_PASS`.
4. `bash -lc 'set -euo pipefail; manifest_ids=$(mktemp); fixture_ids=$(mktemp); trap '\''rm -f -- "$manifest_ids" "$fixture_ids"'\'' EXIT; awk -F "\t" '\''NR>1 && $2=="state" {print $1}'\'' scripts/fixtures/iop-agent-chronos-ownership-manifest.tsv | LC_ALL=C sort >"$manifest_ids"; jq -r '\''.records[] | select(.source_manifest=="original") | .logical_id'\'' scripts/fixtures/iop-agent-chronos-transfer-state-v1.json | LC_ALL=C sort >"$fixture_ids"; test "$(wc -l <"$manifest_ids")" -eq 10; cmp "$manifest_ids" "$fixture_ids"; test "$(jq -r '\''[.records[] | select(.source_manifest=="d04-addendum") | .logical_id] | sort | join(",")'\'' scripts/fixtures/iop-agent-chronos-transfer-state-v1.json)" = "state:cache-root,state:temp-root"; printf "STATE_ID_PROVENANCE_PASS\n"'` — original and addendum IDs remain exact.
5. `bash -lc 'set -euo pipefail; set +e; rg -n '\''/home/|/Users/|token|credential|secret|private_key'\'' scripts/fixtures/iop-agent-chronos-transfer-state-v1.schema.json scripts/fixtures/iop-agent-chronos-transfer-state-v1.json scripts/fixtures/iop-agent-chronos-transfer-state-v1.contract-test.jq; scan_rc=$?; set -e; case "$scan_rc" in 0) exit 1;; 1) :;; *) exit "$scan_rc";; esac; printf "SENSITIVE_SCAN_PASS\n"'` — no host path or secret pattern appears and scanner errors fail closed.
6. `git diff --check` — exits 0 with no output.

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