Skip to content

Engine Design

The modules the engine is built from, what each one owns, and how a specification moves through them.

Shape of the engine

The engine is a Python package with a single console entry point. It has two runtime dependencies — a YAML parser and a terminal colour library — and no daemon, no server and no database. Every command is a short-lived process that reads a specification, optionally touches the filesystem, and exits with a code.

Modules

cli

Argument parsing and dispatch. Loads and validates the specification once, then routes to a command. Owns the drift gate on apply and the process exit code.

parser

Reading YAML into a dictionary. No validation and no transformation — it only fails on a missing file, malformed YAML, an empty file, or a root that is not a mapping.

validator

The single validate() function. Collects every schema and path problem and raises one ValidationError containing all of them.

engine

Everything that reads or writes the filesystem: plan, apply, verify, doctor and the project scaffolds. This is one module, not separate planner and executor components.

fingerprint

The canonical manifest, its SHA-256, the .orv.lock contents and the drift comparison.

policy

The policy command: reads policy.forbid and reports forbidden names found inside the declared structure root.

errors

The six exit codes, as module-level constants.

ui

Terminal output helpers for success, warning and error lines.

Path through the modules

cli          read arguments, resolve the file
  ↓
parser       YAML → dict            (fails: exit 4)
  ↓
validator    schema and path rules  (fails: exit 1)
  ↓
engine       plan / apply / verify / doctor
  ↕
fingerprint  canonical hash, .orv.lock, drift

Parsing and validation happen once, in the CLI, before any command runs. Commands therefore always receive a specification that is already well-formed.

Filesystem boundary

Only the engine module writes. It creates the root directory named by structure.name and the declared directories inside it, and it writes nothing else. There is no deletion path in the code: undeclared directories are reported by plan and doctor, never removed.

Certain directories are ignored entirely when scanning a workspace — .git, .vscode, __pycache__ .pytest_cache — at any depth.

Persistence

The engine keeps one file: .orv.lock, written to the working directory on the first successful apply. It records the engine version, a timestamp, the specification fingerprint, the structure name, the schema version and the style.

There is no state directory, no snapshot store and no run history. The lock is the whole of the engine's memory, and its only functional role is the drift comparison that gates apply.

Failure behaviour

Failures surface as exit codes rather than exceptions: 1 for validation, 2 for an invalid structure, 3 for drift, 4 for file problems, 5 for anything unhandled. Unhandled exceptions are caught at the entry point and reported as an internal error.

Apply is not transactional. It creates the root and then creates each declared directory in turn; if one fails, the ones already created remain and the command reports an error.