Implement single-owner managed takeover

This commit is contained in:
2026-06-18 15:18:38 +00:00
parent 598b665307
commit c02106987f
14 changed files with 329 additions and 53 deletions

View File

@@ -67,12 +67,15 @@ Published destination bundle paths contain `.distributor.json`. See [Destination
- No destination state and no destination content: publish new outputs.
- Matching destination state: skip as already published.
- Older destination state for the same source id: replace if transfer policy allows it.
- Newer destination state: skip by default.
- Invalid destination state, identity mismatch, different source id, or same-created digest mismatch: fail by default.
- Newer destination state for the same source id: skip by default.
- Valid single-owner state with an identity or source mismatch: replace only when destination `takeover.mode` allows it.
- Invalid destination state, identity or source mismatches not allowed by `takeover.mode`, or same-created digest mismatch: fail by default.
- Content without `.distributor.json`: fail as unmanaged content by default.
When destination state is older than the source, `transfer.on_destination_older` controls whether publication may proceed and `reconciliation.mode` controls how managed outputs are updated.
For single-owner takeover replacement, `reconciliation.mode: merge` does not retain omitted outputs from the previous source identity. The destination is rewritten as a managed replacement for the current source.
`reconciliation.mode: replace` is the default. It deletes only managed output paths recorded in `.distributor.json` plus the state file, verifies the destination bundle path is empty, then writes the newly planned outputs and state. The new state `outputs` array is exactly the newly planned output set.
`reconciliation.mode: merge` retains prior managed outputs that are omitted from the new plan. It overwrites planned paths only when those paths are already recorded in existing state as managed. If a newly planned path already exists in storage but is not recorded in state, publication fails as an unmanaged path collision. The new state `outputs` array is the cumulative managed output set.
@@ -150,12 +153,13 @@ For single-owner state, the state owner must match the selected pipeline and des
`run --dry-run` loads config, resolves credentials, discovers source bundles, opens destinations, inspects destination state, builds publish plans, and prints actions. It does not write outputs, `.distributor.json`, or SSH `known_hosts` entries. For reconciliation, dry runs report the same high-level action labels as execution; inspect the configured destination's `reconciliation.mode` to determine whether `replace_older` will replace the managed set or merge into it.
For shared-root destinations, dry runs are owner-scoped. A `replace_older` action replaces or merges only the current owner according to `reconciliation.mode`; unrelated owners remain managed by the shared-root state.
For shared-root destinations, dry runs are owner-scoped. A `replace_older` action replaces or merges only the current owner according to `reconciliation.mode`; unrelated owners remain managed by the shared-root state. Paths owned by another owner still fail as conflicts.
Review these action labels before publishing:
- `publish_new`: destination state is absent, or a shared-root owner is absent and planned paths are publishable.
- `replace_older`: destination state is older than the source.
- `replace_takeover`: single-owner destination state is valid managed state and `takeover.mode` allows replacement across an identity or source mismatch.
- `skip_same`: destination state already matches the source.
- `skip_destination_newer`: destination state is newer than the source and is skipped.
- `force_replace`: destructive replacement selected because `--force` is present and policy permits it.