Skip to content

Clarify the Apply-to-Finalize lifecycle #179

Description

@lloydrichards

What problem are we solving?

The documented lifecycle places Finalize after Apply, but the current Finalize behavior is derived independently from Blueprint and Catalog and does not consume ApplyResult.

finalizeService.preview(blueprint, finalizeConfig);
finalizeService.run(blueprint, finalizeConfig);

The CLI reports failed writes and then continues into Finalize preparation:

const result = yield* applyService.apply({ apply, repoRoot });

if (result.failed.length > 0) {
  yield* Console.log(`Failed: ${result.failed.length} files`);
}

const previewScripts = yield* finalizeService.preview(
  blueprint,
  finalizeConfig,
);

Consequently, commands may be offered for a repository where required paths were skipped or failed. Finalize cannot explain which applied state it is finalizing because it only knows what the Blueprint intended.

There is also unclear execution ownership. run does not run commands; it reconstructs the script collection and returns closures:

return allScripts.map((script) => ({
  script,
  execute: () => executeScript(spawner, script, config.repoRoot),
}));

The CLI first previews scripts, selects them using command strings, calls run to reconstruct them, and then matches selected commands:

const selectedCommandSet = (scripts) =>
  new Set(scripts.map((script) => script.command));

Finalize itself deduplicates using both command and working directory:

const key = `${script.command}::${script.workdir}`;

These identities disagree when the same command is valid in different targets or workdirs.

Why does this matter?

flowchart TD
  B[Blueprint] --> P[Plan]
  P --> A[Apply]
  A --> R[ApplyResult]
  B --> FP[Finalize preview]
  B --> FR[Finalize run / prepare]
  R -. not consulted .-> FR
  FP --> S[CLI selection by command text]
  FR --> S
  style R stroke:#dc2626,stroke-width:2px
Loading

The system currently cannot answer several lifecycle questions consistently:

  • Should dependency installation run after one or more generated paths fail?
  • Does skipping a conflicted path affect commands contributed by that module?
  • Are Finalize commands a projection available during planning, or a post-Apply action?
  • Which module owns process execution and streamed results?
  • How does a selected preview entry identify the exact command later executed?
  • Why are next steps omitted when no scripts are present?

This issue is about defining a coherent seam and lifecycle behavior. It does not prescribe whether execution ultimately belongs to scaffold or to a CLI adapter.

Scope

  • Relationship between Blueprint-derived Finalize intent and the actual ApplyResult.
  • Policy for failed, skipped, and partially successful Apply results.
  • Stable identity from command presentation through selection and execution.
  • Ownership of command preparation versus process execution.
  • Elimination of preview/execution reconstruction drift.
  • Next-step collection independent of whether executable scripts exist.
  • Preservation of ordered phases: finalize, configuration-derived commands, post-finalize.

Acceptance criteria

  • The lifecycle explicitly states what repository outcome Finalize operates on.
  • Failed and skipped Apply paths have defined, tested effects on Finalize availability or execution.
  • A command selected from a preview identifies exactly one prepared command, including cases with identical command text in different workdirs.
  • Finalize commands are resolved once per workflow or equivalently guaranteed not to drift between presentation and execution.
  • Ownership of process execution and streamed results is clear from the public interface and naming.
  • Next steps are available even when the Blueprint contributes no executable Finalize scripts.
  • Tests cover partial Apply failure, skipped conflicts, duplicate command text across workdirs, and no-script next steps.

Blocked by

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkAutonomous slice that can be completed without human interactionenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions