feat(users)!: --class human|server becomes --root-door closed|open #83

Merged
dan-claude-bot merged 5 commits from feat/root-door-trait into main 2026-07-20 12:05:47 +00:00
3 changed files with 60 additions and 7 deletions
Showing only changes of commit 4bbf1babe0 - Show all commits

View file

@ -67,6 +67,21 @@ on the way to cutting its first release, and this file starts there.
told to re-run bootstrap — never a door welded shut on a machine whose only
entrance it was.
The resolver matches **whole fields, not substrings** — the marker is one
line of space-separated `key=value` pairs, so it pads both ends and matches
on field boundaries. Review caught the first cut doing unanchored matching,
which resolved any value that *extended* a real one: `root-door=closedish`
read as `closed` and passed close-root's gate — the single arm that
authorizes an irreversible act — and `class=humanoid` did the same through
the compat arm, both contradicting the resolver's own promise that a value
outside the set resolves empty and fails closed. Only reachable by hand-
editing a marker, so never a live incident, but this is the one function
every consumer trusts and it owes them exactness rather than nearly. Both
vocabularies are anchored: fixing only the current spelling would have left
every pre-#77 box carrying the hole. Whitespace is normalised first, so a
hand-edit using tabs reads the same rather than trading one silent misread
for another.
**New markers are written in the new vocabulary only.** Writing both would
keep an old rig reading a new marker, but it would entrench the retired
spelling on every box rig ever converges and make the disagreement case

View file

@ -158,14 +158,27 @@ read_role_marker() {
# unchanged from before: a marker that names no door policy cannot authorize
# shutting a door.
root_door_of() {
local marker="$1" new="" old=""
case "$marker" in
*root-door=closed*) new=closed ;;
*root-door=open*) new=open ;;
local marker="$1" new="" old="" padded
# FIELD-ANCHORED, not substring. The marker is one line of space-separated
# `key=value` fields (bootstrap writes it with a single printf), so padding
# both ends and matching whole fields is exact. Unanchored patterns matched
# any value that EXTENDS a real one: `root-door=closedish` resolved as
# `closed` and passed close-root's gate — the one arm that authorizes an
# irreversible act — and `class=humanoid` did the same through the compat
# arm. That contradicted this function's own promise above, that a value
# outside the set resolves empty and fails closed. Only reachable by hand
# editing, but this is the function every consumer trusts, so it owes them
# exactness rather than "close enough" (found in review on #77).
# Whitespace is normalised first so a hand-edit using tabs or double spaces
# is read the same way rather than silently failing to match.
padded=" ${marker//[[:space:]]/ } "
case "$padded" in
*" root-door=closed "*) new=closed ;;
*" root-door=open "*) new=open ;;
esac
case "$marker" in
*class=human*) old=closed ;;
*class=server*) old=open ;;
case "$padded" in
*" class=human "*) old=closed ;;
*" class=server "*) old=open ;;
esac
if [ -n "$new" ] && [ -n "$old" ] && [ "$new" != "$old" ]; then
printf 'conflict'; return 0

View file

@ -1604,6 +1604,31 @@ check "root_door_of: a tenant marker names no door at all" 0 "[]" \
door_of 'role=claude-box tenant=yes host=no'
check "root_door_of: disagreement is a conflict, not a coin flip" 0 "[conflict]" \
door_of 'role=custom root-door=open class=human host=no join=login'
# FIELD-ANCHORED, not substring (found in review on #77). A value that EXTENDS
# a real one must resolve EMPTY and fail closed, exactly as the function's
# header promises — before anchoring, `closedish` read as `closed` and PERMITTED
# close-root, the one arm that authorizes an irreversible act. Both vocabularies
# are checked: the compat arm had the identical hole, and a fix that anchored
# only the current spelling would leave every pre-#77 box exposed to it.
check "root_door_of: a value EXTENDING the current spelling resolves empty" 0 "[]" \
door_of 'role=x root-door=closedish host=no'
check "root_door_of: a value extending the pre-#77 spelling resolves empty too" 0 "[]" \
door_of 'role=x class=humanoid host=no'
check "root_door_of: a value PREFIXED by junk does not match either" 0 "[]" \
door_of 'role=x notroot-door=closed host=no'
# ...and the gate itself must refuse on those, not merely resolve empty: the
# resolver returning "" is only safe because every consumer treats it as a
# refusal, so the end-to-end behaviour is what gets pinned.
DOOR_FIX="$(mktemp -d)"
printf 'role=x root-door=closedish host=no\n' > "$DOOR_FIX/bogus"
# shellcheck disable=SC2016 # $1/$2 are the inner shell's positionals, not ours
check "close-root: refuses a marker whose door value merely LOOKS closed" 1 "names no root-door policy" \
bash -c '. "$1/commands/lib/users-config.sh"; assert_marker_closes_root "$2"' _ "$ROOT" "$DOOR_FIX/bogus"
# Whitespace normalisation: a hand-edit using tabs is still a real marker and
# must read the same, or anchoring would trade one silent misread for another.
check "root_door_of: tab-separated fields read the same as space-separated" 0 "[closed]" \
door_of "$(printf 'role=x\troot-door=closed\thost=no')"
rm -rf "$DOOR_FIX"
if [ "$(id -u)" -ne 0 ]; then
check "users close-root: refuses non-root" 1 "must run as root" "$ROOT/commands/users-close-root.sh"
else