The ledger is the one directory vivalence owns on a machine. Everything the system needs to remember between runs — which packages exist, which instances are shelved, what is currently running — lives under it.
minimum vocabulary required:
record—registry.json, the list of package locations. Managed bytap/untap, read at boot. The only mount truth.store—registry/, the default directory packages materialize into. A location heuristic, nothing more.instances/— where created instances land when no target is given.instance— one runnable system at its mount:instances.jsonrecords them, locks and logs key by them.
~/.viva/
registry.json the record: package locations (absolute = pinned checkouts,
relative = store children)
instances.json known instances on this machine
.env the machine's own environment, hand-authored (viva instances use x --ledger upserts one line)
locks/ <instance>_<process>.lock — what is running
logs/ <instance>/ span + stdout/err streams
sessions/ <shell-pid>.json — per-shell selections (viva instances use)
registry/ the store — remote taps clone here; each child its own git clone
instances/ each child an instance home: the declaration,
its .env, and (at runtime) mountpoint/one resolution law
Every path has a convention default and an environment override, and the override
always wins: VIVA_LEDGER_MOUNT moves the whole home (default ~/.viva),
VIVA_REGISTRY_MOUNT moves the store (default <ledger>/registry),
VIVA_INSTANCE_MOUNT names the one instance a runtime renders — and a *_MOUNT is
always a path, never a slug. Paladin throws on a slug rather than resolving one. The
instance lookup belongs to the operator: viva instances use <slug> resolves the slug and
writes the resolved path.
instances.json is the identity authority: the record names instances; manifests name
recipes. A module’s manifest.slug says which recipe a registry reference resolves to,
and nothing more — every reader of instance identity (locks, logs, run targets, the
doctor) goes through the record, and a mount the record does not hold is simply not an
instance yet: viva instances/tap <path> --slug=<name> adopts it.
seven strata
“The environment” is not one bag — it is seven voices in fixed precedence, and the strongest voice holding a key wins:
flag --instance=x / --ledger=/x / --env=file.env this invocation only
cwd standing in an instance directory
instance the instance's own .env, at its root
.env ./ .env scan, then VIVA_ENV_FILE, then --env (within: later wins)
os exported shell variables (VIVA_* / PUBLIC_VIVA_* / SECRET_VIVA_*)
session <ledger>/sessions/<shell>.json viva instances use x
ledger <ledger>/.env hand-authored; --ledger upserts hereviva ledger doctor prints every variable with the stratum that won it, and dims the
shadowed voices under each — “why is my .env not applying” is answered by reading the
column.
Every source hands its raw bag to one ingress,
paladin.assign(bag, stratum), and the key decides what the value is: SECRET_*
goes to the secret store, VIVA_* and PUBLIC_VIVA_* to the environment, anything else
is ignored. No filename and no caller may decide it, so SECRET_VIVA_JWT is a secret in
whichever of the two files you write it. An explicit source (--env, VIVA_ENV_FILE)
that yields no VIVA knowledge throws instead of silently doing nothing.
viva instances use italian selects an instance for the current shell. The id is the
invoking shell’s own pid: ghost.sh takes ${VIVA_PROCESS_ID:-$PPID}, so every shell
carries a stable id with nothing to configure. Each shell’s selection lives in its own
session file, so two terminals work two instances in parallel. Add --ledger to set the machine default instead. Trailing
segments chain: viva instances use italian run selects and runs in one invocation.
One deliberate edge: chaining re-enters the full resolution, so standing inside
another instance’s directory outranks the selection you just made — the directory you
are in is the stronger voice, by design.
The ledger itself is machine state and never git-managed — the record pins absolute paths, the locks hold pids. Its children are the git units: every tapped package in the store is its own clone, and a shelved instance is yours to version.
record over discovery
A package sitting in the store but absent from the record stays unmounted — that is
a feature, not a gap. Presence is not activation; tap activates, untap deactivates,
and the working copy survives both. Packages can live anywhere on disk: the store is
only where they land when nothing else is said.
At boot, supply() reads the record and mounts each entry. Only when the record is
absent entirely does it seed one — by discovery over the repository’s own commons/,
so a fresh clone boots the standard packages without ceremony.
viva registry/doctor is the record read against the store: stale references (recorded,
gone from disk), untapped residents (in the store, absent from the record — a declaration
nested under a recorded root counts as tapped), and the census of what every tap supplies,
by owner and type. viva registry/list runs the same supply() first, so on a fresh ledger
the first list is what seeds the record. viva ledger/doctor stays on the ledger’s side of
the line: one row per organ of the home, with counts and anomalies, never the census.
instance, singular
instances/ holds many instances; a runtime ever renders one. instance create copies an
instance wholesale — the declaration and its environment schema — to instances/ or to
an explicit target, and everything derived (mountpoint/ databases) grows inside that
home. Two runs of the same declaration are two shelved copies, not two moods of one.