# Previewing a Fleet Roster v1-to-v2 Migration **Issue:** #758 · **Card:** FCM-M4-001 · **Effect boundary:** preview only `mosaic fleet migrate-v1 preview` inventories a v1 roster and emits a canonical v2 candidate plus recovery evidence. It does not write a roster, apply environment projections, invoke systemd or `tmux`, contact connectors or remote hosts, launch an agent, run a canary, or execute rollback. FCM-M4-002 owns reversible cutover and rollback. ## Inputs ```bash mosaic fleet migrate-v1 preview \ --source roster-v1.yaml \ --decisions migration-decisions.json \ --observations reviewed-observations.json ``` The command emits one JSON object and exits nonzero when the preview is blocked, including when any of `--source`, `--decisions`, or `--observations` is missing. Decision and observation JSON is validated fail-closed: unknown fields, malformed values, and records for non-local agents are rejected. Decisions must supply a positive v2 `generation`, a reviewed `fleetHost` whenever v1 agents include `host` or `ssh`, explicit `defaultRuntime`, and per-local-agent provider, model, reasoning, enabled state, and launch policy. A supported socket field declared by v1 remains authoritative, including an explicit empty value representing the default tmux server; a matching `socketName` decision is accepted, an incompatible decision blocks, and a decision supplies the socket only when both supported v1 socket fields are genuinely absent. If v1 omitted `tool_policy`, decisions must supply an explicit replacement; it is never derived from `class`. `model_hint` is never split or treated as authority. Observations are separate reviewed evidence keyed by local agent name: ```json { "coder0": { "systemd": "inactive", "tmux": "missing" } } ``` Only `active` plus `present` maps to `running`; only `inactive` plus `missing` maps to `stopped`. Missing, extra, unknown, or contradictory evidence blocks output. An observed-running agent cannot be marked disabled. Observed-stopped agents always remain stopped. ## Field disposition | v1 field | v2 disposition | | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `version`, `transport`, `tmux`, `defaults`, `runtimes` | Inventoried and structurally compiled; omitted values retain v1 defaults, while present-empty holder/work-directory/reset values block for explicit disposition | | agent `name`, `alias`, `runtime`, working directory, persona/reset flags | Copied or explicitly defaulted only when absent; present-empty alias/work-directory values block for explicit disposition, and `~` stays canonical while expanding only for environment preflight | | `provider`, `model_hint`, `reasoning_level` | Explicit provider/model/reasoning decisions; no model-hint inference | | `class`, `tool_policy` | Only approved aliases canonicalize automatically; other classes require explicit preserve/replace disposition and shared-resolver validation | | `kickstart_template` | No v2 field; explicit inventory-only disposition required | | agent `host`, `ssh` | `host != fleetHost` is demonstrably remote and inventory-only; `host == fleetHost` stays local; ssh-only, missing fleet-host evidence, or contradictory targets block | | agent `socket` | Same-host candidate only when it matches the canonical fleet socket; conflicts block for explicit future disposition | | root `connector` | Inventory-only; never contacted or reconciled | | unknown fields or snake/camel synonym collisions | Inventoried and block readiness | | `.env.generated` | Rebuild from canonical roster data | | no legacy `.env` | `absent`; no legacy action required | | legacy `.env` containing generated keys only | `regenerate-only`; replace later from canonical roster data | | legacy `.env` containing strict local keys | `relocate-local`; preserve those keys in `.env.local` during a later reviewed cutover | | legacy `.env` containing forbidden/unsafe/sensitive/malformed keys | `quarantine`; private input only, with diagnostics limited to code, key, and SHA-256 | The only automatic aliases are `implementer → code`, `reviewer → review`, and `operator-interaction → interaction`. Similar or domain-specific names are never inferred. Automatic classes do not accept competing disposition records. Semantic validation delegates to the existing baseline-plus-`roles.local` resolver after the candidate is compiled by the existing v2 compiler. ## Evidence and recovery boundary Ready output includes source and candidate SHA-256 identities, value-free field inventory, excluded remote/connector entries, explicit environment dispositions with sanitized diagnostics, and the lifecycle evidence used for each local candidate. Canonical evidence ordering compares Unicode code points directly and does not depend on process locale. Recovery is marked non-executable and assigns the executable gate to FCM-M4-002. Before any later cutover, preserve these artifacts: 1. authoritative v1 roster backup; 2. agent environment backup, including `.env.local` and private quarantine inputs; 3. reviewed lifecycle observations; 4. canonical candidate v2 roster and its SHA-256. See [backup and restore](../operations/backup-restore.md). Preview output is migration-readiness evidence, not proof that migration, canary, or rollback occurred.