Skip to content

Ensure Apply executes the repository state represented by Plan #175

Description

@lloydrichards

What problem are we solving?

Plan is described as the repository-aware model of outcomes for one snapshot, but Apply can execute against repository contents other than those used to build the Plan.

Planning reads the repository and classifies each path:

const repoSnapshot = yield* snapshot.load({
  paths: /* planning paths and ancestors */,
  repoRoot,
});

return yield* projectPlan({ planningPaths, repoSnapshot });

For composed outcomes, Apply later reads the filesystem again and uses those newer contents as its composition base:

const existingContents =
  action.writeMode === "modify"
    ? yield* loadFileContents(fullPath)
    : Option.none<string>();

const composedContents = yield* compositionEngine.compose(
  action.path,
  baseContents,
  action.operations,
);

Apply also receives repoRoot independently from the Plan:

apply({ apply, repoRoot })
preview({ apply, repoRoot })

Why does this matter?

The lifecycle currently permits this sequence:

sequenceDiagram
  participant CLI
  participant Plan
  participant Repo
  participant Apply

  CLI->>Plan: build against repository state A
  Plan->>Repo: read state A
  Plan-->>CLI: reviewed outcomes
  Note over Repo: file changes to state B
  CLI->>Apply: execute reviewed Plan
  Apply->>Repo: read state B
  Apply->>Repo: compose and write from B
Loading

Consequences include:

  • A user can review one repository change and execute another.
  • preview is not purely a projection of the supplied execution intent because it also reads current disk state.
  • The same Plan can be paired with a different repository root.
  • Repository drift has no explicit lifecycle outcome; it may become a different composition or a generic write failure.
  • Tests cannot state a clear contract for changes occurring between Plan and Apply.

This issue is about restoring trust in the Plan -> Apply handoff. It intentionally does not prescribe whether that trust is represented through hashes, materialized contents, snapshot identity, or another mechanism.

Scope

  • Define what repository state an Apply is entitled to execute after a Plan has been reviewed.
  • Make repository drift observable as a deliberate typed outcome rather than silently changing execution.
  • Ensure preview and execution have consistent repository-state semantics.
  • Cover authoritative and composed outcomes.
  • Cover attempts to execute a Plan against unrelated repository state.

Acceptance criteria

  • The repository-state assumptions represented by a Plan are explicit and testable.
  • Apply does not silently execute a different composition when a relevant path changes after planning.
  • Repository drift produces a deliberate typed failure or equivalent explicit result.
  • Preview cannot report a change based on repository state that Apply would interpret differently without surfacing drift.
  • Tests cover modification, deletion, and creation of relevant paths between Plan and Apply.
  • Tests cover attempting to execute a Plan against repository state other than the state it represents.
  • The CLI can communicate that the Plan is stale without misreporting the condition as a generic invalid root or execution failure.

Blocked by

None - can start immediately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkAutonomous slice that can be completed without human interactionbugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions