A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live
test/cases/121_liveness_doctrine.sh
An object of kind test
What it is
- URI
majordomus://test/test/cases/121_liveness_doctrine.sh- Kind
test- Identity
test/cases/121_liveness_doctrine.sh- Path
test/cases/121_liveness_doctrine.sh- Provenance
- declared
- Media type
text/plain- As a capability
- test.test/cases/121_liveness_doctrine.sh
Front matter
{}Content
# The four liveness rules, checked as documents against the checkout rather than a fixture.
#
# ADR 0039 landed them as four separate rule objects, all advisory and all documented-only,
# because no scan existed yet. Three of them are no longer documented-only:
# `scripts/liveness-check` ships in the `structure` job and reports one shape for each, and
# this case now holds the converse of that relation. The gate named the three rules it
# enforces and none of the three named the gate or the cases back, which is the one-way
# reference this repository keeps rediscovering — a scan can be renamed, narrowed or deleted
# and the rules would go on reading as though nothing had changed.
#
# What is proved here: all four documents exist with the identities ADR 0039 fixed; each
# carries the front-matter fields the rules README requires; each declares an enforcement
# block the repository can back, in whichever of the three modes it claims — a named
# validator must exist as a function in lib/, and a block without one must name the tests
# that prove it or give the reason nothing executable can; every depends_on reference
# resolves to a rule that really exists at the version named, so the set loads instead of
# erroring as a missing dependency; and the three rules `scripts/liveness-check` says it
# enforces list this case and `test/cases/122_liveness_gate.sh` under their front matter's
# `tests:`, where a typed reader sees them, not only in prose.
#
# Since the scans ADR 0039 named as follow-ups exist, the doctrine also has a shape: the two
# mechanically decidable rules are blocking and each names the scan that decides it, and the
# two the ADR reserved for review are advisory and say why in reviewed_because. That split is
# asserted below, so neither half can drift — a rule cannot be quietly lowered back to
# advisory once its scan exists, and one cannot be raised to blocking while its only
# declaration is that review decides.
#
# Whether a rule is obliged to carry proof at all is not decided here. That is
# scripts/ci/rule-proof-check, which asks it of every rule in the set rather than of these
# four, and test/cases/125_rule_proof.sh mutation-tests it.
. "$ROOT/test/lib.sh"
RULES="$ROOT/.ai/repo/rules/project"
[ -d "$RULES" ] || { echo " $RULES is missing"; exit 1; }
ids="project.execution-state-is-authoritative
project.every-wait-is-bounded
project.commands-run-non-interactively
project.recovery-is-idempotent"
# every id in the doctrine resolves to exactly one file that claims it
for id in $ids; do
matches="$(grep -l "^id: $id\$" "$RULES"/*.md 2>/dev/null | wc -l | tr -d ' ')"
[ "$matches" = 1 ] || { echo " $id is claimed by $matches files, expected exactly 1"; exit 1; }
done
field() { sed -n "s/^$2: //p" "$1" | head -1; }
for id in $ids; do
f="$(grep -l "^id: $id\$" "$RULES"/*.md)"
# the front matter the rules README requires of every rule
for key in version kind title description statement status class; do
v="$(field "$f" "$key")"
[ -n "$v" ] || { echo " $id: front matter is missing '$key'"; exit 1; }
done
[ "$(field "$f" kind)" = rule ] || { echo " $id: kind is not 'rule'"; exit 1; }
[ "$(field "$f" status)" = active ] || { echo " $id: status is not 'active'"; exit 1; }
# a class the repository can actually back: blocking requires an x-majordomus block,
# and that block requires a validator function that exists in lib/
class="$(field "$f" class)"
case "$class" in
advisory|blocking)
# An enforcement block is valid in one of three modes, and the class does not decide
# which: a rule that names a validator is dispatched and the function must be there;
# a rule that names tests instead is gated — the cases prove it and nothing calls it;
# a rule that names neither may give the reason nothing executable can express it,
# and the reason is the whole declaration. An advisory rule may carry any of them;
# before the second mode existed it could carry none, and asserting that here would
# now refuse a gated advisory rule for being proven. What a class no longer decides is
# whether proof is required at all — since `advisory` stopped being an exemption it is
# required of every rule — and scripts/ci/rule-proof-check is what asks it, over the
# whole set rather than these four.
if grep -q '^x-majordomus:' "$f"; then
validator="$(sed -n 's/^ validator: //p' "$f" | head -1)"
if [ -n "$validator" ]; then
grep -rq "mj_validate_$validator()" "$ROOT/lib" ||
{ echo " $id: no mj_validate_$validator() in lib/"; exit 1; }
elif ! grep -q '^ tests:' "$f"; then
# the third mode (ADR 0048): the block declares that review decides, and says
# why. A reason must be written; an empty one is no declaration.
grep -qE '^ reviewed_because: *[^ ]' "$f" ||
{ echo " $id: x-majordomus block names no validator, no test and no reviewed_because"; exit 1; }
fi
fi
;;
*)
echo " $id: class '$class' is neither advisory nor blocking"; exit 1
;;
esac
# every declared dependency resolves to a rule that exists at the version named
deps="$(sed -n 's/^depends_on: \[\(.*\)\]$/\1/p' "$f" | head -1 | tr ',' ' ')"
for dep in $deps; do
dep_id="${dep%@*}"; dep_ver="${dep##*@}"
dep_id="$(echo "$dep_id" | tr -d ' ')"; dep_ver="$(echo "$dep_ver" | tr -d ' ')"
[ -n "$dep_id" ] || continue
target="$(grep -l "^id: $dep_id\$" "$RULES"/*.md "$ROOT/.ai/repo/rules/vendor"/*/*.md 2>/dev/null | head -1)"
[ -n "$target" ] || { echo " $id: depends_on $dep_id, which no rule declares"; exit 1; }
have="$(field "$target" version)"
[ "$have" = "$dep_ver" ] ||
{ echo " $id: depends_on $dep_id@$dep_ver but that rule is version $have"; exit 1; }
done
done
# the doctrine's own dependency spine: the other three rest on the first
for id in project.every-wait-is-bounded project.commands-run-non-interactively; do
f="$(grep -l "^id: $id\$" "$RULES"/*.md)"
grep -q 'project.execution-state-is-authoritative@1' "$f" ||
{ echo " $id does not depend on project.execution-state-is-authoritative@1"; exit 1; }
done
# The relation between the gate and the rules, read from both ends. scripts/liveness-check
# names the three rules it serves in its own header; each of those three must name the two
# cases that prove the gate. Read from one end only, this is exactly the reference that
# rots in silence: the gate claimed three rules and not one of them claimed it back, so the
# rules went on saying "the mechanical half does not exist yet" while it ran on every push.
GATE="$ROOT/scripts/liveness-check"
[ -f "$GATE" ] || { echo " scripts/liveness-check is missing; the assertion below would pass vacuously"; exit 1; }
served="project.every-wait-is-bounded
project.commands-run-non-interactively
project.execution-state-is-authoritative"
for id in $served; do
grep -q "$id" "$GATE" ||
{ echo " scripts/liveness-check no longer names $id; this case and that header disagree"; exit 1; }
f="$(grep -l "^id: $id\$" "$RULES"/*.md)"
# The cases are read from the front matter's `tests:` list, the only place a typed reader
# (the proof graph, rule-proof-check) looks. Prose naming them is for a person; a rule
# whose prose names the gate while its front matter says review decides is the same
# one-way relation, moved from between the gate and the rule to inside the rule.
tests="$(awk '/^---$/ { n++; next } n == 1 && /^ tests:/ { print }' "$f")"
for c in test/cases/121_liveness_doctrine.sh test/cases/122_liveness_gate.sh; do
case "$tests" in
*"$c"*) ;;
*) echo " $id does not list $c under tests:, so its gate is claimed from one side"
exit 1 ;;
esac
done
grep -q 'scripts/liveness-check' "$f" ||
{ echo " $id does not name scripts/liveness-check, the gate that decides it"; exit 1; }
done
# the split ADR 0039 drew, now that both scans exist: the mechanically decidable rules are
# decided by a scan, the other two by review, and each says which in its own block. A
# reviewed rule may also name the cases of the one shape the gate reads for it (the
# converse check above asks that of execution-state); the test wins as its mode and the
# reason stays the declaration for everything the gate cannot see.
for id in project.commands-run-non-interactively project.every-wait-is-bounded; do
f="$(grep -l "^id: $id\$" "$RULES"/*.md)"
[ "$(field "$f" class)" = blocking ] ||
{ echo " $id: its scan exists, so it is decided by one — class is '$(field "$f" class)', not blocking"; exit 1; }
grep -q '^ tests:' "$f" ||
{ echo " $id: blocking and names no test; the scan that decides it must be named"; exit 1; }
for t in $(sed -n 's/^ tests: \[\(.*\)\]$/\1/p' "$f" | head -1 | tr ',' ' '); do
t="$(echo "$t" | tr -d ' ')"
[ -n "$t" ] || continue
[ -f "$ROOT/$t" ] || { echo " $id: names $t, which is not in the tree"; exit 1; }
done
done
for id in project.execution-state-is-authoritative project.recovery-is-idempotent; do
f="$(grep -l "^id: $id\$" "$RULES"/*.md)"
[ "$(field "$f" class)" = advisory ] ||
{ echo " $id: ADR 0039 reserved it for review, and nothing here decides it mechanically"; exit 1; }
grep -qE '^ reviewed_because: *[^ ]' "$f" ||
{ echo " $id: advisory by design and does not say why review decides it"; exit 1; }
done
echo " 4 liveness rules: identities unique, front matter complete, classes backed, dependencies resolve, 3 name the gate back; 2 decided by their scan, 2 by review"