Skip to content

feat: freeze versioned recommendations.v1 JSON contract for Phase 2 output - #143

Merged
slittycode merged 1 commit into
mainfrom
claude/magical-diffie-1d2652
Jun 5, 2026
Merged

feat: freeze versioned recommendations.v1 JSON contract for Phase 2 output#143
slittycode merged 1 commit into
mainfrom
claude/magical-diffie-1d2652

Conversation

@slittycode

Copy link
Copy Markdown
Owner

What & why

ASA's Phase 2 emits device recommendations across three free-shaped arrays
(abletonRecommendations, mixAndMasterChain, secretSauce.workflowSteps) with
free-text values ("10 ms", "3:1") and no machine-checkable freeze. This PR
adds a normalized, versioned, schema-validated recommendations contract for
that 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.md invariant #1 (never overrides Phase

  1. by construction, and is deterministic/testable with no Gemini spend.

Changes

  • 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_measurements minItems:1.
  • apps/backend/recommendations_contract.pyproject_recommendations
    flattens the three arrays and parses value→value+unit (logic ported from the
    recommendation scorer); validate_envelope checks against the literal schema
    file
    via jsonschema.
  • apps/backend/server.py — attaches the validated envelope to the
    producer_summary interpretation result; it travels into
    GET /api/analysis-runs/{run_id} at stages.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 — document
    the contract and its semver/compat policy.
  • apps/backend/requirements.txt — pin jsonschema==4.26.0 + transitives.

Design notes for reviewers

  1. jsonschema dep is deliberate. The repo's house style validates schemas
    with stdlib mirrors (live12_catalogue.py). Here the whole point is the
    CI-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.
  2. Citation-gating. Schema requires cited_measurements (minItems:1); the
    projection 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.
  3. range/unit are nullable best-effort — the static Live 12 catalogue
    carries no min/max, so range is a per-unit tolerance neighborhood (not a
    device hard-limit) emitted only for numeric value + known unit (invariant fix: chunk Demucs inference and stop boot-time memory spikes #4).
  4. Chain of custody is test-backed end-to-end: server attaches it
    (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).
  • Server wiring test + runtime snapshot-survival test.
  • Full backend suite green: 1153 tests OK. tsc --noEmit green (UI change is
    type-only).

DoD

  • Schema committed + versioned
  • All Phase-2 output validated against it in CI (unittest discover)
  • One round-trip test
  • Documented (ADR 0003)

🤖 Generated with Claude Code

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 slittycode left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :1 alternation in _VALUE_RE is dead code. Ratio strings are caught by the ratio_match early-return at line ~117 before _VALUE_RE.search(s) runs. Harmless, but a reader will wonder why it's there.

  • interpretation.ts:157cited_measurements: string[] doesn't encode minItems: 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

@slittycode
slittycode merged commit d0cde21 into main Jun 5, 2026
4 checks passed
@slittycode
slittycode deleted the claude/magical-diffie-1d2652 branch June 5, 2026 06:36
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