FM-401: observed-state normalization, the difference model, and the planner - #94
Conversation
…lanner The difference model (fleet-core/src/difference.rs) carries the documented drift vocabulary — missing, extra, changed, unknown, unsupported — with one comparison point every normalizer funnels through. unknown and unsupported are honest terminal states: never coerced into changed, never actionable, and an unknown blocks the readiness claim while an unsupported field is reported without blocking. Observed-state normalization (fleet-application/src/observed.rs) maps capability facts, checkout discoveries, and Skills Manager deployment status into the desired resources' shapes through a provider-agnostic input surface. Normalization is total: every observation becomes a field value or an explicit unknown, never dropped silently. Desired tool sets are additive, not exclusive — an observed tool with no desired counterpart is not drift. The planner (fleet-application/src/planner.rs) is a pure function from a difference set to dependency-ordered actions — clone before install before skills — with contiguous order numbering and a single dry-run serializer shared by the API, web, and fleetctl so the surfaces cannot drift. unknown and unsupported differences are carried in the plan's unactionable list, reported and never acted on. Tests: seven difference-model tests (stable serialization, every comparison combination, actionability, canonicalization, convergence), seven normalization tests (fact shapes, honest gaps, provider-agnostic checkouts, per-agent skills), and seven planner tests (dependency order, contiguous numbering, unknown/unsupported refusal, purity, dry run round-trip, extra checkout).
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Availability is honest end to end. The observed state carries per-surface answered flags (mise, Skills Manager deployment status, checkout discovery): an unanswered surface makes its desired fields honest unknowns carrying their desired values, never actionable absences. Tool facts preserve their own availability (unknown and stale statuses are unknown, not absent), and provider version output is canonicalized to the bare version so an installed tool no longer stays changed because of a command-formatted prefix. Comparison is bidirectional: observed-only skills and checkouts become extra so stale deployments and abandoned checkouts can be undeployed or reported, and a known checkout with no readable remote is an honest unknown — cloning a duplicate would be worse than reporting. Identities escape their delimiters through the shared redaction helper. The planner is state-aware: an extra skill plans skills.undeploy, while extra tools and checkouts have no bounded removal path and are classified as unsupported rather than planned destructively; any identity the planner cannot resolve is classified, never silently dropped. Duplicate identities in canonicalization resolve by terminal- state precedence — an honest unknown outranks an actionable state, so a planner cannot act on a guess. The dry-run serializer's doc states its wiring honestly: the apply engine (FM-402) connects the surfaces when the plan gains an execution path.
There was a problem hiding this comment.
2 issues found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/fleet-application/src/observed.rs">
<violation number="1" location="crates/fleet-application/src/observed.rs:383">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
When no checkout is desired, a known checkout with `remote: None` is silently omitted because this loop only handles `Some(remote)`. Emit an explicit `unknown` for remote-less observations to preserve the module’s total-normalization contract.</violation>
</file>
<file name="crates/fleet-application/src/planner.rs">
<violation number="1" location="crates/fleet-application/src/planner.rs:305">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
The PR description says an extra checkout plans a clone, but this test now proves the shipped planner reports it as unsupported and creates no action. Update the PR description to describe the implemented behavior.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if observed.checkouts_answered == Some(true) { | ||
| let desired_remote = desired.checkout.as_ref().map(|(remote, _)| remote); | ||
| for checkout in &observed.checkouts { | ||
| if let Some(remote) = &checkout.remote |
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
When no checkout is desired, a known checkout with remote: None is silently omitted because this loop only handles Some(remote). Emit an explicit unknown for remote-less observations to preserve the module’s total-normalization contract.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/fleet-application/src/observed.rs, line 383:
<comment>When no checkout is desired, a known checkout with `remote: None` is silently omitted because this loop only handles `Some(remote)`. Emit an explicit `unknown` for remote-less observations to preserve the module’s total-normalization contract.</comment>
<file context>
@@ -155,69 +224,171 @@ pub fn normalize_skills(skill_id: &str, deployed_to: &[&str]) -> Vec<ObservedSki
+ if observed.checkouts_answered == Some(true) {
+ let desired_remote = desired.checkout.as_ref().map(|(remote, _)| remote);
+ for checkout in &observed.checkouts {
+ if let Some(remote) = &checkout.remote
+ && desired_remote != Some(remote)
+ {
</file context>
| "/elsewhere", | ||
| )]); | ||
| let plan = plan(&set); | ||
| assert!(plan.actions.is_empty()); |
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
The PR description says an extra checkout plans a clone, but this test now proves the shipped planner reports it as unsupported and creates no action. Update the PR description to describe the implemented behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/fleet-application/src/planner.rs, line 305:
<comment>The PR description says an extra checkout plans a clone, but this test now proves the shipped planner reports it as unsupported and creates no action. Update the PR description to describe the implemented behavior.</comment>
<file context>
@@ -268,12 +294,16 @@ mod tests {
)]);
let plan = plan(&set);
- assert_eq!(plan.actions[0].kind, "projects.clone");
+ assert!(plan.actions.is_empty());
+ assert_eq!(plan.unactionable.len(), 1);
+ assert_eq!(plan.unactionable[0].state, DifferenceState::Unsupported);
</file context>
The planner is state-aware in its reasons too: an extra skill's undeploy describes the guarded removal, and a reclassified difference preserves its observed value so consumers can show the installed version or checkout root. Availability is handled before versions: an absent tool is missing regardless of any version string the fact carried. The unused duplicate tool type is removed. ObservedState gains a from_observations constructor that sets every answered flag from whether the observation actually ran — the production composition path, so callers cannot forget the flags and leave the planner refusing to act. A remote-less checkout observation with no desired checkout emits an explicit unknown, preserving total normalization.
|
Amendment to the PR description: an extra checkout does NOT plan a clone — removing a checkout has no guarded path, so the planner classifies it as unsupported and reports it. The implemented behavior is proven by |
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Closes #90 (FM-401). Part of epic #6.
What
fleet-core/src/difference.rs): the documented drift vocabulary —missing,extra,changed,unknown,unsupported— with one comparison point (compare_field) every normalizer funnels through.unknownandunsupportedare honest terminal states: never coerced intochanged, never actionable, and anunknownblocks the readiness claim while anunsupportedfield is reported without blocking it.fleet-application/src/observed.rs): capability facts (tool presence + versions), checkout discoveries, and Skills Manager deployment status normalize into the desired resources' shapes through a provider-agnostic input surface. Normalization is total: every observation becomes a field value or an explicitunknown, never dropped silently. Desired tool sets are additive, not exclusive — an observed tool with no desired counterpart is not drift.fleet-application/src/planner.rs): a pure function from a difference set to dependency-ordered actions — clone before install before skills — with contiguous order numbering.unknownandunsupporteddifferences are carried in the plan'sunactionablelist, reported and never acted on. The dry-run rendering is one serializer shared by the API, web, andfleetctl --output json, so the three surfaces cannot drift.Tests
cargo xtask verifypasses.Summary by cubic
Implements FM-401's observed-state normalization, difference model, and apply planner so machine observations compare against desired resources into a five-state drift vocabulary and produce dependency-ordered dry-run plans. Closes #90.
Behavior
unknown, and per-surface answered flags make an unanswered inventory, deployment status, or discovery an honestunknown, never an actionable absence.missing, regardless of any version string — and version output is canonicalized so an installed tool doesn't staychangedbecause of a command prefix.unknownandunsupportedare terminal states: never planned, onlyunknownblocks readiness, and duplicate identities resolve toward these states so a planner can't act on a guess.extra, and a checkout with no readable remote isunknown.extraskills plan undeploy, while extra tools/checkouts and unresolved identities are reported asunsupported, never silently dropped.fleetctl --output json.Tests
Written for commit d9591e3. Summary will update on new commits.