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/30_command_registry.sh
An object of kind test
What it is
- URI
majordomus://test/test/cases/30_command_registry.sh- Kind
test- Identity
test/cases/30_command_registry.sh- Path
test/cases/30_command_registry.sh- Provenance
- declared
- Media type
text/plain- As a capability
- test.test/cases/30_command_registry.sh
Front matter
{}Content
# majordomus-covers: none
#
# The command registry and the dispatcher must describe the same surface. Neither derives
# from the other — bin/majordomus decides what executes, share/commands.yaml decides what a
# command means — so this case is the reconciliation between them, and it reads both from
# source rather than restating either.
#
# It runs against the repository's own files, because the subject is this repository's
# command surface, not a fixture's.
# claims: command-surface
. "$ROOT/test/lib.sh"
REG="$ROOT/share/commands.yaml"
[ -f "$REG" ] || { echo " no command registry at share/commands.yaml"; exit 1; }
# The registry must parse with the tool's own parser, not with a permissive one: a registry
# the tool cannot read is a registry nothing can enforce.
MJ_BIN_DIR="$ROOT/bin"; MJ_LIB_DIR="$ROOT/lib"; export MJ_BIN_DIR MJ_LIB_DIR
# shellcheck source=../../lib/common.sh
. "$ROOT/lib/common.sh"
"$MJ" init >/dev/null
FLAT="$T/commands.flat"
mj_yaml_flatten "$REG" > "$FLAT" 2>"$T/flat.err" || {
echo " share/commands.yaml does not parse: $(cat "$T/flat.err")"; exit 1; }
grep -qx 'version=1' "$FLAT" || { echo " registry version must be 1"; exit 1; }
get() { sed -n "s|^commands\.$1\.$2=||p" "$FLAT"; } # index, field -> value
count=0; while [ -n "$(get "$count" id)" ]; do count=$((count+1)); done
[ "$count" -ge 15 ] || { echo " registry holds only $count commands; the dispatcher carries more"; exit 1; }
ids=""; public=""; internal=""
i=0; while [ "$i" -lt "$count" ]; do
id="$(get "$i" id)"; vis="$(get "$i" visibility)"
ids="$ids $id"
case "$vis" in
public) public="$public $id" ;;
internal) internal="$internal $id" ;;
*) echo " $id declares visibility '$vis' (public | internal)"; exit 1 ;;
esac
i=$((i+1))
done
# 1. no duplicate ids. Discovery that silently picks the first or the last entry is worse
# than no discovery, because it looks like it worked.
dupes="$(printf '%s\n' $ids | LC_ALL=C sort | uniq -d)"
[ -z "$dupes" ] || { echo " duplicate command id(s): $dupes"; exit 1; }
# 2. unknown keys are errors here as everywhere else in this repository
unk="$(mj_yaml_unknown_keys "$FLAT" "$ROOT/share/allow/commands.txt" || true)"
[ -z "$unk" ] || { echo " unknown key(s) in share/commands.yaml: $(printf '%s' "$unk" | tr '\n' ' ')"; exit 1; }
# 3. every dispatched command has a registry entry, and every public entry is dispatched.
# The dispatch table is read from the binary, so adding a command there adds it here.
dispatched="$(grep -oE '^ [a-z|]+\)$' "$ROOT/bin/majordomus" | tr -d ' )' | tr '|' '\n' | LC_ALL=C sort -u)"
[ "$(printf '%s\n' "$dispatched" | wc -w | tr -d ' ')" -ge 15 ] || {
echo " could not read the dispatch table from bin/majordomus"; exit 1; }
for c in $dispatched; do
printf '%s\n' $ids | grep -qx "$c" || {
echo " dispatched but absent from share/commands.yaml: $c"; exit 1; }
printf '%s\n' $public | grep -qx "$c" || {
echo " dispatched and listed in the help text, but classified internal: $c"; exit 1; }
done
# version is dispatched ahead of the option parser, so it is not in the case list above
printf '%s\n' $public | grep -qx version || { echo " version is not a public registry entry"; exit 1; }
for c in $public; do
[ "$c" = version ] && continue
printf '%s\n' "$dispatched" | grep -qx "$c" || {
echo " public in the registry but not dispatched by bin/majordomus: $c"; exit 1; }
done
# 4. a command is public exactly when the usage text lists it. That is the whole definition
# of internal, and it is checked in both directions so that "internal" cannot become a
# place to hide a command from the obligations a public one carries.
expect_exit 0 "$MJ" --help
for c in $public; do
expect_grep "^ $c" || { echo " public command not listed in --help: $c"; exit 1; }
done
for c in $internal; do
expect_no_grep "^ $c " || { echo " internal command is listed in --help: $c"; exit 1; }
done
# 5. closed vocabularies stay closed
i=0; while [ "$i" -lt "$count" ]; do
id="$(get "$i" id)"
case "$(get "$i" class)" in
read-only|state-mutating|generated-output-mutating) ;;
*) echo " $id declares class '$(get "$i" class)'"; exit 1 ;;
esac
case "$(get "$i" requires_task)" in
required|optional|none) ;;
*) echo " $id declares requires_task '$(get "$i" requires_task)'"; exit 1 ;;
esac
st="$(get "$i" stage)"
grep -qE "^stages\.[0-9]+\.id=$st$" "$FLAT" || {
echo " $id names stage '$st', which the registry does not declare"; exit 1; }
case "$(get "$i" syntax)" in
"majordomus $id"*|"majordomus --help") ;;
*) echo " $id: syntax does not begin with 'majordomus $id'"; exit 1 ;;
esac
# a public command names the demonstration that represents it; an internal one names none
d="$(get "$i" demo)"
case "$(get "$i" visibility)" in
public) [ -n "$d" ] && [ "$d" != none ] || { echo " public command $id declares no demo"; exit 1; } ;;
internal) [ -z "$d" ] || [ "$d" = none ] || { echo " internal command $id declares demo '$d'"; exit 1; } ;;
esac
i=$((i+1))
done
# 6. the declared exit codes are the codes the implementation can actually produce. Every
# MJ_EX_* constant a command's module references must be declared, so a new failure path
# cannot ship undocumented.
code_of() { case "$1" in OK) echo 0 ;; USAGE) echo 2 ;; CONTRACT) echo 10 ;; DRIFT) echo 11 ;;
MISSING) echo 12 ;; INTERNAL) echo 13 ;; REFUSED) echo 15 ;; esac; }
i=0; while [ "$i" -lt "$count" ]; do
id="$(get "$i" id)"
if [ -f "$ROOT/lib/$id.sh" ]; then
declared="$(sed -n "s|^commands\.$i\.exit_codes\.[0-9]*=||p" "$FLAT" | LC_ALL=C sort -n)"
for name in $(grep -oE 'MJ_EX_(OK|USAGE|CONTRACT|DRIFT|MISSING|INTERNAL|REFUSED)' "$ROOT/lib/$id.sh" | sed 's/MJ_EX_//' | LC_ALL=C sort -u); do
c="$(code_of "$name")"
printf '%s\n' "$declared" | grep -qx "$c" || {
echo " $id: lib/$id.sh can exit $c (MJ_EX_$name) but the registry does not declare it"; exit 1; }
done
fi
i=$((i+1))
done
# 7. the checks above are not vacuous. Each mutation must be caught by the check that claims
# to catch it, or the check is decoration rather than enforcement.
probe="$T/probe.yaml"
sed 's|^ stage: setup$| stage: nosuchstage|' "$REG" > "$probe"
mj_yaml_flatten "$probe" > "$T/probe.flat" 2>/dev/null
grep -q '^commands\.0\.stage=nosuchstage$' "$T/probe.flat" || { echo " the stage probe did not take"; exit 1; }
if grep -qE '^stages\.[0-9]+\.id=nosuchstage$' "$T/probe.flat"; then
echo " stage check is vacuous: the probe stage resolves"; exit 1; fi
sed 's|^ - id: doctor$| - id: init|' "$REG" > "$probe"
mj_yaml_flatten "$probe" > "$T/probe.flat" 2>/dev/null
pids="$(sed -n 's|^commands\.[0-9]*\.id=||p' "$T/probe.flat")"
[ -n "$(printf '%s\n' $pids | LC_ALL=C sort | uniq -d)" ] || {
echo " duplicate-id check is vacuous: the probe produced no duplicate"; exit 1; }
printf 'ok registry: %s commands, %s public, reconciled against the dispatch table\n' \
"$count" "$(printf '%s\n' $public | wc -w | tr -d ' ')"