feat: freeze versioned recommendations.v1 JSON contract for Phase 2 output - #143
Conversation
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
left a comment
There was a problem hiding this comment.
Verdict: APPROVE (posted as COMMENT — can't self-review)
Summary
Adds a frozen, versioned recommendations.v1 JSON contract that projects Phase 2 device cards into a normalized {device, parameter, value, unit, range, cited_measurements[]} envelope — the same stability promise ADR 0001 gave Phase 1 measurements, now for Phase 2 recommendations. The implementation is purely additive: it reads Phase 2 arrays, produces a derived sibling field on the interpretation result, and never touches Phase 1 values. Well-described, well-tested, architecturally clean. CI: 3/4 green (Frontend, WASM, Chromatic); Backend in progress. Contract test suite runs clean locally (30/30 OK).
Findings
Worth considering (non-blocking):
-
recommendations_contract.py:92— The:1alternation in_VALUE_REis dead code. Ratio strings are caught by theratio_matchearly-return at line ~117 before_VALUE_RE.search(s)runs. Harmless, but a reader will wonder why it's there. -
interpretation.ts:157—cited_measurements: string[]doesn't encodeminItems: 1. The schema and server-side projection both enforce it, so this is a type-precision gap, not a bug.[string, ...string[]]is the TypeScript idiom — probably not worth the ergonomic cost.
Neither warrants a revision.
Test results
Local contract suite: 30 pass, 0 fail.
CI: Frontend ✓, Loudness WASM ✓, Chromatic ✓, Backend pending (slow 1153-test suite).
Phase boundary check
Clean. project_recommendations only reads from the Phase 2 result dict. cited_measurements values are the Phase 1 path strings that Gemini itself emitted in phase1Fields — they're threaded through as opaque strings, not re-derived from measurements. interpretation_result["recommendations"] = … in server.py adds a sibling key and overwrites nothing existing. Degrade-on-error matches the catalogue-gate and loudness-backend philosophy exactly.
Generated by Claude Code
What & why
ASA's Phase 2 emits device recommendations across three free-shaped arrays
(
abletonRecommendations,mixAndMasterChain,secretSauce.workflowSteps) withfree-text values (
"10 ms","3:1") and no machine-checkable freeze. This PRadds a normalized, versioned, schema-validated
recommendationscontract forthat surface — the same stability promise ADR 0001 gave Phase 1 measurements,
now for Phase 2 recommendations.
It is a derived, additive projection of the existing cards, not a change to
what Gemini emits — so it honors
PURPOSE.mdinvariant #1 (never overrides PhaseChanges
apps/backend/schemas/recommendations.v1.schema.json— Draft 2020-12 schema.Envelope
{version, recommendations[]}; each entry exactly{device, parameter, value, unit, range, cited_measurements[]},additionalProperties:false,cited_measurementsminItems:1.apps/backend/recommendations_contract.py—project_recommendationsflattens the three arrays and parses
value→value+unit (logic ported from therecommendation scorer);
validate_envelopechecks against the literal schemafile via
jsonschema.apps/backend/server.py— attaches the validated envelope to theproducer_summaryinterpretation result; it travels intoGET /api/analysis-runs/{run_id}atstages.interpretation.result.recommendations(degrades to absent on any error).
apps/ui/src/types/interpretation.ts— TS mirror (RecommendationsContract).docs/adr/0003-recommendations-contract-v1.md+ CLAUDE.md index — documentthe contract and its semver/compat policy.
apps/backend/requirements.txt— pinjsonschema==4.26.0+ transitives.Design notes for reviewers
jsonschemadep is deliberate. The repo's house style validates schemaswith stdlib mirrors (
live12_catalogue.py). Here the whole point is theCI-gate, and a hand-rolled mirror that resembles the schema reintroduces
exactly the drift ADR 0001 warned about — so the contract validates against
the committed file with a real validator. Rationale in ADR 0003.
cited_measurements(minItems:1); theprojection admits only cited cards. Uncited cards are excluded from this
normalized view by design and remain in the raw arrays, where the existing
warn-and-keep catalogue gate flags them — nothing user-facing is dropped.
range/unitare nullable best-effort — the static Live 12 cataloguecarries no min/max, so
rangeis a per-unit tolerance neighborhood (not adevice hard-limit) emitted only for numeric value + known unit (invariant fix: chunk Demucs inference and stop boot-time memory spikes #4).
(
test_server), and it survives persistence into the run snapshot(
test_analysis_runtime) — verified, not assumed.Tests
test_recommendations_contract.py(30): schema validity, projection-validates,round-trip, freeze (additionalProperties / minItems / required / enum / version).
tsc --noEmitgreen (UI change istype-only).
DoD
unittest discover)🤖 Generated with Claude Code