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:
Claude Opus 5
2026-08-01 23:21:46 +06:00
committed by bdeshi
parent ccce537ae4
commit 8a4718d775
4 changed files with 35 additions and 7 deletions
+10 -6
View File
@@ -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