Repository navigation
The CLI specification describes the code that exists - #767
Merged
Merged
Conversation
4.4 DESCRIBED A MODE SWITCH THAT IS GONE. RootReader, RootReaderWriter,
confinedRoot, rootBase, and OpenWritableUnsandboxed have zero occurrences in
the tree; fsroot exports OpenExisting and OpenScratch, and both sandbox.
Deleting section 4 was the ruling, and the section was not the whole of it --
the removed types also appeared in section 1's diagram, section 2's prose,
section 3's data flow, and section 6's first design property. Deleting one
would have stranded four dangling references.
section 1's diagram three boxes become two: sandboxedDir and
sandboxedScratchDir, differing in LIFETIME rather
than in access
section 2's Roles two implementations, and no access mode to select
section 3's flow "(mode-dependent)" becomes "(always; the kernel
bounds)"
section 4 deleted; 5 and 6 renumber
section 5's first the caller chooses a lifetime, not an access level
design property
The op.Path box had its two accessors backwards. fsroot.go:627 says Rel is
"the root-relative path used for sandboxed I/O ... the half that
serializes", and that "direct filesystem I/O flows through Path.Abs, which
stays OS-native". The diagram called Abs "unconfined I/O" -- naming a
confinement that no longer varies, and missing what the accessor is for.
SCOPE IS SPENT TWICE IN 10-command-line-interface.md, so section 1 is
retitled "What this governs". A scope is now a named execution context, and
a document that uses the word for its own subject matter as well teaches the
reader to read past it.
--scope stays OUT of section 4's reserved table, with a paragraph saying
why: that table is the SHARED set, and only writ binds --scope. A name
reserved for one program is a different promise from one every program
keeps. Without the paragraph, a reader comparing the table against
`writ --help` finds a flag it omits and cannot tell whether that is
deliberate.
Section 15's references to `writ status` become `writ reconcile` where they
describe the command. The one at line 102 stays: it recounts the phase-8
rename, and rewriting history to match the present is how a document stops
explaining itself.
Two pre-existing box-width irregularities in 4.4's op.Path diagram are left
as found -- two lines run one column wider than their neighbours, and they
are not this change's to fix.
This was referenced Sep 1, 2026
Closed
David-Noble-at-work
added a commit
that referenced
this pull request
Sep 2, 2026
… the sink (#778) * Plan thread 1's three remaining items before any of them starts Three of the four items left on #740 had no issue and no plan. Now #774, #775 and #776 each have both, cross-referenced in both directions -- each issue links its plan, each plan names its issue, and every plan links the other two and the epic's. The plans land together on #774's branch, following the precedent #768 set: plans for work that is not yet started ride with the first piece that is. This pull request will close #774 alone when its work is done; #775 and #776 close when theirs is. #774 -- writ reconcile. The rename and the 30 stdout call sites are ONE piece of work, not a rename followed by a refactor: the renderings being rewritten live in the package being renamed. This is also #762's phase 2, and both plans say so. The surface is the one #772 settled -- a report, no command flags, the globals used faithfully. #775 -- lore. The search table moves onto the shared TableFormatter, three hand-rolled flag sets go, and thirteen fmt.Print calls are triaged. #741 is kept separate so a user-visible defect is not buried in a refactor, and closes under #775 only if a multi-byte rune test -- written red first -- proves the fix. #776 -- enforcement. Three tests, sequenced LAST because they are red until the other items land and they certify those items rather than precede them. Each of #774, #775 and #743 turns exactly one test green, which makes each of those pull requests' "done" observable rather than asserted. The third test is the one that would have caught #753 and #754: a root that registers the flags and leaves that ignore them looks like compliance to the other two. 740's table of remaining items now names the issue and plan for each, and the paragraph that said three needed filing says they are filed. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW * Rename writ status to writ reconcile Phase 1 of #774, and phase 2 of #762: the two plans record that these are one piece of work. THE PACKAGE MOVES WITH ITS HISTORY. git mv cmd/writ/writ/status -> cmd/writ/writ/reconcile, then status.go -> reconcile.go and the integration test to match, so `git log --follow` crosses the rename. report.go keeps its name. The package clause, the doc comments, and the surface follow: Config, Execute, BuildReport; StatusConfig -> ReconcileConfig; parseStatusConfig -> parseReconcileConfig, whose doc comment already said parseReconcileConfig -- someone started this rename before and got one line in; newStatusCmd -> newReconcileCmd; runStatus -> runReconcile. THE COMMAND IS `writ reconcile`. Use, Long, and Example say so; "Status indicators" becomes "Entry states"; "Status is report-only" becomes "Reconcile produces a report", which is the surface #772 settled -- a report, no command flags, the globals used faithfully. No alias: `writ status` stops existing. The package doc records why the name went away and came back: step 47 named it status because "reconcile" promised a mutation the command did not perform; #762 returned the name once repair was chartered. THE SCENARIO TEST invokes the new name and asserts under it. Six Go comments outside the package that described defects in `writ status` now say `writ reconcile`; the one that says the command "shipped for months" answering -o yaml with a dashboard says it shipped as `writ status` then, because otherwise the sentence is false. THE DOCUMENTS. Seven architecture and guide documents and nine plans changed outright -- the two user-facing guides among them also lose a `--json` that no longer exists, in favor of `-o json`. Two documents are the record of the original step-47 rename, and "reconcile replaces reconcile" is nonsense, so those carry a note after their H1 and keep their text. The remaining `writ status` in the tree is in history notes and in the plans that name the retired command as the thing retired. FOUND ON THE WAY: three of #762's phase-1 boxes were completed by #767 and never ticked. Ticked now and recorded as stale rather than quietly corrected, per the process. The 30 stdout call sites are NOT touched here. That is phase 2, and it is the reason this is one piece of work rather than two: the 22 in report.go are rewritten next, in the package that now has its final name. make build, make vet, make test, gofmt -l, and Test-GuideFrontmatter.sh are all green. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW * The reconcile report is the result, and the pipeline renders it Phase 2 of #774. The 22 fmt.Print* calls in reconcile/report.go are deleted, not routed: the human rendering is `-o table` or `-o list` applied to the report's JSON, per the two-stage model, so there is no text renderer to route and no format decision left in the package. WHAT GOES. presentJSON, presentText, presentEntries, and State.String() -- the glyph the text report drew, which no output now carries. reconcile.Execute goes with them; BuildReport is the entry, and it returns the Report to the command. Config.JSON goes, and with it the one-bool bridge at config.go:160 that read outputOptions.Format == "json" so that a boolean could stand in for a format -- the thing #740 retired --json to stop. WHAT REMAINS. runReconcile builds the report and hands it to emitResult, exactly as verify does. Every --output value, --jq and --filter now apply to it the way they apply to any other command's result, because the report reaches BuildPipeline instead of bypassing it. The help text's "Entry states" lists the labels JSON emits -- linked, copied, missing -- rather than glyphs, and says that --jq '.entries' selects the delta alone. THE TEST, AT TWO LEVELS. TestReport_EveryFormatRenders pins the Report's shape under the shared pipeline: State marshals as its label, the four sections survive stage-1 normalization, none of the eight formats is silently rejected. TestWritDeployScenario_Deploy gains a loop over the seven non-JSON formats against the real binary and fails on the human dashboard or on silence -- and that is the assertion this phase exists to turn green, because the defect was in the command's wiring, not in the pipeline. make test skips the scenario suite; make test-scenario ran it, and it passed. On "write the failing test first": the scenario assertion is red on the previous tree by construction -- presentText's first statement was fmt.Println("Layers:"), and the assertion fails on that string. It was written and the wiring changed in one pass, so red was argued rather than observed. The plan says so, at the right size. Eight of the thirty stdout sites remain: four SerializeGraphs dry-run dumps and two in migrate, which are phase 3, and two in verify already gone. Also riding here, unrelated to writ and logged where it was asked: a question for the configuration design (#441), recorded in configuration.md's Open questions and in thread 4's plan -- should a config system be instantiated from the flags cobra has parsed and viper has bound, rather than binding its own? Source 5 of resolution says "the parsed pflag set, read once" without saying whose. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW * Under --dry-run the plan is the result, and migrate joins the convention Phase 3 of #774: the last eight of writ's thirty stdout sites. THE FOUR DRY-RUN DUMPS. deploy, decommission, upgrade and secret each wrote their graphs to os.Stdout with SerializeGraphs and returned. Each Execute now returns ([]*op.Graph, error) -- the plan under --dry-run, nil otherwise -- following verify.Execute, and the command hands a non-nil plan to emitResult. Every --output value, --jq and --filter apply to a dry run the way they apply to any other result, and `-o yaml` on a dry run is a parseable plan rather than a dump. No new helper was needed. Graph has its own MarshalJSON and MarshalYAML, so the Go value's document form is SerializeGraphs's -- as a JSON array or YAML sequence rather than a multi-document stream, which is the one visible difference and a better one: `--jq '.[0]'` works. 28 test call sites now discard the plan they never asserted on. Two dry-run tests assert it comes back, and the scenario suite runs `deploy --dry-run -o json` against the real binary and requires a non-empty JSON array -- red before this, when -o json yielded YAML. MIGRATE. Its own --format retires for -o. FormatMigrationPlan and the eleven text helpers under it are deleted; NewMigrationView builds the value the pipeline renders, and the human rendering is -o table or -o list on that JSON. Options.Format was read by nothing and goes with it. The session's one os.Stdout write was its result -- the analysis and graph at completion -- and is emitted through an Options.Emit hook the command supplies, since the session has no writer of its own. THE DOCUMENTS. 10-command-line-interface.status.md ticks writ's renderings box: all 30 sites, with the phase that took each. The conformance table in 10-command-line-interface.md moves writ to "none". FOUND ON THE WAY, logged where each belongs. manage-environments.md teaches `writ inspect --format`, a command that does not exist (#777, not this work). Two lore guides teach --format, recorded in #775's file list and resolved there. make build, make vet, make test (106 ok, 0 FAIL) and make test-scenario are green; gofmt -l is empty. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW * Prove each global by its effect, and close the plan Phase 4 of #774, the last. Three more assertions in the scenario suite, against the real binary, so that every global writ registers is shown to be consumed rather than merely present -- a flag on a root that no leaf reads looks like compliance, which is how #753 and #754 shipped. --jq '.entries' returns the delta as a JSON array. --filter with a malformed expression is refused, and the refusal names the expression: were the flag ignored, the command would print the report and exit 0, a typo honored as a request for the default. --store pointed at a store that was never written is refused for its missing run index rather than reporting on the default store as though it had complied. With -o (phase 2, eight formats) and --dry-run (phase 3, the plan as a JSON array), that is all five. AND THE RENAME IS PINNED. TestReconcile_StatusIsGone executes the root with `status` and requires "unknown command", then executes `reconcile --help` so the test cannot pass by the root being broken. Until now the old name's absence was verified by grep; the plan's own test row 1 asked for a test. THE ACCEPTANCE CRITERIA are written into the plan as a checklist -- three goals, seven test rows, the gates -- and the pull request carries a copy. Every box is checked before merge. One cannot be checked from this branch: row 7, the scenario suite on all five platforms, is four-fifths CI, and it is checked on the pull request when every scenario leg is green. docs/cli regenerates cleanly: reconcile.md present, status.md gone, and no --format on migrate.md. It is gitignored here and published from develop by docs-publish.yaml, so there is nothing to commit for it. The plan is complete. 740's table marks this item landed; #775 is next. Claude-Session: https://claude.ai/code/session_01SqQVFZz7AdiLZkGoAcUqRW --------- Co-authored-by: David Noble <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #766.
4.4described a mode switch that no longer existsRootReader,RootReaderWriter,confinedRoot,rootBase, andOpenWritableUnsandboxedhave zerooccurrences in the tree.
fsrootexportsOpenExistingandOpenScratch, and both sandbox.Deleting §4 was the ruling; the section was not the whole of it. The removed types also appeared in §1's
diagram, §2's prose, §3's data flow, and §6's first design property — so deleting §4 alone would have
stranded four dangling references.
sandboxedDirandsandboxedScratchDir, differing in lifetimeThe
op.Pathbox had its accessors backwards.fsroot.go:627:Relis "the root-relative path usedfor sandboxed I/O … the half that serializes", and "direct filesystem I/O flows through
Path.Abs, whichstays OS-native." The diagram called
Abs"unconfined I/O" — a confinement that no longer varies, and notwhat the accessor is for.
"Scope" was spent twice
§1 was "Scope and principles", and a scope is now a named execution context. Retitled "What this
governs", with a note saying why.
--scopestays out of §4's reserved tableThat table is the shared set, and only
writbinds--scope. A name reserved for one program is adifferent promise from one every program keeps. The paragraph saying so is the point — without it, a reader
comparing the table against
writ --helpfinds a flag it omits and cannot tell whether that is deliberate.writ status→writ reconcileWhere the text describes the command. The reference at §3.1 stays: it recounts the phase-8 rename, and
rewriting history to match the present is how a document stops explaining itself.
Rescoped
Regenerating
docs/cli/**moved to #762. It is a consequence of renaming the command, so it belongs with therename in that plan's Phase 2 — leaving it here made #766 unclosable without work it does not own.