Skip to content

Design: the RuntimeEnvironment holds a named set of roots — (root-name, rel) identity #597

Open

Description

@David-Noble-at-work

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

  1. 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 of name; 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, and pkg/xdg exists 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.
  2. 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 starting Users/<account>/… never rebinds per account).
  3. 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.
  4. 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

  1. David-Noble-at-work commented on Oct 2, 2026

    @David-Noble-at-work
    ContributorAuthor

    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 adopt to 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.md already says "read from elsewhere, write here wants two roots", and plan-space reserves the @name/… spelling for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions