---
log: 013
title: "The triangle that checks itself"
date: 2026-06-06
task: "#46–#49 triangle reconciler + tests; #47 model; #48 viz"
tags: [tooling, salvage, epistemics, testing, catalog]
shareable: true
---

## Cap (what & why)
"Is our analysis of ParVagues tracks actually any good?" To answer it instead of
hoping, we built the **triangle**: reconcile every track across three corners —
**A** the `.tidal` score (ground truth from code), **C** the site's gig metadata
(`tracks.json` ingredients), **B** the Ardour recordings — and *light the cells*
where they agree or fight. One view, a measured coverage number.

## Manœuvre (how)
`build_catalog_view.py` joins the corners (73 distinct tracks) and computes an
A↔C agreement per track, reusing `tidal_score`'s own extractor so both sides parse
identically (DRY). Modeled the output as a pydantic `CatalogView` (single source of
truth → generated TS), validated on emit. Standalone `triangle.html` (Ship's Bridge
palette) renders it, served by `serve.py`. Then we did what PLN asked — **tested it
mechanically**: 28 pytest cases (parser regressions, the agree taxonomy, real-corpus
invariants, coverage regression guards, the DRY contract).

## Prise (findings / artifacts)
- 73 tracks · score parsed 73/73 · **A↔C: 47 agree · 5 partial · 2 conflict · 3 divergent · 16 no-claim**.
- **Recording: 40 have a candidate take, 33 unrecorded. EDA coverage = 1/63 takes** — the real gap, now a red number on screen.
- Two flagged "conflicts" turned out to be **our tooling's fault, not the metadata's**:
  - *Burn this Book* — score parsed EMPTY because the whole track is an indented
    `do`-block and the parser only read column-0 `dN`. `drums_nes`/`cpluck` were there
    all along. Fixed the parser; it now agrees.
  - *Blue Gold* — `ccc0.tidal` (a 30-min workshop ébauche from a given kit) genuinely
    has none of the claimed `suns_keys`/`suns_guitar`. The site's `tracks.json` had
    **fallen back to the first `.tidal` in the `ccc/` folder** because the real file
    (`collab/mousquetaires/blue_gold.tidal`) isn't there. A wrong *link*, not wrong
    metadata. New `divergent` level + alias-fragmentation flag catch exactly this.
- Files: `build_catalog_view.py`, `tidal_score.py` (indented-dN), `models.py`
  (`CatalogView`), `tools/gen_ts_types.py`, `tests/` (28 green), `triangle.html`.

## Sel (the shareable learning)
The cardinal sin our first pass committed: **`agree()` reported "I couldn't read the
score" as "the score disagrees."** A blind parser that blames the data is worse than
no check at all — we'd have "corrected" correct metadata. The fix is epistemic, not
just code: separate *unparsed* / *divergent* / *conflict*, and never let a parser miss
masquerade as a finding. Katana-first: sharpen the blade before you cut.

## Hameçon (hook)
We built a tool to fact-check the catalog. The first three things it flagged were its
own bugs. The metadata was righter than the machine — and 67 of 73 ParVagues tracks
hide their sounds behind `gFunc` abstractions, so "the score is ground truth" is true,
but *reading* it is the hard part.
