C.W.K.
Stream
Lesson 02 of 04 · published

Plan and Apply Are One Plan

~15 min · firelink, plan-apply, immutable, toctou

Level 0Cold Ash
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"You approve exactly what you previewed — or you don't get to approve at all."

One Immutable Plan, Two States

Every mutation in Firelink runs the same lifecycle: a request resolves a capability, passes preflight, becomes an immutable operation plan, waits behind an armed confirmation, executes exclusively, verifies its postconditions, and lands as a durable, audited result. The plan is the heart of it, and dry-run and apply are not two operations — they're two states of that one plan. Dry-run creates and persists the exact plan; the UI shows its targets, warnings, and expected effects; apply executes that same plan, by id, behind an armed two-click. A typed plan holds canonical ids and adapter-resolved arguments — never raw command tokens.

Apply Executes a Plan — It Never Rebuilds One

Here's the rule that makes preview trustworthy: the backend never accepts an apply request that independently rebuilds the operation from new raw values. Apply takes a plan id, not a fresh set of parameters. You cannot preview a gentle operation and then apply a different, harsher one by changing the payload — because apply doesn't read a payload, it executes the stored plan. What you saw in the dry-run is exactly, byte for byte, what runs. The preview isn't an estimate; it's the artifact that gets executed.

A Changed World Invalidates the Plan

What if the world moves between preview and apply? If the source state, the registry revision, or the target selection changes, the plan is invalidated and must be regenerated and re-previewed. This is the classic time-of-check-to-time-of-use gap, closed deliberately: you can't preview against one reality and apply against another. The plan is pinned to the world it was built in, and if that world shifts, your approval no longer applies — you have to look again. Combined with an expiry on the plan id, this means an apply is always against a fresh, unchanged, explicitly-approved reality.

Make apply execute a stored, immutable plan by id — never rebuild the operation from whatever the client sends at apply time. When the thing you approved and the thing that runs are guaranteed identical, and any change to the world invalidates the approval, 'preview' becomes a promise instead of a guess.

Code

Dry-run persists the plan; apply executes it by id·python
def dry_run(request) -> Plan:
    cap = resolve_capability(request)          # typed, from the registry
    preflight(cap)
    plan = Plan(
        id = new_plan_id(),
        capability = cap,                       # canonical ids + resolved args
        world_fingerprint = fingerprint(cap),   # source state + registry revision
        expires_at = now() + TTL,
    )
    store.save(plan)                            # the EXACT artifact the UI previews
    return plan

def apply(plan_id, armed: bool):
    plan = store.get(plan_id)                    # by id — NOT a fresh payload
    if plan is None or plan.expired(): raise PlanInvalid
    if not armed: raise NeedsArmedConfirmation
    if fingerprint(plan.capability) != plan.world_fingerprint:
        store.invalidate(plan_id)                # world moved -> re-plan, re-preview
        raise PlanStale
    return execute(plan)                          # runs the stored plan, verbatim

# There is no apply(plan_id, new_service=..., new_target=...). Apply reads no
# parameters. You cannot preview one thing and apply another.

External links

Exercise

Take any 'confirm before doing' flow you know (a checkout, a bulk delete, an infra change with a preview). Ask: does 'confirm' re-send the parameters, or does it commit exactly what was previewed? Find a case where the preview and the committed action could differ (the cart changed, a price updated, a target was added). Design the fix: a stored, id-referenced plan plus an invalidate-on-change rule. What does the user see when the world moved under them?
Hint
If 'confirm' posts the same form fields again, preview and action can drift. The robust shape is: preview produces an id, confirm sends only that id, and the server refuses if the underlying state changed — forcing a re-preview rather than silently acting on stale approval.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.