Skip to main content

Module registry

Module registry 

Source
Expand description

Machine-global registry of currently-active DevFlow project roots.

Gates::list_open (gates.rs) is scoped to one project_root, and every caller inherits that scope — there is nowhere in this codebase that answers “what is DevFlow doing across every project on this machine?” without shelling out to ps and find (see 23-ORPHAN-FORENSICS.md). This module is that answer: a (project_root, phase) pair is registered on the same code path that already writes state.monitor_pid, so a running phase cannot be missing from the registry.

Storage shape (23-03 revision, cross-AI review BLOCKER 4): one file per (project_root, phase) under a roots/ subdirectory of the cache dir, enumerated with read_dir. Registration writes only its own file — there is no load-modify-write step and therefore no lost-update race to defend. A corrupt or truncated entry costs one entry, never the whole registry.

Structs§

RegisteredRoot
A registered (project_root, phase) pair — one DevFlow phase this machine is (or recently was) running.

Enums§

RegistryError
Errors produced by registry operations.

Functions§

cache_dir
Resolve the DevFlow cache directory. The ONLY env-reading function in this module. Resolution order: DEVFLOW_CACHE_DIR (test/override hook), then XDG_CACHE_HOME/devflow, then HOME/.cache/devflow. Returns None when none of the three is set.
deregister
deregister_in against the resolved machine-global cache dir. Deregistration is best-effort observability cleanup, so any error (including cache_dir resolving to None) is swallowed — mirrors how every call site invokes this with let _ =.
deregister_in
Remove the entry file for (project_root, phase), if present. With the per-file storage shape this is a single remove_file on entry_path_in — no load, no rewrite, and therefore no way to disturb a sibling entry belonging to another phase or another root. A missing file (never registered, or already deregistered) is treated as success rather than an error.
entry_path_in
The deterministic per-registration entry file path for (project_root, phase). The digest is only a filename disambiguator — the authoritative project_root lives inside the file itself, and load_roots_in reads it from there, so a digest collision costs at most one shadowed entry and never a wrong path.
load_roots
Every registered root in the resolved machine-global cache dir. An empty Vec when cache_dir resolves to None.
load_roots_in
Every registered root, sorted by (project_root, phase) so output is deterministic (read_dir order is not). read_dirs the roots directory, parsing each .json entry and skipping any that is unreadable or unparsable — exactly as Gates::list_open already skips unparsable gate files. Returns an empty Vec when the directory is absent. Never returns Result; enumeration must degrade, not die.
prune_missing
prune_missing_in against the resolved machine-global cache dir. 0 when cache_dir resolves to None.
prune_missing_in
Remove the entry file for every registered root whose project_root no longer exists on disk, plus every entry file that cannot be parsed at all (so unreadable files cannot accumulate forever), returning the number of files removed. Removal is per-file remove_file; there is no rewrite of surviving entries, so pruning cannot disturb a registration written concurrently with it. Deliberately NOT called from load_roots_in — that must stay side-effect-free so a read-only command cannot mutate machine state; callers invoke this explicitly.
register
Register (project_root, phase) into the resolved machine-global cache dir. A silent Ok(()) no-op when cache_dir resolves to None — registration is best-effort observability, never a reason to fail a launch.
register_in
Register (project_root, phase) into the machine-global registry under cache_dir. Pure with respect to env. Creates the cache directory and the roots directory if absent (both private, mode 0o700 — T-23-33) and writes only this registration’s own file, atomically — there is no load step, no merge step, and no rewrite of any other entry. Re-registering the same pair simply overwrites its own file with a fresh registered_at.
roots_dir_in
The roots/ subdirectory of a cache dir, where per-registration entry files live.