1. 11 Oct, 2026 11 commits
    • rig_health: Bluetooth is a sound card, a blind probe is amber, a dead recorder is not an alarm · e3196836
      A Bluetooth headset (bluez_output.*) now counts as a hardware clock, so
      monitoring on it no longer raises a permanent critical. When pw-top goes quiet
      the clock check is WARN, never NA: cannot-measure is a third state. 'Recovered'
      is only said for a real recovery (not when the rig closed or the probe went
      blind). A health.json left by a gig-log that died uncleanly reads as not
      observed instead of aging into a red stale dot on every day it is not running.
      PLN (Algolia) authored
    • CHANGELOG, archive, TODO.d: rig signal-path health, and what is left open · 8dde011a
      Sprint 12 entry, one learning line, and the follow-ups: the two owner decisions
      (Master sinks, built-in mics), the witnessed drills, the work blocked on
      uncommitted changes in gig-up.sh and rig_units.py, and the measured idle CPU of
      the watchers.
      PLN (Algolia) authored
    • bridge: show the signal path as a rig row · f9c4df6d
      health_item() turns the health.json gig-log publishes into one more panel item,
      through the same tray_summary() the tray renders, so the two faces cannot
      disagree. A stale observer is 'broken', no observer is 'absent'; neither can
      read as up.
      PLN (Algolia) authored
    • perf-tray: show rig health (icon dot on trouble, one readout row) · 699e93c7
      Reads the health.json gig-log publishes and renders tray_summary(): a red dot
      for down or a stale observer, amber for degraded, nothing when healthy. A
      single owner-unit action (start lcxl3-driver) appears only while the surface
      check fails; the tray never writes the graph. A missing file reads as 'not
      observed', never as ok.
      PLN (Algolia) authored
    • gig-log: publish rig health (audio clock, surface driver, sound engine) · 3c161f8c
      Four faults on 2026-10-11 fell between the gate and the per-unit watchdogs, each
      leaving every unit active. rig_health judges the signal path from what gig-log
      already reads: the pw-top stream (are Ardour/SuperCollider clocked by a sound
      card or by the MIDI bridge), /proc/asound (raw LCXL3 present but no driver
      port), and the gear sample (sclang up, scsynth dead). Hysteresis, atomic
      health.json with a heartbeat, notify + evidence snapshot only on a failure
      transition. Steady state adds no fork.
      PLN (Algolia) authored
    • autoroute: only issue pw-link for links that are missing · bc859a23
      The loop re-issued all 24 orbit links every 2 s and every pw-link connects to
      the PipeWire daemon: 12 min of CPU in 85 min, about 15% of a core at idle.
      Index the existing links from the one snapshot in pure bash and link only the
      absent pairs.
      PLN (Algolia) authored
    • autoroute: keep Master on hardware and name the control feed from the live graph · e566bbd6
      A sink node re-created under a running Ardour left Master with no hardware
      output: every unit active, the rig silent. Additive only (never prunes), off
      switch TIDAL_AUTOROUTE_MASTER=0, ports taken from the graph not assumed FL/FR.
      The control-surface assertion used an old PipeWire port spelling and was a
      silent no-op on 1.0.5; resolve it from the live graph. One pw-link snapshot per
      tick instead of three.
      PLN (Algolia) authored
    • midi-autoconnect: rule 4 costs one aconnect per tick while healthy · c53e4731
      Bash regex instead of a grep fork, and the driver's own virtual port as proof
      of service so systemctl is only asked when the raw board is present without
      it.
      PLN (Algolia) authored
    • midi-autoconnect: start lcxl3-driver when a raw LCXL3 is on the bus · 34b60d4a
      gig-up picks the painter once at launch; a board that enumerates later never
      got one and no fader reached Ardour. The unit stays manual (it crash-loops and
      Conflicts= the v2 painter on a v2-only box), so the loop that already knows
      which generation is plugged in re-runs the chooser: start, not restart, raw
      kernel client only, never the v2 painter, bounded retries with a cooldown.
      PLN (Algolia) authored
    • check-gig: a dead surface driver blocks the launch · 9aeee916
      The LCXL LEDs probe exits 0 with no surface on the bus, so its only failure
      is hardware present and its translator down: no faders reach Ardour, no paint.
      That is 'there is no gig', not advice. It sat as one WARN among eleven under
      two stale FAILs and went unread.
      PLN (Algolia) authored
    • gig-preflight: judge CPU knobs against the thermal-pilot's declared rung · 6a3c71ca
      Rung 1 (epp=balance_power, ceiling 65%) is the pilot's resting posture, so
      the fixed 'must be 100%' check turned every launch into a NO-GO whose fixes
      fought the pilot. Pass when the knobs match the declared rung; FAIL only when
      they disagree (a second writer). Stale or absent state keeps the old strict
      behaviour.
      PLN (Algolia) authored
  2. 06 Oct, 2026 2 commits
  3. 28 Sep, 2026 12 commits
  4. 26 Sep, 2026 3 commits
  5. 25 Sep, 2026 12 commits
    • foundry, check-mix: say who actually heard what, and stop one mode lying · 084e692b
      66318b6e's comment and test docstring both claimed PLN heard the polled-loop
      stutter. He did not. The Foundry server was not running: port 8765 was
      another project's http.server, established two steps after the fix landed.
      The bug is real and of exactly the reported shape, and it stays fixed - but
      attributing a fix to a symptom it did not cause is how a wrong diagnosis
      becomes documentation, which is this morning's -inf-faders mistake again.
      Both places now say where it came from. The commit message is history and is
      left alone.
      
      And check-mix's --arm table called an unarmed `Mix` route "not recorded, by
      choice" while --mix blocks on precisely that. Two modes of one tool must not
      disagree about the same route.
      PLN (Algolia) authored
    • design: the Go Live wizard, and where an alert has to land · 34c5974d
      Detection stopped being the problem at 6a94b47b. What failed on 2026-09-24 is
      that every dot was green and nobody was looking at any of them: at T-2 the
      eyes are on Pulsar and the hands are on the LCXL, and neither surface said a
      word. So this is attention routing, not checking.
      
      Three principles, then the shape. One source of truth (check-gig --json; no
      surface grows its own check). An alert lands on the surface the eyes are
      already on, which is a different surface at T-30, T-5 and T-2. And a wizard
      is ordered and blocking where a dashboard is neither: one blocker, its fix,
      a re-check of that ONE probe because the full gate costs 16 s and a
      16-second wait per fix is how a wizard gets skipped.
      
      It never converges on its own. Arming a track, raising a fader and saving are
      things only PLN's hands in Ardour can do, and a tool that edits the session
      he is performing from is a tool that fights him.
      
      Also records the one alert that earns the right to interrupt a set - Ardour
      not recording while Tidal plays - and pins it to the FILES lens, because the
      OSC surface claimed arm:false for all 76 minutes it was actually recording.
      
      Three open questions left for PLN rather than answered here: which surface
      the T-2 alert lands on, whether an at-my-own-risk skip is acceptable, and
      how long the during-set silence window is. The last one needs one rehearsal,
      not an opinion.
      
      Retention and the Mix audit are marked done in the belt-and-braces list, and
      Tidal 08 is ticked: PLN armed it at 17:38 and the saved session now reads
      rec-enable=1 on all twelve.
      PLN (Algolia) authored
    • gig-record: retention as a command, dry by default, never the live take · 674c3b0b
      The black box writes ~72 MB/hour into a gitignored directory and nothing
      deleted anything, so it only grew: a rehearsal week is >5 GB and the repo's
      ">500 MB is reported or deleted, never silent" line bites after one.
      
      An automatic sweep is the wrong shape here. A timer or an ExecStopPost is the
      one mechanism that could delete the take nobody meant to keep, which is the
      entire reason the recorder exists. So: `prune [DAYS]`, dry by default. It
      prints what it would remove and removes nothing until --yes.
      
      Three things are never candidates, in the order they would hurt:
      
        * whatever the running recorder holds open, read from /proc/PID/fd rather
          than guessed from mtime — after a segment roll the newest file is not the
          one being written, and a long session's current segment can easily be
          older than the window. A held-back file is named BEFORE the
          nothing-to-do line, or the report would be a lie about the one file that
          matters most (the test that found this).
        * anything that is not a %Y-%m-%d_%H%M%S.opus segment, so a slice_*
          excerpt cut on purpose is not swept up with the raw capture.
        * anything inside KEEP_DAYS (14, overridable).
      
      OUT_DIR is overridable with GIG_RECORD_DIR for one reason: a delete command
      whose tests cannot run without pointing at real takes is a delete command
      nobody tests. The six new tests assert what it REFUSES to delete, including
      a real process holding a real fd.
      
      The gate advises at 500 MB and names the command. It never prunes.
      PLN (Algolia) authored
    • pulsar: errors are information, not punishment · 3a24802c
      The notifications package auto-hides a notification after 5 s only when it
      is NOT dismissable. Everything raised through addError/addFatalError is
      dismissable, so every error waits for a click forever, at 450 px of pale-red
      fill with near-black text over a dark theme — and a fatal one SHAKES, three
      times, which is punitive UX with an actual keyframe.
      
      Nothing is lost by hiding them: the same package keeps a timestamped log of
      every notification, reachable with notifications:toggle-log. That is what
      makes a timeout safe rather than careless — it hides the interruption, not
      the information.
      
      notifications.js gives every dismissable notification a life, longer the
      worse it is (4 s info to 25 s fatal). Hovering pauses it, because reading is
      not dismissing. Clicking cancels it for good, because a click means "I am
      working on this". Notifications the package already auto-hides are left
      alone: one owner per behaviour.
      
      notifications.less makes the panel dark, translucent and blurred so the code
      underneath stays readable, moves the type into the colour of the icon strip
      instead of a wall of fill, deletes the shake, and draws a 2 px countdown bar
      from the same duration the script used — read through a custom property, so
      the bar cannot disagree with the timer. Ship's Bridge tokens, so the editor
      and the armada UIs say "error" in the same colour.
      
      They live here rather than in ~/.pulsar so the reasoning is in git; the
      dotfiles hold one require and one @import. The stylesheet was compiled
      before landing. init.js needs a window reload to take effect; styles.less is
      live on save.
      PLN (Algolia) authored
    • check-mix: audit the Mix capture track, and say how to add one · 7a88f1d8
      Master is a bus. It has no rec-enable, so Ardour can never record it, so an
      unarmed orbit has nothing to fall back to — the whole cost of the 24th. The
      answer inside Ardour is a `Mix` TRACK fed from Master/audio_out 1+2, armed:
      stem grade, same take, same sample clock as the orbits.
      
      `--mix` audits it, and exits 4 when there is no such route, because absence
      is a choice and the gate must tell "you have not built the net" apart from
      "your net is broken". Absent prints the 30-second Add Track recipe rather
      than nagging. Once it exists, four things make it a net that looks present
      and records nothing, and they are checked in the order they bite:
      
        1. output wired back to Master — a unity-gain FEEDBACK LOOP through the PA,
           which is the state you get by following Ardour's Add Track dialog and
           stopping, since a new track goes to Master by default;
        2. not armed — the Tidal 08 failure one route further out;
        3. input not on Master, or only half on it — one channel the mix and the
           other an orbit reads as a healthy file and is unusable;
        4. fader off unity while the route is DiskIOPostFader — the fader is IN the
           recording, and at -inf the file is digital silence.
      
      Two gate probes, split the way the gig-record pair is: "mix track" ADVISES
      that there is none, "mix track wired" BLOCKS on one that is broken.
      
      Ten new tests. The mutated fixtures each break exactly one thing, so a
      failure names the fault; ExtConnection is asserted NOT to count as an
      in-session link, or a correctly wired track would report a feedback loop.
      PLN (Algolia) authored
    • foundry: the audition loop belongs in the audio thread, not on a 4 Hz poll · 66318b6e
      The loops panel looped an <audio> element from a `timeupdate` listener:
      `if (currentTime >= end) currentTime = start`. `timeupdate` fires at most
      about every 250 ms, and analyze_chops offers slices of 0.25-2.0 s
      (engine/loops.py:322) — the poll interval was the same length as the thing
      being looped. Playback overshot `end` by up to a whole region and only
      snapped back on the next tick, and every snap was a seek on a streamed
      element, which rebuffers and gaps the seam. Audible result: a stutter at
      4 Hz, reported as "play/pause toggle every 200ms". It was.
      
      No poll rate fixes a 250 ms region, so the fix is a deletion. An
      AudioBufferSourceNode with loop/loopStart/loopEnd loops in the audio thread:
      sample-accurate, gapless, no polling and no seeking, correct for a chop and
      for a 30 s loop alike. The AudioBuffer is already decoded because drawWave
      needs it, so audition is now instant after the waveform appears, and the
      context is resumed on the click that needs it.
      
      tests/test_audition_loop.py is a mechanical guard on the served page: three
      of its four assertions fail against the previous version, verified.
      PLN (Algolia) authored
    • gig-log, gig-record: a burst read whole, and a target that can be checked · c6d298eb
      Two bugs I introduced or left standing in the two commits before this one.
      
      SELECT PLUS READLINE DRAINS ONE LINE PER EVENT. readline() on a
      TextIOWrapper pulls a whole chunk off the fd and hands back one line; the
      rest sits in the wrapper's decoded buffer, where select() on the fd cannot
      see it. A fader sweep is 100+ lines a second arriving in bursts, so the
      tail of a gesture would be logged only when the NEXT gesture arrived,
      stamped with the wrong second. Measured on a real pipe: 1 of 50 lines,
      against 50 of 50 once the reader owns its own byte buffer. The old
      blocking iterator never had this; select made it possible and nothing
      tested it, because the fake pipe never wrote anything.
      
      Same file: a rebind left the old aseqdump running. Harmless while rebinds
      only happened after the stream ended -- and the previous commit made them
      ordinary, since the driver restarted six times that night. The
      steady-state check now also reuses one 'aseqdump -l' for both questions.
      
      RUNNING IS NOT LISTENING. gig_record resolves its sink once, at start, and
      gig-up starts units BEFORE it opens Ardour -- so in the flow that is
      actually pressed it resolves while Ardour is closed, falls back to the
      default sink, and if the laptop speakers are then cut (correct on stage)
      the black box records a sink nothing reaches. 'gig_record.sh check' asks
      whether what it HEARS is still where Master GOES, read off the running
      process rather than recomputed; a new ADVISE probe runs it and skips when
      Ardour is closed. "Not running" stays the other probe's question.
      
      The master_sink awk now has tests, including the state the gig was in with
      Master linked to two sinks at once -- it takes the first, which is a real
      choice and not a good one, so the script names the sink it picked and the
      test makes the ambiguity visible instead of silent.
      PLN (Algolia) authored
    • log 049: four green lights and a dead lens · e85dc610
      Captured at task close, not reconstructed. The learning worth keeping is
      that a green light is a claim about liveness and liveness was never the
      question — and that a test in this repo had pinned the MIDI re-resolve
      design so firmly that 'cheap by construction: an idle surface costs
      nothing at all' went un-re-examined until an idle surface cost a gig.
      PLN (Algolia) authored
    • bridge: a GATE panel, because every dot was green on the night it failed · dd851bc2
      PLN, 2026-09-25: "a better 'tableau de bord' to confirm all is armed".
      
      The Bridge answered liveness: units active, processes alive, surface
      connected. On 2026-09-24 all of that was green while Tidal 08 was
      unarmed, gig-log's MIDI lens held a dead subscription and the black box
      had never run. None of those are liveness questions, so no number of
      green process dots could have shown them.
      
      The panel renders tools/check-gig.py --json and decides nothing itself.
      Severity, wording and — the part that matters on a screen — the FIX all
      come from the probe that owns the question, so a probe added to check-gig
      appears here for free. A bespoke "armed" tile would have been a third
      place the truth lives, which is the failure this rig keeps paying for.
      
      Read-only by construction: check-gig is fenced by gig_gate.mutations()
      and tested for it, so unlike RIG UP this button changes nothing and makes
      no sound. It runs off the request thread like _Converge, because the repo
      layer takes ~40s and holding a poll open reads as a hung dashboard.
      
      Also: two failing rows said nothing useful. gig_gate._detail() shows a
      probe's LAST line, and check-mix ended on a paragraph — so the dashboard
      read "is tools/gig_record.sh." and "which looks exactly like Tidal
      emitting nothing. It isn't." Both now end on the fact: "1 orbit(s) NOT
      ARMED — Tidal 08" and "9 route(s) cannot be heard — Tidal 02, ...". A
      verdict is read standing up, in a field.
      
      Verified against a throwaway instance on a spare port: 43 checks, NO-GO,
      ardour armed at the top naming Tidal 08.
      PLN (Algolia) authored
    • gig-record: the black box joins the inventory, and follows Master · 83ced8fd
      tools/gig_record.sh has existed since OPAL, is item 9 of RUNBOOK.md's
      checklist, and had never run once on this machine — no unit, no row in
      rig_units.py, so nothing in the authored inventory knew it existed and no
      converge asserted it. Precisely gig-log's own story, which is why
      CosmicFest has no session log.
      
      The cost this time: 2026-09-24 lost 17m44s off the head of a 94-minute
      set (REC at 18:27:41, set from 18:09:57), so salut_nu and love_first
      exist in no recording at all. Orbit 08 went with them for every track,
      and Ardour cannot help — Master is a BUS, no playlist, no rec-enable, so
      the DAW cannot record the mix even in principle. One stereo opus of the
      master bus makes both losses survivable.
      
      It no longer records the default sink. That sink was the laptop card on
      the 24th while Ardour's Master had been hand-linked to the UMC, so the
      net would only have caught the show because TODO_GIG step 4 was never
      done — do that step and the black box records silence, which is the worst
      failure a black box has, because it looks like it is working. It now
      follows whatever sink ardour:Master/audio_out 1 is linked to, falls back
      to the default when Ardour is closed, pins to GIG_RECORD_TARGET when told,
      and says out loud which road it took.
      
      on-demand, not login: ~72 MB/hour, so gig-up starts it and idle hours are
      not recorded. PROOF stays None because 'pgrep -x ffmpeg' would be
      satisfied by any stranger's transcode; its liveness is a recent segment
      mtime, the same assertion the gig-log probe makes.
      
      The probe ADVISES rather than blocks — a missing net does not stop a set.
      
      Also: nothing checked that rig_units.SERVICES and rig-install.sh's
      UNIT_SRC name the same units, which is the 'list lived in three places'
      failure this module was written to end. Three tests now do, shrink-only
      on perf-tray's $HOME-only unit. Verified they fail when the new row is
      removed.
      PLN (Algolia) authored
    • gig-log: the MIDI lens asks the kernel who it is wired to, on a clock · 187b17ce
      Zero control moves were logged for the whole 2026-09-24 set, and every
      cheap check said fine. Two independent faults, both found by looking:
      
      THE CHECK RAN ON TRAFFIC, NOT ON A CLOCK. The re-resolve lived inside
      'for line in self._proc.stdout', so it only ran after a line ARRIVED. A
      bound port that goes quiet blocks that iterator forever, and aseqdump does
      not exit — so the loop parked on a blocking read from 01:01 through a
      94-minute set. Now a timed select: the liveness question is asked every
      UPGRADE_S whether or not the board said anything. The old test asserted
      this the other way round, on the reasoning that running in the read loop
      was 'cheap by construction: an idle surface costs nothing at all'. An idle
      surface cost the gig. 'Costs nothing when idle' and 'notices when idle'
      are the same code path and only one can be true.
      
      A NAME AND AN ADDRESS ARE BOTH USELESS HERE. 38 hours after the set, 133:0
      'ParVagues LCXL3' still resolved to that exact address under that exact
      name — so neither 'is it gone' nor 'did it move' could see anything. What
      happened is the third thing: lcxl3-driver restarted repeatedly between
      00:34 and 10:27, and the kernel handed each new sequencer client the same
      number. Our subscription died with the old one. Verified live: client 133
      was 'Connecting To: 129:0, 14:0' and gig-log's aseqdump, client 135, was
      not in the list.
      
      So subscription_alive() asks the kernel's own wiring table, matching the
      CHILD's pid — the same discrimination lcxl3-driver's board_link_alive
      makes, for the same reason. It reads False on the real dead link and True
      on SuperCollider, which is wired to the same port; 'cannot tell' is None,
      never False, so a box where aconnect differs does not thrash.
      
      gig-log status now names the binding, its subscription state and the
      session's control-event count. It used to answer 'is it writing', which
      was true and useless on the night the file grew at 1 Hz with nothing in
      it. A dashboard that shows LIVE while a whole lens is dead is the green
      check this rig has learned not to trust.
      
      Note: tools/tests/test_one_tidal_parser.py fails at HEAD too, on
      fold-orbits.py. Untouched here.
      PLN (Algolia) authored
    • check-mix: audit the record button, and let the gate say NO-GO on it · 6a94b47b
      Twelve tools audit this rig's faders, mutes, routing, ghosts, preload,
      samples and thermals. None audited arm state, and on 2026-09-24 Tidal 08
      sat at rec-enable=0 for the whole set — alone among the twelve — so Ardour
      never created the file. Eleven stems, and that orbit was in all fifteen
      tracks' control maps.
      
      Arm lives in the same mixer, in the same XML, under the same verb, so it
      goes in check-mix rather than a new screw. Only the Tidal NN routes block:
      Master has no rec-enable at all, which is how Ardour says 'bus' — reported
      as n/a, because sending PLN hunting a button that does not exist is worse
      than saying nothing. Mic and Keys are reported, never gated.
      
      A new 'ardour armed' probe carries its own fix (click record + Ctrl+S, not
      raise + Ctrl+S), and 'ardour faders' now passes --no-arm so neither probe
      reports the other's failure. Run against the session as it stands, the gate
      says NO-GO and names Tidal 08.
      
      The disk caveat cuts both ways and the docstring says which: this proves
      the session BOOTS armed. It is exactly right for the Tidal 08 case, where
      saved and live agreed all night. Nothing here reads Ardour's OSC surface —
      that surface reported arm:false for all 76 minutes it was recording.
      PLN (Algolia) authored