Skip to main content

Module executor

Module executor 

Source
Expand description

Safe Executor — 0.5.2.

The layer that turns a reviewed PlanDocument into deterministic on-disk changes. It behaves like a cautious senior engineer applying changes: if anything is uncertain, it refuses.

§Posture

  • Refusal-first. Every primitive outside a tight safe subset is rejected with a named ExecutionError variant. The supported set for 0.5.2 is small by design (add_field, rename_field); the list grows only as each primitive’s edit + migration story is proven safe.
  • All-or-nothing. A partial apply is worse than no apply. The executor builds the full change set first (dry-run), verifies every precondition, then commits atomically — writing every target to a sibling .tmp and renaming on success. A mid-flight failure rolls back everything touched.
  • Never writes arbitrary SQL. The only SQL that can land on disk comes from build_migration_sql, whose shape is entirely determined by the primitive being applied. There is no path from an AI prompt to a hand-written SQL statement.
  • Every check runs twice. The executor re-runs Plan::validate and review_plan before touching anything, even though the document was validated when saved. Schemas drift, and this layer is the last place to catch that drift before a migration is written.

§What 0.5.2 supports

  • Primitive::AddField — adds a column via ALTER TABLE … ADD COLUMN … and patches the generated apps/<app>/models.rs (struct, COLUMNS, INSERT_COLUMNS, from_row, insert_values). Adds use chrono::{DateTime, Utc}; if the new field needs it and the file doesn’t already import it.
  • Primitive::RenameFieldALTER TABLE … RENAME COLUMN plus a scoped rename inside the same models.rs.

§What 0.5.2 refuses

Every other primitive returns ExecutionError::UnsupportedPrimitive with a one-line reason. These land in later pull requests, not silent “best effort” writes:

  • add_model, remove_model, rename_model — require cross-file scaffolding (apps tree, migrations, admin + views updates).
  • remove_field, remove_relation — destructive; gated on a --force style flag that doesn’t ship in 0.5.2.
  • change_field_type, change_field_nullability — require SQLite table-recreation migrations which need their own review pass.
  • add_relation, update_admin — out of scope for 0.5.2.
  • create_migration — developer-only; refused at the ExecutionError::DeveloperOnlyForbidden gate before it ever reaches the dispatch.

§Testability

The core logic (plan_execution) is pure: it takes a ProjectView (in-memory snapshot of the files it cares about) and returns an ExecutionPreview. No filesystem I/O. The impure entry execute_plan_document wraps it with disk reads, the confirmation-friendly preview, and atomic writes.

Structs§

ExecuteOptions
Knobs for execute_plan_document. Kept as a struct so the executor can grow flags without a breaking signature change.
ExecutionPreview
Dry-run output. Produced by plan_execution before any write, and displayed by the CLI as the “Plan to apply” preview.
ExecutionResult
Reported after a successful apply. Filenames are relative to the project root passed to execute_plan_document.
ParsedModelsFile
PlannedFileChange
One file the executor will write. Create expects the file to not exist; Update expects it to match expected_current_contents byte for byte — any mismatch means a human touched the file after the plan was reviewed, which is a ExecutionError::FileConflict.
ProjectView
Parsed view of the project files the executor cares about.
RetrofitReport
Result of scanning a schema for belongs_to relations that were materialised before 0.9.0 (no on_delete / required metadata).

Enums§

ExecutionError
Every way the executor can refuse. All variants are refusal-first: nothing has been written when one of these is returned.
FileChangeKind

Functions§

execute_plan_document
Run a plan against the project on disk.
plan_execution
Compute the exact set of file changes that executing doc would produce, without touching the filesystem.
plan_retrofit_foreign_keys
Phase 2: generate a retrofit plan for every belongs_to relation that lacks on_delete metadata. Returns a report the caller can either print (dry-run) or materialise (write migrations + update schema).
render_preview_human
Render an ExecutionPreview as an operator-friendly block. The CLI prints this before asking for confirmation.