design: Plan/Undo semantics — non-invertible actions and rollback guarantees #2

Open
opened 2026-06-11 15:38:53 +02:00 by yorunikakeru · 1 comment
Owner

Spec defines `Plan { steps: Vec<(Action, Undo)> }` and promises "либо весь Plan, либо откат". Open design questions to settle before coding:

  1. Undo for non-invertible actions. `power_profile performance` — the undo is "whatever it was before", which means the Planner must capture prior state at plan time and that state may be stale by execution time. `service_enable` undo is disable — but only if the service wasn't already running for another reason.
  2. Rollback failures. Step 3 of 5 fails, undo of step 2 also fails — now the system is in a state matching neither old nor new intent, while the spec claims partial application is impossible. Define the actual guarantee (best-effort rollback + mark generation failed?) honestly.
  3. Idempotency/convergence. Stateless Planner recomputes on every Watcher event; plans must be no-ops when current state already matches intent, or the loop oscillates (e.g. two profiles toggling the same service at different priorities).
  4. Priority resolution. IR actions carry priority [0,100]; equal-priority conflicts are validation errors at DSL level, but the Planner still needs deterministic ordering for non-conflicting concurrent profile activations.

Output: a short design doc in this repo; spec update follows.

Spec defines \`Plan { steps: Vec<(Action, Undo)> }\` and promises "либо весь Plan, либо откат". Open design questions to settle before coding: 1. **Undo for non-invertible actions.** \`power_profile performance\` — the undo is "whatever it was before", which means the Planner must capture prior state *at plan time* and that state may be stale by execution time. \`service_enable\` undo is disable — but only if the service wasn't already running for another reason. 2. **Rollback failures.** Step 3 of 5 fails, undo of step 2 also fails — now the system is in a state matching neither old nor new intent, while the spec claims partial application is impossible. Define the actual guarantee (best-effort rollback + mark generation failed?) honestly. 3. **Idempotency/convergence.** Stateless Planner recomputes on every Watcher event; plans must be no-ops when current state already matches intent, or the loop oscillates (e.g. two profiles toggling the same service at different priorities). 4. **Priority resolution.** IR actions carry priority [0,100]; equal-priority conflicts are validation errors at DSL level, but the Planner still needs deterministic ordering for *non-conflicting* concurrent profile activations. Output: a short design doc in this repo; spec update follows.
Author
Owner

Context update (Planner core = Haskell behind FFI, Rust actor wrapper): Plan and its Undo entries
serialize across the FFI boundary to the Rust Executor side. Two additions to this design doc:

  1. Undo state captured at plan time (e.g. previous power profile) is baked into the serialized Plan —
    define staleness policy: does Executor re-validate preconditions per step, or trust the snapshot?
  2. The Haskell core is pure (`IR + SystemState -> Plan`), so commit/rollback decisions on step results
    either round-trip back into Haskell (step result in, next decision out) or live in the Rust wrapper. Pick
    one — it determines where the "commit or rollback" logic from the spec's reconciliation loop actually
    lives.
Context update (Planner core = Haskell behind FFI, Rust actor wrapper): Plan and its Undo entries serialize across the FFI boundary to the Rust Executor side. Two additions to this design doc: 1. Undo state captured at plan time (e.g. previous power profile) is baked into the serialized Plan — define staleness policy: does Executor re-validate preconditions per step, or trust the snapshot? 2. The Haskell core is pure (\`IR + SystemState -> Plan\`), so commit/rollback decisions on step results either round-trip back into Haskell (step result in, next decision out) or live in the Rust wrapper. Pick one — it determines where the "commit or rollback" logic from the spec's reconciliation loop actually lives.
Sign in to join this conversation.
No labels
core
ffi
haskell
rust
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
FrogOS/Planner#2
No description provided.