# Reconciliation Roadmap This document records planned destination reconciliation work that is not part of the current implementation. Current destination replacement, state, and recovery behavior is documented in `docs/operations.md`, `docs/config.md`, and `docs/integrations/destination-state.md`. This work should be implemented before the shared-root multi-pipeline state work described in `docs/roadmap/multipipeline.md`. The merge mode described here is still scoped to one pipeline/destination owner for a destination bundle path; it does not by itself allow multiple pipelines to share one `.distributor.json`. ## Destination Reconciliation Modes Current destination replacement behavior treats each publish as the complete desired managed output set for the destination bundle path. When a newer source replaces an older destination state, `distributor` deletes prior managed outputs that are no longer present in the new plan, then writes the new outputs and state. That behavior is useful for snapshot-style producers, but it is not ideal for incremental producers that publish new reports without retaining prior report state. Planned work: - Add a per-destination reconciliation mode. - Keep current behavior as the default mode. - Add a mode that writes new outputs while retaining prior managed outputs that are not present in the new plan. - Keep reconciliation mode per destination. Replacement and merge behavior are destination safety policies, and one pipeline may need both a snapshot-style `latest` destination and an accumulating `archive` destination from the same source bundle. Proposed configuration: ```yaml destinations: - id: archive backend: s3 bucket: reports prefix: weather/morning/archive reconciliation: mode: merge - id: latest backend: s3 bucket: reports prefix: weather/morning/latest path_mapping: mode: fixed reconciliation: mode: replace ``` Accepted modes: - `replace`: current behavior. The destination bundle path is reconciled to the new planned output set. Prior managed outputs not present in the new plan are removed. - `merge`: new planned outputs are written into the managed destination set. Prior managed outputs not present in the new plan are retained. ## State Model Destination state needs to represent cumulative managed outputs for merge mode. The current state file already records an `outputs` array, but that array currently represents the outputs from the most recent publication. Planned state semantics: - `source.manifest` continues to record the latest source manifest used for destination comparison. - `outputs` records the complete currently managed output set for the destination bundle path. - In `replace` mode, the new state `outputs` are exactly the newly planned outputs. - In `merge` mode, the new state `outputs` are: - previous managed outputs not overwritten by the new plan; - plus newly planned outputs. - Persist reconciliation metadata so state interpretation remains explicit after config changes and so later migration, reconcile-state, and prune tooling can explain why historical outputs were retained, for example: ```json { "reconciliation": { "mode": "merge" } } ``` If state schema changes are required, bump the destination state schema version and keep parsing/validation rules explicit. ## Safety Rules Merge mode must not become an unsafe overwrite path. Required safety behavior: - A new output may overwrite a prior output only when that path is already recorded as distributor-managed in the existing `.distributor.json`. - A new output path that exists in storage but is not recorded as managed should fail as unmanaged content by default. - Prior managed outputs not included in the new plan are retained in merge mode. - Prior managed outputs not included in the new plan are deleted in replace mode. - Failed-write cleanup should delete only outputs written by the failed attempt, not retained prior managed outputs. - Forced replacement behavior remains explicit, bounded, dry-runnable, and constrained to the destination bundle path. ## Planning And Execution Work Implementation work: - Add destination config type `reconciliation.mode`. - Default `reconciliation.mode` to `replace`. - Validate mode values as `replace` or `merge`. - Thread reconciliation mode into publish planning and execution requests. - During replace execution, preserve current managed cleanup behavior. - During merge execution: - inspect existing state outputs before writing; - allow overwrites only for paths already managed by existing state; - write new outputs with overwrite enabled only for managed existing paths; - retain previous managed outputs that are not overwritten; - write destination state with the cumulative managed output set. - Ensure generated HTML outputs and copied source outputs use the same reconciliation semantics. - Ensure link metadata and primary URL behavior remain deterministic when retained outputs exist. Derive `links.primary_url` from the newly planned outputs for the current publish, not from retained historical outputs. ## Testing Important tests: - Config defaults `reconciliation.mode` to `replace`. - Config rejects unknown reconciliation modes. - Replace mode deletes prior managed outputs omitted from the new bundle. - Merge mode retains prior managed outputs omitted from the new bundle. - Merge mode overwrites a path already recorded as managed. - Merge mode fails when a new output path collides with unmanaged destination content. - Merge mode state records the cumulative managed output set. - Failed merge writes clean up only outputs from the failed attempt. - Fixed-path destinations support both modes. - Source-only, generated-HTML-only, and source-plus-HTML publish policies all honor reconciliation mode. - S3, SSH/SFTP, local, and fake backend tests cover any backend overwrite or cleanup behavior that changes. ## Documentation Work When implemented, update current-behavior docs in the same change: - `docs/config.md`: document `reconciliation.mode`, defaults, and examples. - `docs/operations.md`: explain replace versus merge behavior and recovery. - `docs/integrations/destination-state.md`: document any state schema or semantic changes. - `docs/internal/publish.md` and related internal docs: record planning and execution invariants. - Relevant examples under `examples/`, especially archive/latest style configurations. ## Boundaries - Do not add lifecycle retention policies such as keep-latest-N or delete older than a duration as part of this work. - Do not let producers choose reconciliation mode through source manifests or upload requests. - Do not weaken unmanaged-content safety checks. - Do not make merge mode the default; preserve current replacement behavior for existing configurations. - Do not claim unmanaged colliding files in merge mode. Claiming unmanaged content should remain an explicit force or repair workflow, not normal publish behavior.