Bump proptest-state-machine from 0.3.1 to 0.8.0 - #9
Closed
dependabot[bot] wants to merge 2 commits into
Closed
Conversation
Multiview — an efficient, hardware-accelerated Rust live video multiview/mosaic generator. It ingests many live sources (RTSP/HLS/SRT/RTMP/NDI/file/synthetic), composites them into a templated grid on CPU or GPU, and writes HLS/file output; the encode-once-mux-many design fans one encode to many transports. Highlights: - Fixed-cadence output clock — one valid frame per tick, never stalls; inputs are sampled, never pacing (the cardinal continuous-output invariant). - Custom CPU/GPU compositor with a fixed linear-light colour pipeline (NV12 throughout); per-tile overlays, captions (HLS WebVTT + DVB-sub), analog/wall clocks, audio meters, and fault badges. - 16-crate Rust workspace under strict typing + TDD + adversarial-review guardrails; LGPL-clean default build (GPL codecs + NDI opt-in). - Multi-arch Docker images, docker compose examples, and SemVer release automation. Status: early stage — the engine ingests, composites, encodes, and writes HLS/file output today; the web UI, control API, and live RTSP/NDI/RTMP output servers are built as libraries and on the near-term roadmap (see ROADMAP.md). Dual-licensed MIT OR Apache-2.0. Developed iteratively with AI assistance; the full internal development history is retained privately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bumps [proptest-state-machine](https://github.com/proptest-rs/proptest) from 0.3.1 to 0.8.0. - [Release notes](https://github.com/proptest-rs/proptest/releases) - [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md) - [Commits](proptest-rs/proptest@proptest-state-machine-0.3.1...proptest-state-machine-0.8.0) --- updated-dependencies: - dependency-name: proptest-state-machine dependency-version: 0.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
troykelly
pushed a commit
that referenced
this pull request
Jun 7, 2026
…iple active programs) Design for running SEVERAL concurrent output programs, from the multi-program-research ultracode workflow (4 code-grounded lanes + verified synthesis): - A unified `Program` abstraction over three kinds: (A) multiview composite (today), (B) passthrough = REMUX with no re-encode (zero decode/encode — the muxer copies the demuxed packets), (C) transcode (decode→[scale]→encode). A/C share the existing ProgramEncoder → StreamEgress encode-once-mux-many tail; B reuses the same per-sink mux consumer fed by a packet copy instead of an encoder. - Engine: per-program `OutputClock` + `EngineRuntime` (NO master clock — shared read-only TimeSource, independent output clocks), so different cadences (25 vs 60fps, inv #3 exact rationals) and per-program isolation (inv #1: one program stalling never stalls another) are STRUCTURAL. A `Program` actor under the existing Supervisor/RestartPolicy; a `ProgramSet` coordinator owns the shared source registry + placement. - Efficiency (the product): decode-once-use-many via a reference-counted shared source registry/framestore (a source feeding N programs/tiles decoded ONCE); shared GPU residency; admission control (can the box afford a new program — decode MP/s, encode sessions, VRAM) tied to the ADR-0017/0018 placement engine; per-program + global resource-adaptive degradation (inv #9). - Config: a backward-compatible `[[programs]]` model (adjacently-tagged by kind; today's single top-level program = one implicit program). - API/UI: programs as a first-class resource (CRUD + per-program start/stop 202+op-id, live-apply class inv #11) + per-program realtime state + a UI program list/editor. The synthesis verified its claims against the as-built code (EngineRuntime is already per-program-shaped + isolation-clean; Actor/Supervisor/ RestartPolicy exist; the remux primitives exist but no packet-copy sink is wired). Adds docs/research/multi-program.md + ADR-0030 (Proposed) + the MP-0..MP-8 backlog; indexes both READMEs. Docs only — no code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
pushed a commit
that referenced
this pull request
Jun 8, 2026
…aphics cap ROOT CAUSE of the demo burning 5 CPU cores at 4K with the GPU ~80% idle: the wgpu compositor is Vulkan-based, but the nvidia image had NO Vulkan loader (`libvulkan1`) and NVIDIA_DRIVER_CAPABILITIES lacked `graphics`. With neither, wgpu's request_adapter returns nothing and compositing SILENTLY falls back to the CPU reference path — decode + composite on CPU while the 4060 does only NVENC. Verified on the box: with `graphics` the toolkit mounts nvidia_icd.json + libGLX/EGL_nvidia, but `libvulkan1` (the loader that resolves the ICD) is still absent, so wgpu sees no device. Fix = install libvulkan1 + set the graphics cap; the chain becomes loader → nvidia ICD → 4060 adapter → GPU compositing. This is the enablement; a follow-up adds capability DETECTION + a clear UI warning so "GPU present but unusable for compositing" is surfaced, never silently wasted (bulletproof + easy-to-use), and feeds the real-time placement loop (inv #9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
pushed a commit
that referenced
this pull request
Jun 8, 2026
…the placement loop (ADR-0035) From a verification-hardened fan-out (21 agents; 11/14 claims held, 3 refined). Key finding: the placement engine is ~90% BUILT but never WIRED — the cost model (cost.rs), the DRF placement policy (select.rs select_device), the engine controller (placement.rs observe), and the degradation loop (degrade.rs) all exist, pure + tested, but are NEVER called with live loads in the run; the LoadSource feeds only the UI footer. And the silent GPU→CPU fallback is computed-then-discarded: EngineRuntime::backend_kind() already knows it fell back, but that fact reaches NOTHING — not an event, not a warning, not a hardware cross-check. ADR-0035 defines the sense→detect→warn→plan→apply subsystem on the EXISTING parts: - DETECT: probe actually-usable backends (wgpu adapter device_type, NVDEC/NVENC via libav) + cross-check vs NVML-discovered hardware. - WARN: an actionable HealthWarning catalog (gpu-present-no-vulkan-adapter w/ the libvulkan/graphics remediation, software-decode/encode-on-gpu-host, nvenc-ceiling, vram-pressure, cpu-saturation, degradation-active), LATCHED/debounced, with a load-bearing NO-FALSE-POSITIVE rule (fires only on hardware-present AND software-tier-resolved → zero warnings on a CPU-only/software host). - PLAN+APPLY: wire LoadSource→select_device/PlacementController into the run, net of co-tenant (ours-vs-total) load, affinity-preserving (never fragment a pipeline, GPU-placement principle), inv #1/#9/#10 — the runtime re-plan runs OFF the output-clock thread (NOT the per-tick hook; folded from the adversarial review). SA-0..N backlog; SA-0 = the smallest win: detect+warn the compositor mismatch so the silent fallback that burned 5 CPU cores becomes a clear banner + /api/v1/health. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
added a commit
that referenced
this pull request
Jun 8, 2026
…degradation rung (PRV-4) Wire preview focus into the resource-adaptive degradation ladder (inv #9, ADR-E007/P001/P004): under sustained overload the closed control loop sheds load cheapest-impact-first BEFORE the program output is touched, and PREVIEW is the very first rung shed. multiview-hal::degradation - Prepend five PREVIEW rungs ABOVE every tile/program rung, in the exact preview-subsystem.md §8 order: ShedFocusWhep -> DropPreviewGridFps -> DropPreviewGridRes -> DropOffAirCueDecoders -> SuspendPreviewEntirely. LADDER (now 13), rung(), and MAX_LEVEL (derived) shift consistently. - Add affects_preview() + the const boundaries first_non_preview_level() and first_program_level(); affects_program() is now expressed against first_program_level() so it remains false for every preview rung (the program-affecting boundary stays at FasterEncoderPreset). The change is purely additive (prepend) — existing rungs keep their relative order. multiview-preview::focus - FocusGate gains suspend()/resume()/is_suspended() — the hook the ladder drives on the preview rungs. While suspended, try_acquire refuses every new focus with FocusDenied::Suspended regardless of cap headroom (the existing 503 fallback: ws-jpeg shape, mapped by GatedWhep), so the operator sheds to the always-available JPEG transport; restored on resume() when load clears. is_suspended() and the poisoned-lock paths fail closed. Held leases are untouched (their sessions are torn down out-of-band by the driver; Drop still frees the slot). Re-asserts inv #1 (no output path touched), #9 (cheapest-impact-first, hysteresis/no-flap unchanged) and #10 (gate is preview-only counters behind a short-lived Mutex the engine never touches). The Hysteresis -> FocusGate::suspend binary glue lives in multiview-cli (out of scope here, concurrently edited); this lands the tested HAL + preview core. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
pushed a commit
that referenced
this pull request
Jun 8, 2026
…iple active programs) Design for running SEVERAL concurrent output programs, from the multi-program-research ultracode workflow (4 code-grounded lanes + verified synthesis): - A unified `Program` abstraction over three kinds: (A) multiview composite (today), (B) passthrough = REMUX with no re-encode (zero decode/encode — the muxer copies the demuxed packets), (C) transcode (decode→[scale]→encode). A/C share the existing ProgramEncoder → StreamEgress encode-once-mux-many tail; B reuses the same per-sink mux consumer fed by a packet copy instead of an encoder. - Engine: per-program `OutputClock` + `EngineRuntime` (NO master clock — shared read-only TimeSource, independent output clocks), so different cadences (25 vs 60fps, inv #3 exact rationals) and per-program isolation (inv #1: one program stalling never stalls another) are STRUCTURAL. A `Program` actor under the existing Supervisor/RestartPolicy; a `ProgramSet` coordinator owns the shared source registry + placement. - Efficiency (the product): decode-once-use-many via a reference-counted shared source registry/framestore (a source feeding N programs/tiles decoded ONCE); shared GPU residency; admission control (can the box afford a new program — decode MP/s, encode sessions, VRAM) tied to the ADR-0017/0018 placement engine; per-program + global resource-adaptive degradation (inv #9). - Config: a backward-compatible `[[programs]]` model (adjacently-tagged by kind; today's single top-level program = one implicit program). - API/UI: programs as a first-class resource (CRUD + per-program start/stop 202+op-id, live-apply class inv #11) + per-program realtime state + a UI program list/editor. The synthesis verified its claims against the as-built code (EngineRuntime is already per-program-shaped + isolation-clean; Actor/Supervisor/ RestartPolicy exist; the remux primitives exist but no packet-copy sink is wired). Adds docs/research/multi-program.md + ADR-0030 (Proposed) + the MP-0..MP-8 backlog; indexes both READMEs. Docs only — no code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
pushed a commit
that referenced
this pull request
Jun 8, 2026
…aphics cap ROOT CAUSE of the demo burning 5 CPU cores at 4K with the GPU ~80% idle: the wgpu compositor is Vulkan-based, but the nvidia image had NO Vulkan loader (`libvulkan1`) and NVIDIA_DRIVER_CAPABILITIES lacked `graphics`. With neither, wgpu's request_adapter returns nothing and compositing SILENTLY falls back to the CPU reference path — decode + composite on CPU while the 4060 does only NVENC. Verified on the box: with `graphics` the toolkit mounts nvidia_icd.json + libGLX/EGL_nvidia, but `libvulkan1` (the loader that resolves the ICD) is still absent, so wgpu sees no device. Fix = install libvulkan1 + set the graphics cap; the chain becomes loader → nvidia ICD → 4060 adapter → GPU compositing. This is the enablement; a follow-up adds capability DETECTION + a clear UI warning so "GPU present but unusable for compositing" is surfaced, never silently wasted (bulletproof + easy-to-use), and feeds the real-time placement loop (inv #9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
pushed a commit
that referenced
this pull request
Jun 8, 2026
…the placement loop (ADR-0035) From a verification-hardened fan-out (21 agents; 11/14 claims held, 3 refined). Key finding: the placement engine is ~90% BUILT but never WIRED — the cost model (cost.rs), the DRF placement policy (select.rs select_device), the engine controller (placement.rs observe), and the degradation loop (degrade.rs) all exist, pure + tested, but are NEVER called with live loads in the run; the LoadSource feeds only the UI footer. And the silent GPU→CPU fallback is computed-then-discarded: EngineRuntime::backend_kind() already knows it fell back, but that fact reaches NOTHING — not an event, not a warning, not a hardware cross-check. ADR-0035 defines the sense→detect→warn→plan→apply subsystem on the EXISTING parts: - DETECT: probe actually-usable backends (wgpu adapter device_type, NVDEC/NVENC via libav) + cross-check vs NVML-discovered hardware. - WARN: an actionable HealthWarning catalog (gpu-present-no-vulkan-adapter w/ the libvulkan/graphics remediation, software-decode/encode-on-gpu-host, nvenc-ceiling, vram-pressure, cpu-saturation, degradation-active), LATCHED/debounced, with a load-bearing NO-FALSE-POSITIVE rule (fires only on hardware-present AND software-tier-resolved → zero warnings on a CPU-only/software host). - PLAN+APPLY: wire LoadSource→select_device/PlacementController into the run, net of co-tenant (ours-vs-total) load, affinity-preserving (never fragment a pipeline, GPU-placement principle), inv #1/#9/#10 — the runtime re-plan runs OFF the output-clock thread (NOT the per-tick hook; folded from the adversarial review). SA-0..N backlog; SA-0 = the smallest win: detect+warn the compositor mismatch so the silent fallback that burned 5 CPU cores becomes a clear banner + /api/v1/health. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 10, 2026
troykelly
pushed a commit
that referenced
this pull request
Jun 16, 2026
…rules (ADR-I006) Add decision point #9 (and three alternatives-considered rows) for the round-4 panel fixes: re-FETCH key/revocation material at lease-acceptance (revocation TOCTOU), the single binding-anchor chokepoint in LeaseStore::install_binding, and the retry-stable per-operation Idempotency-Key. Correct the now-stale #8 note that said the binding anchor is recorded via record_binding_id (it moved into install_binding). Also tidies the round-4 test scaffolding (`upload_binding_for`): rustfmt of the multi-line return type + rename `signer`/`signed` to `envelope_signer`/ `signed_lease` to satisfy clippy::similar_names under --features heartbeat (no behaviour change). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
pushed a commit
that referenced
this pull request
Jun 16, 2026
Extend round-4 decision point #9 + add an alternatives row: the device-identity anchor must come from SIGNED material, so instance_binding_id is bound into the SignedLease signing bytes and verified at install — never anchored from an unsigned LeaseBinding sidecar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
troykelly
pushed a commit
that referenced
this pull request
Jun 17, 2026
…turation residual; refresh SourcesPage L2 copy
Pre-PR review close-outs (orchestrator-flagged):
- pipeline.rs: the `select_admission_pick` budget comment cited "ADR-0035 §5" for
the per-GPU perf-class CostBudget table being a future refinement — that
citation is WRONG (ADR-0035 has no §5 and does not scope the budget table).
Corrected to cite ADR-0018's hard-gate principle ("hard gates are the real
safety": VRAM headroom + capability + NVENC-session), and state the perf-class
table is future work (not yet built) — rule 27, no aspirational citation.
- pipeline.rs: `select_live_decode_pick` now DISCLOSES the known residual inline
— a VRAM-roomy but decode-engine-saturated GPU passes both the permissive
Mpix/s budget and the headroom gate, so a live add CAN be admitted onto a GPU
that cannot sustain another decode. Acceptable for this ship: the never-off-air
contract holds (an over-subscribed decode degrades the NEW tile, never the
program — inv #1/#2, the clock samples last-good) and inv #9's closed-loop
degradation sheds the cheapest tile if it bites. Comment-only; no code change.
- web SourcesPage: the apply-semantics callout + saved description said network/
file sources "go live via config export + restart" — now FALSE after L2 (they
apply live on a full-engine run). Refreshed the copy to match: synthetic +
network/file apply live on a full-engine run; NDI/YouTube/AES67 (and any kind
on the software engine) restart. The UI already reads the real
X-Multiview-Apply header — this is the explanatory copy catching up (rule 27).
Web gate (npm ci then): eslint --max-warnings=0 EXIT 0; tsc --noEmit EXIT 0;
lingui compile EXIT 0; vitest run 632 passed / 63 files EXIT 0 (incl the 2
SourcesPage suites 10/10). Rust: fmt clean; clippy -p multiview-cli
--features ffmpeg,overlay,gpu EXIT 0 (the comments compile clean).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aperim-agent
added a commit
that referenced
this pull request
Jun 18, 2026
…pine) (#181) * test(control,cli): RED, 3 new files +624 for ADR-W018 level 2 — network live add TDD-first: failing tests for network source live add/edit functionality. Tests expect APIs not yet implemented: - typed_resources.rs delta: LiveSourceCapability + with_live_sources + capability-driven header - live_apply_header_http.rs: bind_and_serve HTTP honesty (live only when spawner wired) - live_network_apply.rs (ffmpeg-gated): e2e real ingest loop + state machine + never-falter proof These tests pin the exact behavior: network/file kinds live on full-pipeline, ndi/youtube/aes67 always restart, capability derives from ingest.is_some(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(config,control): add LiveSourceCapability for ADR-W018 L2 network live-add - config/schema.rs: add SourceKind::is_network_media() classifier (rtsp/hls/ts/srt/rtmp/file/rist) - control/state.rs: add LiveSourceCapability struct {synthetic, network} with constructors + is_live(kind) - control/state.rs: add AppState.live_sources field (default synthetic_only) - control/state.rs: add AppState::with_live_sources() builder - control/lib.rs: re-export LiveSourceCapability These provide the run-path capability signal the control plane threads to declare which source kinds the engine can live-apply, ensuring X-Multiview-Apply headers stay honest per build/run path (synthetic-only on software, synthetic+network on ffmpeg). BREAKING: bind_and_serve and related functions will need the live_sources param in next commits; routes/sources.rs will call state.live_sources.is_live(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(control): update live_apply_upsert to check LiveSourceCapability Changes live_apply_upsert to call state.live_sources.is_live(kind) instead of just checking is_synthetic(). This allows network/file kinds to be live-applied when the capability is set (full-pipeline run), and keeps ndi/youtube/aes67 always restart. Also updates the kind-change check to use is_live() for both synthetic->network and network->non-live transitions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cli): ADR-W018 L2 — live add/edit network sources via the uniform ingest path Network/file kinds (rtsp/hls/ts/srt/rtmp/rist/file) now apply LIVE on the running full-pipeline engine through ONE uniform ingest path — the level-2 spine of the DEV-stack, re-authored against current main (the original PR #108 commits 9-way-conflict; pipeline.rs churned 49x since). - cli live_sources: SourceSpawn + IngestSpawner seam + SpawnedProducer; LiveSourceHub::start_with_ingest threads the run's spawner; the worker's SpawnSource arm tears down any prior producer (edit = store-reuse swap) then spawns through the seam; no spawner => held + warned (slate, inv #10). - cli pipeline: spawn_ingest_producer extracted — IngestSupervisor::start and the new LiveIngestSpawner call the SAME fn, so a runtime add runs exactly the startup supervised ingest_loop (reconnect/jitter/PTS-normalize/rw-timeout). Placement (ADR-W018 §7, gpu): drive_streaming publishes the pinned LiveIsland; select_live_decode_pick re-polls NVML and consults the SAME select_device scorer with the candidate set restricted to the island device (never frags/ migrates) — admit => island ordinal stamped, reject/island-vanished => that source decodes in software, warned. The output never falters (inv #1). - cli control: bind_and_serve takes the run's LiveSourceCapability; upsert_source routes a network kind to request_spawn_source (store create/reuse + route key + config mirror at the frame boundary, heavy spawn off-thread BEFORE the rebind — inv #1/#10); ndi/youtube/aes67 held with the reason. - cli main: ControlPlaneWiring carries the ingest spawner; the capability sent to bind_and_serve is DERIVED from ingest.is_some() — the header claims live for network kinds exactly when a real spawner backs it. Pipeline path wires Pipeline::live_ingest_spawner(); software path wires None. - control routes/sources: live_apply_upsert answers X-Multiview-Apply from the run capability; OpenAPI + doc updated. ADR-W018 amended for the as-built L2. Restores the e2e chroma pin PR #179 deferred (live_decode_chroma.rs): a live-added decode's U/V plane stats match the startup decode of the same clip. Gates (lane-local target, env -u CARGO_TARGET_DIR, -j1): fmt clean; clippy GREEN on default, ffmpeg, and ffmpeg+overlay+gpu; control typed_resources 23/23; 8 gpu placement unit tests incl the seam-pin spy; both ffmpeg e2e realtime proofs GREEN (live add reaches LIVE, remove slates to NO_SIGNAL, churn never falters). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(cli,web): correct the inert-budget citation + disclose decode-saturation residual; refresh SourcesPage L2 copy Pre-PR review close-outs (orchestrator-flagged): - pipeline.rs: the `select_admission_pick` budget comment cited "ADR-0035 §5" for the per-GPU perf-class CostBudget table being a future refinement — that citation is WRONG (ADR-0035 has no §5 and does not scope the budget table). Corrected to cite ADR-0018's hard-gate principle ("hard gates are the real safety": VRAM headroom + capability + NVENC-session), and state the perf-class table is future work (not yet built) — rule 27, no aspirational citation. - pipeline.rs: `select_live_decode_pick` now DISCLOSES the known residual inline — a VRAM-roomy but decode-engine-saturated GPU passes both the permissive Mpix/s budget and the headroom gate, so a live add CAN be admitted onto a GPU that cannot sustain another decode. Acceptable for this ship: the never-off-air contract holds (an over-subscribed decode degrades the NEW tile, never the program — inv #1/#2, the clock samples last-good) and inv #9's closed-loop degradation sheds the cheapest tile if it bites. Comment-only; no code change. - web SourcesPage: the apply-semantics callout + saved description said network/ file sources "go live via config export + restart" — now FALSE after L2 (they apply live on a full-engine run). Refreshed the copy to match: synthetic + network/file apply live on a full-engine run; NDI/YouTube/AES67 (and any kind on the software engine) restart. The UI already reads the real X-Multiview-Apply header — this is the explanatory copy catching up (rule 27). Web gate (npm ci then): eslint --max-warnings=0 EXIT 0; tsc --noEmit EXIT 0; lingui compile EXIT 0; vitest run 632 passed / 63 files EXIT 0 (incl the 2 SourcesPage suites 10/10). Rust: fmt clean; clippy -p multiview-cli --features ffmpeg,overlay,gpu EXIT 0 (the comments compile clean). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): explicit DecodePlacement tri-state — a placement-rejected live decode forces software (2a67e4e) Carries the 2a67e4e amendment my initial re-author flattened away — a real overcommit/fragmentation bug, not a refinement. THE BUG (in my prior commit): select_live_decode_pick returned Option<String>, so a placement reject returned None, leaving IngestPlan.cuda_ordinal = None. In open_and_stream that means `new_preferring_hw(.., want_hw=true, None)` — NVDEC on libav's DEFAULT CUDA device. On a single-GPU over-headroom island the default device IS that island (overcommit); on a multi-GPU host it may be a DIFFERENT GPU (silent island fragmentation, forbidden by ADR-0018 never-fragment). "No ordinal" cannot distinguish *no placement decision* (default device fine) from *placement rejected* (hardware must NOT open). THE FIX: an explicit, closed tri-state DecodePlacement::{Default, Pinned(ordinal), SoftwareOnly} on IngestPlan + a single decoder_open_args gate: - SoftwareOnly -> (want_hw=false, None): hardware never attempted, even when NVDEC is compiled/present/not env-disabled. - Pinned(ordinal)/Default -> keep the canonical want_hw_decode env reading (MULTIVIEW_DISABLE_NVDEC opt-out still wins over a pin); Pinned threads the island ordinal. - select_live_decode_pick -> select_live_decode_placement: admit -> Pinned (or Default when the island resolved no ordinal); reject AND island-vanished -> SoftwareOnly, with truthful FORCING-software warnings. - drive_streaming stamps Pinned; ingest_plan_for defaults Default; open_and_stream routes through decoder_open_args. TDD: restored tests/decode_placement.rs (the f0a6b7f/2a67e4e4 RED pin) — it opens the REAL decoder via the gate's exact args and asserts a SoftwareOnly source comes back software (used_hw=false, no cuvid named); the gpu-lane unit tests now assert the placement value AND the decode-open behaviour per placement (not just the picked ordinal). ADR-W018 §7 amended for the as-built tri-state. Gate (env -u CARGO_TARGET_DIR, lane-local, -j1): fmt clean; clippy --workspace EXIT 0; clippy -p multiview-cli --features ffmpeg,overlay,gpu EXIT 0; decode_placement 2/2, gpu placement 8/8, live_network_apply + live_decode_chroma 1/1 each (ffmpeg). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(control,cli): gate RIST live-apply on the rist feature — never over-claim X-Multiview-Apply:live (panel major) Codex panel (#181) MAJOR: RIST was advertised live-appliable unconditionally — `is_network_media()` includes `Self::Rist` and the binary derives `LiveSourceCapability::synthetic_and_network()` from `ingest.is_some()`. On an `ffmpeg`-WITHOUT-`rist` build the hub wires a real ingest spawner (so rtsp/hls/ts/srt/rtmp/file correctly flip to `live`), but a RIST source's `ingest_plan_for` arm is a typed refusal (`librist` not built) — so the header claimed `X-Multiview-Apply: live` for a spawn that refuses, and the tile rode the slate. The header over-claimed. Fix: `LiveSourceCapability` gains a separate `rist: bool` the binary sets from `cfg!(feature = "rist")` (via the new `with_rist` builder); `is_live` returns `self.rist` for `SourceKind::Rist` and `self.network` for the other network kinds. `synthetic_only` ⇒ rist:false; `synthetic_and_network` ⇒ rist:true (the canonical "full network" meaning), narrowed by the binary's `serve_control_plane` to the build's actual rist truth. RIST classification (`is_network_media`) is unchanged — RIST IS network media; only its *live-apply capability* is feature-gated, the honest level. Tests (control typed_resources, 23→25): `rist_stays_restart_when_the_engine_ build_lacks_the_rist_feature` (with_rist(false) ⇒ RIST restart + no UpsertSource, while a non-RIST network kind still flips live) and `rist_applies_live_when_the_ engine_build_carries_the_rist_feature` (with_rist(true) ⇒ RIST live + UpsertSource). Gate (env -u CARGO_TARGET_DIR, lane-local, -j1): fmt clean; clippy --workspace EXIT 0; clippy -p multiview-cli --features ffmpeg EXIT 0 AND --features ffmpeg,rist EXIT 0 (both cfg branches compile); control typed_resources 25/25. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): ADR-W018 §7 round-2 — placement fail-closed + bounded-join startup producers Codex panel round-2 on #181 found three real data-plane defects beyond the round-1 tri-state; all fixed here: - EMPTY-ISLAND fall-through (blocker): LiveIngestSpawner consulted select_live_decode_placement only when an island was published; an empty slot (startup admission rejected the GPU / no NVML) kept DecodePlacement::Default -> default-device NVDEC. decode_placement_for now forces SoftwareOnly when admission was attempted but named no island (fail closed, ADR-W018 §7). - ADMIT-with-no-ordinal (major): an island that passes budget but resolves no CUDA ordinal returned Default -> default-device NVDEC on the LIVE path (the compositor island is already pinned). Now forces SoftwareOnly. - TWO-WRITER race on a reused single-writer TileStore for a live EDIT of a STARTUP-origin source (blocker): teardown only joined hub-owned producers; a startup producer JoinHandle lives in IngestSupervisor, so the old decode thread could still write while the replacement published. register_stop now returns an exited latch every producer carries as an ExitGuard (flips on return or panic); teardown raises stop then bounded-waits on the latch (await_exits) before the replacement publishes -- covering startup-origin producers, not just hub-owned ones. ProducerStop.stop is pub(crate) so the cross-module registration tests raise it; the gpu-gated placement matches are if-let-else (clippy single_match_else). Gate (lane-local, env -u CARGO_TARGET_DIR, -j1): fmt + clippy --workspace + clippy -p multiview-cli --features ffmpeg,overlay,gpu + test -p multiview-cli --features ffmpeg + test --workspace all EXIT 0. Completes the stalled dev108-live-add lane WIP (round-2 authored there; the 5 ExitGuard call-site completions + the ProducerStop accessor + clippy/test fixups by the integrator). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): build ExitGuard before spawn so a failed thread-spawn never orphans the latch (round-2 panel) Codex round-2 panel on #181 flagged the ExitGuard-on-spawn-failure gap: register_stop inserts the stop entry + returns the exited latch BEFORE Builder::spawn, but the ExitGuard that flips exited was constructed INSIDE the closure — so a (rare) thread-spawn Err left an orphaned exited=false entry, and a later teardown of that id busy-waited the full TEARDOWN_JOIN_GRACE (3s) before detaching (a regression from the round-1 no-op join; bounded + off the output-clock thread, never an inv #1/#10 breach). Fix: construct the ExitGuard BEFORE the spawn and move it into the closure at all 7 producer-spawn sites (run.rs synth generator; pipeline.rs audio/tone/captions/youtube-reresolve + the spawn_ingest_producer decode thread; live_sources.rs hub synth). Its Drop now flips exited whether the thread runs (drops on exit) OR Builder::spawn fails (the closure owning the guard is dropped) — so a failed spawn deregisters cleanly and teardown never busy-waits an orphan. Gate (lane-local, env -u CARGO_TARGET_DIR, -j1): fmt + clippy --workspace + clippy -p multiview-cli --features ffmpeg,overlay,gpu + test -p multiview-cli --features ffmpeg + test --workspace all EXIT 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(adr): ADR-W018 §7 — admit-no-ordinal forces SoftwareOnly (not Default) on the live path + RIST live-apply is rist-feature-gated (round-3 panel) Two doc-drift minors the round-3 Codex panel flagged (rule 27 — the ADR prose contradicted the verified code): - §7 said an admitted live decode with no resolved CUDA ordinal stamps Default; the round-2 fix forces SoftwareOnly on the live path (Default is the startup placement, where nothing is pinned yet). Corrected. - The level-2 amendment said rist is included unconditionally; the code gates RIST live-applicability on cfg!(feature=rist) (with_rist), keeping an ffmpeg-without-rist build at restart. Corrected. Docs-only; the Rust build/clippy/tests are unaffected (green at 46ceea3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: verify <verify@local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aperim-agent
added a commit
that referenced
this pull request
Jul 10, 2026
…sions (ADR-RT010, task #9) (#231) * docs(adr): ADR-RT010 — live authz revocation on established WS/SSE sessions Design-first for realtime security task #9 (PR #211 auth-panel finding A1, pre-existing): WS/SSE capture the Principal (role + scoped_object_ids) at connect ONLY, so a mid-session scope narrow/widen, role downgrade, or key revoke keeps delivering now-unauthorized deltas (and displaying now-hidden cached objects) until reconnect. Decision: auth-generation re-resolve (wait-free AtomicU64 the session samples) + hybrid response — graceful in-place $resync (new ResyncReason::AuthzChanged) for scope changes, forced 4403 disconnect for loss of read access. ApiKeyStore becomes the interior-mutable single source of truth (RwLock map + generation; revoke/set_principal bump it, principal_for_key re-resolves). Invariant #10: a per-session read-side decision (one atomic load fast path, control-plane read-lock only on a change) — no engine lock/await/channel; resume-by-seq + the #211 object-scope filter + the ADR-RT009 watermark all intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(control): RED — live authz revocation on established WS/SSE sessions (ADR-RT010) Failing test for realtime security task #9: the new authz-revocation surface does not exist yet, so these fail to compile (26 errors) against the intended API — ApiKeyStore::{generation,principal_for_key,revoke,set_principal}, SessionStream::{with_live_reauth,reauthorize,resync_frame}, ReauthOutcome, ResyncReason::AuthzChanged. Covers: scope revocation of one object stops delivering it mid-session; a revoked key disconnects the established session; a still-reading role downgrade is adopted without a spurious disconnect; scope narrowing emits a $resync rebuild directive (closes the display hole); scope widening reveals new objects; a session without live re-auth (local-admin/JWT) is untouched; and the store mutators bump the generation + re-resolve (set_principal keeps the digest, revoke removes the key). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(control,events): honor mid-session authz changes on WS/SSE (ADR-RT010, task #9) GREEN for realtime security task #9 (PR #211 auth-panel finding A1). WS/SSE captured the principal's role + object scope at connect ONLY, so a mid-session scope narrow/widen, role downgrade, or key revoke kept delivering now-unauthorized deltas (and left now-hidden objects on the client's display) until reconnect. - auth: ApiKeyStore is the interior-mutable single source of truth — RwLock key map + wait-free `generation: AtomicU64`. `revoke`/`set_principal` bump the generation under the write lock (a session observing it reads the mutation); `principal_for_key`/`generation` re-resolve. `register` stays &mut self (construction via RwLock::get_mut, no lock, no test churn); locks are poison-resilient. - realtime: SessionStream gains a live-authz handle (store-managed API keys only; local-admin/JWT keep connect-time authz). `reauthorize()` samples the generation (one atomic load fast path) and, on a change, re-resolves: scope narrow/widen → adopt + `ReauthOutcome::ScopeChanged`; key revoked / role can't Read → `Disconnect`. The transport re-resolves BEFORE projecting each delta (split recv_event from frame_for) and on a 5s idle REAUTH_TICK, so a change filters the very next delta with no seq gap; ScopeChanged emits a server `$resync` (new ResyncReason::AuthzChanged) + new-scope re-snapshot (closes the display hole); Disconnect closes WS 4403 / ends SSE. - events: ResyncReason::AuthzChanged (+ AsyncAPI schema regen). Invariant #10: a per-session read-side decision — no engine lock/await/channel, no back-pressure on the publish path. Resume-by-seq + the #211 object-scope filter + the ADR-RT009 watermark all intact (regression suites green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(control): RED — connect-window revoke/re-scope escapes live reauth; $resync strands switcher (ADR-RT010, #231 panel) The 3-reviewer auth panel on PR #231 found two authorization/protocol defects and a vacuous test: 1. TOCTOU: a store API key revoked or re-scoped in the window BETWEEN connect-auth and live-authz install is silently retained — `install_live_reauth` gates on a racy `principal_for_key().is_some()` re-probe and `with_live_reauth` samples the generation AFTER the connect principal was resolved. 2. `$resync` advertises a `Switcher` rebuild `build_resync_frames` never sends, stranding a rebuild-not-merge client's switcher state. Add the baseline-aware `with_live_reauth_at` + observable `live_role` API surface (here still buggy — `with_live_reauth_at` ignores the baseline; `resync_frame` still lists Switcher) and the failing regression tests. Also strengthen the role-downgrade test to observe adoption via `live_role()` (was vacuous). RED (3 new tests fail, 7 pass): revoke_racing_connect_is_caught_not_retained Unchanged != Disconnect rescope_racing_connect_is_adopted_not_stale Unchanged != ScopeChanged resync_rebuild_topics_match_the_re_snapshotted_set [Tiles,Devices,Switcher] != [Tiles,Devices] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(control): close live-authz connect-race + $resync switcher gap + register poison-swallow (ADR-RT010, #231 panel) GREEN for the RED regression tests. Addresses the 3-reviewer auth panel on PR #231: 1. Connect-race TOCTOU (critical): `resolve_principal` now captures the store authorization generation BEFORE resolving the key and threads it as the live-authz baseline (`Some` for store keys, `None` for local-admin/JWT) through `RealtimeViewer` / `ws_handler` / `sse_handler` / `install_live_reauth`. Install wires the handle for EVERY store key — dropping the racy `principal_for_key(&key_id).is_some()` re-probe that conflated "revoked store key" with "not a store key" and stranded a revoked-in-window session authorized forever. Both transports run a pre-snapshot reauthorize gate, so a revoke landing between auth and install disconnects with no snapshot leak, and a re-scope adopts the new scope before the first snapshot. 2. `$resync` switcher gap: `resync_frame` no longer advertises `Topic::Switcher` (never re-snapshotted); the rebuild set equals what `build_resync_frames` sends (Tiles + Devices), so a rebuild-not-merge client is not stranded. 3. `ApiKeyStore::register` recovers a poisoned lock instead of silently dropping the insert (rule 37), consistent with `read_keys`/`write_keys`. 4. `live_role()` accessor makes role adoption observable (de-vacuoused the downgrade test); the role-can't-Read arm is documented as currently-unreachable forward-compat. fmt + clippy clean; multiview-control suite green (incl. 3 new connect-race/resync regressions + the corrected scope-narrow assertion); cargo check --workspace green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(adr-rt010): correct $resync topic set (drop switcher) + deterministic connect-race handling (#231 panel) - resubscribe is [tiles, devices] — exactly what build_resync_frames re-snapshots; switcher is excluded (not object-scoped, never re-snapshotted → would strand a rebuild-not-merge client's switcher state). - Replace the "caught at the next generation bump (self-healing)" claim (which documented the connect-race rather than closing it) with the deterministic fix: baseline generation captured at auth + pre-snapshot reauthorize gate + handle installed for every store key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(control,adr-rt010): honest bounded-latency framing + accurate poison/close justifications (#231 re-review) The 3-panel re-review of the fix delta found NO logic defect (the concurrency/#10 review passed) — three honesty/guardrail corrections: - RR1: the pre-snapshot gate closes the auth→install RETENTION race, but a change in the sub-tick window after the gate is caught by the first pump tick (bounded latency), not "no leak". Fully closing it would require holding the store lock across the socket send, which invariant #10 forbids. Reworded the WS/SSE gate comments + ADR-RT010 to the honest guarantee. - RR3: justified the `let _ = socket.send(Close)` discard (rule 20 — the socket is being torn down) and corrected the poison-recovery justification in register/read_keys ("insert/remove/reassign whole KeyRecord values", not "replaced wholesale" — rule 27). Switcher-exclusion verified against object_authz_scope_id: Device*/Cast*/MediaPlayer/ input-bound-Tile events are Some(scoped); all Switcher events are None (unscoped), so a scope change does not affect switcher visibility and it is correctly omitted from the $resync rebuild set. Comment/doc only; no logic change. fmt + clippy clean; tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
aperim-agent
added a commit
that referenced
this pull request
Jul 10, 2026
…, inv #9) (#237) * test(audio): RED — cross-fade leaks mixer strips (invariant #9) `repoint_crossfade` adds a temporary outgoing `InputStrip` that fades out; on completion `retire_completed_fades` only *unroutes* it (flips a bool) and drops it from the `ProgramBus` route table, but the strip stays resident in `Mixer.inputs` forever. Every cross-fade therefore leaks one strip — unbounded RAM growth on a 24/7 multi-program host, violating invariant #9 (bounded queues/pools "allocated at start, never grow"). Add read-only strip-count observability (`Mixer::slot_count` / `live_input_count` and `ProgramBus::mixer_slot_count` / `live_strip_count`) and a failing test that drives 200 completed cross-fade cycles and asserts (1) only the channel strip stays live after each fade and (2) physical strip storage stays bounded. Fails RED at cross-fade #0: live_strip_count == 2, expected 1 (the faded-out strip is never reclaimed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): reclaim completed cross-fade mixer strips Replace the mixer's append-only InputStrip vector with a slot-map: Vec<Option<InputStrip>> plus parallel u64 generations and a free-list. Completed cross-fade outgoing strips are now removed from the mixer and their slots returned for reuse instead of merely being unrouted and retained forever. Physical strip storage is bounded by the high-water mark of concurrently-live strips (one channel + one outgoing slot for serial cross-fades), satisfying invariant #9. RoutePoint now carries the slot generation. Every read/write validates both index and generation, so a stale handle cannot address or free a reused slot (the ABA corruption footgun in the resilience brief). Generation exhaustion tombstones the slot rather than wrapping. Add a 2,000-cross-fade soak proving the physical storage stays flat at exactly 2 slots, plus an adversarial stale-handle test covering reads, writes, submit, double-remove, slot reuse, and fresh-occupant integrity. The untouched click, equal-power, true-peak, and post-ramp cursor tests remain green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(audio): overlapping cross-fades retire independently and reuse slots Additive regression closing the review-flagged gap: start OVERLAP concurrent cross-fades one tick apart so each earlier outgoing strip is still mid-fade when the next begins, then tick to completion. Asserts the channel plus all OVERLAP outgoing strips are live at once, that the live-strip count passes through every intermediate value down to 1 as the strips retire on distinct ticks (independent, not all-at-once retirement), and that physical mixer storage stays exactly at the concurrent high-water (OVERLAP + 1) across 4 rounds — proving every reclaimed slot is reused, never leaked. Test-only; no production change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
aperim-agent
added a commit
that referenced
this pull request
Jul 10, 2026
…tch pool (inv #9) (#238) * test(compositor): RED — persistent-backend LUT + band-scratch reuse contract Efficiency findings #5 (transfer LUTs rebuilt every composite tick) and #6 (per-band accumulator vec-allocated every tick) on the CPU reference compositor. Asserts the persistent RunBackend builds the transfer LUTs once per stable transfer-set and reuses the band scratch after warmup, plus that reused ticks stay byte-stable. Fails to compile: RunBackend has no lut_build_count()/scratch_alloc_count() yet (E0599). GREEN adds the memoized LutCache + reusable ScratchPool. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(compositor): GREEN — memoized transfer LUTs + reusable CPU composite pool Efficiency findings #5 and #6 on the CPU reference compositor (the golden-frame oracle), which composites once per output tick forever (invariant #1). Both per-tick costs are now paid once and reused, owned by the persistent RunBackend. #5 — transfer LUTs. composite_with_threads rebuilt LutSet::for_transfers every tick (thousands of pow/exp node evaluations: a BT.709 EOTF+OETF pair samples 6144+4096 nodes, a PQ EOTF 12288). New LutCache memoizes the LutSet keyed on the order-independent SET of transfer characteristics in play (canvas + tiles) and rebuilds only when that set changes. #6 — per-band composite scratch. composite_band vec-allocated the premultiplied accumulator + a bool coverage vec every tick (~33 MB for a full-cover 1080p band), and composite_with_threads vec-allocated the output NV12 planes. New ScratchPool (CPU twin of gpu::pool::SurfacePool) reuses one BandScratch per worker band; coverage is a generation-stamped sentinel (Coverage) so neither the accumulator nor the coverage map is cleared per tick; output planes are leased and returned to bounded one-slot caches on Nv12Image::drop. Grow-only; steady state performs zero allocation. Both the free composite* functions (transient LUTs + pool) and the persistent RunBackend (memoized + reused) route through one composite_core kernel, so the pooled backend stays byte-for-byte the free-function oracle. Bit-exactness is pinned by the untouched pixel-driven oracle (composite_band_reference) via the composite_tile_driven equivalence proptest, backend_select byte-for-byte, and parallel_bands thread-count invariance — all still green. RunBackend::Cpu now carries a Mutex-guarded CpuBackend (interior mutability, &self composite, poison-recovered — matches GpuCompositor's Mutex<SurfacePool>); adds observability accessors lut_build_count()/scratch_alloc_count(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(compositor): RED — force ≥2 workers on the pooled parallel path Panel finding (rule 18): the `*_parallel_path` cases in composite_scratch_reuse.rs drove `RunBackend::composite`, which sizes its worker fan-out from `auto_thread_count()`. On a single-vCPU CI runner that is 1, so both "parallel" cases silently ran the SERIAL branch — a worker-scratch aliasing or shared-LUT race would pass CI undetected and the parallel bit-exactness claim was not deterministically guarded. Drive a not-yet-existing `RunBackend::composite_with_thread_count(n)` seam with a fixed FORCE_THREADS and assert the multi-band branch actually ran via a not-yet-existing `band_count()` accessor (`>= 2`), independent of host CPU count. Add `forced_parallel_matches_forced_serial_byte_for_byte`: pins the reused-pool parallel output to the single-band serial reference byte-for-byte (tile spanning several band boundaries + non-trivial bg + partial opacity) — a real regression that fails if two workers alias one band's scratch or a band mis-addresses its rows. Fails to compile: 16× E0599 (no method `composite_with_thread_count` / `band_count` on `RunBackend`) — the RED. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(compositor): GREEN — deterministic forced-thread seam for the pooled parallel path Add the seam the RED needs, source-only (the test is untouched): * `RunBackend::composite_with_thread_count(n)` (+ its `CpuBackend` twin) — as `composite` but with an explicit CPU worker count. `composite` now delegates to it with `auto_thread_count()`, so production is unchanged; a test can force the multi-band parallel branch (or serial) on any host. The band split only partitions the same deterministic per-pixel pipeline, so the output is byte-identical for any `n_threads` — this changes which path runs, never the pixels. `#[doc(hidden)]`; the GPU arm ignores `n_threads`. * `RunBackend::band_count()` / `ScratchPool::band_count()` — the grow-only high-water band-slot count (1 = serial branch, worker count = parallel), the deterministic signal that the multi-band branch actually ran. Proves the new `forced_parallel_matches_forced_serial_byte_for_byte` guard is a real regression, not tautological: injecting a one-off band row-misaddress (`py_start += 2`) into `composite_parallel` makes only that case FAIL on the Y-plane byte-identity assertion; reverted, all 7 pass. Gate (worktree-local target): fmt --all --check clean; clippy -p multiview-compositor --all-targets -D warnings clean; test -p multiview-compositor all green incl golden-frame guards (backend_select 4/4, composite_tile_driven 7/7, parallel_bands 2/2, lut_vs_reference 4/4, composite_scratch_reuse 7/7); cargo check --workspace clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
aperim-agent
added a commit
that referenced
this pull request
Jul 10, 2026
#242) * test(audio): RED bounded-memory test for publish_at union-span over-alloc `AudioStore::publish_at` allocates the full `[min(base,at), max(head,block_end))` union-span buffer BEFORE applying the drop-oldest capacity clamp. A late/reordered RTP-audio packet whose rebased index lands up to ~10 s (DEFAULT_DISCONTINUITY_FRAMES = 480_000, the rebaser's re-anchor threshold) below the live head therefore makes the transient buffer ~10 s of audio even though the store capacity (STORE_CAPACITY_FRAMES = 96_000 = 2 s) is far smaller. Allocation is decoupled from capacity: a burst of ~9.9 s-reordered packets is a per-packet zero+memcpy CPU amplification vector on the ingest data plane, and "queues drop, never grow" (invariant #2/#5/#9) is violated for the transient. The transient is observable without a global allocator (the crate is `unsafe_code = forbid`): `Vec::drain` shifts survivors down but never reclaims capacity, so the over-allocated span stays visible via the new doc-hidden `window_backing_capacity_frames` probe even after the post-merge clamp shrinks the length back to capacity. RED — the new test fails on the transient bound: backing 479999 frames > capacity 96000 + one block 4800 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): bound publish_at transient to capacity, drop-oldest before alloc `AudioStore::publish_at` sized its merge buffer to the FULL `[min(base,at), max(head,block_end))` union span and only applied drop-oldest AFTER, via a post-merge `Vec::drain`. A late/reordered RTP-audio packet whose rebased index lands up to ~10 s below the live head (the rebaser only re-anchors past DEFAULT_DISCONTINUITY_FRAMES = 480_000, ≫ STORE_CAPACITY_FRAMES = 96_000) therefore allocated a ~10 s (~3.8 MB stereo) transient per packet even though the store retains only 2 s — a zero+memcpy CPU-amplification vector on the ingest data plane, and "queues drop, never grow" (invariant #2/#5/#9) violated for the transient. Apply drop-oldest UP FRONT: clamp the working base to `max(union_base, new_head - capacity_frames)` before allocating, so `merged` is never wider than `capacity_frames` and no evicted frame is ever allocated. A new `overlay_from` helper copies each source (existing window, incoming block) into the clamped buffer, skipping the frames that fall below the new base — that is the drop-oldest, now applied during the copy rather than by a trailing `drain`. The final published window is byte-identical to before in every case (the clamp is a no-op whenever the union span already fits capacity, so in-capacity placement, reorder-within-capacity, gap silence-fill and last-write-wins all behave exactly as before); only the transient allocation shrinks. Measured (test publish_at_reordered_far_below_head_allocates_bounded): a packet reordered ~10 s below the head now backs a 96_000-frame buffer, was 479_999 — the transient is bounded by capacity, not the discontinuity span. All existing publish_at/store tests pass unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
aperim-agent
added a commit
that referenced
this pull request
Jul 10, 2026
…nv#9, generation-safe) (#244) * test(framestore): RED — steady-state ring must prune consumed frames (inv#9) The per-tile media-time ring (TileStore::ring, RING_CAPACITY=256) bounds only by COUNT and never prunes frames the output clock has already advanced past. Output `now` is monotone (inv #1/#3), so a frame older than the current latch can never be selected again — yet under perfect 1:1 pacing (the shipping PtsWallClock path) the ring sits pinned at 256 dead frames (~3.2 GB/tile at a 1×1 2160p tile), an invariant #9 bounded-memory defect. Adds a doc-hidden `retained_frames()` introspection accessor (for this test and future soak gates) and a failing bounded-memory test that publishes+reads 1:1 well past the ceiling and asserts the ring retains a tiny trailing window, not the full backlog. Fails RED today: "retained 256 of cap 256". The two existing decode-ahead guards (read_at_does_not_race_ahead, state_at_ages_a_finite_clip) batch-publish BEFORE any read, so they are unaffected — the fix must keep them green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(framestore): prune consumed frames from the media-time ring (inv#9) The per-tile latch-on-tick ring (ADR-T009) bounded only by COUNT and never released frames the output clock had advanced past, so even under perfect 1:1 pacing it sat pinned at RING_CAPACITY (256) dead frames — ~3.2 GB/tile at a 1×1 2160p tile, an invariant #9 bounded-memory over-allocation. (This ring is the media-time sampler, distinct from the inv#2 single-slot last-good store `LatestSlot`, which is untouched.) Root cause is a missing prune, not an oversized ceiling: output `now` is monotone (inv #1/#3), so any entry stamped strictly before the current latch is provably never selected again. Fix: * readers (`read_at`/`state_at`) record a monotone latch watermark via `fetch_max` (one relaxed atomic on the output-clock thread); * the producer's next `publish_arc` drops entries older than the watermark, inside the existing O(capacity) copy-on-write on the sampled input thread — never on the output clock; * a backwards stamp (source-generation change) resets the watermark so the re-anchored frame is never pruned before a reader latches it. Steady state collapses to the latched frame + any decode-ahead future (~2 frames/tile: 3.2 GB → tens of MB). RING_CAPACITY stays as a documented hard ceiling bounding the worst case (a stalled output clock that stops advancing the watermark while the producer runs on). Lock-free single-slot semantics and the latch-on-tick selection rule are unchanged. The two decode-ahead guards batch-publish before any read, so the watermark stays at its MIN sentinel and nothing is pruned — they pass unchanged. Turns the RED steady-state bounded-memory test green (retained 256 -> 2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(framestore): harden prune guards — boundary, reconnect, monitored tile Mutation-hardening for the media-time ring prune (kills the surviving mutants `cargo mutants --in-diff` would otherwise report on tile.rs): * prune_keeps_the_latched_boundary_frame — the frame AT the watermark must survive (kills `>=` -> `>` on the retain bound); * backwards_stamp_resets_the_watermark_so_a_reconnect_is_not_pruned — a re-anchor (reconnect/discontinuity) resets the watermark so the fresh low generation is not pruned by the old high latch (kills deleting the reset); * state_at_alone_bounds_the_ring_for_a_monitored_uncomposited_tile — state_at advances the watermark too, so a monitored-but-not-composited tile stays bounded (kills deleting state_at's fetch_max). Each encodes a real behaviour (boundary retention, reconnect resilience, off-canvas monitoring), not just a mutant trap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(framestore): kill equal-timestamp re-anchor mutant in ring prune The `< with <=` mutant on publish_arc's backwards check (`at < tail.at`) treats a duplicate/clamped equal stamp as a discontinuity — resetting the latch watermark and defeating the prune. No existing test published two frames at the same instant, so the mutant survived `cargo mutants --in-diff`. This test establishes the watermark (publish @0/@40/@80ms, read_at 80ms), republishes at the SAME 80ms instant, then asserts the ring still pruned the consumed @0/@40ms frames (retained == 2, not 4) and newest-wins at the shared instant. Fails on the `<=` mutant, passes on `<`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(framestore): fuse ring select+watermark-advance; document prune safety Data-plane pre-merge hardening for the inv#9 ring prune (team-lead checklist): - Fuse frame selection and the prune-watermark `fetch_max` into one private `latch_and_advance`; `read_at` and `state_at` both route through it. A reader now physically cannot select a ring frame without first advancing the watermark past it, so `publish_arc`'s prune is correct BY CONSTRUCTION for any future reader (e.g. a wired-up degradation sampler) — fail-safe, not fail-checked. - `read_at`'s only new per-tick cost stays one relaxed-atomic `fetch_max` (no lock, no allocation); the read site says so. - State the prune boundary at the `retain`: keep entries at-or-after the watermark, drop ONLY those strictly before it. The latched boundary frame sits exactly at the watermark and is never pruned. Behaviour-preserving: all 15 ring tests + the state-machine suite pass unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(framestore): RED — cross-generation watermark race drops new-gen boundary frame The ADR-T009 data-plane review (Codex) found a concurrency defect in the ring prune watermark: a reader holding a superseded ring snapshot can advance the media-time watermark AFTER a reconnect re-anchors to a fresh, low media-time generation. The next forward publish then prunes the new generation's own frames as "older than the stale watermark", dropping the boundary frame the compositor is showing (invariant #2 / #9). Deterministic two-thread reproduction: two barriers force the exact interleaving publish(reconnect) -> stale latch_and_advance -> publish(fwd), so the RED is reliable rather than a flaky stress loop. Fails on the current media-time watermark (read_at(0) yields the later frame 101 instead of the reconnected boundary frame 100); passes once the watermark is keyed on the globally-monotonic publish sequence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(framestore): key ring prune watermark on publish seq for cross-generation safety The prune watermark was a media timestamp, which is NOT monotonic across a reconnect: after a backwards re-anchor to a fresh low-media-time generation, a reader still holding the superseded high-media-time snapshot could `fetch_max` the watermark to an old high value AFTER the re-anchor, and the next forward publish then pruned the new generation's own frames (retain `>= watermark`) — dropping the boundary frame the compositor is showing (inv #2 / #9). The store()-reset on backwards could not close it: the stale reader's advance lands after the reset. Fix: key the watermark on the tile-global publish `seq` (from LatestSlot), which is strictly increasing and NEVER reset across generations. A newer generation's frames always carry strictly higher `seq`s than any frame an old snapshot holds, so a stale cross-generation `fetch_max` can only leave the watermark BELOW them and `retain(|e| e.seq >= watermark_seq)` keeps them. This makes the prune generation-safe by construction — no generation counter, no bit-packing, no reset — and removes the now-unnecessary backwards watermark reset (sequence monotonicity subsumes it). RingEntry carries its publish `seq`; the media-time `at` remains the selection key. Read path stays wait-free/allocation-free (one relaxed `fetch_max`, now on `seq`). Turns the RED cross-generation race test green; all existing latch/prune/reconnect/decode-ahead tests pass unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(framestore): retitle reconnect test to the seq-monotonicity mechanism The prune watermark is now keyed on the tile-global publish seq rather than media time, so a reconnect is protected by sequence monotonicity, not by a watermark reset. Retitle backwards_stamp_resets_the_watermark_* -> backwards_stamp_reanchors_* and correct its comment/message to describe the real mechanism (rule 27: no misleading test docs). Assertion comparisons are byte-identical — only the name, comments, and message strings change; no weakening. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 11, 2026
aperim-agent
added a commit
that referenced
this pull request
Jul 11, 2026
…Page (ADR-W030, #9) (#263) * docs(decisions): ADR-W030 — GET /system/capabilities honest default-build surface Design-first record (task #9/#176, mgmt-completeness §2.7): assemble an honest capability+licence DTO from hal::probe + cfg!(feature) + the resolved AdapterReport rather than serialize a non-existent CapabilityReport (that rich telemetry is the separate SA-1+ lane #180). Pins effective_license to AGENTS.md §G / ADR-0012, build{} inline, viewer-read no-BOLA auth, control-keeps-no-hal-dep, and the static with_capabilities snapshot (invariant #10). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(control,cli): RED — failing /system/capabilities DTO+route+licence tests Failing tests first (rule 18). The SystemCapabilities DTO + CLI hal->DTO mapping compile with incomplete first-cut bodies so assertions fail: - control: gpl-codecs -> GPL (resolve still returns LGPL-clean); route 404 (not yet registered) for the viewer-200 + bad-bearer-401 cases. - cli: software backends available on the default build (no backends yet); a resolved adapter classifies the compositor (class still None). RED evidence: control: FAILED. 3 passed; 3 failed (gpl_codecs.., viewer_reads.., a_bad_bearer..) cli: FAILED. 1 passed; 2 failed (software_backends.., a_resolved_adapter..) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(control,cli): GET /api/v1/system/capabilities + honest build-capability DTO (ADR-W030) Implement the honest default-build capability + licence surface (task #9/#176, mgmt-completeness §2.7) to green. multiview-control: - SystemCapabilities DTO (system.rs): backends (codec decode/encode + software composite), compositor class/device/driver, build{effective_license, redistributable,features,ndi}, optional NDI attribution. Primitives/enums only — control keeps zero dep on multiview-hal. - BuildInfo::resolve pins the compliance mapping (§G/ADR-0012): gpl-codecs -> GPL, else LGPL-clean; every shippable build redistributable. EffectiveLicense serializes to the exact strings 'LGPL-clean' / 'GPL'. - GET /api/v1/system/capabilities route (routes/system.rs), viewer-read, system-global (no BOLA axis); registered in routes/mod + openapi. - AppState::with_capabilities — a static startup snapshot, no engine channel (invariant #10); default is the coarse software-only surface. multiview-cli: - system_capabilities.rs maps hal::probe + cfg!(feature) + the resolved AdapterReport onto the DTO; graceful (absent backend -> available:false, never panics). Composite-stage GPU backends are NOT emitted as available:false rows (hal has no env probe for them) — the compositor field is authoritative. - control.rs wires .with_capabilities(...) at startup (adapter read only under the gpu feature). The SA-1+ vendor-caps deep probe (per-codec profiles, NVENC sessions, VRAM, PSI, per-backend compiled_in) is the separate tracked lane #180. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(web,control): CapabilitiesPage at /system/capabilities + regen OpenAPI/client (ADR-W030) Wire the web surface for GET /api/v1/system/capabilities and regenerate the committed spec + generated client. web: - api/system.ts + api/systemQueries.ts: getSystemCapabilities + the useSystemCapabilities() TanStack hook (static startup snapshot; not polled). - pages/CapabilitiesPage.tsx (/system/capabilities): three panels — Backends matrix, Compositor tier, Build & licence (effective licence, redistributable, compiled features, NDI attribution). Status by value + label, never colour alone (WCAG 1.4.1). Nav entry + route added. - pages/CapabilitiesPage.test.tsx: MSW render test (availability, compositor class, exact licence string, NDI attribution shown iff the ndi feature is on). control: rename the handler capabilities -> system_capabilities so its OpenAPI operationId no longer collides with preview::capabilities in the generated client. regen: docs/api/openapi.json + web/src/api/schema.ts. Web gate green: eslint 0-warnings, tsc --noEmit, lingui compile + vite build, vitest (CapabilitiesPage 2/2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * i18n(web): extract lingui catalogs for the CapabilitiesPage strings Adds the CapabilitiesPage + nav <Trans> strings to the source and locale catalogs (en/ar/pseudo) so the i18n catalog-freshness CI gate passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * i18n(web): compile lingui catalogs for the CapabilitiesPage strings Run the gate's canonical i18n:extract --clean + i18n:compile and commit the regenerated compiled catalogs (en/ar messages.ts) so the i18n catalog-freshness CI gate passes (the production macro strips the source fallback, so compiled catalogs must match the .po sources). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(cli): cfg-gate system-capabilities licence assertions per build profile Codex review (rule 16) on PR #263: the compile-time-resolver test unconditionally asserted `LGPL-clean` + no-NDI-attribution, but `resolve_system_capabilities` correctly returns `GPL` under `--features gpl-codecs` and includes attribution under `--features ndi`. The test therefore would FAIL for exactly the compliance-sensitive profiles it claims to validate, passing on default CI only because CI builds default features — a mis-scoped test. Gate the expectations on the profile the test binary is compiled with (`GPL` when `gpl-codecs` else `LGPL-clean`; attribution present when `ndi` else absent), mirroring the `cfg!` bridge inside the resolver. This strengthens coverage (the GPL/NDI profiles are now asserted, not skipped) rather than weakening it; the exhaustive per-combo mapping is already runtime-tested in `multiview-control` via `BuildInfo::resolve`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
aperim-agent
added a commit
that referenced
this pull request
Jul 12, 2026
* docs(adr): ADR-M014 — SA-1+ vendor-caps deep probe design (#180) Design record for task #180 (the SA-1+ deep-probe half deferred from #9/#176 by ADR-W030). Captures the load-bearing findings: - surfacing-not-probe reframe: ADR-0017 DeviceLoad already probes the live half via the runtime-loaded NVML poller; the new work is the static vendor L2 caps EnvProbe defers + a host block; - the inv#10 static-in-snapshot vs live-on-telemetry boundary (VRAM total / NVENC session-cap ceiling / engine topology / codec profiles / cgroup+PSI presence in the caps snapshot; live gauges stay on the telemetry stream, excluded — folding them in forces per-request probing or a stale-snapshot lie); - additive-only DTO extension keeping control's zero-hal-dep boundary; - hal caps.rs (VendorCaps + VendorCapsProbe seam) + host.rs API; - the #180-A (GPU-free-completable, ships post-#271 as one fanned chain) vs #180-B (double-gated rule-6 seam + rule-26 GPU runners, grouped #198) decomposition; - the red-first TDD plan and the LGPL-clean/deny-clean licence posture. Status Proposed — flips to Accepted when the #180-A code lands (repo #97 pattern). Docs-only; no code, no dependency change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(adr): ADR-M014 revision — address 7 Codex cross-vendor findings (#180) Cross-vendor design review (Codex gpt-5.6-sol) returned BLOCKED with 7 valid findings against the A/B boundary and the honesty claims. Revised on the same lane; verified the exact DeviceLoad fields firsthand against multiview-hal/src/load.rs before redrawing the line. BLOCKING (A/B-boundary contradictions): 1. rule-6 seam contradiction — caps.rs (`VendorCaps` + `VendorCapsProbe` + call site + ALL its tests) is now ENTIRELY #180-B, introduced atomically with >=1 real hardware-validated impl. #180-A has NO VendorCapsProbe and no mock-probe test (only host.rs + surface-existing DeviceLoad). Scrubbed the §5 territory table (LANE-ENG-A host.rs vs LANE-ENG-B caps.rs) and the §6 TDD plan. 2. rule-26 over-claim — cited the exact static slice DeviceLoad produces today (DeviceId {vendor, stable_id, pci_bus_id} + vram_total_bytes, load.rs:220-250) and restricted #180-A to precisely those; moved model/driver/engine-topology/ session-cap-ceiling/per-codec (all needing a never-exercised vendor query) to #180-B's GPU-runner gate. Footnoted the one nuance (device name is already read by the sibling device_perf NVML call) and kept it in #180-B with its per-device cohort. SHOULD-FIX (accuracy): 3. "byte-compatible" was false — now "backward-compatible, not byte-identical" (host/detection/observed_at add keys even on a software build; old clients ignore unknown fields). Fixed the §6 control test to assert additive backward-compat, not byte-identity. 4. Licensing/default-build — named the exact off-by-default gates (cuda/vaapi/qsv/videotoolbox); NVML runtime-loaded via nvml-wrapper; deny is re-run on the real dep change, not asserted prospectively. SHOULD-ADDRESS (completeness/honesty): 5. inv#10 at CONSTRUCTION — stated the one-way construction boundary: caps assembled once at startup off the output-clock thread from a non-blocking LoadSource::poll() copy with immediate absent-fallback; no engine lock/channel borrowed; engine start/output never awaits caps assembly. 6. rule-27 provenance — added an `observed_at` startup timestamp; the wire contract frames every caps field as a fact-as-observed-at-startup (known-vs-unknown), never current-vs-stale. 7. detection{} granularity — replaced 3 global booleans with a `ProbeStatus` enum {not_attempted|succeeded|unsupported|failed}: L1/L3 host-global, L2 rides each device's `caps`; `psi: ProbeStatus` and `thermal_sensors: Option<Vec>` distinguish confirmed-absence from probe-failure. Also updated the docs/decisions/README.md M014 index line to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(adr): ADR-M014 — mechanize inv#10 construction boundary + observed_at always-serialized (#180) Second cross-vendor re-review (Codex-confirmed 6/7 resolved) left two scoped items before merge; both fixed on the same lane. 1. Finding #5 (inv#10 at CONSTRUCTION) — was asserted ("off the output-clock thread", "engine never awaits") without the mechanism. §1 now states it concretely, verified firsthand against the code: - WHERE: caps assembly extends the same one-shot AppState::with_capabilities seam W030 fills (multiview-cli/src/control.rs) — on the CLI control-plane bring-up (Tokio control-IO plane), which by ADR-0009's two-plane split is NOT the output-clock data-plane OS thread. - WHAT: the single LoadSource::poll() pass is a BOUNDED VENDOR QUERY (an NVML/DRM sample_all() pass, per-pass-bounded by PollInterval, via the default_load_source() the system-metrics task already selects; load.rs:657/:705) — not a cached read, not a per-request probe. Corrected the imprecise "non-blocking" wording (an NVML call is bounded, not non-blocking) here, in Consequences, and in the README index line. - ORDERING: the engine OutputClock runs on its own dedicated data-plane OS threads and neither awaits nor is awaited by caps assembly; caps borrows no engine lock/channel/poller (polls its OWN LoadSource) → structurally incapable of coupling to the engine. Absent-fallback = honest absent state, not "not-yet-ready". Added an Alternatives row (assert-outcome-without-mechanism, rejected) and a §6 cli test that exercises the one-shot own-source boundary. 2. NEW: observed_at must not use skip_serializing_if. §1 makes it the always- present provenance anchor, but §2 lumped it with the optional/defaulted fields. Now exempted: observed_at is ALWAYS serialized (a required OpenAPI field, stamped on every snapshot incl. the absent-fallback); devices/host/ detection stay optional/defaulted. Added the §6 control-test assertion (observed_at present even with empty devices + None host) and an Alternatives row. Verified firsthand: crates/multiview-hal/src/load.rs (LoadPoller::poll → probe.sample_all, trait doc "bounded vendor query"), multiview-cli/src/ system_metrics.rs (default_load_source, spawn = tokio task), multiview-cli/src/ control.rs:279 (with_capabilities one-shot). All doc links resolve (added ADR-0009). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps proptest-state-machine from 0.3.1 to 0.8.0.
Commits
7f1367fMerge pull request #641 from proptest-rs/release-1.11a63bf7eproptest-state-machine v0.8.0d86e9ffadd changelog for #6407940313proptest v1.11.03ec998cfix #638 changelog8ceb00cMerge pull request #639 from lukoktonos/bits1289c8df1aMerge pull request #638 from folkertdev/f16-supportca9d8e1changelog #638a39869fimply f16 feat by unstable85c5ca0Merge pull request #637 from folkertdev/min-max-assoc-constantsDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)