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 told to re-run bootstrap — never a door welded shut on a machine whose only
entrance it was. 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 **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 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 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 # unchanged from before: a marker that names no door policy cannot authorize
# shutting a door. # shutting a door.
root_door_of() { root_door_of() {
local marker="$1" new="" old="" local marker="$1" new="" old="" padded
case "$marker" in # FIELD-ANCHORED, not substring. The marker is one line of space-separated
*root-door=closed*) new=closed ;; # `key=value` fields (bootstrap writes it with a single printf), so padding
*root-door=open*) new=open ;; # 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 esac
case "$marker" in case "$padded" in
*class=human*) old=closed ;; *" class=human "*) old=closed ;;
*class=server*) old=open ;; *" class=server "*) old=open ;;
esac esac
if [ -n "$new" ] && [ -n "$old" ] && [ "$new" != "$old" ]; then if [ -n "$new" ] && [ -n "$old" ] && [ "$new" != "$old" ]; then
printf 'conflict'; return 0 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' door_of 'role=claude-box tenant=yes host=no'
check "root_door_of: disagreement is a conflict, not a coin flip" 0 "[conflict]" \ 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' 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 if [ "$(id -u)" -ne 0 ]; then
check "users close-root: refuses non-root" 1 "must run as root" "$ROOT/commands/users-close-root.sh" check "users close-root: refuses non-root" 1 "must run as root" "$ROOT/commands/users-close-root.sh"
else else