fix(framework): track anchor presence separately from value (T51 WP0b rework)
ci/woodpecker/pr/ci Pipeline was successful

${VAR:-} collapsed exported-empty into genuinely-unset, mis-filing
both-empty and the two mixed empty/unset states as informational where
the charter requires WARNs. Presence now tracked via [[ -v VAR ]]
alongside value: NOTES only when both variables are genuinely absent;
any present-but-empty or single-present state emits a loud WARN per
missing/empty anchor, each naming the launchers as authority (unset and
empty get distinct wording). Suite: the wrong both-empty note arm
replaced by three warn arms (both-empty, host-empty+brain-unset,
host-unset+brain-empty); warn arms accept >=1 warn with zero notes
(single-present states warn once, both-present-empty warns twice — per
anchor, by design); red control now mutates presence tracking back to
the nonemptiness collapse (the reviewed defect shape) and must emit
NOTEs for both-empty. 10 arms green; gates green.
This commit is contained in:
2026-08-24 07:32:35 -05:00
parent 023540b151
commit 7ad47152c2
2 changed files with 40 additions and 18 deletions
@@ -392,19 +392,34 @@ check_structure_anchor_provisioning() {
# Severity follows the doctor's existing conventions: pass/note are quiet # Severity follows the doctor's existing conventions: pass/note are quiet
# (note unless --verbose), warn counts toward --fail-on-warn. # (note unless --verbose), warn counts toward --fail-on-warn.
local host_root="${MOSAIC_HOST_ROOT:-}" brain_home="${MOSAIC_BRAIN_HOME:-}" local host_root="${MOSAIC_HOST_ROOT:-}" brain_home="${MOSAIC_BRAIN_HOME:-}"
if [[ -n "$host_root" && -n "$brain_home" ]]; then # T51P2WP0BRW B1: presence is tracked SEPARATELY from value — `${VAR:-}`
# collapses exported-empty into genuinely-unset, which mis-filed both-empty
# and the mixed empty/unset states as informational. Only BOTH-genuinely-
# absent may be informational (charter state 3); any present-but-empty or
# single-present state warns.
local host_set=0 brain_set=0
[[ -v MOSAIC_HOST_ROOT ]] && host_set=1
[[ -v MOSAIC_BRAIN_HOME ]] && brain_set=1
if [[ "$host_set" -eq 1 && "$brain_set" -eq 1 && -n "$host_root" && -n "$brain_home" ]]; then
pass "Structure anchors provisioned: MOSAIC_HOST_ROOT=$host_root MOSAIC_BRAIN_HOME=$brain_home (paths reported only; not expanded, not consumed)" pass "Structure anchors provisioned: MOSAIC_HOST_ROOT=$host_root MOSAIC_BRAIN_HOME=$brain_home (paths reported only; not expanded, not consumed)"
return return
fi fi
if [[ -z "$host_root" && -z "$brain_home" ]]; then if [[ "$host_set" -eq 0 && "$brain_set" -eq 0 ]]; then
note "Structure anchors not provisioned in this environment. Launcher-equivalent derivation (INFORMATIONAL, NON-AUTHORITATIVE — seats receive the authoritative values from the launchers): MOSAIC_HOST_ROOT would default to the operator home; MOSAIC_BRAIN_HOME would default to the brain tree resolved at launch. Doctor does not guess values for consumption; it audits provisioning." note "Structure anchors not provisioned in this environment. Launcher-equivalent derivation (INFORMATIONAL, NON-AUTHORITATIVE — seats receive the authoritative values from the launchers): MOSAIC_HOST_ROOT would default to the operator home; MOSAIC_BRAIN_HOME would default to the brain tree resolved at launch. Doctor does not guess values for consumption; it audits provisioning."
note "Provision both anchors via the seat launchers (launch-seat.sh / launch-seat-claude.sh export them; see T51 spec §1.2a)." note "Provision both anchors via the seat launchers (launch-seat.sh / launch-seat-claude.sh export them; see T51 spec §1.2a)."
return return
fi fi
if [[ -z "$host_root" ]]; then # At least one variable is present (possibly empty), or exactly one exists:
warn "MOSAIC_HOST_ROOT is missing or empty in this environment while MOSAIC_BRAIN_HOME is set — declaration consumers fail closed without it (spec §1.2a). The seat launchers are the authoritative source." # every missing/empty anchor gets its own loud WARN naming the launchers.
else if [[ "$host_set" -eq 0 ]]; then
warn "MOSAIC_BRAIN_HOME is missing or empty in this environment while MOSAIC_HOST_ROOT is set — the projects/ mirror and brain declaration resolve from it (spec §1.2a). The seat launchers are the authoritative source." warn "MOSAIC_HOST_ROOT is not set in this environment while MOSAIC_BRAIN_HOME is — declaration consumers fail closed without it (spec §1.2a). The seat launchers are the authoritative source."
elif [[ -z "$host_root" ]]; then
warn "MOSAIC_HOST_ROOT is present but EMPTY in this environment — declaration consumers fail closed without a usable value (spec §1.2a). The seat launchers are the authoritative source."
fi
if [[ "$brain_set" -eq 0 ]]; then
warn "MOSAIC_BRAIN_HOME is not set in this environment while MOSAIC_HOST_ROOT is — the projects/ mirror and brain declaration resolve from it (spec §1.2a). The seat launchers are the authoritative source."
elif [[ -z "$brain_home" ]]; then
warn "MOSAIC_BRAIN_HOME is present but EMPTY in this environment — the projects/ mirror and brain declaration resolve from it (spec §1.2a). The seat launchers are the authoritative source."
fi fi
} }
@@ -9,7 +9,7 @@
# 1. both present+nonempty -> pass ([OK]), no warns, no notes # 1. both present+nonempty -> pass ([OK]), no warns, no notes
# 2a. host missing, brain set -> warn naming MOSAIC_HOST_ROOT + launchers # 2a. host missing, brain set -> warn naming MOSAIC_HOST_ROOT + launchers
# 2b. brain missing, host set -> warn naming MOSAIC_BRAIN_HOME + launchers # 2b. brain missing, host set -> warn naming MOSAIC_BRAIN_HOME + launchers
# 2c. present-but-EMPTY counts as missing (both empty -> state 3; one empty -> state 2) # 2c. present-but-EMPTY counts as missing (warns; NEVER informational)
# 3. neither present -> informational notes, NON-AUTHORITATIVE, never warn # 3. neither present -> informational notes, NON-AUTHORITATIVE, never warn
# Arms include genuinely-UNSET (env -u) forms, not only empty strings. # Arms include genuinely-UNSET (env -u) forms, not only empty strings.
# Red control: empty-vs-unset distinction removed in a mutated copy -> suite red. # Red control: empty-vs-unset distinction removed in a mutated copy -> suite red.
@@ -64,8 +64,8 @@ run_case() {
oks=$(printf '%s\n' "$out" | grep -c '^\[OK\]' || true) oks=$(printf '%s\n' "$out" | grep -c '^\[OK\]' || true)
if [[ "$expect" == ok && "$oks" -gt 0 && "$warns" -eq 0 && "$notes" -eq 0 ]]; then if [[ "$expect" == ok && "$oks" -gt 0 && "$warns" -eq 0 && "$notes" -eq 0 ]]; then
echo "ok - $label" echo "ok - $label"
elif [[ "$expect" == warn && "$warns" -eq 1 ]]; then elif [[ "$expect" == warn && "$warns" -ge 1 && "$notes" -eq 0 ]]; then
echo "ok - $label (warned)" echo "ok - $label (warned x$warns)"
elif [[ "$expect" == note && "$notes" -gt 0 && "$warns" -eq 0 ]]; then elif [[ "$expect" == note && "$notes" -gt 0 && "$warns" -eq 0 ]]; then
echo "ok - $label (noted)" echo "ok - $label (noted)"
else else
@@ -100,22 +100,29 @@ run_case "empty-string brain home warns (empty != set)" warn \
# ── state 3: neither present (genuinely unset) → notes, never warn ───────── # ── state 3: neither present (genuinely unset) → notes, never warn ─────────
run_case "both unset yields non-authoritative notes" note \ run_case "both unset yields non-authoritative notes" note \
-u:MOSAIC_HOST_ROOT -u:MOSAIC_BRAIN_HOME -u:MOSAIC_HOST_ROOT -u:MOSAIC_BRAIN_HOME
run_case "both empty-string yields non-authoritative notes" note \ run_case "both empty-string warns (empty is present, not absent)" warn \
MOSAIC_HOST_ROOT= MOSAIC_BRAIN_HOME= MOSAIC_HOST_ROOT= MOSAIC_BRAIN_HOME=
run_case "host empty + brain unset warns" warn \
MOSAIC_HOST_ROOT= -u:MOSAIC_BRAIN_HOME
run_case "host unset + brain empty warns" warn \
-u:MOSAIC_HOST_ROOT MOSAIC_BRAIN_HOME=
# ── red control (mutation): empty-vs-unset distinction removed → red ─────── # ── red control (mutation): presence tracking removed → red ────────────────
# Mutant replaces the -n tests with plain set-tests, so an EMPTY value counts # Mutant regresses to the reviewed defect shape: presence derived from
# as present: the empty-host arm above would flip to pass and the suite reds. # NONEMPTINESS (the `${VAR:-}` collapse) instead of true -v tracking. Both-empty
# then looks genuinely-absent and is mis-filed as informational; the both-empty
# warn arm above finds no WARN and the suite reds.
MUT="$ROOT/mosaic-doctor.mutant" MUT="$ROOT/mosaic-doctor.mutant"
sed 's/\[\[ -n "$host_root" \&\& -n "$brain_home" \]\]/[[ -v MOSAIC_HOST_ROOT \&\& -v MOSAIC_BRAIN_HOME ]]/' \ sed 's/\[\[ -v MOSAIC_HOST_ROOT \]\] \&\& host_set=1/[[ -n "${MOSAIC_HOST_ROOT:-}" ]] \&\& host_set=1/; s/\[\[ -v MOSAIC_BRAIN_HOME \]\] \&\& brain_set=1/[[ -n "${MOSAIC_BRAIN_HOME:-}" ]] \&\& brain_set=1/' \
"$DOCTOR" > "$MUT" "$DOCTOR" > "$MUT"
if cmp -s "$DOCTOR" "$MUT"; then if cmp -s "$DOCTOR" "$MUT"; then
echo "SKIP red control (mutation anchor not found — sed pattern drifted)" >&2 echo "SKIP red control (mutation anchor not found — sed pattern drifted)" >&2
else else
outm=$(env MOSAIC_HOST_ROOT= MOSAIC_BRAIN_HOME="$BRAIN" bash -c \ mut_fn=$(sed -n "/^check_structure_anchor_provisioning() {/,/^}/p" "$MUT")
"warn() { echo \"[WARN] \$*\"; }; note() { echo \"[NOTE] \$*\"; return 0; }; pass() { echo \"[OK] \$*\"; return 0; }; $(sed -n "/^check_structure_anchor_provisioning() {/,/^}/p" "$MUT"); check_structure_anchor_provisioning" 2>&1) outm=$(env MOSAIC_HOST_ROOT= MOSAIC_BRAIN_HOME= bash -c \
if printf '%s\n' "$outm" | grep -q '^\[OK\]'; then "warn() { echo \"[WARN] \$*\"; }; note() { echo \"[NOTE] \$*\"; return 0; }; pass() { echo \"[OK] \$*\"; return 0; }; $mut_fn; check_structure_anchor_provisioning" 2>&1)
echo "ok - red control bites (mutant treats empty as present; shipped does not)" if printf '%s\n' "$outm" | grep -q '^\[NOTE\]'; then
echo "ok - red control bites (mutant collapses empty into informational; shipped does not)"
else else
fail "red control did not reproduce the regression shape (mutant output unexpected)" fail "red control did not reproduce the regression shape (mutant output unexpected)"
fi fi