Commit d2c1388a by PLN (Algolia)

docs(armada): logs 030 + 031 — the half-connected rig, and the corpus one column behind

030: why "it should auto-connect" did not. The LCXL was dark because the loaded
track was a 0-byte file (the surface was right); the orbits were a real
regression, tidal-ardour-autoroute disabled, with 1-4 and 10-12 surviving as
leftovers so it read as "half works". Root cause: both reconcilers lived only in
$HOME, unversioned. Now one authored inventory, three consumers.

031: the #94 remap finished corpus-wide, 169 files all shift +1. Two systemic
bugs that only a corpus-wide run could surface — a depth-blind chain-step
boundary, and commenting out a $-step that was a partial application operand
(an arity bug no paren checker can see). Verified by diffing before/after SETS,
not counts.
parent 406a4610
---
log: 030
title: "The rig that only half-connected"
date: 2026-08-23
task: "n/a (found live, at launch)"
tags: [rig, tooling, systemd, midi, infra]
shareable: true
---
## Cap (what & why)
PLN launched the rig and reported two faults in one breath: the LCXL was dark
except three yellow knobs, and Tidal's twelve orbits weren't reaching Ardour's
`Tidal 01..12`. Both had a script that was *supposed* to make them automatic.
The question wasn't "how do I fix it" but "why doesn't this hold by itself?"
## Manœuvre (how)
Checked the instruments before the rig — the habit that has paid three times now.
The LED watcher was healthy and *painting correctly*: its log said
`rose_eternelle.tidal → 3 controls`, and that file was **0 bytes**. The surface
was telling the truth about an empty track. Not a MIDI fault at all.
The orbits were a real regression. `tidal-ardour-autoroute.service` was
`disabled` + `inactive`. Orbits 1-4 and 10-12 were still wired as *leftovers*
from an earlier session, so it presented as "half works" rather than "the
service is off" — the shape that sends you debugging audio instead of units.
Then the root cause, one layer up: both reconcilers existed **only** as plain
files in `~/.local/bin` and `~/.config/systemd/user`, while every other unit
symlinks into `tools/`. Nothing version-controlled ⇒ a `disable` was
unrecoverable and invisible to `git status`.
## Prise (findings / artifacts)
- `4d9f105` — both units + scripts into the repo, `$HOME` symlinked back.
- `406a461``tools/rig_units.py`, THE inventory. Three consumers, no
re-typing: the Bridge imports `SERVICES`, `gig-up.sh` calls `--ensure`, and
`tools/parvagues-rig.target` is *generated*. One handle:
`systemctl --user start parvagues-rig.target`.
- 24/24 orbit links restored; 6/6 units enabled-per-policy and active.
- The list had been in **three** places (Bridge table of 5 rows, gig-up loop of
4 names, each unit's `[Install]`) — and the Bridge's copy silently omitted
`parvagues-bridge`.
## Sel (the shareable learning)
**`enable` and `start` are different fixes, and confusing them is how a service
stays dead for weeks while every pre-gig check passes.** `gig-up.sh` only ever
ran `start`. A start repairs tonight and changes nothing about the next login.
Three smaller ones, each earned:
- `is-enabled: linked` reads like a healthy word and means **not enabled**.
- `systemctl disable` on a *linked* unit **deletes the symlink itself**, leaving
a running-but-unresolvable unit.
- A unit file symlinked into a **feature branch** vanishes on `git checkout` — I
broke the live rig's symlinks doing exactly that. Live infra belongs on master.
## Hameçon (hook)
"Three yellow knobs on a dark controller. I went looking for a MIDI feedback
loop and found a zero-byte file — the board was telling me the truth and I
almost called it a liar."
## Sillage (what it unlocks)
Adding a service to the rig is now one row in `rig_units.py`. `--status` gives a
one-line answer to "why isn't it wired", which is the check I should have run
first instead of reading the audio graph.
---
log: 031
title: "The corpus was one column behind"
date: 2026-08-23
task: "#94 (remap phase 2, widened from the setlist to the corpus)"
tags: [surface, tooling, migration, haskell, corpus]
shareable: true
---
## Cap (what & why)
PLN: *"so many tracks still having ^42 instead of ^41 for kicks its confusing."*
Remap phase 2 column-aligned the board so column N IS orbit N — but it was
scoped to the **fourteen** files of the OPAL set. Everything else still spoke
the layout from when gMask held CC 41, where an orbit's gate row started one
column late: d1 → ^42, d2 → ^43.
## Manœuvre (how)
`button-column-audit.py` found **169** such files and, decisively, **all of them
shift +1** — one layout generation, not a scatter of typos. That made it a
migration rather than a cleanup.
Ran the migration corpus-wide, which is a genuinely different test from running
it on a setlist, and it refused two batches rather than writing bad Haskell.
Both refusals were real bugs:
1. **The chain-step boundary was depth-blind.** A step ends at the next line
starting with `$`/`#` — but a `$` inside an open paren is a *continuation*.
Multi-line bodies truncated after one line, left depth 1, and the balance
check refused the file. 10 of 169 were unmigratable. Also: the line
mentioning `^NN` is often *not* the step start, so the fix walks back to the
nearest enclosing step whose segment both balances and contains the hit.
2. **Commenting out a `$`-step can under-apply the step above it.** Found only
by `silent-eval`: 4 files stopped compiling while 165 were fine.
$ midiOff "^57" (mask "<[t f]!0 ...>") -- a FUNCTION, still hungry
$ stack [ ... ] -- ITS ARGUMENT
Comment the `stack` and GHC says *"midiOff is applied to too few arguments"*
and the whole do-block dies — all twelve orbits — which is far worse than the
cross-orbit trigger the comment-out was avoiding. `#`-steps stay commentable;
`$`-steps are now annotated and left live, with the FIXME saying so.
## Prise (findings / artifacts)
- `b5ad8b6` on `claude/button-column-migration` — 169 tracks + both tool fixes.
- Layout-shifted files **169 → 0**. Own-column button refs **35.7% → 78.6%**.
- pvlint **30 → 26 errors**; warnings 996 → 1041 (the +45 are annotated-but-live
`$`-steps honestly reporting a cross-column reach).
- Non-compiling tracks **17 → 17, identical sets**. Silent orbits: the same 19,
same tracks. `fix-button-roles`: 3 clean rewrites applied, then 0.
- `--plan` re-run: **0 moves.** Converged in ≤2 passes.
## Sel (the shareable learning)
**Diff the SETS, not the counts.** "17 before, 17 after" means nothing until you
know it is the *same* 17 — that comparison is the only thing that proved the
final tree introduced zero new breakage, and it's what caught my first attempt
breaking 4 files while the totals looked plausible.
Corollaries worth keeping:
- **Measure risk on the set; exercise a rewriter on the whole corpus.** The rare
shapes only exist out there. Both bugs were invisible to 14 files.
- **A refusal that costs a batch is cheap; a rewrite that compiles and is wrong
is not.** Both bugs were findable *because* the tool bailed instead of guessing.
- **Errors down, warnings up can be the honest outcome.** I'd rather 45 warnings
saying "this still fires across columns" than silence.
- Line numbers move when you insert FIXMEs — strip `(line N)` before diffing
verdicts, or everything looks changed.
## Hameçon (hook)
"A paren-balance checker saved me from writing broken Haskell into 169 files.
Then the type checker caught the bug the paren checker couldn't see — commenting
out one line had quietly starved the function above it."
## Sillage (what it unlocks)
`^41` is d1's kick gate everywhere now, so muscle memory transfers between
tracks. Both `migrate-columns.py` fixes benefit any future `.tidal` rewriter.
Remaining: 118 same-role button pairs waiting on gSel (#54), and 17
pre-existing non-compiling tracks nobody has triaged — see memory
`project_button_migration_debts`.
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