# Catalog State And Workflow Implementation Roadmap This is the active staged implementation plan for `docs/roadmap/catalog.md`. The feature roadmap defines the target state model and policy decisions; this document defines the implementation sequence for an LLM coding agent to follow stage by stage. Future behavior must remain under `docs/roadmap/` until implemented. Update current-behavior docs only in the stage that implements the corresponding behavior. ## Current Baseline `distributor` currently writes destination `.distributor.json` using single-owner schema version `2` or shared-root schema version `3`. Destination behavior is selected through several low-level knobs: `state.mode`, `reconciliation.mode`, `takeover.mode`, and `transfer`. The target behavior is a clean alpha break: - all newly written destination state uses schema version `4`; - all destinations use `state.mode: catalog` internally; - user-facing destination behavior is selected by `workflow: additive` or `workflow: replacement`; - `workflow` defaults to `additive`; - legacy destination policy fields are rejected, not aliased; - legacy state schema versions `1`, `2`, and `3` are not migrated. ## Implementation Principles - Keep the source manifest contract unchanged. - Keep backend adapters unaware of catalog and workflow policy. - Treat `workflow` as runtime config, not persisted state. - Keep unmanaged content protected after catalog state exists. - Preserve output `created_at` when a path changes owner; update only `updated_at`. - Prefer removing legacy state/config paths over compatibility shims. - Preserve current public APIs outside destination state/config unless the catalog roadmap explicitly changes them. ## Active Implementation Stages ## Stage 1: Destination Workflow Config Clean Break Goal: replace low-level destination policy config with the user-facing workflow switch. Implementation scope: - Add destination `workflow` config with accepted values `additive` and `replacement`. - Default omitted `workflow` to `additive`. - Remove or make unsupported the destination-level config fields `state`, `reconciliation`, `takeover`, and `transfer`. - Ensure configs containing those legacy fields fail clearly. Prefer strict YAML unknown-field failure by removing struct fields; add explicit validation only if clearer errors are needed without weakening strict decoding. - Preserve backend, publish, transform, path mapping, links, retention, and other non-policy destination fields. - Update example configs only when the implementation stage also updates current docs; otherwise keep this stage focused on config code and tests. Tests: - `go test ./internal/config` - Omitted workflow defaults to `additive`. - `workflow: additive` and `workflow: replacement` validate. - Unknown workflow values fail. - Legacy `state`, `reconciliation`, `takeover`, and `transfer` fields fail. - Existing valid examples are updated or tests are adjusted in the same change if examples currently use legacy fields. Completion criteria: - New configs express destination replacement/additive intent with one field. - No normal config path accepts legacy destination policy knobs. ## Stage 2: Catalog State Schema Version 4 Goal: implement the schema version `4` catalog state model in `internal/state`. Implementation scope: - Add catalog state types for top-level schema version `4`, `state.mode: catalog`, and `outputs`. - Add compact per-output source identity with `id`, `digest`, and `created`. - Add catalog output records with required `path`, `pipeline_id`, `destination_id`, `source`, `kind`, `sha256`, `size`, `created_at`, and `updated_at`. - Add optional `source_path`, `transform`, and `url`, present only where allowed by the catalog roadmap. - Validate duplicate paths, invalid owner ids, invalid source identities, invalid output paths, invalid digests, negative sizes, invalid timestamps, invalid kind/transform combinations, and invalid URLs. - Marshal catalog state deterministically with the existing JSON formatting conventions. - Update `ParseDocument` so schema version `4` returns catalog state. - Treat schema versions lower than `4` as superseded legacy state for publish planning, not as readable/migrated active state. Keep enough detection to identify legacy state and avoid treating it as arbitrary invalid JSON. - Reject unsupported future schema versions. Tests: - `go test ./internal/state` - Parse/validate/marshal valid catalog state. - Reject malformed catalog state and invalid output records. - Detect schema versions `1`, `2`, and `3` as superseded legacy state. - Reject future schema versions. - Prove no top-level `owners`, `sources`, workflow, source manifest, pipeline id, destination id, or published timestamp is accepted for catalog state. Completion criteria: - State package has one canonical schema version `4` catalog contract for new writes. - Legacy state is detected but not migrated. ## Stage 3: Catalog Publish Planning Goal: plan publish actions against catalog state and destination workflow. Implementation scope: - Replace publish request inputs that consume state/reconciliation/takeover and transfer policy with destination workflow. - Map workflow to catalog actions: - `workflow: additive`: upsert planned outputs and retain all other managed catalog outputs. - `workflow: replacement`: upsert planned outputs and delete catalog outputs owned by the current pipeline/destination that are omitted from the plan. - Planned paths that already exist as catalog-managed outputs may be overwritten and become owned by the current pipeline/destination. - Planned path collisions with storage content not recorded in catalog state fail as unmanaged content. - Matching planned outputs may skip writes when source identity and output digest already match, if that optimization can be implemented without changing externally visible results; otherwise writing idempotently is acceptable. - Existing schema `< 4` state is superseded: - replacement workflow may plan a bounded destination-root clear before writing planned outputs; - additive workflow may plan overwrites for planned paths only and leave unplanned files unmanaged. - Invalid JSON or future schema state remains a conflict, not a superseded legacy state. - Preserve path mapping, publish policy, transforms, links, fixed-path selection, and output collision checks. Tests: - `go test ./internal/publish` - Additive workflow publishes new catalog state. - Additive workflow overwrites an existing managed output and retains unrelated outputs. - Replacement workflow removes omitted outputs owned by the current pipeline/destination. - Replacement workflow preserves unrelated catalog outputs owned by other pipeline/destination pairs. - Managed output ownership moves to the current pipeline/destination when a planned path is overwritten. - `created_at` is preserved when an existing path changes owner; `updated_at` changes. - Unmanaged path collisions fail. - Legacy schema `< 4` state follows the superseded-state rules above. - Invalid/future state fails. Completion criteria: - Publish planning no longer depends on single-owner/shared-root comparison semantics. - Workflow behavior is fully driven by `workflow`. ## Stage 4: Catalog Publish Execution Goal: execute catalog publish plans and write schema version `4` state. Implementation scope: - Write only schema version `4` catalog `.distributor.json`. - Implement additive execution as managed upsert of planned outputs plus catalog state update. - Implement replacement execution as managed upsert plus deletion of omitted current-owner catalog outputs. - For replacement over superseded legacy state, clear the bounded destination root before writing planned outputs and catalog state. - For additive over superseded legacy state, overwrite planned paths and write a catalog containing only planned outputs; leave unplanned files unmanaged. - Preserve cleanup behavior on failed writes: - additive cleanup removes outputs newly created by the failed attempt where practical; - replacement cleanup follows existing managed-replacement safety where practical; - state is written only after selected outputs are written. - Preserve link metadata, generated output digests, source output digests, content type behavior, and backend-safe writes. Tests: - `go test ./internal/publish ./internal/app` - Additive execution writes planned outputs, overwrites managed planned paths, retains unrelated managed outputs, and writes catalog state. - Replacement execution deletes omitted current-owner outputs and preserves unrelated owner outputs. - Superseded legacy replacement clears bounded destination root only. - Superseded legacy additive leaves unplanned files on disk but out of catalog state. - Failed writes do not leave misleading catalog state. - Local, fake-backed SSH, and fake-backed S3 app paths exercise the same publish behavior. Completion criteria: - Successful `run` writes only v4 catalog state. - Destination outputs match additive/replacement workflow semantics. ## Stage 5: Run Reporting, Notifications, And CLI Surface Goal: update user-visible run behavior to describe workflow/catalog actions instead of legacy replacement/takeover actions. Implementation scope: - Replace legacy action labels tied to state/reconciliation/takeover/transfer with these stable workflow-oriented labels: - `publish_new` - `upsert_additive` - `replace_catalog` - `skip_same` - `force_replace` - `fail_unmanaged` - `fail_conflict` - Ensure text and JSON run summaries include workflow-relevant counters. - Include `workflow` in run action records where useful. - Update fixed-path dry-run warnings to describe additive upsert or replacement clearly. - Ensure notifications use the new action labels. - Remove reporting assumptions that depend on `replace_older`, `replace_newer`, `replace_conflict`, or `replace_takeover`. Tests: - `go test ./internal/app ./internal/cli` - Text dry-run output distinguishes additive from replacement workflow. - JSON output includes workflow and stable action labels. - Summary counters are deterministic. - Notifications fire for additive and replacement writes. - Existing CLI commands still parse and execute with the new config shape. Completion criteria: - Operators can understand from dry-run output whether a destination will upsert or replace managed catalog outputs. ## Stage 6: Catalog Prune And Reconcile-State Goal: update maintenance commands to operate on catalog state with the agreed selector model. Implementation scope: - Prune: - selected `--pipeline` and `--destination` prune only outputs currently owned by that pipeline/destination; - no `--all-owners` prune mode in the initial catalog implementation; - do not add `--source-id`, `--path-prefix`, or `--kind` selectors. - Reconcile-state: - selected `--pipeline` and `--destination` repair only outputs currently owned by that pipeline/destination; - `--all-owners` repairs all catalog outputs; - unmanaged reporting compares storage entries to all catalog output paths, not just selected owner paths. - Rewrite repaired/pruned state as schema version `4`. - Remove legacy single-owner/shared-root maintenance branches. Tests: - `go test ./internal/state ./internal/app ./internal/cli` - Prune selects only current-owner catalog outputs. - Prune deletes selected outputs and removes their catalog records. - Reconcile-state selected owner removes only missing outputs for that owner. - `reconcile-state --all-owners` removes missing outputs for all owners. - Unmanaged reporting excludes all catalog-managed paths and reports unrecorded storage entries. - JSON/text output remains stable and clear. Completion criteria: - Maintenance commands operate only on catalog state and respect the locked selector rules. ## Stage 7: Clean Break Removal And Documentation Goal: remove legacy state/config behavior and document the implemented catalog workflow model. Implementation scope: - Remove dead code for writing schema version `2` single-owner and schema version `3` shared-root state. - Remove legacy config structs/constants/defaults/validation for destination `state`, `reconciliation`, `takeover`, and `transfer` where no longer used. - Remove or rewrite tests that only assert legacy state/config behavior. - Update current-behavior docs: - `docs/config.md` - `docs/cli.md` - `docs/operations.md` - `docs/troubleshooting.md` - `docs/integrations/destination-state.md` - relevant `docs/internal/` docs - `docs/policy/architecture.md` - `docs/policy/development.md` - Update examples to use `workflow` and remove legacy fields. - Keep roadmap-only material out of current docs. - Once implemented and documented, remove or rewrite `docs/roadmap/catalog.md` so completed behavior is not described only as future work. Tests and checks: - `go test ./...` - `rg -n "state:|reconciliation:|takeover:|transfer:" examples docs --glob '!docs/roadmap/**'` - `rg -n 'single_owner|shared_root|schema version `2`|schema version `3`' docs internal` - `rg -n "workflow: additive|workflow: replacement|schema_version.*4" docs examples` Completion criteria: - Current docs and examples describe the catalog workflow model. - Legacy destination policy fields and legacy write paths are gone. - Full test suite passes. ## Refactors To Avoid - Do not add top-level `owners` or `sources` catalogs. - Do not persist workflow in `.distributor.json`. - Do not keep deprecated config aliases for legacy destination policy fields. - Do not implement state migration from schema versions `1`, `2`, or `3`. - Do not add catalog-specific prune/reconcile selectors beyond the agreed initial ownership scope. - Do not move catalog policy into storage adapters. ## Open Questions No open questions are known. The catalog roadmap locks the state shape, workflow values, default workflow, clean-break policy, legacy-state behavior, output field selection, `created_at` preservation, and maintenance command scope.