diff --git a/README.md b/README.md index 0cfdd5a..494efb2 100644 --- a/README.md +++ b/README.md @@ -2,295 +2,84 @@ writ -**Designing system architectures by exhaustion — a systems-engineering layer -over [writ](https://github.com/writ-lang/writ).** +**Design system architectures by exhaustion, with +[writ](https://github.com/writ-lang/writ).** -An architecture brief is normally answered by judgement: an architect reads a -few paragraphs, decides what they must have meant, and draws a diagram. The -diagram is then the only record, it agrees with nothing, and the questions the -brief failed to answer are discovered during implementation. +An architecture brief is usually answered by judgement: someone reads it, +decides what it must have meant, and draws a diagram. writ-arch does it +mechanically. A catalogue of candidate components and the brief's requirements +become a writ model; `writ check` enumerates **every** architecture that fits; +and five small tools turn the result into what a team needs. -This repository does the same job mechanically. A **component bank** and the -brief's requirements become a Writ model; `writ check` enumerates *every* -architecture the constraints permit; and four tools turn the result into the -artifacts a team actually needs — a C4 diagram, a list of questions for whoever -wrote the brief, decision records that cannot drift from the design, and a short -list of the unverified facts the whole thing is standing on. +
-Nothing here is a new language. **Every semantic lives in Writ**, and these are -generators and formatters over `writ derive` output. That is deliberate: a second -language with its own meaning would put a compiler between you and the proof. - -## The workflow +## The loop ``` - a brief, in prose - │ - ▼ - catalogue/*.md ──writ-bank──▶ a .writ model - │ │ - │ writ check - │ │ - │ ┌──────────┬───────────┼──────────┐ - │ ▼ ▼ ▼ ▼ - │ writ-assume writ-interview writ-c4 writ-adr - │ │ │ │ │ - │ the facts questions C4 diagrams decision - │ to confirm for the (D2/SVG) records - │ │ author with proofs - │ │ │ - └───────┴──────────┘ answers sharpen the catalogue, and it runs again + catalogue/*.md ──writ-bank──▶ model.writ ──writ check──▶ every valid design + │ + ┌───────────────┬───────────────┬───────────────────────┤ + writ-assume writ-interview writ-c4 writ-adr + facts to questions for C4 diagrams decision records, + confirm the brief's author (D2 / SVG) with proofs + └───────────────┴──── answers sharpen the catalogue, and it runs again ``` -The loop is the point. `writ-interview` is not a report at the end — it is the -step that sends you back to the brief with things nobody had noticed. - -## The five tools - -| | reads | writes | +| Tool | Turns | Into | |---|---|---| -| **`writ-bank`** | a catalogue of parts, as a Markdown page | the model's datums — rosters, spans, one move per part | -| **`writ-assume`** | the catalogue's unconfirmed cells | the few whose truth actually decides the answer | -| **`writ-interview`** | `writ check` findings | the questions to put back to whoever wrote the brief | -| **`writ-c4`** | `writ derive` rows | a C4 diagram in D2, at context or container level | -| **`writ-adr`** | `writ derive` rows and proof trees | decision records, each with what was rejected and why | - -### `writ-bank` — because a form cannot map over a roster - -A Writ form is rename-and-paste: no recursion, no computation, and it cannot map -over its `&rest`. So it cannot turn *"component X provides capability Y"* into -the named span junction that fact requires, nor emit one `pick` move per part. -None of that is a defect in Writ — it is what keeps a model a finite -presentation. It is a defect in what a **human** should be asked to type: a bank -of 150 parts is roughly 4,000 datums, ~750 of them globally unique junction -names invented by hand. - -A page of tables becomes the whole instance: - -```console -$ writ-bank catalogue/corpus.md example/corpus.extras.writ > example/corpus.writ -``` - -**Why Markdown.** A catalogue is a curated knowledge base people argue about, -not a pipeline feed. It wants to render in a pull request, so a reviewer can -challenge "is `llm-classifier` really not reproducible?" without running -anything — and it wants prose. Tables carry the facts; every heading, paragraph -and `` on the page is ignored, and a `notes` column -documents a row without entering the model. - -**Columns are matched by name, not position.** Reorder them freely; add columns -the tools do not know about. A required column that goes missing is a fatal -error naming the column and the section, not a quietly wrong model — and -`run-tests.sh` reverses every column of the components table and asserts the -output is unchanged. - -**Identifier columns must stay single tokens.** Values in `name`, `provides`, -`tech`, `within` and friends become *entity names* in the model, and Writ names -cannot contain spaces — so `apache-spark`, never `Apache Spark`. Slugifying and -keeping display labels outside the model would break the one property `writ-c4` -sells: that everything on the diagram is derived. Free text belongs in `notes`. - -`run-tests.sh` asserts the committed model is **byte-identical** to what the -catalogue regenerates, so hand-editing generated output fails a test rather than -being discovered much later. +| `writ-bank` | a Markdown catalogue of parts | the writ model | +| `writ-assume` | the catalogue's unconfirmed (`?`) cells | the few that actually change an answer | +| `writ-interview` | `writ check` findings | questions for whoever wrote the brief | +| `writ-c4` | one chosen design | a C4 diagram where every box is derived from the model | +| `writ-adr` | the derivations | decision records: what was chosen, what was rejected, on which requirement | -### `writ-assume` — the answer to "who could possibly verify this catalogue?" +All the meaning lives in writ; these are generators and formatters over its +output, in POSIX `sh` and `awk`. -This is the weakest joint in the whole idea, and it deserves stating plainly. -Everywhere else in the stack a mistake announces itself: a bad guard changes the -state count, a bad rule fails the cross-check, a bad claim fails to hold. **A -wrong catalogue cell fails nothing.** It produces a plausible model that proves -wrong things with exactly the confidence it would have had if the cell were -right. The catalogue is unfalsifiable input to a falsifying machine. +## What it finds -Asking anyone to verify every cell does not work — a real bank is hundreds of -claims and nobody will check them. But most of those claims *do not matter*: -flip them and every verdict is identical. So mark unconfirmed cells with a -trailing `?`, and let the tool find the few that decide anything: - -```console -$ writ-assume catalogue/corpus.md example/corpus.claims example/corpus.extras.writ -ASSUMPTIONS THIS DESIGN STANDS ON - - 33 unconfirmed claims in the catalogue. - 4 of them decide something. 29 do not. - -LOAD-BEARING — confirm these before anyone builds from this design - - 1. object-store.bulk = yes - were it `no` instead: - realisable LOST - no-dead-end LOST - 3. llm-vision.persists = no - were it `yes` instead: - rerun-is-affordable gained - 4. direct-db.couples = tight - were it `loose` instead: - crm-stays-loose LOST -``` - -It flips each assumed cell, rebuilds, and asks `writ compare` whether any -property moved. **"Verify 33 claims" becomes "confirm these four"** — and the -four are computed, not guessed. Presentation columns are excluded by -construction rather than by choice: nothing in the library reads them, so no -flip of one could move a verdict. - -Read it as an indictment of the worked example, too. This repository's headline -finding rests on two cells about `llm-vision` that were invented in seconds, and -the tool says so. - -> **The bug worth knowing about.** The first version of this reported *"0 of -> them decide something"* — because every comparison had errored, and an error -> read as "no change". A tool that reports safety because it crashed is worse -> than no tool. A broken run is now fatal and loud, and `run-tests.sh` asserts -> it. - -### `writ-interview` — the step that pays for the exercise +On the worked example — 50 TB of PDFs to classify, feed to AI and surface in a +CRM — 96 of 2,916 component combinations satisfy the brief. More useful are +the two questions nobody had asked: ```console $ writ-interview example/corpus.writ --claims example/corpus.claims -THE MODEL ANSWERED 3 of its questions. It raises 2 for you. - - 184 situations reachable, 96 dead ends. - OPEN QUESTIONS — the brief is silent, and the model refuses to guess - 1. the brief is silent: are the 50TB of PDFs digital-native or scanned? - the extract choice depends on it - declared at: gap `pdf-kind-unknown`, reachable in 2 moves CONTRADICTIONS — a stated requirement the brief does not actually get - 2. re-runnable classification implies persisted text - property: rerun-is-affordable - counterexample: 1. hold-object-store -> 2. enumerate-event-stream - -> 3. extract-llm-vision -> 4. classify-rules-engine - to decide: is the counterexample acceptable (relax the - requirement), or is it a real defect (add a constraint)? + counterexample: hold-object-store -> enumerate-event-stream + -> extract-llm-vision -> classify-rules-engine ``` -Neither question was thought of by anybody. The first is a declared `gap` — the -model saying its rules genuinely run out, rather than guessing past a hole. The -second is a `never` property with a witness: a stack meeting every **stated** -requirement whose extract stage discards its output, so "re-run the -classification" silently means re-processing the whole corpus. **48 of the 96 -designs have it** — half the answer set looks correct and is not. - -It works on any Writ model, not only architecture ones: run it on the -knights-and-knaves puzzle and the paradox comes out as an open question. - -### `writ-c4` — diagrams that cannot lie - -```console -$ writ-c4 example/corpus.writ example/corpus.rules 183 --level container --svg out.svg -``` - -Every box, group, arrow and label is a row out of `writ derive`. **If the diagram -is wrong, the model is wrong** — which is the one property a hand-drawn -architecture diagram can never have. Boundaries become D2 containers, external -systems are drawn dashed, technology rides in the box label, actors are people, -and edges carry their protocol. - -**The edges are runtime data flow, not the build ladder.** `cap.next` is the -order a *designer* fills stages in; the `flow` span is the order *data* moves. -In the worked example the catalogue fans out to both serving and the CRM, while -the ladder is a chain — so drawing the ladder would have produced a diagram that -was merely plausible. The two are never the same thing. - -### `writ-adr` — records that re-derive - -Each decision gets its technology and boundary, the alternatives that were -rejected, **the requirement each one failed**, and the derivation tree as proof: - -| rejected | because | -|---|---| -| `nas` | cannot survive the data volume (`needs-bulk`) | -| `pdf-lib` | cannot read scanned pages (`needs-scan`) | -| `llm-classifier` | not reproducible across runs (`needs-rerun`) | -| `direct-db` | couples too tightly (`needs-loose`) | - -`writ derive --why` alone will not do this. The proof tree for a chosen part -shows only that it *was* chosen; what was rejected and on which requirement is a -different derivation. +Half of the 96 designs meet every *stated* requirement yet discard extracted +text, so "re-run the classification" would mean reprocessing the whole corpus. -## `lib/arch.lib.writ` — and one distinction that matters +And `writ-assume` turns "verify 33 catalogue claims" into "confirm these four": +it flips each unconfirmed cell and reports only those that change a verdict. -The vocabulary splits its fields in two, and confusing them is how a diagramming -need silently becomes a design constraint: +## Run it -- **Constraint fields** (`bulk`, `scans`, `rerunnable`, `persists`, `couples`) - are read by `fits`. They decide which parts may fill which stage, so they - **shrink** the search space. -- **Presentation fields** (`level`, `tech`, `external`, `within`) are read by - `writ-c4` and by nothing in the library. Every one is `fixed`, so they add - **no situations at all** — a design space is the product of the *mutable* - cells. The worked example scores the same 184 situations with them as without. - -Add a presentation field freely. Add a constraint field only when you mean to -rule something out. - -## Running it - -Needs [`writ`](https://github.com/writ-lang/writ) on `PATH`; `d2` only for SVG. +Needs `writ` on `PATH` (and `d2` for SVG): ```sh -./demo.sh # the whole pipeline end to end, on the worked example -./install.sh # -> ~/.local (bin on PATH, lib on WRIT_LIB) -./run-tests.sh # 74 checks over the five tools, the demo included -./run-tests.sh list # the individual tests +./demo.sh # the whole pipeline on the worked example +./run-tests.sh # the tools' tests +./install.sh # -> ~/.local ``` -`demo.sh` is the fastest way to see what this does: it runs a brief through all -six stages and prints each one — catalogue, model, enumeration, questions, -assumptions, diagram, decision records. `run-tests.sh` runs it too, so the -walkthrough cannot rot while the tools move underneath it. - -The tools are POSIX `sh` and `awk` — no runtime, no packages, nothing to -install beyond `writ` itself. - -## What this does not claim - -**The catalogue is the project, and it is the weakest link.** The component -attributes in `catalogue/corpus.md` were written by hand as a demonstration — -every one is marked `?`, because every one is unverified. `writ` proves what -follows from them exhaustively, and will do so just as faithfully if they are -wrong. The rigour on offer is *internal consistency*, not truth. +## Limits -`writ-assume` narrows that from fatal to manageable — it turns "verify -everything" into "confirm these four" — but it cannot tell you whether the four -are *true*. Only somebody who operates the system can, and no amount of tooling -substitutes for that. - -**A catalogue is per-decision, not an encyclopedia.** The tempting reading of -`writ-bank` is that you should build a comprehensive bank of 150 components. Do -not: nobody can author or check that. The version that works is the eight to -twelve candidates a team would genuinely consider for *one* decision, written by -the people who will operate them. At that size every row is checkable, which is -the only size at which any of this is honest. - -**Quantities remain out of scope.** Writ has no arithmetic. Ordinal scales and -precomputed `fixed` arrows carry a long way (a latency budget composes fine over -a finite quantale), but capacity planning, cost in currency and availability -arithmetic belong in a solver built for them, with the answers imported here as -facts. - -**`writ-c4` draws one design at a time.** The model enumerates 96; choosing among -them is a judgement the tool deliberately does not make. - -## Related - -| | | -|---|---| -| **[writ](https://github.com/writ-lang/writ)** | the language, the engine, the CLI | -| **[writ-problems](https://github.com/writ-lang/writ-problems)** | worked models, including `arch/` — the same search without the C4 vocabulary, kept as the teaching version | -| **[writ-vscode](https://github.com/writ-lang/writ-vscode)** | the editor client | +The catalogue is the weakest link: writ proves what follows from it, right or +wrong. Keep it small — the eight to twelve candidates a team would really +consider for one decision, written by the people who will run them — and use +`writ-assume` to find the cells worth checking. writ has no arithmetic, so +capacity and cost belong in a dedicated solver, with results imported as facts. ## License -Copyright (C) 2026 Alex Kunich. **GNU Affero General Public License, version 3 -or later** ([LICENSE](LICENSE)), matching `writ` itself. A catalogue or model you -write is your own work, not a derivative of these tools — granted explicitly in -[`LICENSE.exception`](LICENSE.exception), which also covers the models, diagrams -and decision records the five tools generate. - -Patches are welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md). +Copyright (C) 2026 Alex Kunich. [AGPL-3.0-or-later](LICENSE), like writ. Your +catalogues and models, and everything the tools generate from them, are yours: +see [LICENSE.exception](LICENSE.exception). Patches welcome — +[CONTRIBUTING.md](CONTRIBUTING.md).