13. Layout
Context and decision
XDG geography tells LychD where an object belongs; typed creation and binding receipts decide what it may later change or remove.
The three domains
src/lychd/system/constants.py is the executable path contract. ~ is the effective LychD
user's home. Shared XDG parents are never recursively owned by LychD.
| Domain | XDG root and default | LychD root | Purpose |
|---|---|---|---|
| Codex | XDG_CONFIG_HOME or ~/.config/ |
~/.config/lychd/ |
lychd.toml, lifecycle receipt, Runes |
| Crypt | XDG_DATA_HOME or ~/.local/share/ |
~/.local/share/lychd/ |
durable data, triggers, PostgreSQL, Lab, Core, Extensions |
| Forge | XDG_CACHE_HOME or ~/.cache/ |
~/.cache/lychd/ |
disposable assembly staging |
The Codex contains lychd.toml, .lychd-lifecycle.json, and runes/ including the Animator,
Soulstone, and Portal anchors. The Crypt contains triggers/inbox and triggers/journal,
postgres/init_db.sh, postgres/data, snapshots, lab, core, and extensions.
postgres/data may be an ordinary directory, external mount, or LychD-created Btrfs subvolume.
The Forge contains assembly. Configuration (12) owns what these paths
mean; Security owns credentials and network policy.
Binding sites are shared host namespaces: ~/.config/containers/,
~/.config/containers/systemd/, ~/.config/systemd/, and ~/.config/systemd/user/ (with XDG
overrides applied). The lifecycle and Scribe ownership receipts are regular, non-symlink,
invoking-UID-owned files with mode 0600. They grant authority only over recorded identities and
names, never their shared parents.
Creation, binding, and deletion authority
init --dry-run describes a plan without mutation. init creates missing components as 0700,
preserves existing modes, and journals only successful creation identities. It refuses symlinks,
non-directories, foreign ownership, unsafe effective access or group/other writability, and unsafe
ancestry (except an appropriate sticky root/invoker-owned directory or a foreign read-only mount).
bind applies the same law while planning and committing, but never creates a missing binding
site. The per-UID/per-Codex lifecycle lock is under fixed host /tmp, not caller-selected TMPDIR.
Every traversal is descriptor-relative and no-follow. A missing component is made under a private
same-directory staging name, opened and device/inode-attested, then atomically published without
replacement. The receipt records the opened identity and parent descriptor authority rather than a
replaceable pathname. File publication follows the same rule: write and fsync a candidate,
publish no-clobber, re-attest through the pinned parent, fsync the directory, then journal. A
race winner, replacement, ambiguous result, or failed journal is preserved or quarantined as typed
recovery; it is never broadened into deletion authority.
After a complete transaction, the receipt may adopt the exact device/inode identities of the
dedicated Codex, Crypt, and Forge roots. Shared XDG parents, mounts, source checkouts, and external
model shelves remain outside that grant. del is a separately confirmed destructive lifecycle:
it joins receipt authority to a live inventory, stops managed installation state, and refuses
unknown mounts, identity drift, invalid receipts, foreign objects, or ambiguous ancestry. Recursive
walks reject mount crossings and possible Btrfs root/stub signatures. Deletion moves a
re-attested leaf to a collision-resistant private sibling before type-specific removal; mismatch or
failure restores it when possible, otherwise leaves a typed recovery marker. Published init
creations are never pathname-deleted as rollback.
PostgreSQL substrate
Only an absent PostgreSQL data/ target on trusted Btrfs may receive a new subvolume and +C
attempt. Creation, btrfs subvolume show, no-COW mutation, and confirmation address the leaf
through its inherited parent descriptor. A successful subvolume records canonical UUID and
non-reserved ID as well as device/inode in the version-2 lifecycle receipt. Existing storage is
never retrofitted; +C is only an inheritance policy and a false result is a warning.
LychD never adopts an observed existing subvolume or backfills a missing identity. A mounted
Phylactery requires complete live mount and Btrfs agreement; an unmounted one requires the exact
version-2 creation receipt plus matching live identity and a safe top-level mapping. On success,
LychD emits an attested btrfs subvolume delete --subvolid ID TOP_LEVEL operator handoff and waits
for proven absence before generic retirement. It never invokes sudo.
Host and container geography
The Vessel uses identical host/container targets: Codex is read-only; lab is read-write; core
and extensions are read-only; and when selected, Reactor inbox is read-write and journal is
read-only. PostgreSQL data belongs to its PostgreSQL unit; there is no blanket Crypt mount.
~/work/ is an explicit read-write Outland mount and never gains Three-Domain lifecycle authority.
Tomb's reserved task, workspace, artifact, and cache targets sit beneath
~/.local/share/lychd/tomb/, but it receives no Codex and no trigger/signalling path. Its envelope
is secret-forbidden. This is a designed trust delta, not an executor: Security (09)
owns its credentials/network/isolation and Workers (14) any executor.
Consequences
XDG overrides move roots but not relative topology or ownership law. A known location is useful geography, never permission to adopt, overwrite, or delete somebody else's object. The implemented CLI path is Partial and real-host lifecycle proof remains separate; State of Work owns that maturity boundary.