ADR-0023: Authoring commands act on the sources, and only where a rule is decidable

StateAccepted
Architectural SignificanceMEDIUM
DomainDeveloper Tooling
Document version1.0
RelatedADR-0004 · ADR-0021 · ADR-0022

Reference

Affected code: crates/rto-spec/src/layer.rs#authored_docs, crates/rto-spec/src/adr.rs#declares_adr, crates/rto-spec/src/check.rs#run_layer, crates/rto-render/src/okf/conform.rs#lint_report.

adr_home, which answers where a new document belongs, is in flight as #747 and is deliberately not linked yet: roteiro check refuses a link to a symbol the graph does not hold, which is the rule working rather than an inconvenience.

Summary

Roteiro can read an authored document — classify it, parse it, check its links, gate its version rules — and can generate one from a scaffold. Between those two it can do nothing: every edit to an ADR or blueprint is done by hand, and every rule the gate enforces is a rule a person has to satisfy unaided.

This records the decision to add authoring commands, and — more importantly — where the line falls. They act on the authored sources under version control, never on the OKF bundle, which is a build output that the next render okf overwrites. And each command is admitted only where its rule is decidable without judgement: a command that guesses at prose would put Roteiro's name on a change it cannot defend.

The set is new, fmt, fix, mv, rm, split, merge and index, mirroring W4G1/okf's CLI so the vocabulary is one a reader already has. Three of them are admitted narrowly, and the narrowing is the decision.

Context

ADR-0004 made authoring a pillar and delivered its first tier: roteiro spec scaffold emits a house-style, graph-grounded skeleton. ADR-0021 made the graph's shareable form an OKF bundle, and ADRs and blueprints are concepts in it. #744 made an ADR something a document declares rather than somewhere it sits, and #747 made writing one follow the repository's own layout.

So the reading half is complete and portable. The writing half is a scaffold and nothing else.

Three forces make that gap worth closing now:

Upstream's set is the wrong shape applied to the bundle, and the right shape applied to the sources. okf new/mv/rm/split/merge/fmt/fix/index mutate a bundle. Ours is derived, so mutating it is writing to a cache: correct for a tool whose bundle is the source of truth, pointless for one whose bundle is a projection. Pointed at docs/ the same verbs are exactly what is missing.

Decision makers

Add roteiro docs <command> over the authored sources, with each command admitted only as far as its rule is decidable.

Options considered + consequences

Option A — mirror upstream's set over the bundle

Rejected. The bundle is regenerated by render okf, so every edit is lost on the next render. It would also break the one-way guarantee ADR-0021 rests on: the graph is the source and the bundle is its projection.

Option B — the same verbs over the authored sources, unrestricted

Rejected, and this is the one worth arguing. split and merge over prose are not decidable: a tool cannot know which paragraphs belong to which decision, and one that guesses produces a document a person must then verify line by line — more work than doing it by hand, with a machine's name on the result. fix applied to every lint has the same defect: some of L1–L12 are remedied by writing better prose, not by editing punctuation.

Each command is added, and its scope is stated where the rule stops:

commandwhat it doesadmitted because
newscaffold a document into the repository's own ADR homealready exists as spec scaffold; this is its name in the family, and it writes rather than printing
fmtsingle-space table cells, ISO date normalisationpurely syntactic; the document's meaning cannot change. Frontmatter key order was in this list and was removed — see v1.0
fixapply the lint remedies that are mechanical, and list the restthe mechanical set is decidable; the remainder is reported, never guessed
mvmove or rename, and repair every inbound [[…]]the link graph is already computed, so the repair is derived, not inferred
rmdelete, and report every inbound link that would breakdeletion is decidable; whether to accept the breakage is the author's call
indexregenerate the ADR index tablederived from the documents themselves
splitcarve at explicit markers onlya person marks the seam; the tool does the mechanical part
mergeconcatenate, preserving both histories, and leave a conflict listjoining is mechanical; reconciling two decision records is not

split and merge are the narrowed pair. They are admitted because the mechanical half — creating files, allocating ids, moving sections, rewriting links — is real work worth automating, and refused the judgement half: split acts on a marker a person placed, and merge produces a document that says, in the text, what a human still has to reconcile.

Every command is a no-op without --write. The default is a diff on stdout, because a tool that rewrites documents under version control by default is one people run once.

Consequences

Implementation

Phased, so each lands reviewable:

  1. fmt — the canonical form, and the test that it is idempotent.
  2. fix — over the mechanical lint subset, reporting the remainder.
  3. mv/rm — link repair and breakage reporting, over the existing link graph.
  4. index — the ADR table, and the gate that it matches the documents.
  5. new — spec scaffold given its family name and a --write path.
  6. split/merge — the narrowed pair, last, because they are the ones whose scope most needs the earlier commands to exist first.

Advice Received

The prompt to add these came from the observation that Roteiro is meant to run on other people's repositories, and that everything it offered them was read-only. The narrowing of split and merge is a departure from upstream's set, taken because a tool that guesses at prose and signs the result is worse than one that declines.

Document version history

VersionDateNotes
0.12026-09-02Draft scaffold. Records the decision to add roteiro docs over the authored sources rather than the bundle, and the rule that admits each command only as far as it is decidable. split and merge are narrowed to their mechanical half.
1.02026-09-11Step 1 implemented: roteiro docs fmt. rto_spec::fmt::canonical is the form and roteiro docs fmt [PATHS] is the surface; without --write it prints a unified diff and exits non-zero, so it is usable as a check the way cargo fmt --check is. Measurement moved two of the three stated jobs and narrowed the third. Measured over this repository's 26 ADRs before anything was written: the frontmatter key order is already one order in 26 of 26, and last-modified is already ISO in 26 of 26 — so those survive as idempotence guarantees rather than cleanups, holding a line rather than moving one. "Table alignment" had to be narrowed, because the obvious reading of it is destructive here: an ADR history table carries prose paragraphs in single cells — 59 of them over 1,000 characters, the widest 5,789 — so padding each cell to its column's widest would blow every sibling row out to match. The canonical form is therefore one space either side of every cell, which is the ordinary markdown convention and a form in which a long cell pads nothing. Three rules the draft did not anticipate, each of which a naive implementation gets wrong. (a) A pipe inside inline code is not a column boundary. Found by running fmt over the real docs/ rather than a fixture: ADR-0006 documents the chat-template marker `` `<