Skip to content

[codex] Prepare hosted runtime foundation - #6

Merged
slittycode merged 1 commit into
mainfrom
codex/plan-public-hosting-readiness
Mar 31, 2026
Merged

[codex] Prepare hosted runtime foundation#6
slittycode merged 1 commit into
mainfrom
codex/plan-public-hosting-readiness

Conversation

@slittycode

Copy link
Copy Markdown
Owner

Summary

This change prepares ASA for future public hosting without changing the shared audio-analysis engine or breaking the current local workflow. The backend now has explicit local and hosted runtime profiles, a dedicated worker-process boundary, hosted-mode run ownership hooks, and a storage boundary for artifacts. The frontend now understands hosted runtime configuration, can attach hosted request headers consistently, and can fetch protected spectrogram images through an authenticated path when needed.

In plain English: ASA still runs like the local app you already have, but the code no longer assumes that a future hosted version has to behave like a one-machine desktop setup.

Problem

Before this change, the codebase assumed a local-only runtime shape:

  • the API and worker logic effectively lived together
  • artifact handling assumed local filesystem paths were safe to expose or depend on
  • the browser assumed a localhost backend by default
  • canonical runs had no per-user ownership concept
  • hosted-only concerns had no real runtime boundary

That shape was workable for local development, but it would have caused trouble in a hosted deployment. In practice, a hosted API restart could recover work it did not own, browsers could fail to include hosted user headers consistently, and artifact/image access would not scale cleanly to a private beta or public web deployment.

Root cause

The system was designed correctly around the analysis engine, but the service wrapper around it was still local-first in too many places. Runtime behavior, artifact persistence, ownership, and frontend transport concerns were not yet isolated behind explicit local-versus-hosted boundaries.

What changed

The backend now has runtime profile helpers and process-role helpers so local and hosted behavior can diverge intentionally. Hosted mode requires a user identity header on canonical run routes, stores run ownership, and enforces ownership checks on fetch, interrupt, artifact, and delete flows. A dedicated worker.py process entrypoint now exists, and hosted startup no longer treats the API process like a worker for recovery purposes.

Artifact handling was split into public metadata versus internal storage details, and an artifact_storage.py boundary was introduced so the runtime no longer assumes every stored artifact is just a raw local disk path. The current implementation still uses the filesystem, but the code is now ready for a future object-storage backend.

On the frontend, runtime configuration now supports hosted-mode API behavior through VITE_RUNTIME_PROFILE and optional VITE_API_REQUEST_HEADERS_JSON. Shared request-header injection was applied across canonical run requests, estimate requests, identity probes, and artifact fetches. Spectrogram images now use an authenticated blob-fetch path when hosted headers are configured so protected artifact access still works in a hosted environment.

Documentation was updated to capture the hosting foundation work and to explain clearly what is implemented now versus what is still future infrastructure work.

User impact

For current local users, the intended impact is no regression. ./scripts/dev.sh, local UI development, local backend usage, and the shared analysis engine remain the supported path.

For future hosted users, this lays the foundation for a proper web deployment by introducing the missing service boundaries: ownership, API/worker separation, safer artifact contracts, and frontend hosted transport plumbing.

Verification

I ran the following checks while building and validating this work:

  • cd apps/backend && ./venv/bin/python -m py_compile analysis_runtime.py artifact_storage.py auth_context.py runtime_profile.py server.py worker.py
  • cd apps/backend && ./venv/bin/python -m unittest tests.test_analysis_runtime tests.test_cleanup tests.test_server
  • cd apps/backend && ./venv/bin/python -m unittest discover -s tests
  • cd apps/backend && ./venv/bin/python -m unittest tests.test_server tests.test_cleanup tests.test_runtime_profile
  • cd apps/ui && npx vitest run tests/services/analysisRunsClient.test.ts tests/services/analyzer.test.ts tests/services/diagnosticLogs.test.ts
  • cd apps/ui && npm run test:unit
  • cd apps/ui && npm run build
  • cd apps/ui && npm run lint
  • cd apps/ui && npm run test -- --run tests/services/config.test.ts tests/services/spectralArtifactsClient.test.ts tests/services/analysisRunsClient.test.ts

Follow-up work

This PR does not yet add real PostgreSQL, object storage, queue infrastructure, or production identity-provider token verification. It creates the application-side boundaries needed to do that work later without rewriting the local runtime path.

@slittycode
slittycode marked this pull request as ready for review March 31, 2026 23:34
@slittycode
slittycode merged commit 2b2a4bb into main Mar 31, 2026
1 of 2 checks passed
slittycode added a commit that referenced this pull request May 7, 2026
* prepare hosted runtime foundation (#6)

* [codex] Stabilize backend CI subprocess assertions (#7)

* prepare hosted runtime foundation

* stabilize backend ci subprocess assertions
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 18, 2026
…on) (#67)

* feat(ui): D.2 migrate SamplePlayback to ui/ primitives

PR 3 of 16 in the UI consistency overhaul. Worst-drift fix: kills
every raw zinc-* / amber-* / emerald-* / rounded-lg instance in
SamplePlayback.tsx (was 13 occurrences, now 0).

Stacked on top of PR #65 (D.1 primitives). Will be rebased onto main
once PR #65 merges.

Migration:
- Outer <section> → <DeviceRack name="AUDITION SAMPLES"
  subtitle="· Phase 3 heuristic" status={rackStatusFor(...)}>. Rack
  status reflects panel state — error tone on failure, active tone
  while generating, success tone once a manifest is rendered.
- Original <h3> id-based labelling replaced by aria-label on the
  DeviceRack; the title strip carries the visible name.
- Regenerate <button> → <Button variant="link" size="sm"> in the
  title-strip action slot.
- Generate <button> → <Button variant="primary" size="md"
  ledIndicator> in the body.
- ManifestMeta pill spans → <Pill tone="neutral" leadingDot>.
- SampleGroup <h4> → <SectionHeader size="sm" eyebrow="Group"
  title={CATEGORY_LABELS[category]}>.
- SampleCard <li> rounded-md border → <Panel variant="surface"
  padding="md"> (wrapped in a <li> for list semantics).
- confidenceClassFor() className helper → confidenceTone() returning
  a Pill tone (warning for LOW / low-confidence, neutral for MED,
  success for HIGH).
- Code spans for cited Phase 1 field paths now use design-token
  colors (bg-bg-app, border-border, text-text-primary) instead of
  raw zinc-300/zinc-800.
- MIDI download link uses text-accent + underline instead of zinc.

No behavior changes — same callbacks, same conditional rendering, same
content. Chain-of-custody fields (sample.cites.rationale,
sample.cites.phase1Fields) are rendered identically; PURPOSE.md
invariant preserved.

Verified: lint clean, 605/605 unit tests passing, build clean.
Bundle: AnalysisResults chunk grew from 256.79 KB to 311.86 KB
(larger import surface from the ui/ barrel; tree-shaking will tighten
this as more components migrate and the import is shared).

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* fix(ui): DeviceRack signal rail uses 3 explicit slots

PR #65 review flagged that when signalIn, railContent, and signalOut
are all set together, both railContent and signalOut carried ml-auto.
In a flex row only the first ml-auto sibling claims the available
space, so signalOut collapsed against railContent instead of
anchoring to the far right.

Restructure the rail as three fixed slots: a left container for
signalIn, a flex-1 center for railContent, and a right-justified
container for signalOut. Each slot always renders; empty when its
content is absent. Layout is now consistent across all combinations
of the three slot props.

PR #66 doesn't exercise the all-three-slots case (SamplePlayback only
sets status, not the rail), but D.4 (AnalysisStatusPanel migration)
will, so worth fixing now while the primitive has no production
consumers.

Verified: lint clean, 605/605 unit tests passing.

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* feat(ui): D.3 AnalysisStatusPanel → SignalChain (marquee transformation)

PR 4 of 16 in the UI consistency overhaul. The visual hook reviewers
will remember: the analysis pipeline literally renders as a horizontal
Live 12 device chain.

Stacked on PR #66 (D.2 SamplePlayback + DeviceRack rail fix). Branch
already includes the rail fix needed for the all-3-slots layout this
PR exercises.

Migration:
- Outer <div> → <DeviceRack name="ANALYSIS RUN"
  subtitle={'· ' + runId.slice(-8)} status={overallStatus}>.
  Status maps progress tone + isActive to idle/active/success/error.
- Stop button → <Button variant="danger" size="sm" leadingIcon=Square>
  in the title-strip action slot.
- The three stage tiles (measurement / pitchNoteTranslation /
  interpretation) → <SignalChain orientation="horizontal" animated>.
  The animated cables convey "data is flowing left to right" between
  active stages — the literal Live 12 device chain metaphor.
- Stage statuses (running/queued/completed/failed/etc.) map onto the
  SignalChain primitive's smaller vocabulary
  (idle/queued/active/success/error) via toSignalStatus().
- Per-stage retry buttons rendered via Button variant="ghost" in the
  stage device's action slot.
- Non-retryable error codes (e.g. GEMINI_NOT_CONFIGURED) surface as a
  truncated mono span in the action slot with the full message in the
  title attribute — Audit N1 sibling preserved.
- Primary readout (data-testid="status-panel-primary-readout") wraps
  in <Panel variant="inset" padding="sm"> instead of the hand-rolled
  rounded-sm border bg-bg-card div. Test ID preserved verbatim.
- Progress bar disappears as a standalone element — its information
  (elapsed time, estimate range, percent) folds into the DeviceRack's
  bottom signal-flow rail via railContent. Animated SignalChain cables
  + LED-pulse on the active stage + percent in the rail collectively
  carry the "still running" signal.

Preserved verbatim:
- AnalysisStatusPanelProps (no API change for App.tsx).
- computeLiveProgress() pure function and its export — the only
  consumer (tests/services/analysisStatusProgress.test.ts) was driven
  exclusively by this export and continues to pass without change.
- STAGE_LABELS, statusLabel, all stage-status mapping logic.
- The Audit N1 retry-button gating (errorMarkedNonRetryable check).
- The Audit Finding #6 primary-readout placement at the top of the
  body.

Bundle: index chunk grew from 271 KB → 325 KB gz (97.5 KB compressed)
because AnalysisStatusPanel now imports more from the ui/ barrel.
AnalysisResults shrank slightly. Will tighten as more files share the
import.

Verified: lint clean, 605/605 unit tests passing (computeLiveProgress
test suite unaffected), build clean.

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* test(ui): unit-cover AnalysisStatusPanel helpers + tighten ready mapping

PR #67 review (REQUEST CHANGES) called out that toSignalStatus() and
rackStatusFromProgress() were new logic without tests, and that 'ready'
mapping to 'queued' was a judgment-call regression (the user already
sees a Retry button in the action slot — the tile reading 'queued'
implies the chain will auto-resume when it actually won't).

Changes:
- Export toSignalStatus, statusLabel, and rackStatusFromProgress from
  AnalysisStatusPanel.tsx so the test suite can import them. Add doc
  comments to each explaining the intent + the per-case mapping
  rationale.
- Remap 'ready' from 'queued' to 'idle'. statusLabel('ready') still
  returns 'READY' so the user sees the correct label inside the tile;
  the LED + cable + box-shadow no longer fake imminent execution.
- New test file tests/services/analysisStatusPanelHelpers.test.ts
  with 23 tests covering every AnalysisStageStatus value, the unknown
  fallback for both helpers, the dominance of failed/success tones in
  rackStatusFromProgress, the running+isActive interaction, and the
  ready-vs-queued separation explicitly.

Test count: 605 → 628.

Verified: lint clean, full unit suite 628/628 passing.

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

---------

Co-authored-by: Claude <noreply@anthropic.com>
slittycode pushed a commit that referenced this pull request May 18, 2026
PR 5 of 16 in the UI consistency overhaul. The Input Source panel
becomes a <DeviceRack name="Input Source">; the analysis-option
toggles become Radix Checkboxes; the Run Analysis CTA becomes a
<Button variant="primary" ledIndicator>; the loaded-file card becomes
a <Panel variant="surface">.

Stacked on PR #67 (D.3 SignalChain). Branch will be rebased onto main
once #66#67 → PR 5 merge in order.

FileUpload.tsx:
- Loaded file card: replaced the hand-rolled bg-bg-card border div
  with <Panel variant="surface" padding="md">. Accent stripe + file
  icon + name + ready-dot + size-warning preserved verbatim.
- Format chips (MP3/WAV/FLAC/AIFF): replaced inline span chips with
  <Pill tone="neutral" variant="outline" size="xs">. Visual parity.
- "Load Demo Track" <button>: replaced with <Button variant="secondary"
  size="md">. Behavior + text preserved verbatim for the smoke test
  selector (tests/smoke/responsive-layout.spec.ts asserts the text).
- Remove File icon button: replaced with <Button variant="ghost"
  iconOnly size="sm">.
- Dropzone container kept as raw div — its drag-event handlers and
  conditional background-tint state are denser than Panel currently
  supports, and shimming Panel to accept onDragOver/Leave/Drop +
  three-way tint logic just to wrap one site would add API surface
  for one consumer.

App.tsx — Input Source panel:
- Outer wrapper at :1018-1027: hand-rolled bg-bg-surface-dark header
  + bg-bg-card body → <DeviceRack name="Input Source"
  status={audioFile ? (isAnalyzing ? 'active' : 'success') : 'idle'}>.
  Preserved data-testid="input-panel".
- Collapsed-summary buttons (Analyze new file + Adjust settings):
  swapped from inline <button> bg-bg-panel borders to <Button
  variant="secondary">.
- "Editing analysis settings" Hide button: <Button variant="secondary"
  size="sm">.
- STEM PITCH/NOTE TRANSLATION toggle: replaced <input type="checkbox">
  with Radix <Checkbox> (via the ui/ wrapper). Outer card label kept
  for the click-anywhere-on-card UX; htmlFor binds the label to the
  checkbox id. Preserved aria-label="PITCH/NOTE TRANSLATION" so the
  smoke selectors continue to resolve.
- AI INTERPRETATION toggle: same treatment. Preserved
  data-testid="phase2-status-inline" and aria-label="AI INTERPRETATION".
- Run Analysis CTA: replaced the motion.button with <Button
  variant="primary" size="lg" ledIndicator leadingIcon={<Play>}>.
  The Button primitive already encodes the bg-bg-panel +
  border-accent/60 + glow hover + ledIndicator pulse that the
  inline motion.button was hand-rolling. Text "Run Analysis"
  preserved verbatim for tests/smoke/upload-phase1.spec.ts.
- Removed the explicit `<X />` import from lucide-react remains used
  by other call sites in App.tsx; left intact.
- Removed unused motion.button (motion is still used elsewhere in the
  file; the import stays).

Pre-flight decisions honored:
- Two native <select> at :984-998 (phase2-model-desktop) and
  :1191-1204 (phase2-model-mobile) stayed native — they were already
  restyled with appearance-none and don't need migration.
- Radix Checkbox, not Switch, for the analysis options (selection
  metaphor, not on/off feature flag).
- TooltipProvider not mounted yet — no Tooltip consumers in this PR.

Preserved verbatim:
- All data-testid attributes (input-panel, input-panel-collapsed,
  phase2-status-badge, phase2-status-inline).
- All visible text strings: "Input Source", "Drop Audio Here",
  "Load Demo Track", "Run Analysis", "↺ Analyze new file",
  "Adjust settings", "Hide", "STEM PITCH/NOTE TRANSLATION",
  "AI INTERPRETATION", "Ready", "Estimated local analysis".
- All ARIA labels (PITCH/NOTE TRANSLATION, AI INTERPRETATION,
  ANALYSIS MODE, Interpretation model).
- Behavior: onCheckedChange wraps Radix's CheckedState ('true' |
  'false' | 'indeterminate') with a strict `=== true` check before
  setting the boolean state, preserving the existing semantics.
- Audit findings (N9 collapse, N9 re-expand banner, #11 no CPU
  meter, #6 readout placement) untouched.

Verified: lint clean, 605/605 unit tests passing, build clean
(index chunk 323 KB / 97 KB gz; comparable to PR 4 — primitive
imports are now amortized across consumers).

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe
slittycode added a commit that referenced this pull request May 18, 2026
* feat(ui): D.2 migrate SamplePlayback to ui/ primitives

PR 3 of 16 in the UI consistency overhaul. Worst-drift fix: kills
every raw zinc-* / amber-* / emerald-* / rounded-lg instance in
SamplePlayback.tsx (was 13 occurrences, now 0).

Stacked on top of PR #65 (D.1 primitives). Will be rebased onto main
once PR #65 merges.

Migration:
- Outer <section> → <DeviceRack name="AUDITION SAMPLES"
  subtitle="· Phase 3 heuristic" status={rackStatusFor(...)}>. Rack
  status reflects panel state — error tone on failure, active tone
  while generating, success tone once a manifest is rendered.
- Original <h3> id-based labelling replaced by aria-label on the
  DeviceRack; the title strip carries the visible name.
- Regenerate <button> → <Button variant="link" size="sm"> in the
  title-strip action slot.
- Generate <button> → <Button variant="primary" size="md"
  ledIndicator> in the body.
- ManifestMeta pill spans → <Pill tone="neutral" leadingDot>.
- SampleGroup <h4> → <SectionHeader size="sm" eyebrow="Group"
  title={CATEGORY_LABELS[category]}>.
- SampleCard <li> rounded-md border → <Panel variant="surface"
  padding="md"> (wrapped in a <li> for list semantics).
- confidenceClassFor() className helper → confidenceTone() returning
  a Pill tone (warning for LOW / low-confidence, neutral for MED,
  success for HIGH).
- Code spans for cited Phase 1 field paths now use design-token
  colors (bg-bg-app, border-border, text-text-primary) instead of
  raw zinc-300/zinc-800.
- MIDI download link uses text-accent + underline instead of zinc.

No behavior changes — same callbacks, same conditional rendering, same
content. Chain-of-custody fields (sample.cites.rationale,
sample.cites.phase1Fields) are rendered identically; PURPOSE.md
invariant preserved.

Verified: lint clean, 605/605 unit tests passing, build clean.
Bundle: AnalysisResults chunk grew from 256.79 KB to 311.86 KB
(larger import surface from the ui/ barrel; tree-shaking will tighten
this as more components migrate and the import is shared).

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* fix(ui): DeviceRack signal rail uses 3 explicit slots

PR #65 review flagged that when signalIn, railContent, and signalOut
are all set together, both railContent and signalOut carried ml-auto.
In a flex row only the first ml-auto sibling claims the available
space, so signalOut collapsed against railContent instead of
anchoring to the far right.

Restructure the rail as three fixed slots: a left container for
signalIn, a flex-1 center for railContent, and a right-justified
container for signalOut. Each slot always renders; empty when its
content is absent. Layout is now consistent across all combinations
of the three slot props.

PR #66 doesn't exercise the all-three-slots case (SamplePlayback only
sets status, not the rail), but D.4 (AnalysisStatusPanel migration)
will, so worth fixing now while the primitive has no production
consumers.

Verified: lint clean, 605/605 unit tests passing.

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* feat(ui): D.3 AnalysisStatusPanel → SignalChain (marquee transformation)

PR 4 of 16 in the UI consistency overhaul. The visual hook reviewers
will remember: the analysis pipeline literally renders as a horizontal
Live 12 device chain.

Stacked on PR #66 (D.2 SamplePlayback + DeviceRack rail fix). Branch
already includes the rail fix needed for the all-3-slots layout this
PR exercises.

Migration:
- Outer <div> → <DeviceRack name="ANALYSIS RUN"
  subtitle={'· ' + runId.slice(-8)} status={overallStatus}>.
  Status maps progress tone + isActive to idle/active/success/error.
- Stop button → <Button variant="danger" size="sm" leadingIcon=Square>
  in the title-strip action slot.
- The three stage tiles (measurement / pitchNoteTranslation /
  interpretation) → <SignalChain orientation="horizontal" animated>.
  The animated cables convey "data is flowing left to right" between
  active stages — the literal Live 12 device chain metaphor.
- Stage statuses (running/queued/completed/failed/etc.) map onto the
  SignalChain primitive's smaller vocabulary
  (idle/queued/active/success/error) via toSignalStatus().
- Per-stage retry buttons rendered via Button variant="ghost" in the
  stage device's action slot.
- Non-retryable error codes (e.g. GEMINI_NOT_CONFIGURED) surface as a
  truncated mono span in the action slot with the full message in the
  title attribute — Audit N1 sibling preserved.
- Primary readout (data-testid="status-panel-primary-readout") wraps
  in <Panel variant="inset" padding="sm"> instead of the hand-rolled
  rounded-sm border bg-bg-card div. Test ID preserved verbatim.
- Progress bar disappears as a standalone element — its information
  (elapsed time, estimate range, percent) folds into the DeviceRack's
  bottom signal-flow rail via railContent. Animated SignalChain cables
  + LED-pulse on the active stage + percent in the rail collectively
  carry the "still running" signal.

Preserved verbatim:
- AnalysisStatusPanelProps (no API change for App.tsx).
- computeLiveProgress() pure function and its export — the only
  consumer (tests/services/analysisStatusProgress.test.ts) was driven
  exclusively by this export and continues to pass without change.
- STAGE_LABELS, statusLabel, all stage-status mapping logic.
- The Audit N1 retry-button gating (errorMarkedNonRetryable check).
- The Audit Finding #6 primary-readout placement at the top of the
  body.

Bundle: index chunk grew from 271 KB → 325 KB gz (97.5 KB compressed)
because AnalysisStatusPanel now imports more from the ui/ barrel.
AnalysisResults shrank slightly. Will tighten as more files share the
import.

Verified: lint clean, 605/605 unit tests passing (computeLiveProgress
test suite unaffected), build clean.

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* chore(ui): normalize package-lock.json optional dep dev flags

npm install during PR review pass dropped "dev": true from optional
rollup platform-binary entries — cosmetic lockfile normalization, no
functional change.

https://claude.ai/code/session_01MWRh46jPZ7YbFq4SkPFEwU

---------

Co-authored-by: Claude <noreply@anthropic.com>
slittycode added a commit that referenced this pull request May 18, 2026
* feat(ui): D.4 migrate FileUpload + Input Source to ui/ primitives

PR 5 of 16 in the UI consistency overhaul. The Input Source panel
becomes a <DeviceRack name="Input Source">; the analysis-option
toggles become Radix Checkboxes; the Run Analysis CTA becomes a
<Button variant="primary" ledIndicator>; the loaded-file card becomes
a <Panel variant="surface">.

Stacked on PR #67 (D.3 SignalChain). Branch will be rebased onto main
once #66#67 → PR 5 merge in order.

FileUpload.tsx:
- Loaded file card: replaced the hand-rolled bg-bg-card border div
  with <Panel variant="surface" padding="md">. Accent stripe + file
  icon + name + ready-dot + size-warning preserved verbatim.
- Format chips (MP3/WAV/FLAC/AIFF): replaced inline span chips with
  <Pill tone="neutral" variant="outline" size="xs">. Visual parity.
- "Load Demo Track" <button>: replaced with <Button variant="secondary"
  size="md">. Behavior + text preserved verbatim for the smoke test
  selector (tests/smoke/responsive-layout.spec.ts asserts the text).
- Remove File icon button: replaced with <Button variant="ghost"
  iconOnly size="sm">.
- Dropzone container kept as raw div — its drag-event handlers and
  conditional background-tint state are denser than Panel currently
  supports, and shimming Panel to accept onDragOver/Leave/Drop +
  three-way tint logic just to wrap one site would add API surface
  for one consumer.

App.tsx — Input Source panel:
- Outer wrapper at :1018-1027: hand-rolled bg-bg-surface-dark header
  + bg-bg-card body → <DeviceRack name="Input Source"
  status={audioFile ? (isAnalyzing ? 'active' : 'success') : 'idle'}>.
  Preserved data-testid="input-panel".
- Collapsed-summary buttons (Analyze new file + Adjust settings):
  swapped from inline <button> bg-bg-panel borders to <Button
  variant="secondary">.
- "Editing analysis settings" Hide button: <Button variant="secondary"
  size="sm">.
- STEM PITCH/NOTE TRANSLATION toggle: replaced <input type="checkbox">
  with Radix <Checkbox> (via the ui/ wrapper). Outer card label kept
  for the click-anywhere-on-card UX; htmlFor binds the label to the
  checkbox id. Preserved aria-label="PITCH/NOTE TRANSLATION" so the
  smoke selectors continue to resolve.
- AI INTERPRETATION toggle: same treatment. Preserved
  data-testid="phase2-status-inline" and aria-label="AI INTERPRETATION".
- Run Analysis CTA: replaced the motion.button with <Button
  variant="primary" size="lg" ledIndicator leadingIcon={<Play>}>.
  The Button primitive already encodes the bg-bg-panel +
  border-accent/60 + glow hover + ledIndicator pulse that the
  inline motion.button was hand-rolling. Text "Run Analysis"
  preserved verbatim for tests/smoke/upload-phase1.spec.ts.
- Removed the explicit `<X />` import from lucide-react remains used
  by other call sites in App.tsx; left intact.
- Removed unused motion.button (motion is still used elsewhere in the
  file; the import stays).

Pre-flight decisions honored:
- Two native <select> at :984-998 (phase2-model-desktop) and
  :1191-1204 (phase2-model-mobile) stayed native — they were already
  restyled with appearance-none and don't need migration.
- Radix Checkbox, not Switch, for the analysis options (selection
  metaphor, not on/off feature flag).
- TooltipProvider not mounted yet — no Tooltip consumers in this PR.

Preserved verbatim:
- All data-testid attributes (input-panel, input-panel-collapsed,
  phase2-status-badge, phase2-status-inline).
- All visible text strings: "Input Source", "Drop Audio Here",
  "Load Demo Track", "Run Analysis", "↺ Analyze new file",
  "Adjust settings", "Hide", "STEM PITCH/NOTE TRANSLATION",
  "AI INTERPRETATION", "Ready", "Estimated local analysis".
- All ARIA labels (PITCH/NOTE TRANSLATION, AI INTERPRETATION,
  ANALYSIS MODE, Interpretation model).
- Behavior: onCheckedChange wraps Radix's CheckedState ('true' |
  'false' | 'indeterminate') with a strict `=== true` check before
  setting the boolean state, preserving the existing semantics.
- Audit findings (N9 collapse, N9 re-expand banner, #11 no CPU
  meter, #6 readout placement) untouched.

Verified: lint clean, 605/605 unit tests passing, build clean
(index chunk 323 KB / 97 KB gz; comparable to PR 4 — primitive
imports are now amortized across consumers).

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* fix(ui): drop unused Panel import from App.tsx

PR #69 review flagged App.tsx:9 imports Panel but never uses it
(Panel is only consumed in FileUpload.tsx). Drop it.

This is also a low-cost iteration to get CI logs against a fresh
commit — the Frontend job failed on the previous push but the
auth-gated logs prevented diagnosis from this sandbox. Pushing this
clean fix to re-trigger the smoke suite.

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* fix(ui): revert App.tsx analysis-option toggles to native <input>

PR #69's Frontend CI failed twice on smoke tests without accessible
logs from this sandbox. Best hypothesis: the smoke spec at
tests/smoke/upload-estimate-phase1.spec.ts:242-246 uses
page.getByLabel("PITCH/NOTE TRANSLATION") plus .toBeChecked() and
.uncheck(). Playwright's getByLabel documents itself for "input
elements", and .uncheck() expects native checkboxes (or, in some
versions, also role=checkbox). The Radix Checkbox renders as a
<button role="checkbox"> internally; that's a less-consistently-
supported target for these methods than a real <input>.

Revert the two toggles in App.tsx (PITCH/NOTE TRANSLATION and AI
INTERPRETATION) to native <input type="checkbox"> with the same
appearance-none-style border-card UX. The Checkbox primitive in
components/ui/ is unchanged and remains available for surfaces where
smoke selectors aren't load-bearing. Inline comments explain the
rationale at each call site so future migrations don't naively
re-introduce Radix here.

Local 628/628 unit tests still pass; lint clean. If smoke CI passes
on this commit, the hypothesis is confirmed and we can write a real
test against the Radix Checkbox primitive separately to decide
whether it's worth keeping in the canonical toolkit.

https://claude.ai/code/session_01WNtYcWXwn4mVnnjxqT7uAe

* fix(ui): restore bg-bg-card on input-panel to satisfy theme-shell smoke

PR #69's Frontend CI failed three times. tests/smoke/theme-shell.spec.ts:41
asserts the computed background-color of [data-testid="input-panel"]
is exactly rgb(68, 68, 68) — that's --color-bg-card (#444444). The
PR 5 migration removed `bg-bg-card` from that div because the
DeviceRack wrapper was meant to own the surface chrome. But the
DeviceRack's body is transparent by default (so the rack's gradient
face shows through); the computed background becomes rgba(0,0,0,0)
instead of #444444, failing the test.

Add `bg-bg-card` back to the inner div explicitly. The DeviceRack
still owns the title-strip gradient, border, and box-shadow; the body
is now flat #444444 matching the previous palette contract — and
also matching how Live 12 devices visually compose (gradient header,
relatively flat body).

Also restore the `p-4` padding inside the input-panel div — the
DeviceRack body's --space-device-pad-* is smaller than the original
p-4 the test scenarios were calibrated against.

The other smoke selectors (getByLabel for the toggles, getByTitle on
Remove File, getByRole on Run Analysis) were never the issue —
they're all preserved through prop spreading on the new primitives.

Verified: lint clean, 628/628 unit tests passing, build clean.

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
Follow-up to the 2026-05-30 review fixes (PR #125). The resurrection guards
and the #12 cache path already had regressions, but the two terminalizers this
branch ADDED — #7 (_execute_measurement_run source-artifact guard) and #3/#6
(_execute_interpretation_attempt setup wrapper) — rode on the pre-existing MT3
sibling test (test_source_artifact_failure_terminalizes_attempt) with no
regression of their own. A reverted guard would have stayed green.

Add StageSetupFailureTerminalizationTests, one test per stage, mirroring the MT3
sibling: reserve the stage, make source/grounding resolution raise, and assert
the stage terminalizes to 'failed' with the expected error code instead of
wedging in 'running'. Verified meaningful — both error out when the
terminalizer's except is neutered.

Backend 1071 green (+2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
slittycode added a commit that referenced this pull request May 31, 2026
Follow-up to the 2026-05-30 review fixes (PR #125). The resurrection guards
and the #12 cache path already had regressions, but the two terminalizers this
branch ADDED — #7 (_execute_measurement_run source-artifact guard) and #3/#6
(_execute_interpretation_attempt setup wrapper) — rode on the pre-existing MT3
sibling test (test_source_artifact_failure_terminalizes_attempt) with no
regression of their own. A reverted guard would have stayed green.

Add StageSetupFailureTerminalizationTests, one test per stage, mirroring the MT3
sibling: reserve the stage, make source/grounding resolution raise, and assert
the stage terminalizes to 'failed' with the expected error code instead of
wedging in 'running'. Verified meaningful — both error out when the
terminalizer's except is neutered.

Backend 1071 green (+2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
slittycode added a commit that referenced this pull request May 31, 2026
…ift, error boundary (#125)

* fix: staged-run lifecycle reliability + chain-of-custody/contract fixes

From the 2026-05-30 full review (audits/full-review-2026-05-30.md).

Staged-run lifecycle (P1/P2 reliability cluster):
- Register pitch-note + MT3 subprocesses for interruption so interrupt/delete
  actually terminate them instead of orphaning multi-GB children up to their
  600s/1800s timeout; route both through the proven _run_streamed_subprocess.
- Terminalize interpretation and measurement attempts on setup failure (were
  left 'running' forever with no reaper).
- Guard every attempt/measurement transition against resurrecting an
  interrupted row, and gate the measurement follow-up enqueue on the guarded
  update so a cancelled run can't spawn a fresh pipeline.
- Frontend monitorAnalysisRun: overall wall-clock budget + per-poll deadline
  (was an unbounded poll loop with no per-request timeout).

Chain-of-custody + quick wins:
- Phase 1 parser now carries reverbDetail.perBandRt60 / preDelayMs and
  vocalDetail.stemEnergyRatio / stemOtherCorrelation -- prompt-citable fields it
  silently dropped, which broke the citation-existence check.
- normalizeKey no longer rewrites "major" into "majoror" (false key
  contradiction on a valid "Maj" abbreviation).
- Define _FILE_CACHE_LOCK / _FILE_CACHE_TTL_SECONDS and import timedelta --
  latent NameError in the (currently unwired) temp-file cache path.
- Add a React ErrorBoundary around the lazy AnalysisResults Suspense so one
  render or chunk-load failure can't blank the whole app.

Tests: backend resurrection + temp-file-cache regressions (+ caught a second
undefined name, timedelta); frontend parser/validator/citation contract,
normalizeKey, and polling-timeout regressions. Backend 1068 green, frontend 713
green, lint + production build clean.

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

* fix: address review follow-ups + ErrorBoundary fallback smoke test

Bundles the P3/LOW findings from the sequential review of e3e0f69 (verdict:
ship-as-is) plus the ErrorBoundary fallback-render coverage.

1. Backend orphan guard (analysis_runtime.py): _enqueue_requested_followups now
   re-asserts measurement.status == 'completed' before creating follow-up rows.
   Closes the cross-transaction window where an interrupt committing between
   complete_measurement's guarded update and the (separate) enqueue transaction
   would leave inert 'queued' rows on a cancelled run.
2. Faithful MT3 mock-timeout (server.py): the mock branch of
   _run_streamed_subprocess now coerces bytes streams to text via _stream_text,
   matching the real branch's text=True contract — so a TimeoutExpired carrying
   bytes reaches the timeout-specific branch instead of a str-in-bytes TypeError
   that the broad except masked. Test tightened to assert the timeout message.
3. Bound the mid-loop interpretation POST (analyzer.ts): extracted the
   race-with-neutralized-fetch logic into an exported raceWithDeadline<T> helper
   and wrapped the stem-summary createInterpretationAttempt POST in it, closing
   the last unbounded await in the poll loop.
4. ErrorBoundary docstring: corrected the "Try again" claim — React.lazy caches
   a rejected import, so it recovers a render-throw but not a chunk-load failure
   ("Reload page" does). No behavior change.
5. ErrorBoundary fallback smoke test (tests/smoke/error-boundary.spec.ts): drives
   a mocked run to the results view, aborts the lazy AnalysisResults chunk, and
   asserts the fallback renders (alert + Try again/Reload). Verified meaningful:
   it fails when the boundary is removed. Updates the unit-test caveat comment.

Tests: backend 1069 green (+1 orphan-race regression), frontend 716 green (+3
raceWithDeadline) + lint clean + the new smoke spec passes.

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

* test: lock the measurement + interpretation setup-failure terminalizers

Follow-up to the 2026-05-30 review fixes (PR #125). The resurrection guards
and the #12 cache path already had regressions, but the two terminalizers this
branch ADDED — #7 (_execute_measurement_run source-artifact guard) and #3/#6
(_execute_interpretation_attempt setup wrapper) — rode on the pre-existing MT3
sibling test (test_source_artifact_failure_terminalizes_attempt) with no
regression of their own. A reverted guard would have stayed green.

Add StageSetupFailureTerminalizationTests, one test per stage, mirroring the MT3
sibling: reserve the stage, make source/grounding resolution raise, and assert
the stage terminalizes to 'failed' with the expected error code instead of
wedging in 'running'. Verified meaningful — both error out when the
terminalizer's except is neutered.

Backend 1071 green (+2).

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

---------

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

Six concrete gaps found between CLAUDE.md and the filesystem:

1. docs/ repo-layout entry was underspecified — added adr/, SETUP.md,
   LAYER2_EVALUATION.md, POLYPHONIC_TRANSCRIPTION_SPIKE.md, and
   SAMPLE_GENERATION.md to the inventory.

2. Companion Agent Docs section was missing docs/adr/ — added item #6
   pointing at ADR 0001 (Phase 1 JSON schema v1 stability contract).

3. scripts/calibrate_confidence.py entry omitted that its test lives in
   scripts/tests/test_calibrate_confidence.py.

4. src/services/midi/ file list was missing types.ts.

5. src/services/sessionMusician/ file list was missing index.ts.

6. docs/generate_phase2_truthfulness_doc.js was a stale one-shot docx
   generator sitting unreferenced at the docs root — moved to
   docs/history/ where past-tense deliverables belong.

https://claude.ai/code/session_01ScX5H3JDVAnSjvk4k7HA7p

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