Repository navigation
Design: the RuntimeEnvironment holds a named set of roots — (root-name, rel) identity #597
Description
Design question (USER, 2026-08-21), filed under its feature during phase 2 of
resource-construction.
The scenario that forces it
writ installs devlore package foo, with the personal/team/base repos rooted at ~. Package foo must
read files from its own tree, download and copy files to /usr/local, and may add to XDG_CONFIG_HOME.
One logical install — one saga, one compensation story — spanning the package tree, the system tree, and
the config tree. A copy whose source is in one tree and whose destination is in another lives in a single
operation, so per-tree graph splitting cannot express it.
Candidates
- Named multi-root (recommended). The environment holds bindings —
home → ~,system → /,
config → $XDG_CONFIG_HOME,pkg → foo's tree; the plan declares the names it consumes; the run binds
names to directories; file identity becomes(root-name, rel). The scenario reads naturally:
(pkg, share/foo/payload)→(system, usr/local/bin/foo), plus(config, foo/settings.yaml)— every
resource confined by its own kernel-enforced root, every name late-binding exactly as ruled (home per
account, system per host — Unix-literal "/" roots panic on Windows: four test fixtures, and writ's System layer TargetRoot #392). Pre-flight generalizes cleanly: every pending(name, rel)must exist
under the run's binding ofname; an unbound name fails the run — the same shape as variable binding.
The design already points here: the phase-2 header notes Rule star's session-root anchor #571's declared-roots direction "extends
naturally into(root-name, rel)", writ's Home/System scopes ARE named roots, andpkg/xdgexists to
name these locations. Residual costs: identity gains a dimension (URI grammar, catalog keying,
serialization change; schema stays 1, old documents die, per precedent); the name vocabulary needs
governance; receipts must record the root-name so compensation rebinds correctly. - Single root via common ancestor (today's
runRootFor, stretched). For the scenario the ancestor is
/— confinement dissolves and every rel degenerates into a machine path, killing the relocation story
phase 2 bought (a home rel startingUsers/<account>/…never rebinds per account). - One root per graph; split the package per scope. Fails at the cross-tree copy (one operation, two
trees) and fractures the install's compensation across runs with no promises between them. - Single root plus unconfined escapes. Reintroduces the raw
os.*side-channel the Windows: restrictive file modes are unenforced (private key, state home, config, secrets) — route every mutation through fsroot #405 campaign
exists to kill. Not a candidate.
Sequencing consequences
- This is its own feature effort, not phase-2 scope — phase 2 completes on the single-root model, which
is the degenerate one-name case; nothing landing there needs undoing. - Identity is rel: git-model plan-space paths; migrate the authoring surfaces #584 PR 2's plan-space grammar must reserve the root-qualification spelling now (e.g.
@config/foo/settings.yaml— a shape that cannot collide with the drive-letter refusal), so the little
language does not break again when this lands.
Relations
#571's candidate 4 (multiple roots as declared capabilities) IS this design — ruling here dissolves that
question's residuals (XDG unreachable through the session root). #563's hermeticity direction (declared
module subsets) is the same capability shape. #392 rules the system anchor this design late-binds.
Feature: #546
Plan and design documents
| Kind | Document |
|---|---|
| Plan | docs/plans/task/597-named-roots.md, lane 33 of #916 |
| Design | 4.6-named-roots.md (status), kept current as each ruling is made |
Activity
fix(fsroot): the rel half is slash-canonical on every platform
feat(file): identity is the rel — the mint flips, and the trace recor…
feat(file): the plan-space path grammar, and every authoring surface …
David-Noble-at-work commentedon Oct 2, 2026
Placed on #916 as lane 33, in PR C, after lane 7
The owner, 2026-10-02: "we have been talking about named roots for a while now ... we will design that after we land this lane." Then: "add the design work to your schedule."
What brought it back: lane 7's phase 4 (#926) teaches
writ adoptto infer each item's scope from the scope model. An adopt run is confined to one root and writes into the layer, so for a custom scope (/srv/staging), a Windows scope (C:\ProgramData) or a relocated Home the layer lies outside the run's root. Lane 7 keeps candidate 2 for now: each adopt run is confined to the deepest directory holding both the scope's root and the layer, found by one fsroot function that replaces deploy's and migrate's copies. Those copies answer wrongly on Windows:C:for a drive's root, and\for two drives where the answer is none.This design, candidate 1, is the end of that widening for deploy, upgrade, migrate and adopt alike: a run holds a target root and the source roots, each confined to its own tree, and two drives are simply two roots.
docs/architecture/4.5-fsroot-variants.mdalready says "read from elsewhere, write here wants two roots", and plan-space reserves the@name/…spelling for it.fix(fsroot): one CommonAncestor, which keeps a drive's root whole and…
docs(architecture): named roots begun: lane 33's plan, and 4.6 tracki…
docs(plan): lane 33 is approved, and closes #571 as lane 44 of the sc…