Problem
Managed dynamic-group rulesets participate in planning as the synthetic dynamic field, but they are deliberately removed from the persisted managed-resource snapshot:
snapshotFromChanges() excludes every synthetic field when state is written.
- group adoption also leaves
dynamic out of resources.<key>.fields.
- the plan fetches the current ruleset and status live from ChurchTools and compares that value directly with the desired configuration.
The live-versus-desired comparison correctly detects that a ruleset must be updated. However, without a last-applied ruleset baseline, ct-cli cannot distinguish these cases:
- the blueprint changed while ChurchTools still matches the last apply;
- ChurchTools drifted while the blueprint stayed unchanged;
- both changed independently.
Consequently, the existing three-way attribution (config, drift, config+drift) is incomplete for dynamic. A ruleset difference can be shown, but its origin cannot be established reliably.
This became visible while reviewing a plan through the REST API and a ChurchTools extension: the state proves ownership of the group, but contains no previous ruleset against which the live and desired values can be classified.
Desired model
Persist a normalized last-applied baseline for managed synthetic state, at least for dynamic-group status and rulesets. This may either be:
- the normalized
dynamic value in the managed resource snapshot; or
- a separate, explicitly versioned synthetic/sub-resource snapshot associated with the managed resource.
The persisted representation should remain an implementation decision. It must not cause synthetic fields to be sent through the ordinary group create/update endpoint.
The resulting comparison should be:
last applied <-> ChurchTools current <-> blueprint desired
Required behavior
- A successful dynamic ruleset/status apply updates the persisted baseline only after the corresponding ChurchTools writes succeed.
ct adopt group --with-dynamic establishes a baseline for the captured live ruleset and status.
- Removing a group from the blueprint does not silently discard its synthetic baseline while the managed state entry remains.
- Existing states without a dynamic baseline remain readable. The first plan must report the provenance as unknown or otherwise avoid falsely classifying the difference as config-only drift.
- Normalization must prevent semantically irrelevant JSON object-key ordering from producing changes. Array ordering must remain significant unless ChurchTools semantics prove otherwise.
- Portable references in desired configuration and host-resolved values in ChurchTools must be compared in the same normalized domain used by the current plan engine.
- State persistence must preserve the current crash-resume guarantee: every successful action leaves a consistent state.
- The plan JSON exposed by CLI and REST must carry correct
source attribution for the dynamic change. Human-readable CLI/Markdown and Extension projections should consume that same canonical result.
Acceptance criteria
- Tests cover config-only, ChurchTools-only, and simultaneous ruleset changes.
- Tests cover status-only changes and unchanged normalized rulesets.
- Tests cover adoption, successful apply, partial/failed synthetic apply, and legacy states without a baseline.
- After a successful apply, a second plan converges to no-op and retains a usable last-applied baseline.
- Documentation explains which synthetic values are persisted and how legacy states acquire their first baseline.
Relationships and scope
This issue does not propose applying or deleting anything automatically, nor does it change the current no-implicit-delete policy.
Problem
Managed dynamic-group rulesets participate in planning as the synthetic
dynamicfield, but they are deliberately removed from the persisted managed-resource snapshot:snapshotFromChanges()excludes every synthetic field when state is written.dynamicout ofresources.<key>.fields.The live-versus-desired comparison correctly detects that a ruleset must be updated. However, without a last-applied ruleset baseline, ct-cli cannot distinguish these cases:
Consequently, the existing three-way attribution (
config,drift,config+drift) is incomplete fordynamic. A ruleset difference can be shown, but its origin cannot be established reliably.This became visible while reviewing a plan through the REST API and a ChurchTools extension: the state proves ownership of the group, but contains no previous ruleset against which the live and desired values can be classified.
Desired model
Persist a normalized last-applied baseline for managed synthetic state, at least for dynamic-group status and rulesets. This may either be:
dynamicvalue in the managed resource snapshot; orThe persisted representation should remain an implementation decision. It must not cause synthetic fields to be sent through the ordinary group create/update endpoint.
The resulting comparison should be:
Required behavior
ct adopt group --with-dynamicestablishes a baseline for the captured live ruleset and status.sourceattribution for thedynamicchange. Human-readable CLI/Markdown and Extension projections should consume that same canonical result.Acceptance criteria
Relationships and scope
This issue does not propose applying or deleting anything automatically, nor does it change the current no-implicit-delete policy.