Skip to content

fix: chunk Demucs inference and stop boot-time memory spikes - #4

Merged
slittycode merged 1 commit into
mainfrom
fix/demucs-memory-and-runtime-bloat
Mar 22, 2026
Merged

fix: chunk Demucs inference and stop boot-time memory spikes#4
slittycode merged 1 commit into
mainfrom
fix/demucs-memory-and-runtime-bloat

Conversation

@slittycode

Copy link
Copy Markdown
Owner

Summary

  • Chunked Demucs inference: Replaces whole-song HDemucs forward pass with overlapping ~8s segmented inference. Peak memory for a 5-min track drops from 15+ GB → ~2.5 GB. Separation quality is identical — this matches how the original Demucs library works internally.
  • Fix stale job requeue on boot: recover_incomplete_attempts() was marking interrupted measurement and symbolic jobs back to queued, causing heavy ML subprocesses (torch + Demucs + torchcrepe) to fire on every server restart. Now marks them interrupted to match the existing interpretation stage behaviour.
  • Cleanup committed bloat: Removes 3 .runtime.backup.* directories and venv.broken.* that were inadvertently committed (~5.7 GB of test artifacts and stale data).

Test plan

  • _demucs_chunked_inference tested on 15s clip and full 5-min FLAC — both produce valid stems
  • Backend unit tests pass: python -m unittest discover -s tests (176 tests, 0 failures)
  • test_recover_interrupted_attempts updated to assert interrupted for all three stage types

🤖 Generated with Claude Code

- Replace whole-song HDemucs inference with overlapping ~8s chunked
  inference (_demucs_chunked_inference). Peak memory for a 5-min track
  drops from 15+ GB to ~2.5 GB; quality is identical as this matches
  the original Demucs library's default segmented approach.

- Fix recover_incomplete_attempts() to mark stale running jobs as
  'interrupted' instead of requeueing them as 'queued'. Previously,
  any measurement or symbolic attempt stuck as 'running' at shutdown
  would auto-fire a heavy ML subprocess on every server boot.

- Update test_recover_interrupted_attempts to match new interrupted
  semantics for measurement and symbolic stages.

- Remove committed runtime backups and broken venv (~5.7 GB):
  .runtime.backup.20260319-* (3 dirs), venv.broken.20260319-153925

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@slittycode
slittycode merged commit 6c6edb9 into main Mar 22, 2026
1 of 2 checks passed
@slittycode
slittycode deleted the fix/demucs-memory-and-runtime-bloat branch March 22, 2026 12:31
slittycode added a commit that referenced this pull request May 13, 2026
…g completion (#18)

* feat: Phase 1.A–1.D depth additions, Track 2 audit, decision-gate v3 + catalog completion

Substantial measurement-depth pass on the ASA Phase 1 pipeline plus a
trustworthy multi-model decision-gate harness. Default Gemini model
(gemini-2.5-flash) now PASSes the stem-aware gate on the Vtss bench
track; Live 12 catalog completion drops UNKNOWN_PARAMETER 16 → 1
across all v3.1 snapshots.

## Depth additions (Phase 1.A – 1.D)

- Phase 1.A cheap wins (5): per-frame short-term LUFS curve, per-frame
  7-band spectral balance time series, instantaneous tempoCurve,
  per-second stereo correlationCurve, 256-point arrangement
  noveltyCurve (up from 64).
- Phase 1.B stem-first refactor: per-stem subtree under
  ``stemAnalysis.{drums,bass,other,vocals}`` with the same shape as
  the full-mix analyzers. Phase 2 can now cite element-specific
  measurements.
- Phase 1.C #0 real time-signature detection (onset-accent
  autocorrelation, replaces the always-4/4 fallback).
- Phase 1.C #1 per-band transient density across the 7
  spectralBalance bands (kick / hi-hat density anchors).
- Phase 1.C #2 per-band stereo correlations (Utility-width-per-band
  recommendations).
- Phase 1.C #3 ``grooveDetail.perDrumSwing.{kick,snare,hihat}`` from
  the mid-band beat-loudness signal.
- Phase 1.C #4 snareDetail / hihatDetail band-limited drum character
  (hits, attack sharpness, body/snap energy ratio, decay).
- Phase 1.C #5 saturationDetail (clipped sample count, peakRatio95to50,
  rmsToPeakRatioDb, saturationLikely).
- Phase 1.C #6 32nd-note sidechain resolution + envelopeShape32
  (legacy envelopeShape preserved via max-pairing downsample);
  pumpingRate now four-valued including thirty_second.
- Phase 1.D #5 RT60 reverb per stem with perBandRt60 (low / lowMids /
  highMids / highs) + preDelayMs via Schroeder-equivalent slope fit.
  Wired through stemAnalysis.{stem}.reverbDetail.

## Track 2 audit pass 1 + measurement-quality fixes

- New ``scripts/audit_pass1.py`` harness — structured 12-section
  markdown audit comparing real-track Phase 1 measurements against
  declared ground truth and sanity rules.
- Bug fix: ``bassDetail.averageDecayMs`` was 0 ms on real bass
  material (Codex PDF finding reproduced). Root cause: decay loop
  searched from onset rather than peak, and checked the raw
  oscillating waveform rather than an envelope. Fixed with a 20 ms
  RMS envelope + peak-anchored search. Synthetic validation: 350 ms
  theoretical → 346 ms measured; 35 ms → 37 ms. Real-track Vtss:
  0 ms (FAIL) → 66–125 ms (PASS).
- Bug fix: ``vocalDetail.hasVocals = true`` false positive on
  instrumental synth content. Added temporal-formant-stability check
  (sustained synth leads have near-static "formants"; real vocals
  shift formants 100+ Hz with syllables); tightened formant tolerance
  200 → 100 Hz; raised threshold 0.45 → 0.55. No-stem run on Vtss now
  correctly reports hasVocals=false at conf 0.48.
- Pre-existing import bugs in ``analyze_segments.py``:
  ``_compute_stereo_metrics`` (from dsp_utils) and ``Counter`` (from
  collections) were undefined. ``segmentStereo`` + ``chordDetail``
  now populate correctly.

## Phase 2 contract + validator + decision gate

- Phase 2 prompt v3: per-stem path enumeration, citation contract
  (``phase1Fields`` required on every recommendation), "Phase-2
  self-reference is not a citation" rule, dedicated sections for
  snare/hi-hat character, saturation, sidechain envelope, reverb
  detail.
- Phase 2 schema (``server_phase2.py``): ``phase1Fields`` array
  required on ``mixAndMasterChain``, ``abletonRecommendations``, and
  ``secretSauce.workflowSteps`` items.
- Frontend validator (``phase2Validator.ts``) overhaul:
    * 8 violation types (NUMERIC_OVERRIDE, GENRE_IGNORES_DSP,
      BOUNDS_VIOLATION, MISSING_CITATION, TRIVIAL_CITATIONS,
      NEW_FIELD_UNCITED, LOW_CONFIDENCE_NOT_HEDGED, RECOMMENDATION_SALVAGED).
    * Bidirectional + wildcard ``pathCoversTracked`` matcher so leaf
      citations satisfy parent tracked paths and ``stemAnalysis.*``
      wildcards work.
    * 19 tracked Phase 1.A + Phase 1.C/D field paths (was 7).
- Decision-gate vitests (4): single-, multi-, real-, and stem-aware
  multi-model comparators that read ``/tmp/decision_gate_*.json``
  snapshots and emit per-model markdown reports.

## Live 12 catalog completion (UNKNOWN_PARAMETER closure)

- Auto Filter: ``parameterAliases`` flat-map
  ``{"Filter Resonance": "Resonance", "Filter Frequency": "Frequency"}``.
  Closes the instrument-side long-form naming bleed.
- Glue Compressor: ``allowedParameters`` expanded by 4
  (Range, Sidechain, Sidechain Gain, Sidechain Dry/Wet).
- ``_validate_phase2_catalog_entry`` resolves aliases before the
  membership check; startup-time loader validates the new
  ``parameterAliases`` shape.
- New ``Phase2CatalogValidationTests`` (11 cases) covers alias
  resolution, expansion, scoping per-device, and negative cases.
- New ``scripts/replay_catalog_validation.py`` — no-Gemini-spend
  harness that re-validates v3.1 snapshots against the live catalog.
  Verdict: UNKNOWN_PARAMETER drops 16 → 1 across all 8 snapshots;
  the remaining hit is ``EQ Eight / "Band 8 Filter Type"`` (separate
  device, v3.2-target).

## Test state

- Backend: 374 tests pass (was 363; +11 ``Phase2CatalogValidationTests``).
- Frontend: 296 tests pass (was 286; +10 validator bidirectional /
  wildcard tests).
- UI lint clean (``tsc --noEmit``).

## Out of scope / follow-ups (documented in SESSION_BLITZ + plan)

- Pro-model citation-depth iteration (3-pro and 3.1-pro still cite
  far less Phase 1.C/D than 2.5-flash).
- gemini-3.1-pro-preview ``DROPPED_INVALID_ARRAY_ITEM`` salvage
  regression seen in earlier v3 runs.
- Compressor "Sustain", "Ableton Project Settings", "Mixer" device
  hits — v3.2 prompt-fix targets.
- Phase 1.D #1 madmom integration (prototype proven, install path
  documented at ``.runtime/reports/madmom_install_prototype_2026-05-12.md``).
- Phase 1.D #2 chord progression deeper labelling.
- ``JSON_SCHEMA.md`` documentation of the catalog schema
  (``parameterAliases`` field).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(backend): add coverage for Phase 1.C analyzers, per-stem orchestrator, and dsp_utils (#19)

Closes the test-coverage gaps flagged in the PR review.

Backend:
- test_analyze.py: new BandDrumDetailTests, SnareDetailTests, HihatDetailTests,
  TransientDensityDetailTests, SaturationDetailTests, RunPerStemAnalysesTests.
  Each covers null/short/silent input guards, schema-field presence, and at
  least one behavioral assertion against a synthetic signal that should trip
  the detector. RunPerStemAnalysesTests covers stems=None, empty dict, all
  loads failing, mono+stereo success, partial-analyzer failure, and the
  stereo-load-skipped path.
- BassDetailTests: regression test for averageDecayMs > 30 ms on a synthetic
  decaying-pulse signal — guards the envelope-based fix from sliding back to
  the pre-fix sub-millisecond range.
- test_dsp_utils.py (new): direct tests for _pearson_corr,
  _downsample_lufs_array, _downsample_band_energies_curve,
  _compute_tempo_curve_from_ticks, and _compute_stereo_correlation_curve,
  with closed-form expected values for perfect/anti/orthogonal correlation,
  constant-input NaN handling, tempo-change detection, and the silent-sub
  None policy.

Docs / comments:
- JSON_SCHEMA.md: flag bassDetail.fundamentalHz as a ZCR approximation that
  biases upward on harmonic-rich basses; steer Phase 2 to prefer
  pitchDetail when stems are available and avoid narrow filter-Q moves
  within ±15 Hz of the value.
- phase2Validator.ts: rewrite the pathCoversTracked docstring so it
  matches what the code actually does (a wildcard token covers all longer
  citation paths under the same prefix; "stemAnalysis.*" does match
  "stemAnalysis.drums.spectralBalance").

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
slittycode added a commit that referenced this pull request May 16, 2026
…branch, store prune (#51)

* audit: chain-of-custody, recommendations-first IA, applied tracker, grammar post-process

Implements the design audit's prescription end-to-end (findings #1#15 + N1/N2/N7/N9/N10
+ follow-ups). The product's chain-of-custody promise — every Phase 2 recommendation
traces back to the Phase 1 measurement that justifies it — is now visually first-class
on every card, not a 9px monospace footnote.

What ships:

* Chain-of-custody (findings #2 + #3). New CitationBlock primitive renders a structured
  "GROUNDED IN" block above every Mix Chain / Patches / Sonic Element card with
  humanized labels (FIELD_LABELS map, ~50 entries + humanizeFieldPath fallback) and a
  ConfidenceBandBadge pill computed from the worst confidence among cited fields.
  Also retires GroundingBadgeList at the Track Layout site; segmentIndexes ride
  through a new extraRows prop.

* Recommendations-first IA (#1). MeasurementDashboard moved to the bottom of the
  results scroll; StickyNav's 9 measurement pills collapse to one trailing
  "Measurements" entry. Producers hit Style → Sonic → Mix Chain → Patches → Session
  before the measurement evidence.

* Header polish (#7, #9, #11). CPU meter removed (browser-tab CPU is misleading
  during backend analysis), "Local DSP Engine v1.6.0" eyebrow removed (resolves
  mobile 3-line wrap), Dense DAW Lab demoted from accent chip to quiet text link.

* Engineering vocab cleanup (#8 + N3/N4/N5/N8). New userLabels.ts service translates
  field paths to producer-readable labels at every render. Button labels renamed
  (Download data / Download report). FAMILY: NATIVE chip dropped from meta-badge rows.
  workflowStage prettified at the view-model layer ("Sound design" not "SOUND_DESIGN").
  AI Interpretation gated copy reworded ("AI interpretation isn't configured…" not
  "Developer kill-switch is off").

* Applied-recommendations tracker (#14 + #15). Per-card checkbox affordance + section-
  header "N of M applied" chip + localStorage persistence keyed by audio content
  SHA256. Producer can rename their file without losing their progress.

* Idle value-prop panel (#5). Replaces the 200px "NO SIGNAL DETECTED" canvas with
  a producer-readable explanation of what ASA does, with honest pacing copy
  (4–5 min Phase 2 wait, not 2–5 min).

* Patches group structure. Mirrors Mix Chain's emoji-eyebrow grouping (Drums / Bass /
  Synth / Master) so producers can jump to the bass patch without scanning 8 cards.

* Input Source collapse (N9). Post-analysis, the Input Source panel collapses to a
  compact summary with filename + duration + "Analyze new file" + "Adjust settings".
  Frees the top of the page for the results the user came for.

* AnalysisStatusPanel primary readout (#6). Stage diagnostic message promoted from
  9px footnote to the visual focus of the progress card. Pre-existing tone-aware
  fill (running / success / failed) preserved.

* Phase 2 failure mode (N1). Header subtitle derives from interpretation stage status
  (no more "PHASE COMPLETE" while INTERPRET still RUNNING/FAILED). StickyNav Phase 2
  pills render disabled with hover-reason when sections didn't populate. Retry button
  gated on error.retryable; non-retryable failures surface the error code inline.

* Misc audit follow-ups: BPM reconciled across exec card + Core Metrics tile (N2);
  Signal Monitor STANDBY canvas hidden when audio isn't playing, freeing ~160px (N7);
  StickyNav label "Device Chain" → "Sections" (N10, less ambiguous with Ableton's own
  effects-routing meaning); BASS group icon swapped from 🫧 → Lucide AudioWaveform
  (#13); toggle helper paragraphs switched from all-caps mono walls to sans-serif
  sentence case (#4 revised).

* Phase 2 grammar post-process (audit final round). The prompt instruction added
  earlier didn't take — Gemini still emits "by recreates / by absorbs / by shapes"
  3rd-person singular forms after "by" in role/reason text. Server-side
  _apply_phase2_grammar_fixes rewrites these to gerunds in-place on
  mixAndMasterChain[].reason, abletonRecommendations[].{reason,advancedTip}, and
  secretSauce.workflowSteps[].{instruction,measurementJustification}. Conservative
  regex (\bby \w{4,}s\b) + denylist guards against plural-noun false positives.

Test coverage:

* UI: 46 test files / 540 tests pass (was 39/422 before the audit). New service tests:
  userLabels (21), phase1Picker (25), citationBlock (12), appliedRecommendations (16),
  formatTrackDuration (12), interpretationSubtitle (10), workflowStagePrettifier (8),
  analysisStatusProgress (6), idleValuePropPanel + phase2NavReason. New DOM tests in
  analysisResultsUi.test.ts cover Track Layout citation, applied-checkbox flow, mix-
  chain citation rendering.

* Backend: 16 new unit tests in test_phase2_grammar_fix.py cover _to_gerund,
  _fix_by_gerund_in_text, _apply_phase2_grammar_fixes including the actual
  screenshot corpus (recreates → recreating, shapes → shaping, matches → matching,
  etc.). Full suite: 463 of 463 ASA tests pass; 1 unrelated pre-existing failure
  in tests.test_url_ingest predates this branch.

Visual verification: Playwright capture pass against a real 126s track confirmed
every surface (15 screenshots in /tmp/asa-shots-audit-final/). Phase 2 returned in
220s; localStorage round-trip verified on applied-checkbox toggles.

Documented limitations:

* The gerund rule is algorithmic — verbs requiring consonant doubling (control →
  controlling, submit → submitting) degrade to "controling" / "submiting". Still
  better than "by controls". Drop a hand-mapped exception into the module if
  observed in real output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Fix smoke tests for retired/renamed surfaces

CI surfaced 7 smoke test failures from this branch's audit changes that
hadn't been propagated to the smoke spec assertions:

* `tests/smoke/ui-details.spec.ts` × 3
  - `NO SIGNAL DETECTED` → `IdleValuePropPanel` (audit #5)
  - `JSON_DATA` / `REPORT_MD` button labels → `Download data` / `Download report`

* `tests/smoke/responsive-layout.spec.ts` × 4
  - `NO SIGNAL DETECTED` (×2) → `IdleValuePropPanel`
  - `CPU` text-presence checks removed; the two viewport-shape tests now
    assert just the model-selector responsive behavior (audit #11 retired
    the CPU meter; there's no element to assert)

* `tests/smoke/file-validation.spec.ts` × 1
  - `re-upload after results resets to file-selected state`: the test
    used `Remove File` (FileUpload component's affordance) to clear after
    results were visible. Post-N9 collapse, the Input Source panel
    replaces FileUpload with a compact summary card whose "↺ Analyze new
    file" button calls the same handleFileClear. Switched the test to
    target the new affordance.

Also updated the e2e exports spec for label consistency (not in the
failing CI job, but the same renames apply):

* `tests/e2e/phase1-exports.spec.ts`
  - `downloadTextArtifact(page, /JSON_DATA/i)` → `/Download data/i`
  - `downloadTextArtifact(page, /REPORT_MD/i)` → `/Download report/i`

Verified locally against the live stack: 45 of 46 smoke tests pass, 1
skipped (was unrelated). The previously-failing 7 are all green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* audit: nightly Phase-1 invariant + test guardrail (2026-05-14)

3 unit tests failing — all fixture-dependent live decision-gate
comparators that assert instead of skipping when Gemini snapshots
are absent. No Phase-boundary violations found.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address review findings — decision-gate skip, gerund exceptions, dead branch, store prune

- Skip decision_gate.{multi,real,stems}.live.test.ts when no /tmp snapshots
  are present (matches the existing decision_gate.live.test.ts pattern).
  Unblocks npm test on CI.
- _to_gerund: add _GERUND_IRREGULARS map for consonant-doubling verbs
  (controls→controlling, submits→submitting, runs→running, etc.) — English
  doubling is stress-conditional, not worth implementing algorithmically.
- _fix_grammar_in_record: drop the always-true conditional return and the
  unused `updated` flag.
- appliedRecommendations: bound the localStorage store to MAX_TRACKED_FILES=50
  (least-recently-updated wins eviction) so it can't grow without bound.

* fix: formatTrackDuration carries seconds boundary; drop dead IdleSignalMonitor

- formatTrackDuration: round seconds to total first, then derive mins/secs.
  Previously Math.round(seconds % 60) could yield 60, producing "0:60" for
  inputs like 59.5. New tests cover 59.5, 59.9, 119.5, 3599.7.
- Delete IdleSignalMonitor.tsx (replaced by IdleValuePropPanel; not imported
  anywhere). Strip the "kept for future use" comments per CLAUDE.md.

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
slittycode added a commit that referenced this pull request May 17, 2026
…esh (#57), nightly audit doc (#59)

* audits: nightly 2026-05-16 — 5 issues (4 test failures + 1 review item)

3 vitest decision_gate.*.live.test.ts failures (missing /tmp snapshot
gating), 1 Playwright upload-estimate-phase1.spec.ts timeout, and one
mixDoctor.estimatePlr fallback flagged for human boundary review. No
Phase 1 ground-truth mutations found.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(claude): add repo-layout, scripts map, Phase 3 UI entry, CSV recipe

Closes four orientation gaps in CLAUDE.md: a top-of-Architecture map
that flags advisory/, experiments/, docs/, and tests/ground_truth/ as
off-path; a Scripts-at-a-glance section covering scripts/ and
apps/backend/scripts/; a pointer from the Phase 3 paragraph to
SamplePlayback.tsx + sampleGenerationClient.ts; and a curl example
plus allowlisted field paths on the csv_export.py module entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add Layer 2 transcription evaluation guide

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(ui): stop Phase 2 results surface from leaking engine output

Removes four classes of broken/placeholder content that made the Phase 2
recommendations read as engine output instead of producer-facing advice
(audit finding #1).

1. Mix Chain card role text — `buildRoleSentence` used to fabricate
   "{stage phrase} by {verb}" by lowercasing the first letter of
   Gemini's `reason`. Because `reason` is a present-tense clause, every
   card produced ungrammatical splices like "Controls bass energy by
   ensures the extreme low-end mono…". Now renders the reason verbatim
   with a capitalized first letter and trailing period; HIGH-END cue
   suffix preserved as "(for …)".

2. Patch Framework `patchRole` — was a 7-key category-keyed fallback
   (`mapPatchRole`) that stamped duplicated placeholders ("Primary tone
   generator" on every SYNTHESIS card). Removed the field, the
   fallback, the JSX paragraph, and the contribution to the
   `inferProcessingGroup` text-concat. The category chip + per-card
   `whyThisWorks` already carry the bucket and explanation.

3. Patches/Mix Chain overlap — `buildPatchCards` now case-insensitively
   filters out devices that also appear in `mixAndMasterChain` so the
   Patches section stops re-listing chain devices. Synthetic fallbacks
   (Stereo Width, MIDI Clip Guide) are built from Phase 1 only and
   bypass the filter intentionally.

4. Interpretation Caution raw-JSON dump — the panel rendered
   `originalValue` verbatim, which for dropped recommendations is a
   JSON-dumped `AbletonRecommendation` (see `_stringify_warning_value`
   in `server_phase2.py`). Added `formatDroppedValue` helper that
   parses JSON-shaped values and renders a compact "device: X ·
   parameter: Y · value: Z" summary. Non-JSON strings pass through;
   invalid JSON falls back to a truncated raw string. Also resolves
   the `$Saturator` symptom — Gemini's hallucinated `$`-prefixed
   device name now surfaces as a readable summary line instead of
   leaking through a raw JSON dump.

5. System Diagnostics dev-only leak — `validateNewFieldCoverage`
   emits "Phase 1 field 'X' is present… this warning is benign"
   coverage signals meant for the engine team, not producers. Added
   optional `audience?: 'dev' | 'user'` to `ValidationViolation`,
   marked `NEW_FIELD_UNCITED` as dev, and updated
   `Phase2ConsistencyReport` to filter dev-audience violations from
   the rendered table AND from the header counts (so the header
   doesn't read "5 warnings shown" above an empty table). The
   underlying `ValidationReport` still carries every violation for
   tests and offline analysis.

Tripwire note: the backend grammar fix
`_apply_phase2_grammar_fixes` in `server_phase2.py` only runs on the
legacy `/api/phase2` endpoint, not the analysis-runs path the UI
uses. After this change it becomes a redundant no-op against the new
render shape; left untouched.

Tests: 16 new unit cases across viewModel, UI rendering, validator,
and consistency report. `npm run verify` passes (lint + 565 unit
tests + build + 44 smoke tests).

Live UI verified against saved run 855f1376… — all nine text-leak
probes (`by ensures`, `by ducks`, `by generates`,
`Primary tone generator`, `Texture and movement stage`,
`Stereo placement stage`, raw `\$Saturator`, `this warning is
benign`, "is present in the measurement payload but no Phase 2…")
return false.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): surface primary citation in collapsed card headers

Audit Finding #3 — "the chain-of-custody citation, the product's whole
differentiator, renders as a footnote."

Adds a one-line `CitationHeadline` primitive inside the collapsed header
of every Mix Chain / Patch / Sonic Element card so producers see the
measurement evidence at scan-time without expanding. The expanded
CitationBlock stays in the body unchanged — this is the collapsed-state
companion, not a replacement.

Each headline reads `{label} {value} →` with the arrow pointing into
the device h4/h3 that follows in the title row, making the implicit
"measurement justified device" statement visible — exactly the audit's
literal example "Crest factor 8.2 dB → Glue Compressor."

Confidence sibling pills (Solid / Workable / Rough / Unreliable, same
ladder as ConfidenceBandBadge) ride alongside the value when the
primary field has a paired *Confidence sibling. This preserves the
chain-of-custody invariant: low-confidence measurements visibly hedge
in the collapsed view too, not just after expansion.

Implementation:
1. `CitationHeadline` added as a sibling export in
   `apps/ui/src/components/CitationBlock.tsx`. Shares the file's
   imports, the `CONFIDENCE_PILL_CLASSES` map, and the audit lineage.
   Composes existing pure helpers (`pickPhase1Value`,
   `formatCitedValue`, `humanizeFieldPath`,
   `pickPhase1Confidence`, `getConfidenceBand`,
   `formatBandPillLabel`) — no new resolver logic.
2. Mount points in `AnalysisResults.tsx`:
   - Mix Chain card header (~line 2056): between the title row and
     the role paragraph.
   - Patch card header (~line 2215): between the title row and the
     MetaBadgeList.
   - Sonic Element card header (~line 1912): between the title row
     and the summary paragraph.
3. Each mount is guarded by `card.phase1Fields.length > 0`. Cards
   without cited fields fall back to today's exact layout.

Live verification against saved run 855f1376… (the audit reproducer):
all 10 Mix Chain cards, 2 Patch cards, and 7 Sonic Element cards
render the headline correctly with appropriate confidence pills
(PUMPING STRENGTH 47% [ROUGH SKETCH 30%], SUPERSAW DETECTED
[WORKABLE DRAFT 78%], ACID BASS DETECTED [SOLID SCAFFOLD 99%],
NOTE TRANSCRIPTION 61% [WORKABLE DRAFT 61%], KEY C Minor
[SOLID SCAFFOLD 93%], TEMPO 145 BPM [SOLID SCAFFOLD 100%]).

Mobile (375px) verified — headlines fit, pills stay aligned, no
overflow.

Tests: 15 new cases — 10 in `tests/services/citationHeadline.test.ts`
(mirrors `citationBlock.test.ts`) covering label/value/arrow render,
null fallback, confidence-pill bands, value formatter parity,
typography hooks. 5 in `analysisResultsUi.test.ts` covering Mix Chain
/ Patch / Sonic integration, empty-fields fallback, and the
low-confidence Unreliable-band path.

`npm run verify` passes: lint + 575 unit tests + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): unify five confidence vocabularies onto the canonical band ladder

Audit Finding #4 — the producer used to see five competing vocabularies
for "how much should I trust this number?" — HIGH/MED/LOW pills on
Detected Characteristics cards, High/Moderate/Low chips on Confidence
Notes, bare CONF X% text on Key/Character metric cards, SCORE X.XX
badges on the BPM card (two render paths), and the canonical four-band
ladder used only by the Session Musician panel. The chain-of-custody
invariant in PURPOSE.md requires low-confidence measurements to produce
visibly hedged advice — five vocabularies fragment that signal so the
producer can't build a single mental model of trust.

This PR promotes the existing four-band ladder
(apps/ui/src/services/sessionMusician/confidenceBand.ts) to the canonical
primitive across every confidence surface. Frontend-only; Gemini still
emits HIGH/MED/LOW and the UI converts locally via a new
`toConfidenceBand` normalizer.

What changed:

1. New normalizer `toConfidenceBand(value)` in `confidenceBand.ts`
   accepts numeric 0-1 floats, 0-100 integers, Gemini string enums
   (HIGH/MED/LOW + High/Moderate/Low variants), and percent strings
   ("62%"). Returns the matching `ConfidenceBand` or null for
   unparseable input. The string enums map to band-mid values (HIGH→0.9,
   MED→0.6, LOW→0.3) so `formatBandPillLabel` reads as honest hedges.

2. `ConfidenceBandBadge` gains:
   - `variant: 'full' | 'compact'` — `compact` omits the copy paragraph
     so the pill can sit inline in card corners and metric-card footers.
     `full` (default) preserves the Session Musician panel behavior
     unchanged.
   - `band?: ConfidenceBand` override prop — when supplied, skips the
     `getConfidenceBand` round-trip. Useful when the caller pre-converts
     a string enum.

3. V1: Detected Characteristics cards (AnalysisResults.tsx ~1650) —
   bespoke HIGH/MED/LOW ternary pill replaced with `<ConfidenceBandBadge
   variant="compact" band={toConfidenceBand(item.confidence)} />`.
   `characteristicPillClass` helper kept alive only for the unrelated
   characteristic-name chips on the Character metric card (line 1000);
   flagged as a follow-up.

4. V2: Confidence Notes chips (AnalysisResults.tsx ~1187) —
   `toConfidenceBadges` viewModel return shape changed from
   `{ label, level }` (3-level legacy enum) to `{ label, band }`
   (canonical four-band ladder). `confidenceClass` helper deleted.

5. V3: Plain `CONF X%` text (3 sites) — replaced with
   `<ConfidenceBandBadge variant="compact" confidence={...} />` in:
   - AnalysisResults.tsx Key card footer
   - AnalysisResults.tsx Character card footer
   - MeasurementDashboard.tsx alternate Key card

6. V4: `SCORE X.XX` badges (2 sites) — replaced with band pills in
   AnalysisResults.tsx Tempo card and MeasurementDashboard.tsx Tempo
   card. `formatBpmScore` deleted from both files. Cross-Check ✓/✗
   StatusBadge in MeasurementDashboard preserved — that's an agreement
   signal (do multiple BPM detectors agree?), not confidence.

7. Dead code retired: `normalizeConfidenceLevel`,
   `parseConfidenceScalar`, `confidenceClass`, two copies of
   `formatBpmScore`. `ConfidenceLevel` type and
   `MelodyInsightsViewModel.confidenceLabel` kept alive — they drive
   non-pill key/value text rows in Sonic Element melody insights
   (flagged for follow-up migration).

3-level → 4-band mismatch documented as an intentional refinement: the
old `normalizeConfidenceLevel` mapped scalar 0.5-0.79 AND "medium" both
to "Moderate"; the new normalizer maps "medium" to 0.6 → workable
(consistent), and scalar 0.25-0.49 to "Rough sketch" (more granular
than the old "Low" bucket).

Tests: 20 new cases across 4 files —
1. `confidenceBand.test.ts` — 9 cases for `toConfidenceBand`
   (null/undefined fallback, 0-1 floats, 0-100 integers, all string
   enum variants, percent strings, NaN/Infinity, negative clamping).
2. `confidenceBandBadge.test.ts` — 4 cases for the compact variant
   and the `band` override prop (label-only when no confidence, tone
   override, Gemini-string routing).
3. `analysisResultsViewModel.test.ts` — rewrote the existing
   `toConfidenceBadges` shape assertion; added a `band: null`
   fallback case.
4. `analysisResultsUi.test.ts` — 5 integration cases proving each
   V1-V4 site renders band pill text and not the old vocabularies.

Live UI verified against saved run 855f1376… — 36 band pills visible
across the page (21 Solid scaffold, 7 Workable draft, 5 Rough sketch,
3 Unreliable). Zero `CONF X%` leakage. Detected Characteristics cards
show the chain-of-custody hedge working as designed (Detuned Supersaw
renders WORKABLE DRAFT in orange while the other 4 detections render
SOLID SCAFFOLD in green). Cross-Check ✓/✗ structurally preserved (the
saved run doesn't populate bpmAgreement, so it doesn't render, but the
code path is untouched).

`npm run verify` passes: lint + ~600 unit + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: refresh stale items against current architecture

CHANGELOG.md "Unreleased" was missing nine shipped features (Phase 3
audition samples, URL ingest, admin DELETE bypass, source-audio route,
CSV export, publicStatus, reassigned spectrogram, chain-of-custody audit
overhaul, BatchedBandpass centralization, hosted runtime foundation,
backend monolith split). Added them.

CLAUDE.md's analyze.py CLI example only listed four flags; expanded to
match the full surface (--standard, --pitch-note-only, --stem-dir,
--stem-output-dir, --pitch-note-backend). Expanded the Frontend key
service files list from 9 to 16 entries to cover the services that
landed during the audit overhaul (httpClient, sampleGenerationClient,
appliedRecommendations + userLabels, phase1Picker + phaseLabels,
audioFile, fieldAnalytics + diagnosticLogs, sessionMusician helpers).

apps/ui/AGENTS.md File Map likewise expanded from 4 to 14 service
entries against the same list.

docs/SAMPLE_GENERATION.md's "Snapshot integration" section claimed an
`AnalysisRunSnapshot.stages.sampleGeneration` shape that does not exist
in apps/ui/src/types/backend.ts — the snapshot tracks only measurement,
pitchNoteTranslation, and interpretation. Replaced the stale TS block
with a description that matches the actual on-demand artifact path
(consistent with the doc's own line 47).

apps/backend/AGENTS.md and apps/backend/ARCHITECTURE.md now flag
symbolic_extract.py as orphaned and broken — it imports a removed
BasicPitchBackend symbol from analyze.py, would raise ImportError at
load, and is not referenced from any other module. Slated for removal.

docs/history/README.md had `library-review-torchfx-2026-05-13.md`
listed twice; removed the duplicate.

docs/ARCHITECTURE_STRATEGY.md "Last updated" stamp bumped to May 2026
with a note that no shifts to the three-layer thesis or library
decisions accompanied the recent feature work.

Date stamps in apps/backend/AGENTS.md and apps/ui/AGENTS.md refreshed
to 2026-05-17.

https://claude.ai/code/session_01QEmFAUJSoRgTqPcFTfcnYW

* fix(ui): audit quick-hits bundle (StickyNav, Mix Chain, Mix Doctor, header)

Closes six items from the audit's "Quick hits (low ROI, real)" list.
Each is independent; bundled into one PR because they're all small
surgical fixes.

1. QH1 — StickyNav clipped "MEASUREMENTS" at 1440px. Was
   overflow-x-auto + min-w-max, which produced an invisible horizontal
   scroll (macOS hides scrollbars by default; last pill looked clipped
   with no scroll affordance). Switched to flex-wrap so pills flow onto
   multiple rows at narrow widths; whitespace-nowrap prevents intra-
   label wrap.

2. QH2 — Mix Chain card-number order badges removed. Cards are grouped
   by processing stage AFTER ordering, so the order numbers appeared
   out-of-sequence within each group ("1, 6, 8, 9 / 2, 4 / 5, 7 / 3 /
   10"), which read as a presentation bug. The visual sequence within
   each group is already meaningful; the badge added confusion without
   information. Dropped.

3. QH4 — Mix Doctor Band Diagnostics table now shows the target
   range alongside the optimal. Previously the column rendered only
   the optimal dB (e.g. "-22.0"), so two bands with similar Delta dB
   could land in different Issue buckets without the producer being
   able to see why. The Issue is determined by absolute thresholds
   (target.minDb / maxDb), not by the diff from optimal. Showing the
   range makes the verdict legible at a glance — Sub Bass -6.4 norm,
   range -16 to -8, exceeds the upper bound by 1.6 dB → TOO-LOUD;
   Low Mids -14.5 norm, range -26 to -14, sits at the boundary →
   OPTIMAL. Threshold logic itself was correct; this is a display fix.

4. QH5 — "Dense DAW Lab" link removed from the header. A prior audit
   pass had de-emphasized it to muted text, but the new audit re-
   flagged it for sitting in the primary flow's header without context
   for what it is. Route stays accessible via direct URL (the
   getAppViewHref helper is preserved for future re-introduction
   behind a settings menu, and main.tsx still routes to the
   DenseDawConcept component when activeView === 'daw-concept').

5. QH6 — header model selector visually demoted. Dropped the
   "Interpretation Model" label and shrank the styling from a bordered
   dropdown to a discreet text-secondary inline select. The audit
   flagged the prior styling as foregrounding an AI-model choice an
   intermediate producer has no basis to make. Selector stays
   accessible (smoke spec requires it visible at desktop viewport;
   `responsive-layout.spec.ts` and `upload-estimate-phase1.spec.ts`
   both guard the `phase2-model-desktop` testid) but now reads as
   background metadata. Aria-label + title attribute carry the long-
   form context for assistive tech and hover discoverability.

6. QH7 — Mix Doctor section title renamed from "MixDoctor" to
   "Mix Doctor". The audit flagged that the section header rendered
   as "Mixdoctor" with a lowercase 'd', inconsistent with every other
   section name. Root cause: `formatWord` in `utils/displayText.ts`
   case-normalizes single-token CamelCase to sentence case, so
   "MixDoctor" was rendering as "Mixdoctor". Two words ("Mix Doctor")
   matches every other section name's pattern (Style Profile, Project
   Setup, Track Layout, Mix & Master Chain, Patch Framework, etc.)
   and renders cleanly through the existing pipeline.

`npm run verify` passes: lint + ~600 unit + build + 44 smoke. Live UI
verified against saved run 855f1376… with probes confirming all six
fixes:
- StickyNav.overflow-x-auto wrapper gone (pills wrap to 2 rows)
- Zero Mix Chain order badges in #section-mix-chain
- "Target (range)" header present, "Target dB" header gone
- "Dense DAW Lab" text absent from page
- "Interpretation Model" label absent from page
- "Mix Doctor" section title present, "Mixdoctor" absent

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Oz <oz-agent@warp.dev>
slittycode added a commit that referenced this pull request May 17, 2026
…ladder (#56)

* docs(claude): add repo-layout, scripts map, Phase 3 UI entry, CSV recipe

Closes four orientation gaps in CLAUDE.md: a top-of-Architecture map
that flags advisory/, experiments/, docs/, and tests/ground_truth/ as
off-path; a Scripts-at-a-glance section covering scripts/ and
apps/backend/scripts/; a pointer from the Phase 3 paragraph to
SamplePlayback.tsx + sampleGenerationClient.ts; and a curl example
plus allowlisted field paths on the csv_export.py module entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add Layer 2 transcription evaluation guide

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(ui): stop Phase 2 results surface from leaking engine output

Removes four classes of broken/placeholder content that made the Phase 2
recommendations read as engine output instead of producer-facing advice
(audit finding #1).

1. Mix Chain card role text — `buildRoleSentence` used to fabricate
   "{stage phrase} by {verb}" by lowercasing the first letter of
   Gemini's `reason`. Because `reason` is a present-tense clause, every
   card produced ungrammatical splices like "Controls bass energy by
   ensures the extreme low-end mono…". Now renders the reason verbatim
   with a capitalized first letter and trailing period; HIGH-END cue
   suffix preserved as "(for …)".

2. Patch Framework `patchRole` — was a 7-key category-keyed fallback
   (`mapPatchRole`) that stamped duplicated placeholders ("Primary tone
   generator" on every SYNTHESIS card). Removed the field, the
   fallback, the JSX paragraph, and the contribution to the
   `inferProcessingGroup` text-concat. The category chip + per-card
   `whyThisWorks` already carry the bucket and explanation.

3. Patches/Mix Chain overlap — `buildPatchCards` now case-insensitively
   filters out devices that also appear in `mixAndMasterChain` so the
   Patches section stops re-listing chain devices. Synthetic fallbacks
   (Stereo Width, MIDI Clip Guide) are built from Phase 1 only and
   bypass the filter intentionally.

4. Interpretation Caution raw-JSON dump — the panel rendered
   `originalValue` verbatim, which for dropped recommendations is a
   JSON-dumped `AbletonRecommendation` (see `_stringify_warning_value`
   in `server_phase2.py`). Added `formatDroppedValue` helper that
   parses JSON-shaped values and renders a compact "device: X ·
   parameter: Y · value: Z" summary. Non-JSON strings pass through;
   invalid JSON falls back to a truncated raw string. Also resolves
   the `$Saturator` symptom — Gemini's hallucinated `$`-prefixed
   device name now surfaces as a readable summary line instead of
   leaking through a raw JSON dump.

5. System Diagnostics dev-only leak — `validateNewFieldCoverage`
   emits "Phase 1 field 'X' is present… this warning is benign"
   coverage signals meant for the engine team, not producers. Added
   optional `audience?: 'dev' | 'user'` to `ValidationViolation`,
   marked `NEW_FIELD_UNCITED` as dev, and updated
   `Phase2ConsistencyReport` to filter dev-audience violations from
   the rendered table AND from the header counts (so the header
   doesn't read "5 warnings shown" above an empty table). The
   underlying `ValidationReport` still carries every violation for
   tests and offline analysis.

Tripwire note: the backend grammar fix
`_apply_phase2_grammar_fixes` in `server_phase2.py` only runs on the
legacy `/api/phase2` endpoint, not the analysis-runs path the UI
uses. After this change it becomes a redundant no-op against the new
render shape; left untouched.

Tests: 16 new unit cases across viewModel, UI rendering, validator,
and consistency report. `npm run verify` passes (lint + 565 unit
tests + build + 44 smoke tests).

Live UI verified against saved run 855f1376… — all nine text-leak
probes (`by ensures`, `by ducks`, `by generates`,
`Primary tone generator`, `Texture and movement stage`,
`Stereo placement stage`, raw `\$Saturator`, `this warning is
benign`, "is present in the measurement payload but no Phase 2…")
return false.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): surface primary citation in collapsed card headers

Audit Finding #3 — "the chain-of-custody citation, the product's whole
differentiator, renders as a footnote."

Adds a one-line `CitationHeadline` primitive inside the collapsed header
of every Mix Chain / Patch / Sonic Element card so producers see the
measurement evidence at scan-time without expanding. The expanded
CitationBlock stays in the body unchanged — this is the collapsed-state
companion, not a replacement.

Each headline reads `{label} {value} →` with the arrow pointing into
the device h4/h3 that follows in the title row, making the implicit
"measurement justified device" statement visible — exactly the audit's
literal example "Crest factor 8.2 dB → Glue Compressor."

Confidence sibling pills (Solid / Workable / Rough / Unreliable, same
ladder as ConfidenceBandBadge) ride alongside the value when the
primary field has a paired *Confidence sibling. This preserves the
chain-of-custody invariant: low-confidence measurements visibly hedge
in the collapsed view too, not just after expansion.

Implementation:
1. `CitationHeadline` added as a sibling export in
   `apps/ui/src/components/CitationBlock.tsx`. Shares the file's
   imports, the `CONFIDENCE_PILL_CLASSES` map, and the audit lineage.
   Composes existing pure helpers (`pickPhase1Value`,
   `formatCitedValue`, `humanizeFieldPath`,
   `pickPhase1Confidence`, `getConfidenceBand`,
   `formatBandPillLabel`) — no new resolver logic.
2. Mount points in `AnalysisResults.tsx`:
   - Mix Chain card header (~line 2056): between the title row and
     the role paragraph.
   - Patch card header (~line 2215): between the title row and the
     MetaBadgeList.
   - Sonic Element card header (~line 1912): between the title row
     and the summary paragraph.
3. Each mount is guarded by `card.phase1Fields.length > 0`. Cards
   without cited fields fall back to today's exact layout.

Live verification against saved run 855f1376… (the audit reproducer):
all 10 Mix Chain cards, 2 Patch cards, and 7 Sonic Element cards
render the headline correctly with appropriate confidence pills
(PUMPING STRENGTH 47% [ROUGH SKETCH 30%], SUPERSAW DETECTED
[WORKABLE DRAFT 78%], ACID BASS DETECTED [SOLID SCAFFOLD 99%],
NOTE TRANSCRIPTION 61% [WORKABLE DRAFT 61%], KEY C Minor
[SOLID SCAFFOLD 93%], TEMPO 145 BPM [SOLID SCAFFOLD 100%]).

Mobile (375px) verified — headlines fit, pills stay aligned, no
overflow.

Tests: 15 new cases — 10 in `tests/services/citationHeadline.test.ts`
(mirrors `citationBlock.test.ts`) covering label/value/arrow render,
null fallback, confidence-pill bands, value formatter parity,
typography hooks. 5 in `analysisResultsUi.test.ts` covering Mix Chain
/ Patch / Sonic integration, empty-fields fallback, and the
low-confidence Unreliable-band path.

`npm run verify` passes: lint + 575 unit tests + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): unify five confidence vocabularies onto the canonical band ladder

Audit Finding #4 — the producer used to see five competing vocabularies
for "how much should I trust this number?" — HIGH/MED/LOW pills on
Detected Characteristics cards, High/Moderate/Low chips on Confidence
Notes, bare CONF X% text on Key/Character metric cards, SCORE X.XX
badges on the BPM card (two render paths), and the canonical four-band
ladder used only by the Session Musician panel. The chain-of-custody
invariant in PURPOSE.md requires low-confidence measurements to produce
visibly hedged advice — five vocabularies fragment that signal so the
producer can't build a single mental model of trust.

This PR promotes the existing four-band ladder
(apps/ui/src/services/sessionMusician/confidenceBand.ts) to the canonical
primitive across every confidence surface. Frontend-only; Gemini still
emits HIGH/MED/LOW and the UI converts locally via a new
`toConfidenceBand` normalizer.

What changed:

1. New normalizer `toConfidenceBand(value)` in `confidenceBand.ts`
   accepts numeric 0-1 floats, 0-100 integers, Gemini string enums
   (HIGH/MED/LOW + High/Moderate/Low variants), and percent strings
   ("62%"). Returns the matching `ConfidenceBand` or null for
   unparseable input. The string enums map to band-mid values (HIGH→0.9,
   MED→0.6, LOW→0.3) so `formatBandPillLabel` reads as honest hedges.

2. `ConfidenceBandBadge` gains:
   - `variant: 'full' | 'compact'` — `compact` omits the copy paragraph
     so the pill can sit inline in card corners and metric-card footers.
     `full` (default) preserves the Session Musician panel behavior
     unchanged.
   - `band?: ConfidenceBand` override prop — when supplied, skips the
     `getConfidenceBand` round-trip. Useful when the caller pre-converts
     a string enum.

3. V1: Detected Characteristics cards (AnalysisResults.tsx ~1650) —
   bespoke HIGH/MED/LOW ternary pill replaced with `<ConfidenceBandBadge
   variant="compact" band={toConfidenceBand(item.confidence)} />`.
   `characteristicPillClass` helper kept alive only for the unrelated
   characteristic-name chips on the Character metric card (line 1000);
   flagged as a follow-up.

4. V2: Confidence Notes chips (AnalysisResults.tsx ~1187) —
   `toConfidenceBadges` viewModel return shape changed from
   `{ label, level }` (3-level legacy enum) to `{ label, band }`
   (canonical four-band ladder). `confidenceClass` helper deleted.

5. V3: Plain `CONF X%` text (3 sites) — replaced with
   `<ConfidenceBandBadge variant="compact" confidence={...} />` in:
   - AnalysisResults.tsx Key card footer
   - AnalysisResults.tsx Character card footer
   - MeasurementDashboard.tsx alternate Key card

6. V4: `SCORE X.XX` badges (2 sites) — replaced with band pills in
   AnalysisResults.tsx Tempo card and MeasurementDashboard.tsx Tempo
   card. `formatBpmScore` deleted from both files. Cross-Check ✓/✗
   StatusBadge in MeasurementDashboard preserved — that's an agreement
   signal (do multiple BPM detectors agree?), not confidence.

7. Dead code retired: `normalizeConfidenceLevel`,
   `parseConfidenceScalar`, `confidenceClass`, two copies of
   `formatBpmScore`. `ConfidenceLevel` type and
   `MelodyInsightsViewModel.confidenceLabel` kept alive — they drive
   non-pill key/value text rows in Sonic Element melody insights
   (flagged for follow-up migration).

3-level → 4-band mismatch documented as an intentional refinement: the
old `normalizeConfidenceLevel` mapped scalar 0.5-0.79 AND "medium" both
to "Moderate"; the new normalizer maps "medium" to 0.6 → workable
(consistent), and scalar 0.25-0.49 to "Rough sketch" (more granular
than the old "Low" bucket).

Tests: 20 new cases across 4 files —
1. `confidenceBand.test.ts` — 9 cases for `toConfidenceBand`
   (null/undefined fallback, 0-1 floats, 0-100 integers, all string
   enum variants, percent strings, NaN/Infinity, negative clamping).
2. `confidenceBandBadge.test.ts` — 4 cases for the compact variant
   and the `band` override prop (label-only when no confidence, tone
   override, Gemini-string routing).
3. `analysisResultsViewModel.test.ts` — rewrote the existing
   `toConfidenceBadges` shape assertion; added a `band: null`
   fallback case.
4. `analysisResultsUi.test.ts` — 5 integration cases proving each
   V1-V4 site renders band pill text and not the old vocabularies.

Live UI verified against saved run 855f1376… — 36 band pills visible
across the page (21 Solid scaffold, 7 Workable draft, 5 Rough sketch,
3 Unreliable). Zero `CONF X%` leakage. Detected Characteristics cards
show the chain-of-custody hedge working as designed (Detuned Supersaw
renders WORKABLE DRAFT in orange while the other 4 detections render
SOLID SCAFFOLD in green). Cross-Check ✓/✗ structurally preserved (the
saved run doesn't populate bpmAgreement, so it doesn't render, but the
code path is untouched).

`npm run verify` passes: lint + ~600 unit + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Oz <oz-agent@warp.dev>
slittycode added a commit that referenced this pull request May 17, 2026
…eader) (#58)

* docs(claude): add repo-layout, scripts map, Phase 3 UI entry, CSV recipe

Closes four orientation gaps in CLAUDE.md: a top-of-Architecture map
that flags advisory/, experiments/, docs/, and tests/ground_truth/ as
off-path; a Scripts-at-a-glance section covering scripts/ and
apps/backend/scripts/; a pointer from the Phase 3 paragraph to
SamplePlayback.tsx + sampleGenerationClient.ts; and a curl example
plus allowlisted field paths on the csv_export.py module entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add Layer 2 transcription evaluation guide

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(ui): stop Phase 2 results surface from leaking engine output

Removes four classes of broken/placeholder content that made the Phase 2
recommendations read as engine output instead of producer-facing advice
(audit finding #1).

1. Mix Chain card role text — `buildRoleSentence` used to fabricate
   "{stage phrase} by {verb}" by lowercasing the first letter of
   Gemini's `reason`. Because `reason` is a present-tense clause, every
   card produced ungrammatical splices like "Controls bass energy by
   ensures the extreme low-end mono…". Now renders the reason verbatim
   with a capitalized first letter and trailing period; HIGH-END cue
   suffix preserved as "(for …)".

2. Patch Framework `patchRole` — was a 7-key category-keyed fallback
   (`mapPatchRole`) that stamped duplicated placeholders ("Primary tone
   generator" on every SYNTHESIS card). Removed the field, the
   fallback, the JSX paragraph, and the contribution to the
   `inferProcessingGroup` text-concat. The category chip + per-card
   `whyThisWorks` already carry the bucket and explanation.

3. Patches/Mix Chain overlap — `buildPatchCards` now case-insensitively
   filters out devices that also appear in `mixAndMasterChain` so the
   Patches section stops re-listing chain devices. Synthetic fallbacks
   (Stereo Width, MIDI Clip Guide) are built from Phase 1 only and
   bypass the filter intentionally.

4. Interpretation Caution raw-JSON dump — the panel rendered
   `originalValue` verbatim, which for dropped recommendations is a
   JSON-dumped `AbletonRecommendation` (see `_stringify_warning_value`
   in `server_phase2.py`). Added `formatDroppedValue` helper that
   parses JSON-shaped values and renders a compact "device: X ·
   parameter: Y · value: Z" summary. Non-JSON strings pass through;
   invalid JSON falls back to a truncated raw string. Also resolves
   the `$Saturator` symptom — Gemini's hallucinated `$`-prefixed
   device name now surfaces as a readable summary line instead of
   leaking through a raw JSON dump.

5. System Diagnostics dev-only leak — `validateNewFieldCoverage`
   emits "Phase 1 field 'X' is present… this warning is benign"
   coverage signals meant for the engine team, not producers. Added
   optional `audience?: 'dev' | 'user'` to `ValidationViolation`,
   marked `NEW_FIELD_UNCITED` as dev, and updated
   `Phase2ConsistencyReport` to filter dev-audience violations from
   the rendered table AND from the header counts (so the header
   doesn't read "5 warnings shown" above an empty table). The
   underlying `ValidationReport` still carries every violation for
   tests and offline analysis.

Tripwire note: the backend grammar fix
`_apply_phase2_grammar_fixes` in `server_phase2.py` only runs on the
legacy `/api/phase2` endpoint, not the analysis-runs path the UI
uses. After this change it becomes a redundant no-op against the new
render shape; left untouched.

Tests: 16 new unit cases across viewModel, UI rendering, validator,
and consistency report. `npm run verify` passes (lint + 565 unit
tests + build + 44 smoke tests).

Live UI verified against saved run 855f1376… — all nine text-leak
probes (`by ensures`, `by ducks`, `by generates`,
`Primary tone generator`, `Texture and movement stage`,
`Stereo placement stage`, raw `\$Saturator`, `this warning is
benign`, "is present in the measurement payload but no Phase 2…")
return false.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): surface primary citation in collapsed card headers

Audit Finding #3 — "the chain-of-custody citation, the product's whole
differentiator, renders as a footnote."

Adds a one-line `CitationHeadline` primitive inside the collapsed header
of every Mix Chain / Patch / Sonic Element card so producers see the
measurement evidence at scan-time without expanding. The expanded
CitationBlock stays in the body unchanged — this is the collapsed-state
companion, not a replacement.

Each headline reads `{label} {value} →` with the arrow pointing into
the device h4/h3 that follows in the title row, making the implicit
"measurement justified device" statement visible — exactly the audit's
literal example "Crest factor 8.2 dB → Glue Compressor."

Confidence sibling pills (Solid / Workable / Rough / Unreliable, same
ladder as ConfidenceBandBadge) ride alongside the value when the
primary field has a paired *Confidence sibling. This preserves the
chain-of-custody invariant: low-confidence measurements visibly hedge
in the collapsed view too, not just after expansion.

Implementation:
1. `CitationHeadline` added as a sibling export in
   `apps/ui/src/components/CitationBlock.tsx`. Shares the file's
   imports, the `CONFIDENCE_PILL_CLASSES` map, and the audit lineage.
   Composes existing pure helpers (`pickPhase1Value`,
   `formatCitedValue`, `humanizeFieldPath`,
   `pickPhase1Confidence`, `getConfidenceBand`,
   `formatBandPillLabel`) — no new resolver logic.
2. Mount points in `AnalysisResults.tsx`:
   - Mix Chain card header (~line 2056): between the title row and
     the role paragraph.
   - Patch card header (~line 2215): between the title row and the
     MetaBadgeList.
   - Sonic Element card header (~line 1912): between the title row
     and the summary paragraph.
3. Each mount is guarded by `card.phase1Fields.length > 0`. Cards
   without cited fields fall back to today's exact layout.

Live verification against saved run 855f1376… (the audit reproducer):
all 10 Mix Chain cards, 2 Patch cards, and 7 Sonic Element cards
render the headline correctly with appropriate confidence pills
(PUMPING STRENGTH 47% [ROUGH SKETCH 30%], SUPERSAW DETECTED
[WORKABLE DRAFT 78%], ACID BASS DETECTED [SOLID SCAFFOLD 99%],
NOTE TRANSCRIPTION 61% [WORKABLE DRAFT 61%], KEY C Minor
[SOLID SCAFFOLD 93%], TEMPO 145 BPM [SOLID SCAFFOLD 100%]).

Mobile (375px) verified — headlines fit, pills stay aligned, no
overflow.

Tests: 15 new cases — 10 in `tests/services/citationHeadline.test.ts`
(mirrors `citationBlock.test.ts`) covering label/value/arrow render,
null fallback, confidence-pill bands, value formatter parity,
typography hooks. 5 in `analysisResultsUi.test.ts` covering Mix Chain
/ Patch / Sonic integration, empty-fields fallback, and the
low-confidence Unreliable-band path.

`npm run verify` passes: lint + 575 unit tests + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ui): unify five confidence vocabularies onto the canonical band ladder

Audit Finding #4 — the producer used to see five competing vocabularies
for "how much should I trust this number?" — HIGH/MED/LOW pills on
Detected Characteristics cards, High/Moderate/Low chips on Confidence
Notes, bare CONF X% text on Key/Character metric cards, SCORE X.XX
badges on the BPM card (two render paths), and the canonical four-band
ladder used only by the Session Musician panel. The chain-of-custody
invariant in PURPOSE.md requires low-confidence measurements to produce
visibly hedged advice — five vocabularies fragment that signal so the
producer can't build a single mental model of trust.

This PR promotes the existing four-band ladder
(apps/ui/src/services/sessionMusician/confidenceBand.ts) to the canonical
primitive across every confidence surface. Frontend-only; Gemini still
emits HIGH/MED/LOW and the UI converts locally via a new
`toConfidenceBand` normalizer.

What changed:

1. New normalizer `toConfidenceBand(value)` in `confidenceBand.ts`
   accepts numeric 0-1 floats, 0-100 integers, Gemini string enums
   (HIGH/MED/LOW + High/Moderate/Low variants), and percent strings
   ("62%"). Returns the matching `ConfidenceBand` or null for
   unparseable input. The string enums map to band-mid values (HIGH→0.9,
   MED→0.6, LOW→0.3) so `formatBandPillLabel` reads as honest hedges.

2. `ConfidenceBandBadge` gains:
   - `variant: 'full' | 'compact'` — `compact` omits the copy paragraph
     so the pill can sit inline in card corners and metric-card footers.
     `full` (default) preserves the Session Musician panel behavior
     unchanged.
   - `band?: ConfidenceBand` override prop — when supplied, skips the
     `getConfidenceBand` round-trip. Useful when the caller pre-converts
     a string enum.

3. V1: Detected Characteristics cards (AnalysisResults.tsx ~1650) —
   bespoke HIGH/MED/LOW ternary pill replaced with `<ConfidenceBandBadge
   variant="compact" band={toConfidenceBand(item.confidence)} />`.
   `characteristicPillClass` helper kept alive only for the unrelated
   characteristic-name chips on the Character metric card (line 1000);
   flagged as a follow-up.

4. V2: Confidence Notes chips (AnalysisResults.tsx ~1187) —
   `toConfidenceBadges` viewModel return shape changed from
   `{ label, level }` (3-level legacy enum) to `{ label, band }`
   (canonical four-band ladder). `confidenceClass` helper deleted.

5. V3: Plain `CONF X%` text (3 sites) — replaced with
   `<ConfidenceBandBadge variant="compact" confidence={...} />` in:
   - AnalysisResults.tsx Key card footer
   - AnalysisResults.tsx Character card footer
   - MeasurementDashboard.tsx alternate Key card

6. V4: `SCORE X.XX` badges (2 sites) — replaced with band pills in
   AnalysisResults.tsx Tempo card and MeasurementDashboard.tsx Tempo
   card. `formatBpmScore` deleted from both files. Cross-Check ✓/✗
   StatusBadge in MeasurementDashboard preserved — that's an agreement
   signal (do multiple BPM detectors agree?), not confidence.

7. Dead code retired: `normalizeConfidenceLevel`,
   `parseConfidenceScalar`, `confidenceClass`, two copies of
   `formatBpmScore`. `ConfidenceLevel` type and
   `MelodyInsightsViewModel.confidenceLabel` kept alive — they drive
   non-pill key/value text rows in Sonic Element melody insights
   (flagged for follow-up migration).

3-level → 4-band mismatch documented as an intentional refinement: the
old `normalizeConfidenceLevel` mapped scalar 0.5-0.79 AND "medium" both
to "Moderate"; the new normalizer maps "medium" to 0.6 → workable
(consistent), and scalar 0.25-0.49 to "Rough sketch" (more granular
than the old "Low" bucket).

Tests: 20 new cases across 4 files —
1. `confidenceBand.test.ts` — 9 cases for `toConfidenceBand`
   (null/undefined fallback, 0-1 floats, 0-100 integers, all string
   enum variants, percent strings, NaN/Infinity, negative clamping).
2. `confidenceBandBadge.test.ts` — 4 cases for the compact variant
   and the `band` override prop (label-only when no confidence, tone
   override, Gemini-string routing).
3. `analysisResultsViewModel.test.ts` — rewrote the existing
   `toConfidenceBadges` shape assertion; added a `band: null`
   fallback case.
4. `analysisResultsUi.test.ts` — 5 integration cases proving each
   V1-V4 site renders band pill text and not the old vocabularies.

Live UI verified against saved run 855f1376… — 36 band pills visible
across the page (21 Solid scaffold, 7 Workable draft, 5 Rough sketch,
3 Unreliable). Zero `CONF X%` leakage. Detected Characteristics cards
show the chain-of-custody hedge working as designed (Detuned Supersaw
renders WORKABLE DRAFT in orange while the other 4 detections render
SOLID SCAFFOLD in green). Cross-Check ✓/✗ structurally preserved (the
saved run doesn't populate bpmAgreement, so it doesn't render, but the
code path is untouched).

`npm run verify` passes: lint + ~600 unit + build + 44 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ui): audit quick-hits bundle (StickyNav, Mix Chain, Mix Doctor, header)

Closes six items from the audit's "Quick hits (low ROI, real)" list.
Each is independent; bundled into one PR because they're all small
surgical fixes.

1. QH1 — StickyNav clipped "MEASUREMENTS" at 1440px. Was
   overflow-x-auto + min-w-max, which produced an invisible horizontal
   scroll (macOS hides scrollbars by default; last pill looked clipped
   with no scroll affordance). Switched to flex-wrap so pills flow onto
   multiple rows at narrow widths; whitespace-nowrap prevents intra-
   label wrap.

2. QH2 — Mix Chain card-number order badges removed. Cards are grouped
   by processing stage AFTER ordering, so the order numbers appeared
   out-of-sequence within each group ("1, 6, 8, 9 / 2, 4 / 5, 7 / 3 /
   10"), which read as a presentation bug. The visual sequence within
   each group is already meaningful; the badge added confusion without
   information. Dropped.

3. QH4 — Mix Doctor Band Diagnostics table now shows the target
   range alongside the optimal. Previously the column rendered only
   the optimal dB (e.g. "-22.0"), so two bands with similar Delta dB
   could land in different Issue buckets without the producer being
   able to see why. The Issue is determined by absolute thresholds
   (target.minDb / maxDb), not by the diff from optimal. Showing the
   range makes the verdict legible at a glance — Sub Bass -6.4 norm,
   range -16 to -8, exceeds the upper bound by 1.6 dB → TOO-LOUD;
   Low Mids -14.5 norm, range -26 to -14, sits at the boundary →
   OPTIMAL. Threshold logic itself was correct; this is a display fix.

4. QH5 — "Dense DAW Lab" link removed from the header. A prior audit
   pass had de-emphasized it to muted text, but the new audit re-
   flagged it for sitting in the primary flow's header without context
   for what it is. Route stays accessible via direct URL (the
   getAppViewHref helper is preserved for future re-introduction
   behind a settings menu, and main.tsx still routes to the
   DenseDawConcept component when activeView === 'daw-concept').

5. QH6 — header model selector visually demoted. Dropped the
   "Interpretation Model" label and shrank the styling from a bordered
   dropdown to a discreet text-secondary inline select. The audit
   flagged the prior styling as foregrounding an AI-model choice an
   intermediate producer has no basis to make. Selector stays
   accessible (smoke spec requires it visible at desktop viewport;
   `responsive-layout.spec.ts` and `upload-estimate-phase1.spec.ts`
   both guard the `phase2-model-desktop` testid) but now reads as
   background metadata. Aria-label + title attribute carry the long-
   form context for assistive tech and hover discoverability.

6. QH7 — Mix Doctor section title renamed from "MixDoctor" to
   "Mix Doctor". The audit flagged that the section header rendered
   as "Mixdoctor" with a lowercase 'd', inconsistent with every other
   section name. Root cause: `formatWord` in `utils/displayText.ts`
   case-normalizes single-token CamelCase to sentence case, so
   "MixDoctor" was rendering as "Mixdoctor". Two words ("Mix Doctor")
   matches every other section name's pattern (Style Profile, Project
   Setup, Track Layout, Mix & Master Chain, Patch Framework, etc.)
   and renders cleanly through the existing pipeline.

`npm run verify` passes: lint + ~600 unit + build + 44 smoke. Live UI
verified against saved run 855f1376… with probes confirming all six
fixes:
- StickyNav.overflow-x-auto wrapper gone (pills wrap to 2 rows)
- Zero Mix Chain order badges in #section-mix-chain
- "Target (range)" header present, "Target dB" header gone
- "Dense DAW Lab" text absent from page
- "Interpretation Model" label absent from page
- "Mix Doctor" section title present, "Mixdoctor" absent

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Oz <oz-agent@warp.dev>
slittycode pushed a commit that referenced this pull request May 18, 2026
PR 6 of 16 in the UI consistency overhaul. Promotes the AnalysisResults
page header to <SectionHeader> and wraps the top TEMPO/KEY/METER/
CHARACTER metric strip in a <DeviceRack name="Measurement Summary">
with <MetricTile> cards inside.

Stacked on PR #69 (D.4 FileUpload). Will rebase onto main after #69
merges.

Header (AnalysisResults.tsx:835-880 → :835-878):
- <h1> + Download buttons row → <SectionHeader size="lg"
  variant="inline" eyebrow="ASA Results" titleRole="page-title">
  with both Download buttons in the action slot.
- The Activity icon prefix on the h1 disappears — the SectionHeader
  LED dot already carries the "results are live" semantic.
- The subtitle (sourceFileName · interpretationSubtitle) stays as a
  separate <p> sibling, preserving data-testid="analysis-results-
  subtitle" and the existing text-role-meta styling. Indented pl-4
  to align under the title after the LED.
- Download data: <button> → <Button variant="secondary"
  leadingIcon=FileJson>. Preserved data-testid="analysis-export-json".
- Download report: <button> → <Button variant="primary"
  leadingIcon=FileText>. Preserved
  data-testid="analysis-export-markdown".

Metric strip (AnalysisResults.tsx:884-1012 → :882-1010):
- Outer <div className="grid grid-cols-2 md:grid-cols-4 gap-3"> wrapped
  in <DeviceRack name="Measurement Summary" density="dense"
  status="success">. The grid layout stays as-is inside the rack body
  so the responsive 2-col / 4-col break still works.
- 4 × AccentMetricCard → 4 × <MetricTile size="lg" accent="accent">.
  API parity drop-in:
  • label (was a flex-span with icon+text) → icon prop + label string.
    Cleaner separation; the MetricTile primitive places the icon in
    the eyebrow row.
  • value / unit / headerRight / footer pass through unchanged.
  • The hand-rolled text-[1.5rem] override on the CHARACTER value
    disappears — MetricTile size="lg" already renders text-2xl which
    is the same 1.5rem.

Imports:
- AccentMetricCard stays imported from ./MeasurementPrimitives.
  Other usages of it remain at :1249-1258 and :1444-1448 (Patches /
  Sonic Elements sections — migrated in later PRs D.5b/c/d).
- Added Button / DeviceRack / MetricTile / SectionHeader from ./ui.

Preserved verbatim:
- All data-testid attributes (analysis-results-root, analysis-results-
  subtitle, analysis-export-json, analysis-export-markdown).
- All visible text strings: "Analysis Results", "Download data",
  "Download report", "TEMPO", "BPM", "KEY SIG", "METER", "CHARACTER",
  "SCANNING...".
- The PhaseSourceBadge + ConfidenceBandBadge + MetricBar + StatusBadge
  + TokenBadgeList rendering inside each tile is untouched.
- Audit Finding #4 commentary preserved (canonical band-pill
  vocabulary).
- Audit Finding #1 ordering comment preserved (MeasurementDashboard
  rendering at the bottom of the scroll).
- The lowConfidenceIndicator + characteristicPills logic untouched.

Verified: lint clean, 628/628 unit tests passing, build clean
(AnalysisResults chunk 255 KB / 63 KB gz; index chunk 323 KB / 97 KB
gz — both stable vs PR 5).

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe
slittycode added a commit that referenced this pull request May 18, 2026
PR 6 of 16 in the UI consistency overhaul. Promotes the AnalysisResults
page header to <SectionHeader> and wraps the top TEMPO/KEY/METER/
CHARACTER metric strip in a <DeviceRack name="Measurement Summary">
with <MetricTile> cards inside.

Stacked on PR #69 (D.4 FileUpload). Will rebase onto main after #69
merges.

Header (AnalysisResults.tsx:835-880 → :835-878):
- <h1> + Download buttons row → <SectionHeader size="lg"
  variant="inline" eyebrow="ASA Results" titleRole="page-title">
  with both Download buttons in the action slot.
- The Activity icon prefix on the h1 disappears — the SectionHeader
  LED dot already carries the "results are live" semantic.
- The subtitle (sourceFileName · interpretationSubtitle) stays as a
  separate <p> sibling, preserving data-testid="analysis-results-
  subtitle" and the existing text-role-meta styling. Indented pl-4
  to align under the title after the LED.
- Download data: <button> → <Button variant="secondary"
  leadingIcon=FileJson>. Preserved data-testid="analysis-export-json".
- Download report: <button> → <Button variant="primary"
  leadingIcon=FileText>. Preserved
  data-testid="analysis-export-markdown".

Metric strip (AnalysisResults.tsx:884-1012 → :882-1010):
- Outer <div className="grid grid-cols-2 md:grid-cols-4 gap-3"> wrapped
  in <DeviceRack name="Measurement Summary" density="dense"
  status="success">. The grid layout stays as-is inside the rack body
  so the responsive 2-col / 4-col break still works.
- 4 × AccentMetricCard → 4 × <MetricTile size="lg" accent="accent">.
  API parity drop-in:
  • label (was a flex-span with icon+text) → icon prop + label string.
    Cleaner separation; the MetricTile primitive places the icon in
    the eyebrow row.
  • value / unit / headerRight / footer pass through unchanged.
  • The hand-rolled text-[1.5rem] override on the CHARACTER value
    disappears — MetricTile size="lg" already renders text-2xl which
    is the same 1.5rem.

Imports:
- AccentMetricCard stays imported from ./MeasurementPrimitives.
  Other usages of it remain at :1249-1258 and :1444-1448 (Patches /
  Sonic Elements sections — migrated in later PRs D.5b/c/d).
- Added Button / DeviceRack / MetricTile / SectionHeader from ./ui.

Preserved verbatim:
- All data-testid attributes (analysis-results-root, analysis-results-
  subtitle, analysis-export-json, analysis-export-markdown).
- All visible text strings: "Analysis Results", "Download data",
  "Download report", "TEMPO", "BPM", "KEY SIG", "METER", "CHARACTER",
  "SCANNING...".
- The PhaseSourceBadge + ConfidenceBandBadge + MetricBar + StatusBadge
  + TokenBadgeList rendering inside each tile is untouched.
- Audit Finding #4 commentary preserved (canonical band-pill
  vocabulary).
- Audit Finding #1 ordering comment preserved (MeasurementDashboard
  rendering at the bottom of the scroll).
- The lowConfidenceIndicator + characteristicPills logic untouched.

Verified: lint clean, 628/628 unit tests passing, build clean
(AnalysisResults chunk 255 KB / 63 KB gz; index chunk 323 KB / 97 KB
gz — both stable vs PR 5).

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request May 31, 2026
…record (#124)

* docs: refresh stale items vs current code; archive completed hosting record

Sweep against the 2026-05-30 audit findings — the doc surface had drifted
through the MT3, Live 12 catalogue, pianoroll/symusic, and recommendation-proof
merges. Adds those features to the inventories where they were missing, fixes
a wrong line reference, removes a dead commit hash, refreshes date stamps, and
archives a completed-work record.

- MT3 polyphonic transcription: add to apps/backend/AGENTS.md (file map +
  guidance), apps/backend/ARCHITECTURE.md (Components table + mt3-transcriptions
  route), apps/backend/README.md (Polyphonic section now reflects opt-in shipping),
  apps/ui/AGENTS.md + apps/ui/README.md (mt3Client.ts + UI surface), CLAUDE.md
  (#19 backend file, #6a frontend service), and all three CHANGELOG.md files.
- Live 12 catalogue + phase2_catalogue_gates, transcription pianoroll +
  symusic, recommendation-proof campaign: add to CHANGELOG.md (root) and
  apps/backend/CHANGELOG.md and apps/ui/CHANGELOG.md.
- CLAUDE.md: fix wrong analysis_runtime.py line ref (685 → ~800), add
  ASA_ENABLE_MT3 and ASA_SAMPLE_SYNTH_BACKEND env vars, correct the tsconfig
  excludes description, drop the unreachable 5c40dd4 commit hash.
- apps/backend/AGENTS.md + ARCHITECTURE.md: drop the same dead 5c40dd4 hash.
- docs/ARCHITECTURE_STRATEGY.md + docs/POLYPHONIC_TRANSCRIPTION_SPIKE.md:
  reflect that MT3 has shipped as an opt-in, additive stage (the spike harness
  remains the offline comparison rig for other candidates).
- docs/SAMPLE_GENERATION.md: fix the schematic node label (synth_kit() → the
  real synth_kick/snare/hat() trio).
- apps/backend/JSON_SCHEMA.md: annotate the shared-vs-full carve-out so the
  full-mode-only keys (keyProfile, tuningFrequency, tuningCents,
  lufsMomentaryMax, lufsShortTermMax, pitchDetail) are explicit per CLAUDE.md
  tripwire #4.
- Archive docs/PUBLIC_HOSTING_FOUNDATION.md → docs/history/public-hosting-foundation-2026-04-01.md
  with an archived-banner; update inbound references in CHANGELOG.md, docs/SETUP.md,
  docs/history/README.md, and docs/history/archive/refactor-state-2026-03-18.md.
- incorporations/forking-plans-2026-05-14.md: add a 2026-05-30 status update
  noting Plan 6 substantially landed via the Live 12 catalogue + gates work.

No code touched — markdown only.

https://claude.ai/code/session_016m6cUFNwb3kD2EzAbgqCHe

* docs: fix self-contradictory archive banner phrasing

The banner said the "Remaining work" section "lives outside this archived
doc" but that section is in the doc itself. Aligns with the clearer phrasing
already used in docs/history/README.md.

https://claude.ai/code/session_016m6cUFNwb3kD2EzAbgqCHe

---------

Co-authored-by: Claude <noreply@anthropic.com>
slittycode pushed a commit that referenced this pull request Jun 2, 2026
Phase 1 v2 emits truePeak: null for digital silence (no defined dBTP),
but the frontend type declared it `number` and several display sites
called .toFixed unguarded — a latent crash on silent input. Worse, the
HTTP envelope coerced null -> 0.0, falsely reporting a full-scale peak
for a silent track (violating measurement-honesty invariants #1/#4).

Backend:
- server_phase1._build_phase1 now preserves null via _coerce_nullable_number
  instead of defaulting to 0.0; PLR fallback already guards None.
- Tests: truePeak passthrough/null + PLR-null-when-peak-null.

Frontend:
- Phase1Result.truePeak typed `number | null`.
- analysisResultsViewModel: formatTruePeak (renders "—" for null, matching
  the house formatNumber convention) and masterCeilingDb (falls back to the
  -0.3 dB cap when there is no peak to duck below); applied at all 6 sites.
- MeasurementDashboard: omit the true-peak marker, PLR gap fill, and PLR
  annotation for silence (LUFS marker still renders).
- backendPhase1Client: accept an explicit null truePeak (legacy path) while
  still rejecting a malformed/missing field.
- exportUtils markdown renders "—" for a null peak.
- Tests: null-silence card renders "—" without crashing; helper unit cases.

Docs: JSON_SCHEMA truePeak note now states null == digital silence.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q
slittycode added a commit that referenced this pull request Jun 2, 2026
…t PLR (#128)

* fix(loudness): Phase 1 v2 - truePeak to dBTP, bpmConfidence to 0-1, coherent PLR

Phase 1 schema v2 migration for the loudness fields, motivated by a consumer
audit: ~12 UI/recommendation consumers already assumed `truePeak` was dBTP
(several were latent bugs), while only the loudness guardrail treated it as the
linear amplitude proxy v1 actually emitted.

- truePeak now emitted in dBTP (0.0 == full scale, >0 == inter-sample over);
  null for silence. bpmConfidence normalized to 0-1 (raw Essentia ~0-5.32 / 5).
- PLR is now a coherent dB-domain subtraction (truePeak_dBTP - lufsIntegrated),
  fixing the prior linear-minus-dB unit mismatch, across analyze_core, the HTTP
  fallback, and the phase1 eval consistency gate.
- Add top-level `phase1Version` ("phase1.v2") so consumers can detect the
  generation; loudness guardrail over-check moves to dBTP (>0); the validator
  drops its special-case bpmConfidence threshold (now shares the 0.4 hedge).
- Re-baseline the golden (truePeak 0.0, plr 5.6); add dedicated analyze_plr and
  dBTP true-peak unit tests; update fixture expectations, types, JSON_SCHEMA.
- ADR 0002 records the v2 bump, superseding ADR 0001 for these two fields.

Verified: backend unittest suite green; frontend tsc + 716 unit tests + build green.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

* chore: ignore Python bytecode repo-wide

The .gitignore only covered apps/backend/__pycache__/, so bytecode generated
outside the backend (e.g. scripts/__pycache__/ when build_live12_catalogue.py
is imported) showed up as untracked. Generalize the __pycache__/ and *.py[cod]
rules to the whole repo.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

* fix(loudness): v2 follow-ups - phase1 eval manifest targets + dBTP labels

- Backend CI fix: phase1_eval_manifest.json truePeak/plr thresholds still
  encoded v1 linear values (click_120 truePeak 0.9 / plr 24.8; sine_220 0.5 /
  7.4). Migrated to the v2 dBTP measurements (click -0.9 / 23.2; sine -6.0 /
  1.0). The plr:consistency gate already passes; only the manifest's expected
  targets were stale.
- Review (#128): three analysisResultsViewModel.ts display sites labeled the
  now-correct truePeak value as "dB"; corrected to "dBTP".

Verified: tests.test_phase1_evaluation green; frontend tsc + 716 unit green.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

* Harden nullable truePeak across the v2 contract

Phase 1 v2 emits truePeak: null for digital silence (no defined dBTP),
but the frontend type declared it `number` and several display sites
called .toFixed unguarded — a latent crash on silent input. Worse, the
HTTP envelope coerced null -> 0.0, falsely reporting a full-scale peak
for a silent track (violating measurement-honesty invariants #1/#4).

Backend:
- server_phase1._build_phase1 now preserves null via _coerce_nullable_number
  instead of defaulting to 0.0; PLR fallback already guards None.
- Tests: truePeak passthrough/null + PLR-null-when-peak-null.

Frontend:
- Phase1Result.truePeak typed `number | null`.
- analysisResultsViewModel: formatTruePeak (renders "—" for null, matching
  the house formatNumber convention) and masterCeilingDb (falls back to the
  -0.3 dB cap when there is no peak to duck below); applied at all 6 sites.
- MeasurementDashboard: omit the true-peak marker, PLR gap fill, and PLR
  annotation for silence (LUFS marker still renders).
- backendPhase1Client: accept an explicit null truePeak (legacy path) while
  still rejecting a malformed/missing field.
- exportUtils markdown renders "—" for a null peak.
- Tests: null-silence card renders "—" without crashing; helper unit cases.

Docs: JSON_SCHEMA truePeak note now states null == digital silence.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

---------

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jun 2, 2026
* WS3a: asa-dsp ↔ Essentia loudness parity harness + report

Before the loudness-spectro-wasm core (asa-dsp, openmeters-derived BS.1770)
can back any ASA product path (WS3b backend, WS3c browser readout), it has
to agree with Essentia — ASA's authoritative Phase 1 loudness source, since
Phase 1 measurements are ground truth (PURPOSE.md invariant #1).

scripts/essentia_parity.py runs a deterministic synthetic corpus (tones,
sweep, white/pink noise, decorrelated stereo) through BOTH paths at each
file's native rate (no resampling):
- asa-dsp via the native measure-cli binary (source-identical to the WASM
  core, so WS3a needs no wasm toolchain), and
- Essentia via AudioLoader -> LoudnessEBUR128, mirroring
  analyze_core.analyze_loudness, plus a dBTP true peak from TruePeakDetector.

Primary gate: integrated LUFS within +/-0.1 LU on every file.

Result (docs/essentia-parity-report.md): 7/8 signals agree within +/-0.02 LU
(most within +/-0.002) — essentially perfect BS.1770 parity on tones and
noise. The sole breach is the 20 Hz->20 kHz log sweep at -0.102 LU (0.002
over the gate), a pathological non-program signal. Verdict is reported as the
harness computed it (FAIL by that single borderline case); the realistic-signal
agreement is the substantive finding. Surfaced at the WS3a hard checkpoint.

README documents the harness as Validation helper #4.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

* WS3a review: simplify run(), flag true-peak divergence for WS3c

Addresses the PR #129 review:
- run() no longer calls build_report() and discards the text; it returns
  rows only. build_report() now runs exactly once, in main(). Pure refactor —
  the harness reproduces identical numbers.
- Report caveats now explicitly flag the broadband true-peak divergence
  (white_noise ~0.6 dBTP gap): WS3c must not expose a browser true-peak
  readout without its own gate. Integrated loudness is unaffected.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

---------

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jun 2, 2026
…meters (#130)

* WS4: patchSmith — downloadable Vital (.vital) presets with cited params

Turns measured synthesis character into a loadable Vital preset where every
parameter cites the Phase 1 measurement behind it (PURPOSE.md invariant #2)
and weak/absent evidence is disclosed rather than guessed (invariant #4).

apps/ui/src/services/patchSmith.ts (pure, deterministic):
- buildPatch(phase1) maps measurements -> Vital controls, each with a
  PatchParameterCitation {label, vitalParam, value, display, phase1Fields,
  rationale, confidence}:
    * supersawDetail.voiceCount/avgDetuneCents -> osc_1 unison voices + detune
    * spectralBalance.subBass (strong)         -> octave-down osc_2 sub layer
    * acidDetail.* + spectralBalance brightness -> resonant filter + cutoff
    * bassDetail.averageDecayMs/type           -> amp envelope decay/sustain
  No measurement -> no bound parameter. Low-confidence/absent sources are
  skipped or defaulted with an explicit hedge; overall confidence = worst band.
- serializeVital(): override-only settings JSON. Confirmed against Vital's
  load_save.cpp that missing wavetables/modulations/lfos arrays load on init
  defaults (no fragile base64 wavetable embedding). Unit mappers encode Vital's
  real ValueDetails (env seconds = value^4; cutoff semitone = 69+12log2(f/440)).

apps/ui/src/components/PatchSmithPanel.tsx: a DeviceRack "Generate Vital patch"
panel (mounted in AnalysisResults) rendering the cited-parameter manifest,
per-param + overall confidence pills, hedges, and the .vital download.

Tests (14): deterministic mapping, citation provenance, hedging on
low-confidence/empty inputs, the unit mappers, and a .vital re-parse asserting
the JSON shape + in-range control values (the machine-verifiable gate).

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

* patchSmith review: cite sub-osc values, drop redundant download button

Addresses the PR #130 review:
- mapSubLayer now routes osc_2_transpose (−1 octave) and osc_2_level through
  applyCitation with spectralBalance.subBass provenance, so every osc_2 value
  in the preset appears in the manifest (invariant #2 completeness) rather than
  only osc_2_on. Test asserts the new citations.
- Removed the redundant header "Download .vital" link from PatchSmithPanel;
  the body primary CTA is the single download affordance.

https://claude.ai/code/session_01Wq1vE1D7o3W9xZKSHXz43Q

---------

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jun 5, 2026
…#143)

Phase 2 emits device recommendations across three free-shaped arrays
(abletonRecommendations, mixAndMasterChain, secretSauce.workflowSteps) with
free-text values and no machine-checkable freeze. This adds a normalized,
versioned, schema-validated contract for that surface.

- schemas/recommendations.v1.schema.json: Draft 2020-12 schema. Envelope
  {version, recommendations[]}; each entry {device, parameter, value, unit,
  range, cited_measurements[]}, additionalProperties:false, cited_measurements
  minItems:1.
- recommendations_contract.py: deterministic projection of the three Phase 2
  card arrays into the normalized shape, validated against the literal schema
  file via jsonschema (not a hand-rolled mirror — avoids the drift ADR 0001
  warned about). Derived/additive: never overrides Phase 1 (invariant #1);
  admits only cited cards (invariant #2); unit/range are nullable best-effort
  since the static Live 12 catalogue carries no min/max (invariant #4).
- server.py: attaches the validated envelope to the producer_summary
  interpretation result; it travels into GET /api/analysis-runs/{run_id} under
  stages.interpretation.result.recommendations (degrades to absent on error).
- interpretation.ts: TS mirror (RecommendationsContract).
- ADR 0003 + CLAUDE.md index document the contract and its semver/compat policy.

Tests: test_recommendations_contract.py (schema validity, projection-validates,
round-trip, freeze), a server wiring test, and a runtime snapshot-survival test.
Full backend suite green (1153), tsc --noEmit green. jsonschema==4.26.0 pinned.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jun 10, 2026
…a local Claude Code CLI (#153)

* docs: fix command accuracy in CLAUDE.md and AGENTS.md

- Fix unittest slash-path form → dotted module form in apps/backend/AGENTS.md
- Add UPDATE_PHASE1_GOLDEN re-baseline command to CLAUDE.md Tripwire #4
- Add first-time Playwright install line to CLAUDE.md frontend commands
- Add record_artifact pointer to analysis_runtime.py entry in CLAUDE.md
- Add --source/--output invocation to build_live12_catalogue.py entry
- Reconcile Node version in apps/ui/AGENTS.md against package.json (no engines field)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(phase2): add 'claude' provider — text-only interpreter via local Claude Code CLI

ASA_PHASE2_PROVIDER=claude routes producer_summary interpretation to
ClaudeCliProvider: runs the operator's local Claude Code CLI headless
(--safe-mode, --tools "", --no-session-persistence), prompt via stdin,
profile responseSchema converted from Gemini dialect and enforced via
--json-schema. Text-only by design — grounds on the prompt's embedded
Phase 1 JSON; audio is never sent; no GEMINI_API_KEY needed. Output flows
through the identical parse/citation/catalogue/recommendations.v1 tail.
Default-off; gemini stays the product default (mirrors the MOSS pattern).

Live verification on a real track (no Gemini key): 26/26 cards cited,
0 invented citation paths, recommendations.v1 envelope 26 entries,
full-surface coverage; 6 warn-and-keep catalogue annotations. Also adds
audits/owner-assessment-2026-06-10.md (whole-project audit write-up).

Tests: tests.test_phase2_provider 42/42; tests.test_server 224/224
(Gemini path behavior-preserved); full backend discover green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: skip Claude Code Review job cleanly when CLAUDE_CODE_OAUTH_TOKEN is unset

The advisory review action has failed every PR since 2026-06-09 in ~25s
because the repo secret resolves empty. Gate the steps on secret presence
so a missing credential yields a passing job with skipped steps instead of
a red check. Re-add the secret in Settings → Secrets → Actions to restore
actual reviews.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(audit): add DJ Metatron batch result — 56/56 cited across both real tracks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: restore claude-code-review.yml byte-identical to main

The claude-code-action token exchange validates that the workflow file on
a PR branch matches the default branch exactly; any divergence (or the
file's absence on pre-workflow branches — the actual cause of the
2026-06-09+ failures) yields 401 'Workflow validation failed'. The skip
guard added in f6d61e5 therefore could never help and itself tripped the
validation; reverting to main's exact content lets the review run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(phase2): make provider policy explicit — selectable, mutually exclusive, no fallback/ensemble; Gemini stays default; claude is local-CLI-only

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jun 10, 2026
- Fix unittest slash-path form → dotted module form in apps/backend/AGENTS.md
- Add UPDATE_PHASE1_GOLDEN re-baseline command to CLAUDE.md Tripwire #4
- Add first-time Playwright install line to CLAUDE.md frontend commands
- Add record_artifact pointer to analysis_runtime.py entry in CLAUDE.md
- Add --source/--output invocation to build_live12_catalogue.py entry
- Reconcile Node version in apps/ui/AGENTS.md against package.json (no engines field)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jul 4, 2026
#204)

Behavior-neutral: analyze_time_signature computed per-candidate accent
evidence (dominance + per-bar-position onset means for bar lengths 3-7)
and threw it away. Now it rides the payload as an additive, full-mode-only
top-level field, strongest candidate first, empty on every fallback branch:

  timeSignatureCandidates: [{timeSignature, dominance, positionMeans[]}]

fundamentalsQuality.domains.meter.evidence gains {bestCandidate, margin,
candidateCount} derived from it, so the trust layer can say HOW ambiguous
the meter read was rather than just that it was ambiguous. This is the
evidence surface the meter improvement track (accuracy program Phases
B/C — the measured weak layer) builds on; downstream must never treat a
candidate as an override of timeSignature (invariant #1).

Contract mirrors (tripwire #4): JSON_SCHEMA.md inventory + full-only list
+ field table; server_phase1._build_phase1 forwarding (caught by the
golden's forwarding guard); src/types/measurement.ts +
backendPhase1Client.ts reconstructor (drops malformed entries);
phase1FullPayload fixture; golden re-baselined (new key, structural).

Tests: meter-evidence summary (backend), parser filtering (Vitest),
full-mode key set. Backend suite 1295 OK; fundamentals gate 15/15;
frontend verify green (877+ unit, 51 smoke).


Claude-Session: https://claude.ai/code/session_01T4wfz87k6kzJqkKLKZE7YL

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jul 4, 2026
Replaces the loudness-interval swing proxy (grooveDetail.kickSwing/
hihatSwing) with a real micro-timing measurement. compute_swing_detail
reads swing from the long/short alternation of 8th-note inter-onset
intervals: swingPercent = long/(long+short)*100 on Ableton's Groove Pool
50-75 scale (50 straight, 66.7 full triplet), so it drops straight into a
groove template. Additive rhythmDetail.swingDetail {swingPercent,
swingConfidence, gridResolution, direction, meanAbsOffsetMs,
offbeatOnsetCount}; reuses the onset array already computed in
analyze_rhythm_detail (no extra DSP pass); null when unmeasurable.

Interval-ratio rather than phase-vs-grid is deliberate: on sparse
material the beat tracker sometimes anchors 'the beat' on a loud offbeat
hat, which flips a phase reading (58% -> 42%). The long/short structure
is invariant to that choice — validated by a phase-shift-invariance test
and against the corpus swing clips (truth 50/54/58/62/66 -> measured
50.0/54.8/57.1/61.9/65.9, all within +-1.3, well inside the +-3 gate).

Corpus: swing clips now render a realistic swung hat pattern (straight
on-beat 8th + delayed off-beat 8th) so the interval alternation exists;
swingPercent promoted from manifest truth to an active +-3.0 check on all
5 swing clips (fundamentals_evaluation learns expected.swingPercent).

UI: reconstructionBrief groove sentence prefers the measured percentage
('swings at about 62% - set the Groove Pool swing to match'), falls back
to the proxy; MeasurementDashboard Swing panel shows the Groove-Pool
number.

Contract mirrors (tripwire #4): JSON_SCHEMA.md swingDetail sub-fields;
types/measurement.ts SwingDetail (rhythmDetail passes through wholesale,
no reconstructor); phase1FullPayload fixture; golden re-baselined
(nested, structural). Tests: test_swing_detail (ratios, phase-flip
invariance, degenerate), brief swing-sentence, corpus expected-keys.
Backend 1299 OK, fundamentals gate green, frontend verify green.


Claude-Session: https://claude.ai/code/session_01T4wfz87k6kzJqkKLKZE7YL

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jul 4, 2026
)

analyze_key now runs KeyExtractor for edma + temperley + krumhansl in
full mode and emits an additive, full-only keyEnsemble field:
{method:'profile_vote.v1', agreement, profiles[], alternates[]}.

SURFACING-ONLY: the shipped key/keyConfidence/keyProfile are unchanged —
still EDMA (golden diff confirms only keyEnsemble was added; the key value
is byte-identical). The vote is evidence until the GiantSteps Key gate
proves it beats EDMA-alone. Pre-registered decision rule (frozen before
the run) in incorporations/key-ensemble-decision-2026-07-04.md: adopt iff
mirexWeighted gain >= +0.02 AND no exact-rate regression beyond 0.01, on
>= 400 evaluable clips. agreement (0-3) is a ready confidence calibrator;
alternates hedge the relative-major/minor error that is EDMA's typical
miss.

Gated on full mode (like tuning*): fast mode is unchanged and pays no
extra KeyExtractor cost. Any/all profile failures degrade gracefully
(null ensemble; EDMA key still ships).

Contract mirrors (tripwire #4): analyze.py + server_phase1 forwarding,
JSON_SCHEMA.md inventory/full-only/field-row, test_audio_fixture full-mode
key set, types/measurement.ts KeyEnsemble, backendPhase1Client forwarding,
phase1FullPayload fixture, golden re-baselined (new key, structural).
Tests: test_key_ensemble (agreement/alternates, full-vs-fast, all-fail).
Backend 1302 OK, fundamentals gate green, frontend verify green.


Claude-Session: https://claude.ai/code/session_01T4wfz87k6kzJqkKLKZE7YL

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request Jul 12, 2026
)

Runs chord/key chroma on a bass-removed stem mix (other+vocals) instead
of the full mix when Demucs stems are available. The bassline is the
single biggest polluter of full-mix chroma, so harmonic-source isolation
is the highest-leverage chord-accuracy lever in the baseline audit.

- mix_stems_mono(stems, ('other','vocals')) in analyze_audio_io.py sums
  the harmonic stems to mono; None when no stem loads.
- analyze_chords / analyze_segment_key gain a harmonic_mono kwarg; when
  present the chroma runs on it. Additive chordDetail.chordSource +
  segmentKey[].source ('full_mix' | 'harmonic_stems') record which ran.
- Stems absent -> full-mix path, BIT-IDENTICAL to before (golden diff adds
  only the two source keys; chord values unchanged; chords-only corpus
  clips hold at ~0.99).

Validation honesty: Demucs model weights are network-blocked in this
sandbox (proxy 403, same restriction as the datasets), so separation
returns None here and the synthetic multi-layer clips exercise the
full-mix fallback, not the harmonic path. The harmonic wiring is proven
by unit tests (mix_stems_mono summing/skip-missing; chordSource +
segmentKey source tagging); the end-to-end accuracy GAIN must be measured
where Demucs weights are reachable (operator machine / CI with network) on
real dense mixes — the synthetic bass is a clean low sine that barely
pollutes chroma anyway. No accuracy improvement is claimed from this
sandbox.

Contract mirrors (tripwire #4): JSON_SCHEMA rows; types/measurement.ts
ChordDetail.chordSource + SegmentKeyEntry.source; backendPhase1Client
chord reconstructor forwards chordSource (segmentKey passes through);
phase1FullPayload fixture; golden re-baselined (structural). Tests:
test_stem_aware_chords. Backend 1306 OK, fundamentals gate green,
frontend verify green.


Claude-Session: https://claude.ai/code/session_01T4wfz87k6kzJqkKLKZE7YL

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant