feat(ui): surface the recommendations.v1 contract (trust pill + table) + retry catch - #185
feat(ui): surface the recommendations.v1 contract (trust pill + table) + retry catch#185slittycode wants to merge 9 commits into
Conversation
…bleton + the rec-proof harness
The ASA -> asa-ableton boundary (and the GOAL.md fixture loop) ran on
hand-extracted interpretation JSON: the result lives embedded in the run
snapshot, the warn-and-keep validationWarnings in a different subtree
(attempt diagnostics), and provenance in a third. asa-ableton's Gate
alpha fixture and every per-fixture phase2.json in the owner plan were
produced by snapshot surgery — an unversioned, fragile handoff.
1. phase2_export.py + GET /api/analysis-runs/{run_id}/export/phase2:
single self-contained phase2-export.v1 envelope — the stored
producer_summary interpretation result verbatim (incl. the frozen
recommendations.v1 projection), the authoritative Phase 1 payload its
citations resolve against (invariant #2 verifiable offline), the full
validationWarnings trail, and provenance. Thin lookup-and-serve,
csv_export.py pattern; 404 RUN_NOT_FOUND / PHASE2_EXPORT_NOT_AVAILABLE.
Derived and read-only — exports stored state, rewrites nothing.
2. recommendation_evaluation.coerce_phase2_payload: --phase2 (and a
fixture-dir phase2.json) now accepts either a bare Phase2Result or
the export envelope, so the downloaded file feeds the scorer as-is.
3. docs/ASA_ABLETON_BOUNDARY.md: the cross-repo contract — file-coupled
not code-coupled, consumer guidance (prefer recommendations.v1
entries; dedupe is a consumer concern — Gate alpha's 60.7%-vs-50%
skip-rate accounting; flagged != invalid), and the v1 freeze policy.
Tests: +15 (10 builder incl. envelope-key freeze, 3 route, 2 unwrap).
tests.test_server 227 OK; adjacent suites 110 OK; full discover matches
the unmodified-tree baseline exactly (env-only matplotlib/torch gaps).
https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
…chno 145 + UKG 2-step 132 Owner confirmed genre fit for house/melodic-techno/acid but swapped the other two recommendation-corpus fixtures to genres they actually produce: - techno_rumble_130 -> hard_techno_rumble_145: distorted sine kick (Drum Buss on the kick chain, Boom 45 Hz), rumble reverb return + held sub, 145 BPM. Stays the pilot fixture (smallest spec, 9 device entries). - dnb_reese_174 -> ukg_2step_shuffle_132: 2-step shuffle at 132 BPM, G minor. Swing lives in the committed MIDI (57% 16ths), asserted via grooveDetail.hihatSwing / perDrumSwing.snare / bassDetail.grooveType. Retires the fixture whose proxy fingerprint misread BPM half-time (174->116). Both new fixtures ship spec-only (phase1Fingerprint: null, no fingerprint file) — the distrusted _synthetic proxy fingerprints are retired with the old slugs, and load_fixture tolerates the missing file (citation checks SKIP until the real Ableton render lands). truePeak intents now use dBTP per ADR 0002 (old manifests carried stale linear targets). New MIDI clips bake correct tempo metas and were byte-verified (the retired dnb melody encoded ~116 BPM, not 174). ukg also ships audio_drums.mid so the shuffle answer key is reproducible without hand-programmed groove. Docs updated: NEEDS.md (genre confirmation resolved, inventory + build queue), plans/owner-actions-recommendation-proof-plan.md (pilot + render order), RECOMMENDATION_VERDICT.md (corpus-composition note; old per-fixture proxy numbers not comparable). Verified: evaluate_recommendations.py --fixture <both> --source baseline (catalog-valid, zero issues), --self-test PASS, 34/34 tests.test_recommendation_evaluation. https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
…G decision) Owner resolved the wire-or-demote choice from NEEDS.md's dead-code finding: the deterministic recommendation engine stays as the scored free baseline in the GOAL.md sub-goal 3 three-source comparison but is NOT to be wired into the product. Score-driven product improvements land on the Phase 2 provider path instead. Recorded in: the module header (abletonDevices.ts), the eval bridge header, NEEDS.md (decision + the now-moot citation-emit candidate), BACKLOG.md, RECOMMENDATION_VERDICT.md, and CLAUDE.md's Backport Candidates line. No code changes — comments and docs only. Verified: npm run lint green, evaluate_recommendations.py --self-test PASS. https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
…ion script --source claude in evaluate_recommendations.py scores a stored Phase2Result from the Claude CLI provider (sibling phase2.claude.json, same ingestion as the gemini source). scripts/gen_claude_phase2.py produces those files at zero Gemini cost: it runs each fixture's stored Phase 1 fingerprint through the exact server path (server._run_interpretation_request) with ASA_PHASE2_PROVIDER=claude, so output flows the identical parse/citation/ catalogue/recommendations.v1 validation tail as Gemini. Research-only (mirrors the evaluate_* convention); deleting both restores the product exactly. Verified: --self-test PASS; --source claude SKIPs cleanly when no phase2.claude.json exists. https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
…ixtures Generated via scripts/gen_claude_phase2.py through the exact server path (ASA_PHASE2_PROVIDER=claude, model sonnet, MAX_THINKING_TOKENS=0): both fully cited (custody penalty 1.000), zero validation warnings, 31 recommendations.v1 envelope entries each. Scored with --source claude against the same proxy fingerprints the recorded Gemini numbers used: acid 0.485 (Gemini 0.172), house 0.424 (Gemini 0.343). melodic_techno_arp_124 evidence + the verdict write-up follow. https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
…rdict Completes the zero-Gemini-cost Claude scoring on the proxy corpus: - melodic_techno_arp_124 phase2.claude.json: 13 rec cards, fully cited, zero warnings, aggregate 0.424 — resolving the "Gemini 0 recs" outlier as Gemini-side (the fixture's fingerprint is interpretable). - RECOMMENDATION_VERDICT.md: dated head-to-head section. Claude (sonnet, text-only) 0.485/0.424/0.424 vs recorded Gemini 0.172/0.343/0.000 on identical fingerprints; mean 0.444 vs 0.172 on the shared subset. Caveats (proxy corpus, modality asymmetry, model class) stated inline. - NEEDS.md sub-goal 3: status updated; source list now includes claude. - docs/PHASE2_PROVIDER.md: 2026-06-11 addendum — scoring result + operating notes (MAX_THINKING_TOKENS=0 for headless calls, model pinning, 300-365s measured latency, session-limit failure mode). - CLAUDE.md: one-line MAX_THINKING_TOKENS note on ASA_CLAUDE_TIMEOUT_SECONDS. - gen_claude_phase2.py: defaults MAX_THINKING_TOKENS=0 and an 1800s timeout (overridable) so the next run doesn't rediscover the thinking-budget trap. Verified: --self-test PASS; full-corpus --source claude scores 3, SKIPs the 2 spec-only fixtures cleanly. https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
The Phase 1 contract spans analyze.py output, JSON_SCHEMA.md, and types.ts with no generated source of truth (tripwire #3). Most of parsePhase1Result passes detail blocks through verbatim, but ~12 parseOptional* reconstructors rebuild field-by-field — and a backend field one of them forgets to forward is silently dropped, breaking valid Phase 2 citations to it. This is the exact mechanism that dropped reverbDetail.preDelayMs / perBandRt60.* and the vocalDetail stem proxies. Add tests/services/phase1CitationContract.test.ts: feed a comprehensive payload through the real parsePhase1Result, run the real citation walker (collectPhase1FieldPaths) on the result, and assert every citable path survives. Each field is populated non-null, so a path is present only if the reconstructor carried it through — drop a field in any parseOptional* and its assertion fails by name. Verified teeth: re-injecting the preDelayMs drop fails exactly that one assertion and no other. Also point tripwire #3 at the new guard. Verified: 59 contract assertions green; full tests/services suite 809 green; npm run lint clean. https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
…) + retry catch The backend attaches a frozen, schema-validated, citation-gated projection of the Phase 2 device cards (recommendations.v1, ADR 0003) to Phase2Result.recommendations, but the UI never read it — so the chain of custody was decorative, not load-bearing, in the render path. Surface it as a verification layer (it must not replace the rich cards: it is flat, drops uncited cards, and carries no grouping). - New apps/ui/src/services/recommendationsContract.ts (pure, node-tested): normKey (raw device|parameter — NOT normalizeParameterLabel), buildContract- ValidatedKeys, isCardValidated (binary; all eligible merged items must match; synthetic/uncited → false), formatContractValue (number+unit+range / string passthrough), projectContractRows (resolves citations, raw-path fallback). - Match at build time in buildMixChainGroups/buildPatchCards/buildPatchGroups (raw device|parameter granularity, where the raw strings still exist), stamp contractValidated on the view-models; synthetic cards (Limiter fallback, MIDI Clip Guide, stereo-width) are never marked. Render a "Cited · validated" Pill (native title=, not Tooltip — no Radix provider) beside each mix/patch card. - New ReconstructionContractPanel.tsx: collapsible DataTable of the verbatim envelope (device · parameter · value · cited measurements) — the machine view exported to the .als generator. Mounted after Patch Framework (V2 only). - App.tsx: add a guarded catch to both retry handlers — a failed pre-monitor create*Attempt POST previously stopped the spinner with no error + unhandled rejection. Mirrors handleStartAnalysis + onError's shouldIgnoreRun guard. Design vetted by two sequential reviews (device-only matching was unsound; Collapsible/Tooltip are not the right primitives; the gain-vs-Output-Gain normalization crux has a regression test). Verified: 14 new contract tests (incl. the normalization crux); tests/services 823 green; npm run lint + build clean. DOM-dependent pill/retry behavior is left to the Playwright smoke gate (needs the live stack). https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
|
Closing as superseded. The per-card Reopening the two parts that are still unique to this work as a fresh PR off current
Generated by Claude Code |
Two changes off current main, salvaged from the superseded #185 (its per-card recommendations.v1 signal already landed via the UI-overhaul series, more richly, as contractEntries): - ReconstructionContractPanel: a collapsible single machine-view of the recommendations.v1 envelope (device · parameter · value · working range · cited measurements) — the verbatim, schema-validated, citation-gated set exported to the asa-ableton .als generator. main pairs the contract per-card; this is the complementary flat table. Built on the CollapsibleCard + DataTable primitives, reusing main's formatContractValue/formatContractRange (no duplicate projection). Renders nothing when the envelope is absent/empty. Mounted after Secret Sauce (V2 only). - Retry-handler catch: handleRetryPitchNoteExtraction / handleRetryInterpretation were try/finally with no catch, so a failed pre-monitor create*Attempt POST (backend down / 4xx) stopped the spinner with no error + an unhandled rejection. Added a guarded catch mirroring handleStartAnalysis + onError's shouldIgnoreRun guard. Verified: npm run lint + build clean; tests/services 830 green. DOM behavior (collapse, retry error banner) covered by the Playwright smoke gate in CI. https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy Co-authored-by: Claude <noreply@anthropic.com>
Status: Branch Deleted, Manual Recovery RequiredCurrent State: This PR was closed 3 weeks ago (2026-06-24) with a "dirty" mergeable state (merge conflict). The source branch What This PR Contains (Critical Analysis for Re-evaluation)This is a multi-commit feature stack (9 commits, +4,769/-38 across 19 files) landing three integrated systems: A. Backend: Phase 2 Export Envelope (Commits 1-7)
Contract Changes (Tripwire #3/#4 boundaries):
B. UI: Recommendations.v1 Trust Layer (Commit 9)
Why It Wasn't Merged ThenThe base branch was stale — main has since advanced with merged PRs #210–#216 (genre generalization, key ensemble gate, stem-aware chords). This PR's base (commit 33701e0) is ~3 weeks behind current main. Recovery Path (For Opus/Fable Agent)Option 1: Clean Cherry-Pick (Recommended if main has no conflicting changes)
Option 2: Full Rebase (If conflicts exist)
Critical Verification Checklist (Before Merge):
Why This MattersThis PR closes the chain-of-custody gap: Phase 2 exports were live in the backend but invisible in the UI. Without it, users can't verify that their recommendations are citation-backed. It's a verification layer, not a replacement for rich cards (correct per PURPOSE.md constraint 2). Recommendation for next agent: This is high-value consolidation work. If cherry-pick conflicts are minimal, ship it. If rebasing is needed, the commits are well-documented and the verification gates are comprehensive. Generated by Claude Code |
Status: Cannot Merge — Branch DeletedThe What happened
To resurrect this PR, a higher-tier agent (Opus/Fable) would need to:
Status reported by Claude Code Generated by Claude Code |
What
Makes the chain of custody load-bearing in the UI. The backend attaches a frozen, schema-validated, citation-gated projection of the Phase 2 device cards —
recommendations.v1(ADR 0003) — toPhase2Result.recommendations, but no component read it. This surfaces it as a verification layer (it cannot replace the rich cards: it's flat, drops uncited cards, and carries no grouping/rationale).Changes
A. Per-card "Cited · validated" trust pill (
recommendationsContract.ts+ view-model stamping)normKey,buildContractValidatedKeys,isCardValidated,formatContractValue,projectContractRows.device|parametergranularity — the only place the raw Phase 2 strings still exist (the per-card pill cannot match in the component because patch cards are merged-by-device and the rawparameteris gone). Synthetic cards (Limiter fallback, MIDI Clip Guide, stereo-width) have no Phase 2 origin and are never marked.contractValidated === true, beside the existing corpus badge, using a nativetitle=(notTooltip— avoids the Radix provider the corpus badge deliberately skips).B. "Reconstruction Contract" table (
ReconstructionContractPanel.tsx)DataTableof the verbatim envelope (Device · Parameter · Value · Cited measurements) — the honest machine view of what's exported to theasa-ableton.alsgenerator. Citation cells resolve against Phase 1, falling back to the raw path so a cell is never blank. Mounted after Patch Framework (V2 only); renders nothing when the envelope is absent/empty.C. Retry-handler catch fix (
App.tsx)handleRetryPitchNoteExtraction/handleRetryInterpretationweretry/finallywith nocatch; a failed pre-monitorcreate*AttemptPOST stopped the spinner with no error + an unhandled rejection. Added a guarded catch mirroringhandleStartAnalysis+ the surroundingonError'sshouldIgnoreRunguard.Design review
Vetted by two sequential reviews before implementation. They caught and the plan/code fixed: device-only matching was structurally unsound (→ raw
device|parameterat build time);Collapsible/Tooltipwere the wrong primitives; thegainvsnormalizeParameterLabel→Output Gainnormalization crux (backend storesgain) now has a dedicated regression test; the tri-state pill was dropped for a binary one.Verification
tests/services: 823 passed (61 files)npm run lint+npm run buildcleanDOM-dependent behavior (pill render, retry error banner) is left to the Playwright smoke gate in CI's Frontend job — it needs the live stack, not bootable in this worktree.
https://claude.ai/code/session_01YB5QFBLL4nbqPSdtcnxrLy
🤖 Generated with Claude Code
Generated by Claude Code