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.