Commit e47361ca by PLN (Algolia)

fix(gate): two typos silencing a track, and the boot check that lied about them

Three of gig-up's four standing NO-GO blockers were one file and one buffering
bug. Cleared them, verified each by planting the failure back.

**something_about_drums.tidal — two typos, both silent by design of the tooling**

  :13  `(<| "~ <s s <s!3 ~> <~!7 [~ s]>>")d1` — a stray `d1` pasted straight
       after the closing paren, no operator, no newline. GHC reported it as
       "Variable not in scope: d1" at 13:56, and because a parse error takes the
       whole do-block with it, the reported line belonged to the GROUP, not to
       the break. The d2 block never ran.
  :81  `d8 $ gF1 $ gM1` — d8 copy-pasted from d1's block without updating the
       family macro. The authored map puts d8 in gM2 with d2 and d3, so d8 was
       wired to the wrong mute family: pressing d8's mute would have taken d1
       with it, live, and nothing about the code looked wrong.

  after: `silent-eval --seeded` OK (every declared orbit emits events),
  `fix-mute-roles --check` WOULD REWRITE: 0 lines, `pvlint (setlist)` ok.

**check-boot-blocks.py — the check was the third defect**

`tools/check-boot.sh` run alone passed clean, every line green, while gig-up
called the same check FAIL. The difference was not the rig. Under gig-up the
checker crashed:

    ValueError: invalid literal for int() with base 10: '6C1o'

`'6C1o'` is two output lines interleaved character-wise. The cause is in the
capture: stdout and stderr are deliberately ONE merged pipe (separate capture
would put every marker before every error and attribute each error to the last
block — the mis-attribution this tool exists to prevent), but GHC block-buffers
stdout while writing errors to stderr unbuffered. Two writers, one pipe, so a
marker could be flushed into a chunk a stderr write had already cut into. Rare
enough to look like a phantom, and it presented as "your boot helpers are
broken" — the rig's most alarming failure — when nothing was wrong.

  - `hSetBuffering stdout LineBuffering` is now the script's first statement, so
    each marker is one atomic write, in order.
  - Markers are self-delimiting (`@@PVBLOCK i start @@`) and matched by regex, so
    a torn one CANNOT parse as intact.
  - A torn marker, or a parse error that cannot be placed, is now INCONCLUSIVE
    (rc=2, "rerun") rather than a crash — and rather than a silent skip, which
    would have hidden a real error behind a green verdict.

Verified: 3 consecutive clean runs, then a planted unbalanced paren appended to
the block starting at :581 was caught and blamed on exactly that block
(rc=1, "block 16 (starts near bt.hs:581) is NOT one statement"). A checker that
cannot catch a planted bug is worth nothing, so that test is the one that counts.

gig-up: 4 blockers -> 2 (surface-grid migration debt, fader baseline).
parent e9d16314
......@@ -10,7 +10,7 @@ d1 $ gF1 $ gM1 -- Kick
# gain 1.2
# gain 1.8
d2 $ gF1 $ gM2 -- Snare melancolie fromagere
$ midiOff "^42" (<| "~ <s s <s!3 ~> <~!7 [~ s]>>")d1
$ midiOff "^42" (<| "~ <s s <s!3 ~> <~!7 [~ s]>>")
$ midiOn "^42" (<| "~ s ~ s*<1!3 2>")
$ "[drumtraks:9]"
# gain 1.45
......@@ -78,7 +78,7 @@ d5 $ gF2 $ gM3 $ slow 2 -- V2 -- TODO : Feedback: reverb? un peu creux?
# modIndex (range 0 2 "^54")
# room 0.4
# gain 1.7
d8 $ gF1 $ gM1
d8 $ gF1 $ gM2
$ midiOn "^92" (ply "2 <2!3 4>")
$ midiOff "^60" (mask "f(4,8)" . chop 8)
$ midiOn "^36" (# "jungle_breaks:45")
......
......@@ -53,11 +53,13 @@ Exit 0 = every block parses as a single statement.
from __future__ import annotations
import subprocess
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
MARKER = "@@PVBLOCK"
MARKER_END = "@@" # closes the marker so a spliced one cannot parse as intact
def pulsar_blocks(text: str) -> list[tuple[int, str]]:
......@@ -84,11 +86,18 @@ def pulsar_blocks(text: str) -> list[tuple[int, str]]:
def build_script(blocks: list[tuple[int, str]]) -> str:
parts = []
# Line-buffer stdout FIRST. stdout and stderr are deliberately one merged pipe
# (see the comment in main()), and GHC block-buffers stdout while writing
# errors to stderr unbuffered — so a marker could be flushed in a chunk that
# a stderr write had already cut into. On 2026-09-05 that produced the marker
# fragment '6C1o' (two lines interleaved) and an unhandled ValueError, which
# made gig-up report "boot helpers FAIL" while check-boot.sh run alone passed
# clean. Line buffering makes each marker one atomic write, in order.
parts = ["import System.IO", "hSetBuffering stdout LineBuffering"]
for i, (start, chunk) in enumerate(blocks):
if not chunk.strip():
continue
parts.append(f'putStrLn "{MARKER} {i} {start}"')
parts.append(f'putStrLn "{MARKER} {i} {start} {MARKER_END}"')
if chunk.startswith(":set") or chunk.startswith(":"):
parts.append(chunk) # ghci directives are not wrappable
else:
......@@ -123,14 +132,35 @@ def main(argv: list[str]) -> int:
# Attribute each parse error to the block whose marker preceded it.
cur: tuple[int, int] | None = None
bad: dict[tuple[int, int], list[str]] = {}
torn: list[str] = []
marker_rx = re.compile(re.escape(MARKER) + r"\s+(\d+)\s+(\d+)\s+" + re.escape(MARKER_END))
for ln in r.stdout.splitlines():
if MARKER in ln:
i, start = ln.split(MARKER)[1].split()[:2]
cur = (int(i), int(start))
m = marker_rx.search(ln)
if not m:
# A torn marker means we have LOST TRACK of which block we are in.
# Carrying on would attribute the next parse error to the previous
# block — the exact mis-attribution this tool was built to avoid —
# and skipping it silently could hide a real error behind a green
# verdict. So record it and report inconclusive below.
torn.append(ln.strip())
cur = None
continue
if "parse error" in ln and cur is not None:
cur = (int(m.group(1)), int(m.group(2)))
continue
if "parse error" in ln:
if cur is None:
torn.append(ln.strip()) # an error we cannot place
else:
bad.setdefault(cur, []).append(ln.strip())
if torn:
print("check-boot-blocks: INCONCLUSIVE — the ghci transcript could not be "
f"read cleanly ({len(torn)} unplaceable line(s)); rerun.", file=sys.stderr)
for t in torn[:5]:
print(f" {t!r}", file=sys.stderr)
return 2
if not bad:
print("check-boot-blocks: OK — every block parses as a single statement.")
return 0
......
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