Repository navigation
--scope, multi-valued and writ-only, replaces the inert --target: absent means every scope defined on this platform; Home and System are well known, Windows adds ProgramFiles and ProgramData, and writ.scopes adds the rest #926
Description
Epic: #451 · Feature: #762 The writ lifecycle surface · Schedule: #916, lane 7, PR C · Waits on: #925 (lane 6)
The ruling (#762, 2026-08-31; this lane ruled 2026-09-23)
--scope, multi-valued, repeatable, writ-only, naming which scopes a run covers. Today writ registers a
persistent --target flag defaulting to Home (cmd/writ/writ/root.go:46) and nothing reads it: every command
sets its root from TargetHome() and deploy walks TargetOrder() regardless. The flag is dead, and its name is
the wrong word (#925).
Why it matters, profoundly, on Windows (the owner, 2026-09-23): there are two well-known scopes, Home and
System, and one should be able to add as many as needed; on Windows ProgramFiles and ProgramData are key.
Custom scopes are the writ.scopes map, name to root (#762 Requirement 8).
The behavior
--scope=<name>, repeatable. Absent: every scope defined on this platform (ruled 2026-09-23) -- what deploy
does today by walking the order, so barewrit deploykeeps meaning "everything" (and writ deploy with no project refuses, though common is implicit and the config path handles zero projects #843, lane 9, makes it
deploycommon).- The builtins and their platform bindings:
Home(unelevated),System, and on WindowsProgramData,
ProgramFiles,ProgramFilesX86(elevated). Builtin names are reserved on every platform and defined on some. - Data is skipped, instructions are refused: a layer directory for a scope undefined on this platform is
skipped silently;--scope=<undefined builtin>is an error; awrit.scopesentry with a builtin's name is an
error. deploy,upgrade,reconcileanddecommissionhonor the selection; the record (deploy replaces the record: readback.Fold is bounded to the current lifetime, one writ deploy invocation across scopes #922) is per invocation
across the scopes selected.--targetis removed; it is not aliased.
Acceptance
Rewritten 2026-10-02 to the rulings recorded in this issue's comments; each tick names its evidence.
-
--scopeondeploy,upgrade,reconcileanddecommission, repeatable (ruled 2026-10-02, open question
3: not onwrit's root);--targetgone;writ deploy --target=Homeis a usage error (64). Evidence:
8b453dc;TestRoot_ScopeOnLifecycleCommandsOnly,TestRoot_TargetIsGone; danoble-ud24-1 exit 64. - without
--scope,deployruns every scope defined on this platform, andupgrade,reconcileand
decommissionread the whole record (ruled 2026-10-02, open question 4);--scope=Homeruns Home alone;
--scope=Home --scope=Systemruns both, inScopeOrder(). Evidence: 8b453dc, 6871349;
TestSelectScopes_NoneSelectsEvery,TestInScopes_NoneNamedReadsEveryEntry,
TestCompareScopes_TheModelsOrder,TestWritDeployScenario_Scopes; danoble-ud24-1. - on Linux,
--scope=ProgramFileserrors "not defined on this platform"; a layer'sProgramFiles/is skipped
with no output. Evidence:TestSelectScopes_RefusesUndefinedBuiltin,TestRoot_RefusesUndefinedScope,
TestCollectLayerSources_TheGivenScopesOnly; danoble-ud24-1 exit 64, "scope ProgramFiles is not defined on
linux; the scopes defined here are System, Home". - a
writ.scopesentry named for a builtin relocates it (ruled 2026-10-01, open question 1); one naming a
builtin this platform does not define, or a scope with no root, is a configuration error (78). Evidence:
8b453dc;TestDefinedScopes_RelocatedAndCustom,TestRefuseScopeConfiguration_UndefinedBuiltin,
TestRefuseScopeConfiguration_NoRoot,TestRoot_RefusesUndefinedBuiltinScopeKey. -
10-command-line-interface.md§4.1 and thewrit deployreference say the above. Evidence: f6e1670. - danoble-ud24-1:
deploy --scope=Home, then baredeploy. Evidence: e2a75b2 records the run. The Windows VM
is gone; CI's Windows jobs on PR C are the Windows proof. - on Windows,
--scope=ProgramDatadeploys under%ProgramData%: deferred to lane 34
(#521), since ProgramData needs elevation (the owner,
2026-10-02: "For now: skip tests against system"). Its root resolves to%ProgramData%in
TestDefinedScopes_Windows.
Plan and design documents
| Kind | Document |
|---|---|
| Plan | docs/plans/feature/762-lifecycle-scopes.md Phase 4; the lane's plan is docs/plans/task/926-scope-flag.md |
| Design | 10-command-line-interface.md §4.1, 2.4-hermeticity-guarantees.md |
Activity
David-Noble-at-work commentedon Oct 2, 2026
Ruled 2026-10-01: a builtin key in
writ.scopesoverrides its rootThe owner: "it's an override if you relocate home or system (the builtins)."
writ.scopes.Homeandwrit.scopes.Systemrelocate those scopes, as #762 Requirement 8 ruled and lane 6 (#925) built. The acceptance line above, "awrit.scopesentry namedSystemis a configuration error (78)", is amended: the error is a key naming a builtin this platform does not define, such asProgramFileson Unix. Plan:docs/plans/task/926-scope-flag.mdonfeature/925-scope-not-target.David-Noble-at-work commentedon Oct 2, 2026
Ruled 2026-10-02 (open question 2 of this lane's plan): the
writ.targetsrefusal stops only the lifecycle commandsFound planning this lane: lane 6's refusal of
writ.targets(#925) ran before every writ command, sowrit config listandwrit config unset writ.targetsboth exited 78, and the key could not be removed with writ. Ruled "fix it the way you proposed": the refusal, and this lane's own refusal of awrit.scopeskey naming a builtin this platform does not define, run fordeploy,upgrade,reconcile,decommissionandadoptonly.writ config unset writ.targetsnow removes the key like any other, asgit config --unsetdoes. Fixed onfeature/925-scope-not-targetwithTestRoot_ConfigRunsWithWritTargets; lands with PR C.fix(writ): the writ.targets refusal stops only the lifecycle commands…
feat(writ): scopes are a model, and --scope selects them on the four …
David-Noble-at-work commentedon Oct 2, 2026
Ruled 2026-10-02 (open question 3 of this lane's plan):
--scopesits on the four lifecycle commands, not on writ's rootFound in phase 2: a
--scopeon writ's root collides with the shared root'sworkflow verify --scope, which names the one recorded scope whose definitions and traces it checks. The root's checker refuses a command that redefines a flag it inherits, since cobra would let the local one win silently (TestRoot_KeepsTheOutputConvention). The two flags name one thing: the scope a lifecycle operation ran in.The owner ruled (a): "(a). go on phase 2. as you know, we'll change the word workflow when we get to it on your schedule."
--scopeis a local flag ondeploy,upgrade,reconcileanddecommission, andworkflow verify --scopematches the recorded scope without regard to case. The wordworkflowchanges in lane 30 of #916 (#1017).This supersedes the acceptance line "
--scoperegistered onwrit's root, repeatable": read it as "--scopeon the four lifecycle commands, repeatable". Landed in 8b453dc onfeature/925-scope-not-target(phase 2).David-Noble-at-work commentedon Oct 2, 2026
Ruled 2026-10-02 (open question 4 of this lane's plan): with no
--scope, reconcile, upgrade and decommission read the whole recordFound planning phase 3: read literally, "absent: every scope defined on this platform" would filter the record too. With no
--scope,reconcile,upgradeanddecommissionwould lose the entries of a custom scope since removed fromwrit.scopes, and the unscoped entries of single-source mode.The owner ruled (a): "(a). go on phase 3". With no
--scopethey read the whole record, and--scopeonly narrows it: the record is the reference. Deploy is unaffected; with no--scopeit deploys every scope defined here.This narrows the acceptance line "absent
--scoperuns every scope defined on this platform" todeploy. Phase 3 builds it asreadback.Entry.InScopes, and orders every operation's scopes through one comparator,readback.CompareScopes, in place of two hard-coded maps and an alphabetical sort; it lands with phase 3's commit.David-Noble-at-work commentedon Oct 2, 2026
Ruled 2026-10-02 (open question 5 of this lane's plan): lifetimes are generations; writ follows git and Nix
Found in phase 3: deploy replaces the record (#913) by minting a new lifetime on every invocation, and the record is the current lifetime's runs alone. So
writ deploy --scope Homewould leave a lifetime holding Home's runs only. Every other scope's entries would drop out of the record while their files stayed deployed:reconcilewould stop seeing them,decommissioncould not reach them, and the next deploy's pre-flight would find their links foreign.The owner framed two alternatives, decommission before re-deploying or carry forward what is not re-deployed, and asked how package managers see it. Only GNU Stow, which keeps no record, re-deploys by tearing down; dpkg, rpm, Homebrew and Nix treat a re-install as a delta against their record. The owner: "we follow git and nix." Ruled (
docs/architecture/5.1-reconciliation.md§ Lifetimes are generations):--scopeand project names narrow it into a new lifetime;What this lane builds: every run records its scope.
cli.NextLifetimereplacescli.NewLifetime: a deploy's new lifetime carries forward, by reference, the current lifetime's runs for the scopes it was not asked to deploy. A bare deploy carries none, so it still replaces everything, including the runs of lifetimes written before runs recorded a scope. A scope the deploy ran and failed keeps its previous runs. The new lifetime becomes current on its first save.TestWritDeployScenario_Scopesproves it with a custom scope, Staging, beside Home. It lands with phase 3's commit.The rest goes where the schedule reaches it, with a comment on each: lanes 11 (#928), 12 (#929), 13 (#930), 16 (#846) and 17 (#971). Adopt's part, which needs no deployment and requires a registered repository, is lane 31 (#1018).
docs(lifecycle): lifetimes are generations: writ follows git and Nix
feat(writ): the operations honor --scope, and a scoped deploy keeps t…
David-Noble-at-work commentedon Oct 2, 2026
Ruled 2026-10-02 (open question 6 of this lane's plan): adopt joins the scope model in this lane
Found answering the owner's question on adopt, two gaps between adopt and the scope model:
HomeorSystem, where deploy recordshomeandsystem. With phase 3's filters,reconcile --scope Homemisses adopted entries, and a--scope Homedeploy carries an adopt run forward as if it belonged to another scope;/itself rather than at the model's root. writ adopt carries the platform (--platform <token>) and leaves a deployment record: its graph carries the files annotation and its trace appends to the current lifetime #931's item 4 had handed inference over the platform's scope set to this lane.Offered: (a) both in this lane, as a phase before the pages, with #761 joining PR C; (b) the case fix here and the inference in lane 31 (#1018); (c) both in lane 31. The owner ruled (a).
The plan's Requirement 4 and phase 4: each item's scope is the one, of the scopes this platform defines, whose root is the deepest that holds the item, a tie going to scope order; the item's path is taken relative to that scope's root, from the model; the run records its scope in lower case. Phase 4 closes #761, lane 32 of #916.
fix(fsroot): one CommonAncestor, which keeps a drive's root whole and…
feat(writ): adopt infers each item's scope from the scope model and r…
docs(writ): the pages and the help say what --scope does, and how ado…
docs(plan): lane 7 closes, its VM run recorded and its tests against …
David-Noble-at-work commentedon Oct 3, 2026
2026-10-03:
writ.scopesbecomeswrit.scope. The owner ruled that the name-keyed families take singular keys ("yes, singular for all three": scopes, segments and variables). #1024 renameswrit.scopestowrit.scopeandwrit.segmentstowrit.segment;docs/architecture/10-command-line-interface.mdalready names the singular keys, and its status page records that the code reads the plural ones until #1024 lands.--scopekeeps its meaning: it selects scopes and defines none.