Commit 764accb1 by PLN (Algolia)

feat(rig): a preflight that reads LIVE STATE — the gap that let gMask sit armed for 74 minutes

PLN, after a change of mine reached his ears before a check did: "we gotta make
things work bro we cant break as we move i thought we had clarity and confidence by
now :) go on, take the longshorter path not hte immediate one".

Right, and the honest diagnosis is not "be more careful". It is that this toolbox
had a whole missing category of check.

Everything in it is COLD:
    pvlint            parses .tidal text
    silent-eval       queries patterns against an EMPTY control map, in a fresh ghci
    surface-columns   reads .tidal text
    check-boot        loads BootTidal in a throwaway ghci
    check-mix         reads the SAVED Ardour session

All five were green all afternoon. Meanwhile gMask sat ARMED AT 127 on the live
board from 15:36 to 16:50 — a global chopping the last eighth out of every bar of
every orbit wrapped in gM1/gM2/gM3, which is every orbit of every track. Nothing
was broken. A switch was on, and NOT ONE tool in the chain looks at the switches.
PLN found it by ear, and the ear should never be the smoke detector.

`gig-log preflight` closes that. It reads the live recorder's surface state and
returns a verdict on the controls that can silence a whole rig from outside any
pattern: gPanic armed, any gMute engaged, a DJ filter parked in a band-killing
position, gMask armed if it is still active.

== THREE DESIGN DECISIONS, EACH FROM A PAST FAILURE ==

1. A STALE LOG FAILS. Exit 2, and distinct from exit 1 (surface unsafe) so a caller
   can tell "I could not check" from "I checked and it is bad". A preflight that
   reports SAFE because it read yesterday's session converts an unknown into a
   false reassurance, which is precisely the shape of every bad afternoon this rig
   has had. is_live() already existed; this is the first thing to gate on it.

2. gMASK'S MEANING IS READ FROM BootTidal.hs, NOT HARDCODED. ^41 was a global gate
   this morning; since 37857225 it is d1's gate and gMask is `id`. Same CC, opposite
   verdict. Hardcoding either answer would be wrong half the time and would go
   stale exactly the way every other cached binding here has
   (feedback_stale_binding_pattern). An unreadable BootTidal assumes the mask is
   STILL ACTIVE — the cautious read, because guessing "retired" silences a real
   FAIL.

3. IT NEVER FAILS ON THE STATE BUTTONS. Gates on 41-44/57-60/76/89-92 are gestures
   PLN arms on purpose. They are LISTED ("armed on purpose (not a failure)") so
   nothing is silently on, but they do not block. A gate that cries wolf about
   intentional performance state is a gate that gets ignored, and then it is worth
   nothing on the night it matters.

It also names what it cannot see rather than implying completeness: Ardour faders
are invisible over MIDI, so the output points at check-mix.py and says that reads
the SAVED session.

== AND IT IS WIRED IN, WHICH IS THE ACTUAL POINT ==
gig-up.sh now ends with a readiness gate that runs BOTH preflight and check-mix.
Everything above that line only proves processes STARTED; these two are the only
steps that ask whether the rig will make a SOUND. Neither aborts the launch — you
may be starting up precisely to fix them. This is the "real readiness gates" half
of feedback_simple_setup, and it means the surface check happens whether or not
anyone remembers to ask for it.

Verified: live run reports SAFE with the board as PLN just left it (48 controls
touched, gMask correctly detected as retired, ^93 untouched and flagged as sitting
at the #55 seed). Negative-tested against a truncated log: exit 2, "recorder is
NOT running", no verdict offered. 5 new tests (309 total), including the
load-bearing one that a stale log must FAIL rather than pass, and that a
COMMENTED-OUT `gMask = id` does not count as retired.
parent 37857225
...@@ -212,5 +212,24 @@ else ...@@ -212,5 +212,24 @@ else
LAUNCH_ARGS=("$DIR"); launch_bin "Pulsar" pulsar LAUNCH_ARGS=("$DIR"); launch_bin "Pulsar" pulsar
fi fi
# 5) READINESS GATE. Not a report — a gate, with a verdict you can act on.
#
# Everything above proves processes STARTED. Nothing above proves the rig will make
# a sound, because the two things that most reliably swallow it are STATE, not
# processes: a global mute/filter/gate parked somewhere on the surface, and an
# Ardour fader parked at -inf. On 2026-07-29 gMask sat armed at 127 for 74 minutes
# while every static check was green, and PLN found it by ear. The ear is not
# supposed to be the smoke detector.
#
# Neither check is fatal to the launch — you may be starting up precisely to fix
# them — so they report and never abort.
echo
if command -v python3 >/dev/null; then
info "readiness: surface state (globals that could be swallowing your sound)"
python3 "$DIR/tools/gig-log.py" preflight 2>&1 | sed 's/^/ /' || true
info "readiness: Ardour faders (reads the last SAVED session — save first)"
python3 "$DIR/tools/check-mix.py" 2>&1 | tail -4 | sed 's/^/ /' || true
fi
echo echo
ok "gig-up done. SuperDirt owns the LaunchControl; Ardour + Pulsar are coming up." ok "gig-up done. SuperDirt owns the LaunchControl; Ardour + Pulsar are coming up."
...@@ -1077,6 +1077,123 @@ def cmd_install(enable: bool = True) -> int: ...@@ -1077,6 +1077,123 @@ def cmd_install(enable: bool = True) -> int:
return 0 return 0
def gmask_is_retired(boot: Path | None = None) -> bool:
"""Is gMask still `midiOn "^41" (mask ...)`, or retired to `id`?
preflight must judge a control by what it CURRENTLY does, not by what it did
when the rule was written. ^41 was gMask (a global gate); since 2026-07-29 it
is d1's gate and gMask is `id`. Same CC, opposite verdict — so read BootTidal
instead of hardcoding, or this gate goes stale the way every other cached
binding in this rig has ([[feedback_stale_binding_pattern]]).
"""
boot = boot or (Path(__file__).resolve().parent.parent / "BootTidal.hs")
try:
for raw in boot.read_text().splitlines():
line = raw.split("--", 1)[0]
m = re.match(r"\s*gMask\s*=\s*(.+?)\s*$", line)
if m:
return m.group(1).strip() == "id"
except OSError:
pass
return False # unknown => assume it still masks, the cautious read
# Controls whose parked position can only ever be judged by the human: the state
# buttons are gestures PLN arms on purpose. preflight reports them, never fails on
# them -- a gate that cries wolf about intentional performance state is a gate that
# gets ignored, and then it is worth nothing when it matters.
GATE_CC = tuple(list(range(41, 45)) + list(range(57, 61))
+ [76] + list(range(89, 93)))
def cmd_preflight(path: Path) -> int:
"""Is the SURFACE safe to play right now? Exit 0 = yes.
Why this exists (2026-07-29). Every other check in this toolbox is COLD: pvlint
parses files, silent-eval queries patterns against an empty control map,
surface-columns reads .tidal text. All three were green all afternoon while
gMask sat ARMED AT 127 on the live board for 74 minutes, chopping an eighth out
of every bar of every orbit. Nothing was broken; a global was simply switched
on, and no tool in the chain looks at the switches. PLN found it by ear, which
is the one instrument that should never be the smoke detector.
So: the missing check is not another parser, it is a reading of LIVE STATE.
The hard rule here is about a stale log. If the recorder is not running, this
command must FAIL LOUD rather than report on an old file — a preflight that
passes because it read yesterday's session is worse than no preflight, and it
is exactly the failure shape this rig produces over and over.
"""
hdr, samples, events, marks, end = load(path)
if not is_live(samples, hdr):
print("gig-log preflight: FAIL — the recorder is NOT running, so this log "
"is history, not state.", file=sys.stderr)
print(" A preflight that reads a stale log is worse than none. Start it:\n"
" systemctl --user start gig-log.service", file=sys.stderr)
return 2
agg = surface_state(events)
retired = gmask_is_retired()
fails: list[str] = []
warns: list[str] = []
armed: list[str] = []
for cc in sorted(agg):
v = agg[cc]["v"]
if v is None:
continue
if cc == PANIC_CC and v > 0:
fails.append(f"^{cc} gPanic ARMED at {v} — every gPanic'd stream is "
f"gain-killed. This is total silence, by design.")
elif cc in MUTE_CC and v > 0:
fails.append(f"^{cc} gMute{MUTE_CC.index(cc) + 1} at {v} — mutes "
f"{v / 127.0:.0%} of cycles on every stream it wraps.")
elif cc in DJF_CC:
lo, hi = djf_bands(v)
verdict = djf_verdict(lo, hi)
line = (f"^{cc} gF{DJF_CC.index(cc) + 1} at {v} — lpf {round(lo)} / "
f"hpf {round(hi)} Hz — {verdict}")
if "⚠⚠" in verdict:
fails.append(line)
elif "⚠" in verdict:
warns.append(line)
elif cc == MASK_CC and v > 0 and not retired:
fails.append(f"^{cc} gMask ARMED at {v} — gates "
f"{v / 127.0:.0%} of cycles on EVERY gM-wrapped orbit. "
f"(Retire it with `gMask = id` and this becomes d1's gate.)")
elif cc in GATE_CC and v > 0:
armed.append(f"^{cc} at {v}")
elif cc in ARDOUR_CC:
pass # Ardour's, and MIDI cannot tell us where the strip sits
W = 74
print("=" * W)
print(f" PREFLIGHT {path.name} recorder LIVE "
f"{len(agg)} control(s) touched this session")
print(f" gMask: {'retired (^41 is d1 gate)' if retired else 'ACTIVE on ^41'}")
print("=" * W)
for line in fails:
print(f" FAIL {line}")
for line in warns:
print(f" WARN {line}")
if armed:
print(f" armed on purpose (not a failure): {', '.join(armed)}")
untouched = [cc for cc in DJF_CC + MUTE_CC + (PANIC_CC,) if cc not in agg]
if untouched:
print(f" untouched this session, so sitting at the #55 seed (safe): "
f"{','.join(f'^{c}' for c in untouched)}")
print("-" * W)
if fails:
print(f" NOT SAFE TO PLAY — {len(fails)} blocking, {len(warns)} warning(s).")
print(" Every one is fixed from the surface: move the knob, unpush the button.")
return 1
print(f" SAFE — nothing global is swallowing your sound"
+ (f" ({len(warns)} warning(s) worth a glance)." if warns else "."))
print(" NOTE this reads the SURFACE only. Ardour faders are not visible over")
print(" MIDI — run tools/check-mix.py for those, and it reads the SAVED session.")
return 0
def cmd_controls(path: Path, since: str | None = None, def cmd_controls(path: Path, since: str | None = None,
until: str | None = None) -> int: until: str | None = None) -> int:
"""Where is every control PARKED — the 3am question when an orbit is silent. """Where is every control PARKED — the 3am question when an orbit is silent.
...@@ -1278,6 +1395,10 @@ def main(argv: list[str] | None = None) -> int: ...@@ -1278,6 +1395,10 @@ def main(argv: list[str] | None = None) -> int:
ct.add_argument("--from", dest="since", metavar="WHEN") ct.add_argument("--from", dest="since", metavar="WHEN")
ct.add_argument("--to", dest="until", metavar="WHEN") ct.add_argument("--to", dest="until", metavar="WHEN")
pf = sub.add_parser("preflight",
help="is the SURFACE safe to play right now? (exit 0 = yes)")
pf.add_argument("file", nargs="?", type=Path)
sub.add_parser("status", help="is it running, what is it writing") sub.add_parser("status", help="is it running, what is it writing")
ins = sub.add_parser("install", help="systemd --user unit") ins = sub.add_parser("install", help="systemd --user unit")
ins.add_argument("--no-enable", action="store_true") ins.add_argument("--no-enable", action="store_true")
...@@ -1294,6 +1415,12 @@ def main(argv: list[str] | None = None) -> int: ...@@ -1294,6 +1415,12 @@ def main(argv: list[str] | None = None) -> int:
return 1 return 1
fn = cmd_report if a.cmd == "report" else cmd_controls fn = cmd_report if a.cmd == "report" else cmd_controls
return fn(p, a.since, a.until) return fn(p, a.since, a.until)
if a.cmd == "preflight":
p = a.file or newest_log(a.dir)
if p is None:
print(f"gig-log: no log found in {a.dir}", file=sys.stderr)
return 1
return cmd_preflight(p)
if a.cmd == "status": if a.cmd == "status":
return cmd_status(a.dir) return cmd_status(a.dir)
if a.cmd == "install": if a.cmd == "install":
......
...@@ -761,3 +761,59 @@ def test_djf_models_BOTH_halves_not_just_the_lowpass(): ...@@ -761,3 +761,59 @@ def test_djf_models_BOTH_halves_not_just_the_lowpass():
assert gl.djf_verdict(lo64, hi64) == "open" assert gl.djf_verdict(lo64, hi64) == "open"
# hard right is a brutal high-pass, not "open" # hard right is a brutal high-pass, not "open"
assert gl.djf_hpf(127) > 7900 assert gl.djf_hpf(127) > 7900
# ---------------------------------------------------------------------------
# preflight (2026-07-29)
#
# Every other check in the toolbox is COLD -- pvlint parses files, silent-eval
# queries patterns against an empty control map, surface-columns reads .tidal
# text. All three were green for an entire afternoon while gMask sat ARMED AT 127
# on the live board, chopping an eighth out of every bar of every orbit. Nothing
# was broken; a global was switched on and no tool in the chain looks at the
# switches. PLN found it by ear.
#
# So preflight reads LIVE STATE, and these tests pin the two properties that
# decide whether it is worth having.
# ---------------------------------------------------------------------------
def test_gmask_retirement_is_READ_from_boottidal_not_hardcoded(tmp_path):
"""^41 was a global gate; it is now d1's gate. Same CC, opposite verdict.
A preflight that hardcodes either answer is wrong half the time, and goes
stale exactly the way every other cached binding in this rig has.
"""
boot = tmp_path / "BootTidal.hs"
boot.write_text(' gMask = (midiOn "^41" (mask "t!7 f"))\n')
assert gl.gmask_is_retired(boot) is False
boot.write_text(" gMask = id\n")
assert gl.gmask_is_retired(boot) is True
def test_an_unreadable_boottidal_assumes_the_mask_is_STILL_ACTIVE(tmp_path):
"""The cautious read. Guessing "retired" would silence a real FAIL."""
assert gl.gmask_is_retired(tmp_path / "nope.hs") is False
def test_a_commented_gmask_line_does_not_count_as_retired(tmp_path):
boot = tmp_path / "BootTidal.hs"
boot.write_text(' -- gMask = id\n gMask = (midiOn "^41" (mask "t!7 f"))\n')
assert gl.gmask_is_retired(boot) is False
def test_a_stale_log_must_FAIL_preflight_rather_than_pass(tmp_path, capsys):
"""THE load-bearing test.
A preflight that reports "SAFE" because it read yesterday's session is worse
than no preflight at all: it converts an unknown into a false reassurance,
which is the exact failure shape this rig produces over and over. Exit 2,
distinct from exit 1 (surface unsafe), so a caller can tell "I could not
check" from "I checked and it is bad".
"""
log = tmp_path / "old.jsonl"
log.write_text(
'{"t": 1000000, "period": 1.0}\n'
'{"t": 1000001, "xruns": 0}\n'
)
assert gl.cmd_preflight(log) == 2
assert "NOT running" in capsys.readouterr().err
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment