Skip to content

Execution Model

What each stage reads, what it writes, and what it deliberately does not do.

The four things involved

Everything in the execution model is a relationship between four distinct things. Keeping them separate is what makes the rest predictable.

  • Expected structure — the directories declared in your specification. The contract.
  • Workspace state — what is actually on disk under the root directory right now.
  • Plan — the difference between the two, computed and printed without changing anything.
  • Recorded fingerprint — the SHA-256 of the specification a workspace was last built from, stored in .orv.lock.

Stage order

specification (.orvienm.yaml)
        ↓
   parse            invalid YAML → exit 4
        ↓
   validate         invalid structure → exit 1
        ↓
   plan             read-only diff
        ↓
   apply            drift → exit 3, otherwise create
        ↓
   verify / status / doctor

Parse and validate run at the start of every command, not only when you invoke them directly. A specification that fails validation cannot reach plan or apply.

What apply owns

Apply creates the root directory named by structure.name and every declared directory inside it. If the root already exists, it creates the declared directories that are missing and leaves the rest alone. That is the whole of its write surface.

It does not delete, move or rename anything, and it does not touch file contents. A directory that exists but is not declared is left exactly as it is — Orvienm does not treat “absent from the specification” as “should not exist”, because a workspace is rarely owned by one tool.

Apply is idempotent in effect: running it against a workspace that already matches reports the structure as compliant and writes nothing. It is not atomic — if directory creation fails partway, the directories already created remain.

Reading a plan

Plan prints one line per directory and a summary. The three markers are:

  • + create — declared, missing on disk. Apply will create it.
  • = keep — declared and already present. Apply will leave it alone.
  • ? undeclared — present on disk, not declared. Apply neither creates nor removes it.

The summary counts each category. No command in the current CLI performs a deletion, and the plan names no operation the engine will not perform.

Drift

Drift in Orvienm means the specification changed, not the filesystem. On the first successful apply the fingerprint of the specification is written to .orv.lock. On later runs, apply recomputes that fingerprint and compares.

If they differ, apply refuses to run and exits 3. Removing .orv.lock accepts the new specification. Passing --dry-run bypasses the gate without writing anything.

A directory deleted by hand does not raise drift. Use verify to check that declared directories still exist, or doctor for the full picture.

Convergence

A workspace has converged when doctor reports HEALTHY: the root exists, no declared directory is missing, no undeclared directory is present, and the fingerprint matches the lock.

verify is a narrower check — declared directories exist, nothing more — and status sits between the two by adding the lock and fingerprint. Verify exits 0 when the structure is complete and 2 when it is not, with no flag needed: it is the structural gate. doctor reports its diagnosis and exits 0 by default; doctor --strict is the invocation that fails a pipeline on anything other than HEALTHY.

A declared directory is satisfied only by a directory. A regular file standing at a declared path does not count as that directory: verify, status and doctor all report the workspace as incomplete, and apply refuses the collision rather than resolving it — it names the path, creates nothing at all, exits 2, and leaves the file exactly as it was.

What is deterministic

Two properties hold precisely, and are worth stating rather than generalising.

  • The fingerprint is computed from a canonical manifest containing only version, style, structure.name and the sorted directory list. Reordering directories, reformatting the file or adding comments does not change it. Changing style does.
  • Given the same specification and the same workspace state, apply produces the same directories.

Both hold for nested entries too: a declared path expands to the directories it implies, and plan, verify and doctor all reason over that same expanded set.

Evolving a specification

Apply creates missing declared directories whether or not the root already exists, so a workspace can grow. What stands between you and a growth is the drift gate, not apply: editing a specification changes its fingerprint, and apply then refuses to run and exits 3 without touching anything.

To accept a change you intended, remove the lock and apply again:

$ orvienm apply .orvienm.yaml
! Drift detected
Current YAML fingerprint differs from .orv.lock.
Update YAML or remove .orv.lock to proceed.     # exit 3

$ rm .orv.lock
$ orvienm apply .orvienm.yaml
Structure already exists. Added missing directories:
  + data/
.orv.lock generated successfully.               # exit 0

$ orvienm plan .orvienm.yaml                    # Create: 0

Apply creates what the new specification added, keeps everything already there, and records the new fingerprint. Plan then reports Create: 0 and the workspace has converged.

Removing the lock is currently the only way to accept a specification change, and it is blunt: it discards the recorded fingerprint rather than acknowledging the move from one to the next, and it accepts every edit in the file at once — including a renamed root or entries you removed. Review the specification before you remove the lock. A dedicated acknowledgement step is an open design question, not a shipped feature.

Current limitations

Apply is also not atomic: if directory creation fails partway, the directories already created remain.

For each command's exact arguments and exit codes see the CLI Reference; for what a specific failure means, see Troubleshooting.