Skip to content

feat(MOR-4274): one scope session — demand, view negotiation and a single CI-V writer - #4522

Merged
morozsm merged 62 commits into
mainfrom
codex/mor-4274-r5-scope-session
Nov 9, 2026
Merged

morozsm merged 62 commits into
mainfrom
codex/mor-4274-r5-scope-session

Conversation

@morozsm

@morozsm morozsm commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Size exception: owner 2026-10-21 (limit 4000 lines); file count waived by the coordinator (owner delegation 2026-09-03)

Change class: feature
Boundary: open-core candidate

49 code + 0 regenerated baselines = 49 files.
Linear: MOR-4274

Scope subscriptions, SDK controls, readiness/reconnect, TX deferral and shutdown now share one acknowledged ScopeSession. Paused clients retain their subscription and view while output turns off; the first active client resumes output once. Last release restores the original panel/output and the view settings and fixed slots that the session changed.

RadioScopeSource binds directly to the session. Absolute views use profile-backed FIXED edges within the tuned receiver’s band where supported; other views use the smallest covering CENTER span, with uncovered coverage reported by the existing hub contract. Health uses typed frame arrival and acknowledged output facts: zero/empty/out-of-range arrivals do not imply a hidden scope.

This is one delivery contract: switching the writer requires migrating SDK, Core RX/lifecycle, hub, reconnect and teardown together and deleting the old paths. There is no compatibility adapter or second session API.

Acceptance and failing-first evidence

The original acceptance RED identifiers below refer to the pre-rebase commits on which those failures were observed; the final branch retains their rebased test changes. Round-2 identifiers are mapped explicitly in the Pre-review section.

Tests were reconciled and committed before the production GREEN commit. The independent baseline archive is 37eef242d8911a990cd9938899439ecb2eb5b250; production edits were never copied into that archive. Final test paths below replace the initial root-level test paths.

Acceptance Tests RED evidence
(a) one enable, LAN and serial tests/runtime/test_scope_session_wire.py::test_real_scope_writer_emits_one_enable_per_transport tests 95599673a: both transports fail on duplicate enable on baseline
(b) exact last-release restore once test_last_release_restores_wire_snapshot_once, tests/runtime/test_scope_session.py::test_one_enable_exact_restore_once tests a65b052df: six baseline failures across three original states and two transports
(c) profile FIXED, CENTER and partial coverage tests/test_rig_loader.py::TestScopeFixedEdgeRanges, tests/test_scope.py::TestScopeCommandBuilders, tests/scope/test_radio_scope_source.py tests 95599673a: profile/command RED; IC-7610 plus IC-7300/IC-705 coverage
(d,e) readiness, ACK and TX test_deferred_enable_and_tx_have_no_writes, test_enabled_requires_acknowledgement, test_known_tx_defers_sdk_scope_wire_writes new-session API is absent on baseline (collection RED); isolated wire TX test fails behaviorally on baseline at a65b052df
(f) typed out-of-range arrivals and silence test_oor_is_arrival_never_hidden, test_missing_frames_after_oor_use_the_arrival_deadline, tests/web/test_scope_hub_channel.py::test_typed_oor_and_uncovered_arrivals_never_publish_hidden session API collection RED; inherited implementation regression RED at 40f4babaa
(g,h) paused output once, immediate resume, real WS seam test_pause_only_output_resume_once_and_release_restores, test_server_simulator_pause_resume_release_counts_scope_writes, tests/test_web_server_coverage.py::test_health_snapshot_cannot_delay_fresh_scope_resume real server + simulator baseline pause assertion fails at 95599673a; readiness-resume RED at 1feceb9de

Baseline selected run: 30 FAIL / 43 PASS. Independent restore/TX baseline run: 7 FAIL. New-session collection RED is structural evidence, not claimed as behavioral baseline evidence. Extra inherited-implementation REDs pin initial-paused demand, pending-view release, shared fixed slots, unnecessary restoration, lost ACK/cancellation, health and explicit-receiver snapshot behavior. All behavioral assertions from both earlier session test sets are retained in one module; duplicates were removed.

The first local review reported five P1 and three behavior-changing P2 findings. Corrections add headless SDK TX-OFF/reconnect driving through existing Core events; preserve selected receiver/cache during addressed readbacks; feed session health from Core frame publication even with paused web demand; consume readbacks/stream observations and bypass uncertain no-op decisions; restore the command-binding test assertions; require fresh VERIFY activity; restrict FIXED to the tuned band; and retain the health/reconcile loop after recoverable command failures. Shared fixed-slot readback invalidation is covered as a sibling case.

Review corrections have real SDK + local WebSocket regressions in tests/runtime/test_scope_session_review.py. Corrected tests replayed against an isolated 3695fcfaf archive fail on receiver selection, false hidden health, real disconnect/connect demand recovery and control-WebSocket reapplication after a front-panel readback. Initial fixture-authentication failures and a soft-reconnect no-op were not counted as behavioral RED. Additional exact REDs cover monitor survival and cross-receiver fixed-slot readback. Test commits precede the correction GREEN commit.

Deleted paths and search proof

Deleted from this PR: _record_scope_demand; _scope_demand_is_stale; _set_radio_scope_output; _execute_scope_command; _scope_reenable_recorded_for; _cancel_scope_reenable_task; _scope_menu_guard_active; _enable_hardware_scope; reconnect inline enable; _schedule_scope_enable_when_ready / _wait_and_enable_scope; SDR-stale fallback enable; poller EnableScope requeue; poller _enable_scope_session and restore_scope_session; serial disable_scope override; latest_scope_demand_generation, _scope_demand_generation, _scope_reenable_generation; WebRadioScopeWriter; hardcoded _SCOPE_FIXED_EDGE_RANGE_STARTS_HZ / _resolve_scope_fixed_edge_range. _scope_disable_grace was already absent at the R4 base; the new session accepts an injected grace parameter.

rg -n '_record_scope_demand|_scope_demand_is_stale|_set_radio_scope_output|_execute_scope_command|_scope_reenable_recorded_for|_cancel_scope_reenable_task|_scope_menu_guard_active|WebRadioScopeWriter|_enable_hardware_scope|_schedule_scope_enable_when_ready|_wait_and_enable_scope|_enable_scope_session|def restore_scope_session\(|_scope_disable_grace|latest_scope_demand_generation|_scope_demand_generation|_scope_reenable_generation|_SCOPE_FIXED_EDGE_RANGE_STARTS_HZ|_resolve_scope_fixed_edge_range' src
# zero matches
rg -n 'def _write_scope_setting' src/rigplane
# exactly one: runtime/_scope_runtime.py, the six-family writer
rg -n 'def disable_scope' src/rigplane/backends
# zero matches

The public SDK restore_scope_session_state delegates into the same actor. ScopeHub's pre-existing callback generation remains for frame callback currency; it does not decide scope demand/readiness.

Search before write:

  • scope enable/disable/session and scope demand/restore/ready: nearest existing implementations were the separate runtime SDK and web poller/session helpers. ScopeSession replaces those owners; the generic hub remains responsible for subscriptions and delivery.
  • fixed edge/range and span/profile/band: nearest mapping was the removed command-level IC-7610 constant. Typed profile data extends the existing loader and command-builder inputs; no model branch is introduced.
  • scope arrival/hidden and scope health/silence: existing native frame callback and health monitor are reused; only the visibility verdict moves into the session's acknowledged state.
  • lock/gate and scope restore/enable: core/priority_exchange.py: PriorityExchangeGate orders individual CI-V exchanges; it has no subscriber/view/original-state contract. The session lock serializes those multi-command state changes while retaining the existing exchange mechanism.
  • subscriber count/active and paused/demand: ScopeHub._notify_demand supplies counts; ScopeDemand types that boundary rather than adding another demand owner. ScopeSessionProvider declares the existing radio-to-web binding, and ScopeSessionHealth types the replacement visibility verdict.
  • scope setting frame/build and deferred configure/validate: _write_scope_setting already used the profile-bound command builders; _scope_setting_frame reuses that construction for validation before accepting desired values and for the sole writer. No second validation table is introduced.
  • simulator scope setter/ACK and recorded scope dispatch/readback: ScopeControlSimulator extends the existing LAN simulator through Core parsers; the default recorded fixture is unchanged.

Profile source and compatibility

Only IC-7610 declares FIXED ranges: official IC-7610 CI-V reference, IC-7610_ENG_CI-V_4.pdf, printed page 15 (PDF page 16), command 27 1E. Its 12 ranges and 1 kHz edge precision are cited in rigs/ic7610.toml. Other profiles remain CENTER-only without a cited range table. FIXED programming and touched slot restoration are ACK-based; this is simulator acceptance, not physical-radio acceptance.

API behavior changes are documented in docs/api/scope.md: SDK FAST now waits for command ACKs; VERIFY additionally waits for observed scope activity. disable_scope releases SDK ownership and restores only when the final owner leaves. Unbound fixed-edge builders require profile ranges or an explicit range_index; bound runtime setters supply profile data. Custom hardware providers used by the web server must supply ScopeSessionProvider. New profile fields are keyword-only, preserving existing positional constructor arguments.

Pre-review

Two local pre-review rounds were completed before this fix brief. Round 1 found five P1 and three behavior-changing P2 findings; 80bcefaad fixes their Core lifecycle/RX/health/observation, binding-test, VERIFY, band-selection and monitor-survival causes. Round 2 found four P1 and three behavior-changing P2 findings. All seven are addressed below. The owner explicitly requested no third pre-review; the coordinator owns the PR verdict. No CLEAN or Agent Review PASS is claimed for this head.

RED failures were observed on the Mac mini before each GREEN change. The observed RED SHA is pre-rebase; the corresponding rebased test commit is also given. The final integrated tree includes origin/main at 8e54424ca (#4518 and #4516).

Round-2 finding / regression Observed RED → rebased test commit GREEN commit
P1 SUB FIXED capture preserves MAIN edge; test_sub_fixed_slot_capture_preserves_main_selected_edge 88e997c2f → d32a627b3 2ff4ae461
P1 initially unchanged span, front-panel drift, control-WS reapply; test_control_ws_reapplies_span_after_real_front_panel_readback c76e7699b → b2571cc44 d183c2183
P1 standalone control-WS setter followed by panel readback and real health monitor; test_standalone_control_ws_setter_retires_after_ack 0aeb0c3dc → d2d7e9ec2 eae27ea56
P1 real Core frame arrival, fresh CI-V beyond silence deadline, then hidden on stopped frames; test_typed_oor_and_uncovered_arrivals_never_publish_hidden (3 cases) b24bd0968 → 864ee6f37 744b0a08a
P2 public SDK retained demand after delayed initial/reconnect identity completion; test_retained_sdk_scope_resumes_after_late_serial_identity (2 cases) 69684ce36 → c61c1a383 c8294870f
P2 MAIN → SUB → MAIN through real selection replies and waveform RX; test_scope_selection_return_refreshes_stream_display_facts (2 cases) 43e6dc2c2 → d97b4e51f 72bc2cb4d
P2 invalid mode/span/edge during readiness/TX deferral, then valid work; test_invalid_deferred_setting_cannot_poison_later_work (6 cases) 8638de0af → 8d746d6ec c0a7545eb

The RED outcomes were, respectively: MAIN edge 2 instead of 1; reapply timeout with hardware span still 4; monitor replay to span 2 instead of retaining panel span 4; missing session arrival; scope output never resumed; stale mode/span after returning to MAIN; and invalid requests returning without ValueError. Fixture-address/identity setup failures encountered while constructing the serial test were corrected before recording its behavioral RED.

Gate round 1

Final candidate: faa83b41da59f30e8bd45c84ed0821a3a9ca6efc; tested tree: 2be31933ddc1f257d1d1db565cffe9a36ec0983e (identical before/after the final commit). 3 P1 + 1 P2 fixed; 3 P2 deferred to MOR-4322.

Finding / regression RED commit Fixing commits
P1 panel/output readbacks now update session facts; real SDK/simulator disable-readback-explicit-enable regression 732f16b8e e35672dba
P1 SUB demand arranges dual streaming through the session; real scope WebSocket starts from single MAIN, delivers SUB and concurrent MAIN/SUB, preserves SDK MAIN selection and restores initial dual mode e56d44dda (dual-receiver fixture correction after 6aff80319) 877d0d5bb, faa83b41d
P1 new acquisition clears stale applied facts; two real WebSocket sessions with a panel-side fixed-slot change, second view applied and newly captured original restored ae23d341f 130f4a397
P2 startup reconciliation timeout/NAK/external-CAT refusal is logged and retained for retry; three failure cases allow an unrelated queued frequency command to complete, then reconcile pending scope work successfully 84966b33d faa83b41d

RED evidence is in test_panel_output_readbacks_allow_sdk_reenable, test_sub_view_real_dispatch_preserves_main_sdk_selection, test_successive_fixed_sessions_capture_fresh_panel_slot, and test_startup_scope_failure_keeps_command_executor_alive (built-in timeout, Core timeout and CommandError). Initial full verification exposed nine serial lifecycle failures from an eager dual-state read; faa83b41d captures that state only when SUB demand needs it, preserving MAIN-only command sequences.

Final Mac mini validation (Darwin host, outside containers):

  • Affected session/source/poller/serial tests: 424 passed, 39.88 s.
  • mini-test -m "not slow" -n auto --maxprocesses 4 --tb=short --timeout=300 --timeout-method=thread --basetemp=/var/tmp/mor-4274-r5-round1-final-pytest: 27,897 passed / 274 skipped / 21 xfailed, zero failures, 8 warnings, 647.19 s.
  • Ruff check and format check: six touched Python files pass. mypy --strict src/rigplane/web src/rigplane/runtime/scope_session.py src/rigplane/runtime/radio_scope_source.py: 32 files pass.
  • Adding touched runtime mixins _civ_rx.py and _scope_runtime.py to strict mypy reports 24 diagnostics in two files under the existing follow_imports=skip policy. Shadow-file comparison with starting head 56fdb0342 gives exactly the same diagnostics after ignoring line shifts; this expanded command is not a passing gate. No typing configuration or suppressions changed.
  • Import-linter: 6 contracts kept. Wire goldens, field-status fixture and state types --check pass. Native dry-run goldens: IC-7610 81, X6200 64, FTX-1 72 matching checks, zero regressions.
  • Final PR size: 2969 additions + 1024 deletions = 3993 changed lines across 49 files, no generated exclusions.

Search-before-write: read_dual searched get_scope_dual / scope_single_dual; nearest _read_scope_receiver only captures receiver viewport settings, so the existing dual getter is supplied as a lazy global-state capture callback. Existing historical simulator measurements below were not recollected for these control/lifecycle fixes.

The remaining P2 findings — selection deduplication without intervening frames, restoration within the shutdown budget, and CENTER half-span units — remain MOR-4322 and are unchanged. No subagents, tmp/pre-review.sh, workflow dispatch/rerun, real-radio access or merge. The coordinator owns gate round 2 (last).

Validation

Scope stress follow-up after #4523 rebase

Candidate: c7388e18c95e89d134df531997b8beaad9190d2b. Only tests/test_scope_stress.py changes in this follow-up. RED reproduced at 7d916c3376bffed68188525e53e76ee4c421e319: both test_scope_stress_slow_consumer_queue_bounded profiles fail. The hand-written writer lacked the release keyword and set_fixed; the IC-7610 response was an error message, not a malformed grant. The grant codec is unchanged.

The replacement uses create_autospec(ScopeSession, instance=True, spec_set=True), derives method signatures from the production session, and compares the real decode_scope_granted result with all fields of a typed GrantedSubscription. The 400-frame queue/memory bound, latest-wins delivery, single blocked sender, output enable/release counts and clean teardown assertions remain.

Mac mini verification (all commands run with TMPDIR=/var/tmp/mor-4274; pytest uses --basetemp=/var/tmp/mor-4274/pytest):

  • mini-test -m slow tests/test_scope_stress.py tests/test_audio_stress.py tests/test_mock_icom_session.py tests/test_session_lifecycle_e2e.py: 5 passed, 29 deselected, 17.35 s.
  • mini-test -m "not slow" -n auto --maxprocesses 4 --tb=short --timeout=300 --timeout-method=thread -q: 27,913 passed / 274 skipped / 21 xfailed, zero failures, 8 deprecation warnings, 656.85 s. Both pytest commands used mini-test --run bash -c to set the remote TMPDIR explicitly.
  • mini-run uv run ruff check .: 4 TID251 errors, two internal rigctld imports in each of .github/scripts/private_core_b14_installed_smoke.py and .github/scripts/private_core_installed_smoke.py. mini-run uv run ruff format --check .: 17 tooling files would be reformatted; 1,250 already formatted. The changed stress test passes both focused checks.
  • mini-run uv run mypy src: 136 errors in 43 files, 365 files checked. These broad checks are not passing gates. Their diagnosed files, source/configuration inputs and formatting candidates are unchanged from the starting rebased head; no unrelated fixes or suppressions were added.

Current aggregate: 2,988 additions + 1,043 deletions = 4,031 changed lines across 50 files. Earlier size and verification entries below describe their named historical candidates. No production behavior changed in this follow-up, so no new performance measurement was collected. The coordinator owns the delta review and size gate; no local pre-review, workflow dispatch/rerun, real-radio access or merge was performed.

Earlier Mac mini validation, rebased candidate: 56fdb0342cff5254255b46ff2b845623b4a4a00e; its tree 7b59ca33b419b6103812d60165475d07eb852805 exactly matches the tree tested before the final commit.

  • mini-test --run bash -c 'PYTHONPATH=.github/scripts RIGPLANE_WORKER_DIAG_DIR="$HOME/validation/mor-4274-r5-review-fixes/worker-diags-final" uv run pytest tests/ -m "not slow" -p pytest_worker_diagnostics -n auto --maxprocesses 4 --tb=short --timeout=300 --timeout-method=thread -q': 27,892 passed / 274 skipped / 21 xfailed, zero failures, 635.39 s. Darwin host, outside containers.

  • First rebased full run: 27,890 passed / two failures. It exposed redundant panel/output SETs after explicit restore and an AST test still looking inside the pre-extraction writer. 56fdb0342 retains current confirmed restoration observations and pins both writer-to-builder and builder-to-command links with fail-closed mutation checks. The two failures plus session/wire/control-WS regressions then passed in a 104-test focused run, followed by the clean full run above. The first full run is retained as RED at tree-identical commit ba0c0e91e (formatting-only freeze).

  • Ruff check and format check: all 47 changed Python files pass.

  • mypy --strict src/rigplane/web src/rigplane/runtime/scope_session.py src/rigplane/runtime/radio_scope_source.py src/rigplane/scope/hub.py src/rigplane/scope/models.py: 34 files pass. Simulator extension: strict check with --follow-imports=silent passes. Including the extension under the repository default follow_imports=skip produces seven Any-import diagnostics; that command configuration is not reported as a clean check.

  • lint-imports: six contracts kept.

  • Regenerated wire goldens with scripts/gen_state_wire_goldens.py; byte-identical. Wire goldens, field-status fixture and state types --check all pass. No fixture or frontend changes, so local Vitest is not selected.

  • Size: 2969 additions + 1024 deletions = 3993 changed lines across 49 files, no generated exclusions. One cohesive session/writer migration; owner size exception above applies.

  • No new pre-review run, workflow dispatch/rerun, real-radio connection or merge.

Natural required PR CI and Agent Review Gate are reported separately at the exact head. No workflow dispatch or rerun was requested; no merge is authorized.

Simulator measurements

Baseline 37eef242d8911a990cd9938899439ecb2eb5b250; AFTER 56fdb0342cff5254255b46ff2b845623b4a4a00e. Mac mini, simulator at 30 fps, receiver 0, requested width 1024, identical recorded waveform geometry and initial panel-on/output-off state. Both runs isolate simulator, Core and v2 client in separate processes.

Measurement Before After
Connect → first useful frame, median 10 414.953 ms 108.492 ms
Core CPU visible, median 30 post-initial top samples 18.00% 11.80%
Core CPU all paused, same sampling 16.55% 10.85%
Delivered pixel frames, 30 s at 30 fps 879 / 900 (29.30 fps) 881 / 900 (29.37 fps)
Six-family SETs: connect / view / disconnect 4 / 2 / 2 2 / 0 / 2
Six-family SETs: pause / resume 2 / 4 3 / 1

AFTER latency range: 93.849–162.787 ms. Paused phase delivered zero pixel frames. CPU is lower in this sample; one run does not establish causality. AFTER also includes the two rebased main commits. Delivery requested width 1024 and was granted native width 689. The final full test run ended before the benchmark began. Pause includes one output-off and two span writes. Static recorded geometry can disagree with programmed span, limiting viewport-write/CPU comparison. Delivery remains approximately 30 fps. Disconnect includes restoration of the original span plus output-off; the original panel was already on.

Commands: mini-run uv run python <private-r5-harness> <output-directory> (10 connect trials plus top -l 31 -s 2 -pid <core_pid> -stats pid,command,cpu,idlew, first sample excluded); the companion transition harness captures actual parsed SET frames; mini-run uv run python qa/bench/dogfood/scope_fps.py --radio simulator --transport sim --duration 30 --max-fps 30 --width 1024 measures delivery.

Exact final benchmark command (the frozen harness internally uses the top command above):

mini-run bash -c 'set -e; out="$HOME/validation/mor-4274-r5-after/56fdb0342"; uv run python "$HOME/validation/mor-4274-r5-after/r5-bench-after.py" "$out"; uv run python "$HOME/validation/mor-4274-r5-after/r5-bench-transitions.py" "$HOME/validation/mor-4274-r5-after/r5-bench-after.py" "$out"; uv run python qa/bench/dogfood/scope_fps.py --radio simulator --transport sim --duration 30 --max-fps 30 --width 1024 > "$out/delivery.json"; uv run python "$HOME/validation/mor-4274-r5-after/analyze-after.py" "$out"; cat "$out/delivery.json"'

Baseline testkit tree: b68843d1d3414406ce2f7607bd78abf11721496d; AFTER testkit tree: b19cc4af0703e370d559cc115d426ff51aede050. The original recorded simulator blob is unchanged (5df023b7827b2c621bc8fed8933ce0ecc287e7c8); AFTER adds the typed scope-control extension (3c77641cda526e3edb12d58c3a8c9ee8bc1eed01). Full captures and top samples remain on the mini; the lane result records their locations.

Baseline already sent output-off on pause; its extra panel writes and duplicate enable paths are the measured redundancy. Baseline viewport setters were NAKed by the recorded fixture, so its viewport counts are attempted writes. AFTER uses the ACK-capable simulator extension with unchanged recorded waveform geometry and the same initial panel-on state; successful physical view actuation is not established by this comparison.

CPU is the Core process only (simulator/client excluded), not browser/GPU CPU. No real radio, installed-build or hardware validation was attempted. IC-705 and IC-7300 were untouched. No frontend or golden fixtures changed, so standalone Vitest is not selected locally; natural PR CI remains authoritative for its selected paths.

🤖 Generated with Claude Code

Journey regression

Rebased onto R3/main 273dcf42ae265fc894435ee6a826c6e25641c4a6. Main: 16 journeys passed. Rebased R5: 15 passed, 1 failed, reproducing the MAIN pixel loss without retries.

Cause/fix: R5 fixed-edge negotiation receives a CI-V NAK from the simulator's limited controls. ScopeHandler._handle_control preserves the previous subscription on rejection (docs/api/scope.md), but frontend ScopeStream.handleMessage cleared all cached frames/geometry, unmounting MAIN during SUB traffic. It now preserves active pixels/geometry and reports the error; startup, null-frame and session/identity retirement remain tested. R3 rendering and journey expectations are unchanged.

  • RED: bca4ec3ef81f0f03799d0c480ae766f5d452f604, scope-subscription-v2.svelte.isolated.test.ts → keeps MAIN pixels and geometry after a receiver %i update is refused during SUB traffic: both receiver cases failed with null MAIN geometry (producer golden fixtures + real decoder).
  • GREEN: b286f0de37928e03c32c427aed90ef87ccede3f5. Journeys 16 passed; full Vitest 15,223 passed + 1 expected failure (599 files). Lint, i18n, type checks, build, component-package verification passed. Fixture captures 65/65 valid; placeholder 16, chrome-inset 85, FTX-1 RIT 1, Chromium/WebKit renderer 10 each passed. Missing WebKit provisioning was resolved. Three obsolete audio-fixture error-retirement assertions were corrected; null-frame/disconnect assertions remain.
  • Python: 27,913 passed, 274 skipped, 21 xfailed (8 warnings) fast; 5 passed, 29 deselected slow. Ruff passed; 48 files formatted. All on the mini with safe temp/output paths. The superseded serial run was intentionally interrupted; final fast run used four workers.
  • Visual caveats: full i18n run 156 passed, 5 failed: four macOS comparisons against Linux-pinned screenshot baselines (all reproduced in focused main checks); one scope-speed contrast sample at 4.33:1, with different foreground color than the focused candidate pass at 4.83:1 against the same background, indicating transition timing. Original failures remain reported; no baselines/assertions loosened.

Aggregate 3,023 + 1,046 = 4,069 changed lines / 53 files; delta 35 additions + 3 deletions / 3 files. Existing size exception retained. Coordinator owns delta/size review; no pre-review script, workflow dispatch, real radio or merge. No hot-path/performance mechanism changed; prior metric evidence retained.

P1 correction after the final Codex round

Final head: 1271cadacfa71f47e8b9ba34b463ba03b8e34596 (tree 02fd973f0345145a5d188c43aa8a8da6c2f02ee1).

  1. Live renderer: SpectrumPanel no longer treats every negotiation error as sample retirement. Its existing geometry/scopeTraceLive path still retires the sample and renderer; a refused update with retained geometry preserves both. The panel regression verifies changing MAIN payloads reach the same mounted spectrum after SUB frames, then verifies actual retirement still unmounts it.
  2. Visible refusal: the existing error subscription now feeds the existing toolbar status slot and StatusIndicator, with English/Russian/Japanese catalog text. The same status snippet is reachable when the toolbar is hidden or the panel is audio FFT. A later accepted grant clears the notice.

Searched: status/error/scopeStatusIndicator and notification/toast/notice; nearest existing: SpectrumToolbar.scopeStatusIndicator and lib/Button/StatusIndicator.svelte; used that slot/component and the existing stream error subscription, without a second retirement mechanism. Deletion grep: rg -n 'scopeStream.error.subscribe.*clearSample' frontend/src/components/spectrum/SpectrumPanel.svelte has no matches.

RED: 0af70a6ba5b1917907d4ffcb3fde8fb90d5977ae, tests against unchanged product code from b286f0de37928e03c32c427aed90ef87ccede3f5. Both scope negotiation refusal (MOR-4274) panel tests failed: the mounted renderer received no subsequent MAIN paint, and the refusal status was absent. The real-server MOR-3513 mixed scope preserves MAIN while PCM and controls advance journey received changing MAIN pixels but recorded 0 spectrum advances and no Scope view refused status. Its opt-in simulator now alternates amplitude payloads through the normal CI-V sender, so a frozen image cannot satisfy the advancement assertion. The final journey awaits rendered advancement before its snapshot and also requires the refusal to be visible.

Final-head Mac mini verification (all with remote TMPDIR=/var/tmp/mor-4274; artifacts under ~/validation/mor-4274-r5-p1/):

  • npx playwright test -c playwright.journey.config.ts: 16 passed. Mixed-scope witness: 18 MAIN wire changes, 4 spectrum advances, 24 SUB-only checks, 0 pixel losses, 0 mount/geometry losses.
  • npx vitest run --maxWorkers=4: 15,225 passed + 1 expected failure, 599 files passed. npm run lint, i18n:check, check, build, and verify:component-kit-api: passed; svelte-check: 0 errors / 0 warnings.
  • Fixture captures: 65/65 valid; placeholder guard: 16 passed; chrome inset: 85 passed; FTX-1 fake-server: 1 passed; renderer suite: 10 Chromium + 10 WebKit passed.
  • Full i18n visual: 156 passed / 5 failed. Four are the same known Linux-pinned production screenshot mismatches on macOS (StudioLine dark/light and FieldLine dark/light). Separately, the previously recorded SCOPE-sheet contrast instability recurred at 4.329799:1; focused unchanged test: 1 passed, 4.772321:1. Saved audits show the same foreground but different effective button backgrounds between those two runs; the one-shot measurement does not await CSS color/background transitions. The original failure remains reported; no contrast-related CSS, baselines, thresholds or assertions were relaxed.
  • mini-test --run bash -c 'export TMPDIR=/var/tmp/mor-4274; uv run pytest tests/ -m "not slow" -n auto --maxprocesses 4 --basetemp=/var/tmp/mor-4274/pytest-p1-final --tb=short --timeout=300 --timeout-method=thread -q': 27,913 passed / 274 skipped / 21 xfailed / 8 deprecation warnings, 655.19 s. The journey fixture is the only Python change; its Ruff lint/format checks pass.

Correction delta from b286f0de3: 92 additions + 6 deletions = 98 changed lines / 7 files. Aggregate: 3,115 additions + 1,052 deletions = 4,167 changed lines / 60 files; the coordinator retains the size/delta gate. No new performance measurement was collected for this refusal-handling correction. No pre-review script, subagents, workflow dispatch, real-radio access or merge was performed.

@morozsm

morozsm commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: PASS faa83b4
Reviewer: gpt-6.1-sol, local Codex CLI pre-review (exact head, read-only, owner's ChatGPT subscription), 1 round(s); builder family codex. The GitHub Codex bot review is advisory and was not awaited.

@morozsm
morozsm force-pushed the codex/mor-4274-r5-scope-session branch from faa83b4 to c7388e1 Compare November 9, 2026 07:50
@morozsm

morozsm commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: PASS c7388e1
Reviewer: gpt-6.1-sol, local Codex CLI pre-review (exact head, read-only, owner's ChatGPT subscription), 1 round(s); builder family codex. The GitHub Codex bot review is advisory and was not awaited.

morozsm and others added 27 commits November 9, 2026 03:08
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inputs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace SDK and web lifecycle writers with one ScopeSession. Negotiate fixed edges from profile data, pause output without releasing views, and restore the exact pre-session state after the final owner leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
morozsm and others added 22 commits November 9, 2026 03:08
…lth regression

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full rebased fast run exposed restore replay and builder AST parity regressions; retained as RED evidence before their correction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… display

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gression

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ution

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tartup

Capture dual state only for requested SUB streaming and retain MAIN-only lifecycle commands. Keep recoverable startup reconciliation failures available for the existing retry driver while the command executor starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Autospec ScopeSession instead of maintaining a hand-shaped scope writer. Assert every granted field through the real decoder while retaining the bounded queue, latest-wins delivery and clean teardown checks for both profiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep cached frames and selected geometry when an update is refused; the server preserves the previous subscription. Startup errors still clear provisional grants, and provider/session retirement still clears retained frames.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morozsm
morozsm force-pushed the codex/mor-4274-r5-scope-session branch from c7388e1 to b286f0d Compare November 9, 2026 08:29
morozsm and others added 2 commits November 9, 2026 03:41
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use geometry retirement for sample cleanup and the existing toolbar status slot for negotiation errors.

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

morozsm commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: PASS 1271cad
Reviewer: coordinator (sumo, claude-opus-5-5) delta review after the 2-round Codex cap. Round 2 (gpt-6.1-sol xhigh, b286f0d) raised 2 P1s. Both are fixed in b286f0d..1271cad (7 files, +92/-6; failing-first commit 0af70a6):
(1) SpectrumPanel's error subscription no longer calls clearSample(), so the spectrum push callback survives a refused view negotiation;
(2) the refusal is surfaced through the existing scopeStatusIndicator/StatusIndicator slot, with i18n key core.scope.viewRefused in EN/RU/JA.
The journey asserts that the spectrum keeps advancing after the refusal, that the refusal status is visible, and pixelLosses==0. No new P0/P1. P2 follow-up: the raw server exception text appears inside the translated prefix.
Size exception: owner 2026-10-21 (limit 4000 lines); file count waived by the coordinator (owner delegation 2026-09-03). 4167 lines: the overage is regression fixes found by CI on this PR.

@morozsm
morozsm added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 6cbd094 Nov 9, 2026
23 checks passed
@morozsm
morozsm deleted the codex/mor-4274-r5-scope-session branch November 9, 2026 09:14
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