state invariant 7 as a rule the gate can hold
"Every feature is a leaf" was enforced by listing the pairs that happen to exist today: content, render and web may not import ext, plus a separate check for siblings. A list only forbids what is already there — a core package added next month could import a feature and pass, which is how an invariant rots while staying technically true. One positive rule now: only cmd/ may import internal/ext/…. It covers packages that do not exist yet and catches a sibling import in the same breath, since a feature importing another feature is the other way one stops being deletable. Four rules become one. Watched rejecting both kinds before keeping it — a core package importing a feature, and a feature importing its sibling — and watched passing the tree as it stands. Test files are excluded, which is right: the demo's own test wires features on purpose. Queue entry G5. It was the last architecture invariant held only by hand.
This commit is contained in:
@@ -216,6 +216,10 @@ that describe it, in the same change.**
|
||||
changing — except `.claude/settings.json`, which is Claude Code's permission list rather than anything
|
||||
about khosra. That exemption is one path: `scripts/budgets.env` and every other file under `scripts/`
|
||||
and `.claude/` stays gated, and khosra's own settings are not exempt from anything. This file is the current description of the machine, not a snapshot of its design.
|
||||
- **Invariant 7 is mechanical now.** "Every feature is a leaf" means only `cmd/` may import
|
||||
`internal/ext/…`, and `verify.sh` states that as one rule rather than a list of the pairs that happen to
|
||||
exist today (ADR-0069) — so a core package added next month cannot quietly import a feature. A feature
|
||||
importing its sibling fails the same rule.
|
||||
- `verify.sh` fails when a `.go` file changes without `docs/state.md` changing, warns when `state.md`'s last
|
||||
commit is older than the last `.go` one — the two together mean the doc ships inside the change, never in a
|
||||
commit trailing it (ADR-0057) — and fails when `cmd/` or
|
||||
|
||||
@@ -111,7 +111,8 @@ is normal**: search will be an engine-built index and a browser-side query, not
|
||||
5. **Permalinks are permanent.** Renames add aliases and redirects. URLs are not reused.
|
||||
6. **Interactions are off the content graph.** A comment invalidates one fragment, never a build.
|
||||
7. **Every feature is a leaf** — a View, Stage, Query, Interaction adapter, Effect, or
|
||||
bundle-as-program, deletable without trauma. Anything wanting a permanent service or a core-model
|
||||
bundle-as-program, deletable without trauma. Mechanical since ADR-0069: `verify.sh` allows `internal/ext/…`
|
||||
to be imported from `cmd/` and nowhere else, siblings included. Anything wanting a permanent service or a core-model
|
||||
change is a **trunk** and waits for a human decision (`exploration.md`).
|
||||
8. **The engine serves correctly with every external service off.** Redis, an index, object storage
|
||||
— each is a shortcut around work the engine can still do itself, slowly. Anything that cannot
|
||||
|
||||
@@ -1129,3 +1129,22 @@ Consequence: cheap — five attributes, no engine change, and the derivative pas
|
||||
— a theme that redefines these fragments and forgets `sizes` silently returns to the old behaviour, and
|
||||
nothing can check that, since only the theme knows its own measure.
|
||||
Revisit if: the engine ever learns a layout, which it should not.
|
||||
|
||||
## ADR-0069 — Invariant 7 is a rule in the gate, not a list
|
||||
Date: 2026-08-01 · Status: accepted (queue entry G5)
|
||||
Decision: `verify.sh` enforces "only `cmd/` may import `internal/ext/…`" as one positive rule, replacing the
|
||||
three enumerated pairs — `content → ext`, `render → ext`, `web → ext` — and the separate sibling check. Test
|
||||
files are excluded, because `go list .Imports` omits them and the demo's own test exercises features on
|
||||
purpose.
|
||||
Why: the enumeration only forbade what already existed. A core package added tomorrow could import a feature
|
||||
and pass, which is exactly how an invariant rots — the list stays true and stops being the rule. Stated
|
||||
positively it also catches a sibling import in the same breath, since a feature importing another feature is
|
||||
the other way one stops being deletable (ADR-0027). "Every feature is a leaf" is the property the whole
|
||||
`internal/ext` split exists to protect, and it was the last architecture invariant held only by hand.
|
||||
Consequence: cheap — one rule where there were four, and it covers packages that do not exist yet. Watched
|
||||
rejecting both kinds: a core package importing a feature, and a feature importing its sibling. Expensive —
|
||||
`cmd/` is now the only place a feature may be named, so a future test that wants to wire features must
|
||||
either live in `cmd/` or duplicate the list, which is the latent item already recorded against
|
||||
`example_test.go`.
|
||||
Revisit if: features need to be composed somewhere a test can import, which is that latent item and not this
|
||||
gate.
|
||||
|
||||
+10
-6
@@ -333,14 +333,18 @@ layer() { # $1 = importing layer, $2..$n = layers it may not import
|
||||
done
|
||||
}
|
||||
violations=$(
|
||||
layer content render web ext
|
||||
layer render web ext
|
||||
layer web ext
|
||||
layer content render web
|
||||
layer render web
|
||||
echo "$pairs" | awk -v c="$mod/cmd" 'index($2,c)==1 && index($1,c)!=1 {print $1" → "$2}'
|
||||
)
|
||||
# Sibling imports between features are what make an agent's read set compound (ADR-0027).
|
||||
siblings=$(echo "$pairs" | awk -v e="$mod/internal/ext/" '
|
||||
index($1,e)==1 && index($2,e)==1 && $1!=$2 {print $1" → "$2}')
|
||||
# Invariant 7, as a rule rather than a list: a feature is a leaf, so `cmd/` is the *only* thing that may
|
||||
# import one. Stated positively, this also covers a core package that does not exist yet — the enumeration
|
||||
# above cannot, and the day someone adds `internal/foo` it would import `internal/ext` unnoticed. Sibling
|
||||
# imports between features fail here too: they are what make an agent's read set compound (ADR-0027), and
|
||||
# they are the other way a feature stops being deletable. Test files are excluded by `go list .Imports`,
|
||||
# which is right — the demo's own test exercises features on purpose (ADR-0069).
|
||||
siblings=$(echo "$pairs" | awk -v e="$mod/internal/ext/" -v c="$mod/cmd" '
|
||||
index($2,e)==1 && index($1,c)!=1 {print $1" → "$2}')
|
||||
if [ -n "$violations$siblings" ]; then
|
||||
bad "import boundary violated (conventions.md layering): $(echo "$violations$siblings" | tr '\n' ' ')"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user