diff --git a/AGENTS.md b/AGENTS.md index f508514..1c48a4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,8 +2,8 @@ ## 1. Decision And Durable Truth -- Treat discussions, examples, review suggestions, experiments, checkboxes, and agent ideas as inputs, not implementation authority. Restate the intended outcome and rationale, reconcile it against the whole product model and evidence, and challenge contradictions or needless complexity. -- Before dependent implementation or implementation agents start, update every affected owning requirement, design, ADR or research conclusion, plus `docs/project-memory.md` and `docs/status.md` when their snapshots change. Checkpoint the consistent truth in Git; chat is not durable truth. +- Highest priority: before implementation, cleanup, or deployment, settle the meaning against the whole product model and evidence, challenge contradictions or needless complexity, and write the accepted decision to its owning truth documents. Discussions, examples, review suggestions, experiments, checkboxes, and agent ideas are inputs, not implementation authority; never turn a partial interpretation into code or a branch. +- Before dependent work starts, update every affected owning requirement, design, ADR or research conclusion, plus `docs/project-memory.md` and `docs/status.md` when their snapshots change. Checkpoint the consistent truth in Git; chat is not durable truth. - If semantics remain disputed, record the hold in `docs/todo.md` and freeze only dependent work. Do not encode a guess as accepted truth. - **Current-truth ("Dongpo pork") rule:** after a correction, current docs, UI, code, comments, configuration, and PR copy state only the accepted behavior and rationale that still constrains it. Remove rejected alternatives and explanations of their removal; Git history owns that history. - Queue new observations in their owner and continue the active milestone unless the user requests immediate investigation or the evidence reveals a P0 blocker. @@ -20,8 +20,8 @@ - A normal desktop or mobile browser is the initial Viewer target. A packaged sender or native capture helper is a later optimization. - Keep media distributed and automatic. Central services own rooms, authentication, signaling, STUN, observability, and bounded fallback resources; do not silently make the product always-SFU. - STUN and direct/peer UDP remain first. Ordinary peer edges receive no TURN candidates by default, and every accepted path ends in bounded success or clear failure. HTTPS/WSS transport is independent of media transport. -- Every non-server endpoint uses one server-authoritative ordinary downstream cap: default `2`, configurable as `1`, `2`, or `3`. Upstream receive is free; Browser role, UA, visibility, and client advertisement do not create another tier. -- SFU/TURN publication, subscription, selected transport, overlap, and server-resource accounting remain held in the current routing TODO until their holistic model is accepted. +- Every non-server endpoint uses one server-authoritative steady outbound media-copy cap: default `2`, configurable as `1`, `2`, or `3`. A peer child or the Host's single SFU publication consumes one slot; upstream receive is free, selected TURN replaces the transport of the same copy, and SFU subscriber egress is accounted at the server. Browser role, UA, visibility, and client advertisement do not create another tier. +- The accepted route model is one room controller with three operations: allocate/distribute, child reparent, and relay abdicate/drain. SFU is one Host publication with per-Viewer subscriptions; TURN is a selected transport for an authorized edge or Host-SFU ingress, not a topology node. These paths use exact generations, media proof, bounded overlap, and independent server admission; they do not create a fixed root-count or room-wide lease rule. ## 4. Canonical Repository And Integration diff --git a/docs/README.md b/docs/README.md index 8c4bf17..b4079e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ # Project Documentation - [需求理解](./需求理解.md): current product scope, priorities, constraints, and acceptance criteria. -- [方案设计](./方案设计.md): current implementation design; unresolved routing semantics are tracked in the current TODO ledger. +- [方案设计](./方案设计.md): accepted implementation design; runtime migration and validation are tracked in the current TODO ledger. - [Project memory](./project-memory.md): durable current product and source snapshot to read at the start of future work. - [Current status](./status.md): bounded current/index snapshot of production, active milestones, decisions, blockers, and links to detail. - [Verification status](./verification-status.md): demand-loaded ledger of current cross-cutting evidence, open proof boundaries, and expensive-test applicability. @@ -10,7 +10,7 @@ - [Maintenance guide](./maintenance.md): Git workflow, context hygiene, document lifecycle, research policy, and automation. - [P2P WebRTC research](./research/webrtc-p2p-screen-sharing.md): evidence, bandwidth model, browser constraints, reference implementations, and feasibility assessment. - [Peer-assisted media research](./research/peer-assisted-media.md): browser shared-encode limits, standard relay re-encoding, deterministic sticky topology, runtime relay-capacity extension, and abandon gates. -- [Low-server-cost media routes](./research/low-server-media-routes.md): SFU-root/peer distribution, selected-edge TURN, hop-level accounting, privacy-safe ICE evidence, and bounded transport gates. +- [Low-server-cost media routes](./research/low-server-media-routes.md): Host-publication/SFU distribution, selected-edge TURN, hop-level accounting, privacy-safe ICE evidence, and bounded transport gates. - [Built-in peer ICE TURN candidate](./research/built-in-peer-ice-turn.md): rejected canary record, retained standards/resource findings, and the selected-edge replacement boundary. - [Advanced peer distribution](./research/advanced-peer-distribution.md): multi-tree/SVC, encoded-object relay, native RTP forwarding, FEC/network-coding, MoQ, and measurable go/no-go gates. - [Native shared-encode sender](./research/native-shared-encode-sender.md): consolidated Draft #16/#18/#22/#23/#25/#28 ladder, unclassified product-gate failure, staged revalidation, and stop line. @@ -23,7 +23,7 @@ - [ADR-0001](./adr/0001-p2p-first-media-topology.md): historical P2P-first baseline; later access and automatic-routing decisions supersede its stale current-state details. - [ADR-0002](./adr/0002-persistent-protected-rooms.md): site access, room-scoped private Viewer grants, explicit public-watch, rotation, and minimal SQLite persistence. - [ADR-0004](./adr/0004-peer-assisted-media-experiment.md): historical standard-WebRTC peer-assisted experiment and reusable evidence. -- [ADR-0005](./adr/0005-automatic-hybrid-media-routing.md): accepted automatic P2P-first direction and generation safety; unresolved capacity and fallback accounting remain held in the current TODO. +- [ADR-0005](./adr/0005-automatic-hybrid-media-routing.md): accepted automatic P2P-first route model, capacity/accounting, fallback roles, and generation safety; runtime migration and validation remain in the current TODO. - [ADR-0006](./adr/0006-fixed-high-native-sender-canary.md): proposed fixed-`HIGH` native canary, current no-go result, and staged revalidation boundary. - [ADR-0007](./adr/0007-demand-driven-dual-representation-quality.md): accepted path-isolated `HIGH + at most one LOW` policy, optional idle-layer stop, evidence contract, and SVC boundary. - [ADR-0008](./adr/0008-window-scoped-audio-capture.md): browser window-audio hint and the Windows WASAPI process-loopback boundary. diff --git a/docs/adr/0001-p2p-first-media-topology.md b/docs/adr/0001-p2p-first-media-topology.md index 9528c26..95dd415 100644 --- a/docs/adr/0001-p2p-first-media-topology.md +++ b/docs/adr/0001-p2p-first-media-topology.md @@ -31,7 +31,8 @@ contradict the private-room cost model or duplicate mature WebRTC capabilities. ## Current Ownership [ADR-0005](./0005-automatic-hybrid-media-routing.md) owns current routing -invariants and unresolved assisted-route boundaries. [ADR-0004](./0004-peer-assisted-media-experiment.md) +invariants and assisted-route roles; runtime migration and validation remain +in the current TODO. [ADR-0004](./0004-peer-assisted-media-experiment.md) owns only historical peer-assisted evidence. Access and room lifetime are owned by [ADR-0002](./0002-persistent-protected-rooms.md). diff --git a/docs/adr/0005-automatic-hybrid-media-routing.md b/docs/adr/0005-automatic-hybrid-media-routing.md index 91b9dd4..3e04aec 100644 --- a/docs/adr/0005-automatic-hybrid-media-routing.md +++ b/docs/adr/0005-automatic-hybrid-media-routing.md @@ -1,6 +1,6 @@ # ADR-0005: Automatic Hybrid Media Routing -- Status: Accepted invariants; assisted-route resource model pending +- Status: Accepted route model; runtime migration pending - Date: 2026-08-20 - Last updated: 2026-08-22 @@ -16,8 +16,7 @@ or understand the topology. The controller may use peers and bounded server-assisted resources, but it must keep the ordinary media graph distributed, deterministic, authorized, and recoverable. -This ADR records the accepted invariants and the assisted-route boundary that -still needs one holistic decision. +This ADR records the accepted invariants and the complete assisted-route model. ## Decision @@ -36,9 +35,13 @@ capacity: - one named configuration value and one shared implementation boundary own the policy. Route code must not repeat literal policy numbers. -This capacity applies to ordinary downstream endpoint edges. It does not by -itself define how SFU publication, SFU subscription, TURN transport, server -egress, or temporary migration overlap are accounted. +Capacity counts active outbound media copies produced by a non-server endpoint: +an ordinary peer child consumes one slot and the Host's single SFU publication +consumes one Host slot. An upstream receive edge is free. A committed selected +TURN transport replaces the transport of its logical edge and consumes the same +single steady slot; a parallel media-producing candidate requires a separately +reserved transition slot. SFU subscriptions consume server egress, not endpoint +capacity. ### Active topology @@ -48,7 +51,10 @@ revision: - the Host has no upstream; - each Viewer has exactly zero or one active upstream; - the active media graph is acyclic; -- every ordinary parent stays within the authoritative endpoint capacity; +- every non-server endpoint stays within its steady sender capacity or one + explicitly reserved transition-overlap slot; +- every active Viewer is source-reachable through peer edges or the current + authoritative Host publication; - healthy unaffected branches remain sticky; and - admission or recovery ends in a valid assignment, an explicit wait, or a bounded failure. @@ -67,44 +73,82 @@ agent, or aggregate unrelated paths into a room-wide quality score. Every prepare, signal, recovery, commit, and rollback is bound to the exact room, share generation, endpoint sessions, route revision, assignment -generation, connection identity, and publication generation when one exists. +generation, connection identity, media-binding generation, and publication +generation when one exists. A stale or mismatched asynchronous result fails closed and cannot revive an old edge. -A route replacement uses make-before-break only when the still-to-be-defined -resource model admits the overlap. The old route remains authoritative until -the new route proves current-generation media with a live track, positive RTP -progress, and decoded-frame progress. ICE `connected` alone is insufficient. -Commit promotes that exact candidate atomically; timeout, failure, stale -identity, or revoked authority destroys it and keeps or restores the previous -valid route. +A route replacement uses make-before-break only when the typed endpoint and +server-resource ledger atomically admits the required reservations. The old +route remains authoritative until the new route proves current-generation media +with a live track, positive RTP progress, and decoded-frame progress. ICE +`connected` alone is insufficient. Commit promotes that exact candidate +atomically; timeout, failure, stale identity, or revoked authority destroys it, +releases reservations idempotently, and keeps or restores the previous valid +route. When a full sender has no overlap slot, the controller may preconnect +signaling and transport and perform one explicit bounded-gap cutover instead of +silently exceeding capacity. Recovery attempts are finite, generation-bound, and stop after success. The -controller must not turn a rare failure into an unbounded retry loop, an -unbounded server fanout, or a hidden third route. +controller must not turn a rare failure into an unbounded retry loop, unaccounted +server fanout, or an untracked parallel route. -## Pending Assisted-Route Decision +## Accepted Assisted-Route Model -The following items must be designed together before dependent implementation -or deployment: +One room controller owns three operations: allocate/distribute, child reparent, +and relay abdicate/drain. They share one target snapshot, one pending transition, +one resource ledger, and monotonic room/session/revision fences. -- who publishes to an SFU and from which topology level; -- which endpoints subscribe and whether one publication can serve roots or - network-restricted leaves at different levels; -- where selected TURN is permitted and whether it transports a peer edge, an - SFU ingress, or another explicitly authorized edge; -- how endpoint capacity, server ingress/egress admission, SFU resources, TURN - resources, and provisional overlap are counted; -- the exact fallback and recovery sequence when the Host, a relay parent, or a - leaf is network-restricted; and -- how relay downgrade, sibling migration, and make-before-break remain bounded - for static capacities `1`, `2`, and `3`. +- The Host is the only source publisher. There is at most one authoritative Host + publication per share generation, and every SFU-fed Viewer subscribes to it. + A Viewer that receives the publication may relay to ordinary children only + after independent outbound RTP, decode, resource, and sender-slot proof; + a strict SFU Viewer remains a leaf. Viewer republishing into a second SFU + publication is outside the current product. +- TURN is a selected transport for an existing authorized logical edge or the + Host-to-SFU ingress. It is not a topology node, a second source, or a global + room lease. Existing direct/STUN edges remain preferred; a failed selected + edge may use exact TURN, and an unavailable logical peer path may fall back to + an SFU subscription. If the Host has no direct first-level path, one Host + publication over direct or selected TURN is preferred to repeated Host-TURN + peer edges. +- Endpoint sender capacity is accounted independently from server ingress/egress, SFU + subscriptions, TURN allocations, and one bounded transition-overlap slot. + Steady capacity is `1`, `2`, or `3` (default `2`); a transition may use + `min(steadyCap + 1, 3)` only for one fenced, deadline-bound handoff and must + return to steady bounds at commit. There is no fixed SFU-root count or + room-wide selected-lease count. +- Every candidate carries exact room, share, endpoint sessions, route revision, + assignment/connection generations, publication generation when applicable, + and media-binding generation. Reserve, prepare, current-generation media + proof, atomic commit, drain, and idempotent release are one transaction. + TTL cleans abandoned reservations; it never revokes a healthy committed edge. +- A child that observes its own upstream as bad uses correlated C+B evidence + for that edge and invokes child-scoped reparent. A relay parent enters + `suspect` only for its own ingress/parent-scope evidence, then stops accepting + new children and repairs its own ingress with reparent or branch-preserving + `replaceIngress`; Host source failures instead use publication repair and + child migration. A relay parent is marked + directional-ineligible only after that repair fails, an explicit sender or + resource failure makes forwarding unusable, or independent downstream edges + (not C+B from the same edge) confirm forwarding remains unusable. It then + retains a healthy ingress while draining affected children, and replaces or + removes a failed ingress in the same target. The quality policy accounts for + currently observable edges and endpoint capacity; the route model does not + hard-code a two-child quorum. The quality evidence owner defines finite + windows and independent-edge corroboration without turning them into a global + score. If the new ingress restores downstream media, + the subtree remains unchanged. Hard failure preempts confirmed parent drain, + which preempts a single-edge soft reparent. A feasible child migration is + never blocked by a sibling with no destination. +- Healthy edges are sticky. Join, departure, capacity release, hard failure, + confirmed ineligibility, server-resource change, and explicit ingress restore + are the route-changing events. Quality evidence changes topology eligibility, + not built-in media layers and not a second route authority. -SFU and TURN are bounded fallback resources, not authorization for an always-SFU -room or unbounded central fanout. Conversely, ordinary endpoint child count -must not be copied onto a server as an implicit server cap. The accepted model -must state both endpoint and deployment resource bounds explicitly and justify -them with measurements. +SFU and TURN remain bounded fallback resources with independent deployment-wide +admission. Resource exhaustion produces the next bounded candidate, an explicit +wait, or failure; it never creates unbounded central fanout. ## Current Production Divergence @@ -114,19 +158,25 @@ media-proven transitions, and bounded recovery remain reusable only where they satisfy this ADR. That release remains a deployment fact and rollback reference. It must not be -merged wholesale into a new route implementation. After the pending model is -accepted, retained code and tests are transplanted from current `main` by -invariant; obsolete policy is replaced at its owner. +merged wholesale into a new route implementation. After this model is merged, +retained code and tests are transplanted from current `main` by invariant; +obsolete policy is replaced at its owner. ## Acceptance Boundary Before a revised controller ships: - property tests cover capacity `1`, `2`, and `3`, one active upstream, - acyclic assignments, stale generations, duplicate signals, departure, - rollback, and admission exhaustion; + acyclic and source-reachable assignments, stale generations, duplicate + signals, departure, rollback, and admission exhaustion; - route changes preserve unaffected branches and never commit before media proof; +- quality tests distinguish child-scoped C+B reparent from parent-scoped + corroborated drain, prove that same-edge C+B cannot drain a parent, require + independent sibling edges for parent corroboration, include a Viewer that is + both child and parent plus the Host-source publication-repair exception, bind + evidence to media-binding generation, and exercise suspect/local-repair + before drain while preserving unrelated sibling evidence; - real-browser tests cover direct peer media, peer relay, server-assisted media, selected transport when configured, failure, and recovery; - measured endpoint upload and server ingress/egress prove the accepted @@ -151,15 +201,14 @@ Positive: - one capacity rule replaces role and browser policy forks; - route safety is expressed through graph, identity, and media invariants; -- the unresolved server-assisted cases stay visible without being guessed into - runtime behavior. +- server-assisted paths remain bounded and explicit without turning the room + into an always-SFU topology. Negative: -- the deployed controller intentionally diverges until the assisted-route model - is accepted and migrated; -- make-before-break cannot be finalized independently of resource accounting; - and +- the deployed controller intentionally diverges until this model is migrated; +- make-before-break consumes explicit endpoint and server reservations and may + require a bounded-gap cutover when no overlap slot exists; and - real SFU/TURN and target-network evidence is still required. ## Relationship to other ADRs @@ -176,5 +225,9 @@ Negative: - [WebRTC statistics](https://www.w3.org/TR/webrtc-stats/) - [WebRTC transports, RFC 8835](https://www.rfc-editor.org/rfc/rfc8835.html) - [TURN, RFC 8656](https://www.rfc-editor.org/rfc/rfc8656.html) +- [RTP topologies, RFC 7667](https://www.rfc-editor.org/rfc/rfc7667.html) +- [PIM-SM Join/Prune behavior, RFC 7761](https://www.rfc-editor.org/rfc/rfc7761.html) - [LiveKit selective subscription](https://docs.livekit.io/transport/media/subscribe/#selective-subscription) - [LiveKit track subscription permissions](https://docs.livekit.io/transport/media/publish/#track-permissions) +- [Kubernetes controllers](https://kubernetes.io/docs/concepts/architecture/controller/) +- [Consistent updates for software-defined networks](https://reitblatt.com/downloads/consistent-updates-hotnets11.pdf) diff --git a/docs/adr/0007-demand-driven-dual-representation-quality.md b/docs/adr/0007-demand-driven-dual-representation-quality.md index 5a4390e..b7fd744 100644 --- a/docs/adr/0007-demand-driven-dual-representation-quality.md +++ b/docs/adr/0007-demand-driven-dual-representation-quality.md @@ -1,6 +1,6 @@ # ADR-0007: Path-Isolated Dual-Representation Quality -- Status: Accepted - Staged Implementation +- Status: Accepted quality/representation decision; topology capacity follows ADR-0005 - Date: 2026-08-19 ## Context @@ -31,8 +31,11 @@ Use path-isolated dual representations: passes, Screener does not add a media-layer selector. 3. Correlated sender/viewer evidence and asymmetric windows classify topology eligibility and diagnostics only. An observed downshift is `suspect`; a - confirmed `FALLBACK` endpoint cannot remain a parent. These application - states do not command ordinary built-in layer changes. + confirmed `FALLBACK` relay parent cannot remain a parent. The edge-versus- + parent scope, same-edge C+B handling, local repair, and drain actions are + owned by ADR-0005's `EdgeHealth`/`RelayEligibility` controller; this ADR + does not turn representation evidence into a second topology authority or + command ordinary built-in layer changes. 4. Explicit subscriber quality or sender layer activation is considered only if the built-in candidate fails its bounded gate. Deactivating an unused `LOW` is a resource optimization, not an acceptance requirement. @@ -58,24 +61,28 @@ machine-learning controller, custom media selector, or continuous room-wide optimizer. Exact thresholds remain implementation inputs until the controlled quality matrix establishes them. -The topology and representation budgets are independent invariants. The host -still has at most two downstream media edges. A native relay forwards selected +The topology and representation budgets are independent invariants. Each +non-server endpoint follows ADR-0005 steady outbound media-copy capacity `C` +(`1`, `2`, or `3`, default `2`); the two-edge host result below is a historical +experiment configuration, not a fixed policy. A native relay forwards selected encoded packets without decoding or re-encoding. Future dual-tree or striped delivery may reduce host upload from about two full copies toward one copy plus necessary redundancy, but it does not block this decision. -The application must not stably retain a confirmed `FALLBACK` endpoint as a +The application must not stably retain a confirmed `FALLBACK` relay parent as a parent. A later planned or explicit `LOW` fallback is leaf-only: complete a generation-guarded child evacuation before changing its quality, and preserve the prior assignment and quality if evacuation fails or becomes stale. An SFU may make an unannounced congestion-protection downshift before the application -can react. Treat that observation as `suspect`, not confirmed `FALLBACK`; if -correlated evidence confirms it across the bounded entry window, evacuate its -children and set downstream capacity to zero. This cannot promise packet-level -preemption, so the root-with-children gate must bound temporary descendant -impact before default enablement. Re-advertising relay capacity still needs the -longer recovery window and a cooldown. A viewer's -advisory request cannot trigger this sequence by itself. +can react. Treat that observation as `suspect`, not confirmed `FALLBACK`; the +representation evidence may contribute to the bounded topology evidence, but +ADR-0005 decides whether it is an edge-local reparent or a relay-parent +cordon/repair/drain. C+B from one edge alone never proves parent-wide failure; +parent drain requires the independent-edge or hard-failure conditions in +ADR-0005. This cannot promise packet-level preemption, so the root-with- +children gate must bound temporary descendant impact before default enablement. +Re-advertising relay capacity still needs the longer recovery window and a +cooldown. A viewer's advisory request cannot trigger this sequence by itself. ## Evidence Contract @@ -110,7 +117,11 @@ for the few receive/decode signals required by the topology predicate. The B/C correlation uses normalized fields derived from negotiated parameters/stats and actual decode behavior; it must never upload raw SDP, raw stats, candidate addresses, or raw device/network identifiers. Opaque server-issued path and -connection-generation IDs remain required for authorization and correlation. +connection-generation IDs remain required for authorization and correlation, +and the evidence carries ADR-0005's exact route identity: room/share +generation, both endpoint sessions, assignment/connection generation, +media-binding generation, route revision, and publication generation when +applicable. Do not build a general telemetry schema. A viewer may request `LOW`, but the request is advisory. It must be carried on @@ -170,9 +181,11 @@ evidence windows remain limited to topology eligibility and diagnostics: subscriber does not attach a `RemoteTrack`, so LiveKit `adaptiveStream` is not directly usable without changing that ownership; it is not required for the SFU bandwidth-adaptation candidate. Before default enablement, a separate - root-with-children gate must inject an autonomous downshift, observe it as - suspect, evacuate on bounded confirmation, and limit temporary descendant - impact; no confirmed `FALLBACK` root may retain children. If built-in selection + root-with-children gate must inject an autonomous downshift and observe it as + suspect. ADR-0005 then chooses local repair, edge-local reparent, or + RelayEligibility parent drain; only that drain branch evacuates children and + limits temporary descendant impact. No confirmed `FALLBACK` root may retain + children. If built-in selection fails the product gates, test explicit standard subscriber quality selection before sender activation/deactivation. The gate must prove that a healthy P2P/`HIGH` path is unchanged, no third layer appears, the expected layer is @@ -286,12 +299,15 @@ Negative: - An idle `LOW` may remain active only after hardware, game-performance, and upload cost passes. If idle stopping exists, verify bytes/frames stop; otherwise record and accept the bounded always-on cost. -- Representation count never exceeds two and host media edges never exceed two. +- Representation count never exceeds two. Host and relay endpoint media copies + follow ADR-0005 steady capacity `C`; the two-edge result is only the bounded + experiment configuration. - A later planned/explicit `LOW` fallback evacuates children first and preserves the prior state on stale/failed evacuation. An autonomous BWE downshift enters - `suspect`; bounded confirmation evacuates its children, and no confirmed - `FALLBACK` endpoint retains downstream capacity. The root-with-children - gate measures temporary descendant impact before default enablement. + `suspect`; ADR-0005's bounded edge/parent evidence then chooses local repair, + child reparent, or parent drain, and no confirmed `FALLBACK` relay parent + retains downstream capacity. The root-with-children gate measures temporary + descendant impact before default enablement. - Relay capacity returns only after the longer stable recovery plus cooldown. - An unavailable/over-budget `LOW` fails visibly for weak paths while healthy paths and `HIGH` remain unchanged. diff --git a/docs/deployment.md b/docs/deployment.md index c6246fc..d835048 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -124,18 +124,22 @@ private to nginx, while WebRTC media reaches LiveKit directly on UDP 7882. ## Candidate boundary and rollback The accepted target for `share.bonfire.icu` remains distributed and automatic: -direct/peer UDP first, then an SFU virtual parent feeding normally one or two -roots, whose peer descendants continue carrying media. Multiple exceptional viewers that cannot attach behind a healthy -root may consume additional server egress only under a separate explicit cap. +direct/peer UDP first, with one Host publication available to authorized SFU +subscriptions when a logical edge cannot use peer transport. An SFU-fed Viewer +may continue as a peer relay only after independent outbound proof; strict +fallback Viewers remain leaves. Server-assisted subscriptions and allocations +use independent deployment admission rather than an endpoint-cap or fixed-root +rule. The repository requires STUN and ordinary peer connections receive STUN-only -ICE. The accepted ladder is direct/peer UDP, then the revision-bound SFU/UDP -virtual parent, then optional authenticated TURN for one controller-selected -exceptional edge. LiveKit participants receive only revision-bound `sfu-config` -URL/token messages and negotiate within LiveKit's separate ICE domain. The -selected-edge TURN config/wire is deployed in the current `9461e20` release with one -UDP URL and a 120-second credential TTL. It was not exercised by a real media -session during this cutover. +ICE. An existing logical edge prefers direct/peer UDP and may use exact selected +TURN when configured; only an unavailable logical ingress uses the Host +publication/SFU path. A Host-SFU ingress may itself use selected TURN. LiveKit +participants receive only revision-bound `sfu-config` URL/token messages and +negotiate within LiveKit's separate ICE domain. The selected-edge TURN +config/wire is deployed in the current `9461e20` release with one UDP URL and a +120-second credential TTL. It was not exercised by a real media session during +this cutover. `PEER_ASSISTED_MEDIA=true` is the process-wide topology/SFU switch. Every normal room gets its own bounded controller state; ordinary peer connections remain diff --git a/docs/project-memory.md b/docs/project-memory.md index ed4d1d9..3dc2ba5 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -7,9 +7,9 @@ Last updated: 2026-08-22 - Build private, low-latency game screen sharing for one broadcaster and a small group of trusted friends. Public or large broadcasts belong on OBS/Twitch-class services. - Web is the current delivery target. Viewers join from desktop or mobile browsers; a packaged or native sender is a later optimization. - Keep routing automatic and media distributed. Direct/peer UDP is preferred; centralized media is fallback infrastructure, not the default topology. -- Every non-server endpoint uses one server-authoritative ordinary downstream capacity: default `2`, statically configurable as `1`, `2`, or `3`. Upstream receive is free, Browser role/UA/visibility does not create a separate tier, and clients cannot raise the deployment value. +- Every non-server endpoint uses one server-authoritative steady outbound media-copy capacity: default `2`, statically configurable as `1`, `2`, or `3`. A peer child or the Host publication consumes one slot; upstream receive is free, selected TURN replaces the same copy's transport, and SFU subscriber egress is accounted at the server. Browser role/UA/visibility does not create a separate tier, and clients cannot raise the deployment value. - The stable routing invariants are one authoritative upstream per Viewer, an acyclic active graph, exact room/session/share/revision/generation authorization, media-proven make-before-break, and bounded success or failure. -- SFU publication/subscription, selected TURN, fallback ordering, temporary overlap, and server-resource accounting must be decided as one route model before routing code changes. No older constant, experiment, or branch is authority for that decision. +- The accepted route model is one room controller with allocate/distribute, child reparent, and relay abdicate/drain operations. One Host publication may serve all SFU-fed Viewers; an SFU-fed Viewer may relay only with independent outbound proof; selected TURN changes an authorized edge transport and is not a topology node. A confirmed bad edge reparents only its child. A relay parent's own ingress failure first enters suspect/cordon and attempts local repair; only failed repair, independent downstream evidence, or explicit sender/resource failure drains its children. Host source failures use publication repair and child migration. Endpoint capacity, temporary overlap, SFU/TURN admission, exact fallback, media binding generations, and rollback are one bounded transaction model. - Ordinary peer ICE is STUN-only. Credentials, room secrets, candidate details, and diagnostic data remain private and narrowly scoped. - Screen audio offers 64/128/256 kbps sender ceilings, defaults to 128, and locks the choice during a share. These are configuration ceilings, not fidelity claims. - Current UI, presence, route labels, and diagnostics describe observed state only; they do not create route authority. @@ -26,10 +26,8 @@ Last updated: 2026-08-22 ## Current Priority 1. Keep this truth set and [the TODO ledger](./todo.md) concise and internally consistent. -2. Report the converged facts and TODO classification to the user and stop for confirmation. -3. After confirmation, produce one holistic routing model covering topology, transport, fallback, capacity, resource admission, and recovery; do not implement it yet. -4. After route-semantics confirmation, update the owning requirement/design/ADR and merge that truth into canonical `main`. -5. Rebuild only approved runtime changes from that exact `main`, validate cap `1/2/3` and route recovery, independently review, then deploy with rollback evidence. +2. Merge the accepted holistic routing truth into canonical `main` before any dependent runtime branch or cleanup. +3. Rebuild only approved runtime changes from that exact `main`, validate cap `1/2/3`, quality ownership, route recovery, and bounded resources, independently review, then deploy with rollback evidence. ## Working Rules diff --git a/docs/research/browser-screen-audio-quality.md b/docs/research/browser-screen-audio-quality.md index 4dfd862..34ff829 100644 --- a/docs/research/browser-screen-audio-quality.md +++ b/docs/research/browser-screen-audio-quality.md @@ -341,7 +341,8 @@ explicit encoded-track contract and need a focused compatibility check before claiming the same result. At the default ceiling, audio contributes at most 128 kbps RTP payload per -active outbound edge, or 256 kbps for the current two-child endpoint cap. The +active outbound edge, or 256 kbps for the configured two-slot endpoint example. +The endpoint cap itself follows the current route policy. The explicit highest preset doubles those bounds to 256/512 kbps, excluding RTP/SRTP/UDP/IP overhead. A Host SFU publication contributes one such ingress; each root/peer forward still sends its own copy. This is small beside the diff --git a/docs/research/built-in-peer-ice-turn.md b/docs/research/built-in-peer-ice-turn.md index 0e49753..e8cc7f2 100644 --- a/docs/research/built-in-peer-ice-turn.md +++ b/docs/research/built-in-peer-ice-turn.md @@ -91,11 +91,14 @@ source boundary; fixed counts and ordering remain held: - one in-memory negotiating attempt bound to room/share generation, viewer and parent sessions, route revision, the replaced connection, and a server-generated new connection identity; -- at most one `peer-selected` lease per room across negotiating and answered - states, with the separate Host-to-SFU ingress attempt excluded from that - last-mile cap; negotiation is serialized with other soft migrations, while an - answered exact lease is active transport and may coexist with an unrelated - soft migration under route carry revalidation; +- one-use grants are bound to the logical edge, both endpoint sessions, route + revision, and connection generations; independent edges may have independent + grants under deployment-wide TURN admission. The exact release used one + `peer-selected` lease per room (excluding Host ingress), but that release + quota is historical rather than a current topology rule; negotiation remains + serialized only when route write sets conflict, while an answered exact lease + is active transport and may coexist with an unrelated migration under route + carry revalidation; - coordinated parent and child rebuild for exactly that edge, using relay-only ICE only on the new connection; - revalidation at grant, rebuild, ready, failure, timeout, route change, diff --git a/docs/research/low-server-media-routes.md b/docs/research/low-server-media-routes.md index 8d133e8..a90f010 100644 --- a/docs/research/low-server-media-routes.md +++ b/docs/research/low-server-media-routes.md @@ -3,9 +3,9 @@ - Research date: 2026-08-22 - Scope: one broadcaster, explicit admission up to sixteen trusted viewers, low latency, and bounded host media fanout -- Status: research and dated route evidence. Current invariants are in - [ADR-0005](../adr/0005-automatic-hybrid-media-routing.md); unresolved transport - and resource design is in [the TODO ledger](../todo.md). +- Status: research and dated route evidence. Current invariants and assisted + transport roles are in [ADR-0005](../adr/0005-automatic-hybrid-media-routing.md); + runtime migration and validation are in [the TODO ledger](../todo.md). ## Historical Candidate Route Ladder @@ -97,8 +97,9 @@ both remain active, that may be `B_HIGH+B_LOW`, not one root bitrate. The SFU-root row, not full-room SFU, is the retained fallback shape. For equal representations, `B_pub = B` and `sum(B_i) = R*B`. Publisher-to-SFU and every SFU-to-root subscriber transport are independent ICE connections and may use a -separately deployed LiveKit transport. Ordinary descendants are STUN-only; only -one controller-selected exceptional edge may later use independent coturn. If a +separately deployed LiveKit transport. Ordinary descendants are STUN-only; +selected coturn is issued only for an exact authorized edge or Host-SFU ingress. +If a publisher leg separately uses LiveKit TURN, retain host upload `B_pub`, TURN ingress `B_pub`, TURN egress `B_pub`, and SFU ingress `B_pub` as distinct interface/service traffic. A relayed root leg likewise adds TURN @@ -500,11 +501,11 @@ Screener later implements application E2EE and key distribution. That accepted tradeoff remains visible in deployment and UI claims. A peer simultaneously re-publishing its received stream to the SFU and serving -peer children is an unresolved route-model alternative. A browser relay would -decode and re-encode, and publication ownership adds another failure domain; the -holistic model must compare that cost with a single authoritative source -publication. No global score, continuous optimizer, geography, IP, UA, or -self-reported capability chooses these routes. +peer children is outside the current product. A browser relay would decode and +re-encode, and publication ownership would add another failure domain; the +current model uses one authoritative Host publication. No global score, +continuous optimizer, geography, IP, UA, or self-reported capability chooses +routes. On today's unicast Internet, a design cannot maximize all three of these for more than one viewer: @@ -526,8 +527,8 @@ lost. | Bounded browser relay DAG | Host and each Web relay emit at most the configured endpoint cap | Ordinary browser, but every relay decodes and re-encodes and adds a hop | Admission defaults to eight and permits 1-16; representative resource/quality gate remains eight viewers | | Native shared-encode host | Host targets one encode for standard WebRTC edges | libwebrtc public-API proxy risk spike, with Pion as fallback | Research evidence; still pays per-edge upload | | Native volunteer encoded-RTP relay | Each volunteer forwards one encoded copy | Native install, RTP/RTCP forwarding, packaging, and opt-in relay policy | Research only; no current product authorization | -| SFU service | SFU emits authorized subscription copies | Service pays measured egress; an authoritative publisher supplies media | Functional single-root evidence exists; topology and admission remain pending | -| Additional server-assisted paths | SFU/TURN emits authorized copies | Additional central ingress, egress, or allocation cost | Resource-admission input; exact topology remains pending | +| SFU service | SFU emits authorized subscription copies | Service pays measured egress; an authoritative Host publisher supplies media | Functional evidence exists; accepted topology/admission still needs runtime validation | +| Additional server-assisted paths | SFU/TURN emits authorized copies | Additional central ingress, egress, or allocation cost | Accepted resource-admission input; validate each bounded path at release | | SVC plus multiple trees | Peers emit striped layer copies across several trees | Layer scheduling, reassembly, redundancy, and more churn state | Separate conditional spike; target endpoint upload near `B` | | Network coding | Peers or servers emit coded blocks | Generations, buffering, decoding, integrity, and a custom media plane | Trace/FEC spike only; optimize loss recovery, not clean bandwidth | | MoQ | Publishers and MoQ relays emit object copies | New transport, packaging, player, relay, and auth stack | Optional central-fallback benchmark; still pays server egress | diff --git a/docs/research/native-shared-encode-sender.md b/docs/research/native-shared-encode-sender.md index dcc8dd3..89c8f58 100644 --- a/docs/research/native-shared-encode-sender.md +++ b/docs/research/native-shared-encode-sender.md @@ -260,7 +260,9 @@ a `HIGH` ceiling and built-in BWE selects from one shared `HIGH+LOW` pair. App evidence affects topology eligibility and diagnostics, not ordinary layer selection. `LOW` may already be active, and idle stop depends on the resource gate. The active representation/layer limit is two, never one per viewer; the -host still has at most two downstream media edges. +the host follows the configured non-server outbound media-copy capacity; the +two-edge result in this experiment is a historical configuration, not a fixed +policy. If a qualified hardware/power-efficient `LOW` path is unavailable or its measured encoder/CPU/GPU game or upload load is unacceptable, `LOW` fails closed diff --git a/docs/status.md b/docs/status.md index 058e083..de14b4e 100644 --- a/docs/status.md +++ b/docs/status.md @@ -14,16 +14,15 @@ This is the current execution index. Git history owns completed timelines; [veri ## Current Source - Canonical source is the clean `main` branch; auxiliary branches and worktrees do not own current truth. -- The accepted endpoint direction is one ordinary downstream cap for every non-server endpoint: default `2`, static `1/2/3`, upstream receive free, and no Browser/UA/visibility tier. +- The accepted endpoint direction is one steady outbound media-copy cap for every non-server endpoint: default `2`, static `1/2/3`, a peer child or Host publication consumes one slot, upstream receive is free, selected TURN replaces the same copy's transport, and no Browser/UA/visibility tier exists. - Automatic P2P-first routing, one active upstream, an acyclic graph, exact identity/generation authority, media-proven make-before-break, and bounded failure remain accepted. -- SFU/TURN placement and accounting are not yet accepted as a complete model. No routing runtime candidate may be merged or deployed until that model is reconciled and written to the owning truth. +- The holistic assisted-route model is accepted: one controller performs allocation, child reparenting, and relay abdication/drain. One Host publication serves SFU subscriptions; TURN is an exact edge transport, not a topology node. Endpoint cap, transition overlap, media-binding generations, and independent SFU/TURN admission are governed by one bounded transaction and remain runtime validation work. ## Current Milestone -1. Converge active constraints, requirements, design, ADR, memory, status, and [the TODO ledger](./todo.md) without retaining obsolete-policy commentary. -2. Report the concrete done/obsolete/decision-needed/unfinished classification and organized workspace state to the user, then stop. -3. After confirmation, write one holistic routing proposal and stop again for route-semantics confirmation. -4. Only then update and merge accepted route truth, rebuild the cap/accounting implementation from exact canonical `main`, validate, review, and deploy. +1. Merge the accepted route truth across requirements, design, ADR, memory, status, and [the TODO ledger](./todo.md). +2. Rebuild the cap/accounting runtime from exact canonical `main`, preserving safe identity, media-proof, and rollback behavior. +3. Validate cap `1/2/3`, child and parent quality ownership, constrained Host/SFU/TURN paths, bounded resources, and rollback before deployment. ## Active Boundaries @@ -34,4 +33,5 @@ This is the current execution index. Git history owns completed timelines; [veri ## Next Stop -Wait for the user's review of this convergence before starting route modeling, another TODO, candidate integration, cleanup, or deployment. +Complete the truth merge and canonical-workspace cleanup first. Runtime changes and +remaining TODOs start only from the resulting clean `main`. diff --git a/docs/todo.md b/docs/todo.md index edb486c..8c365a7 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -6,11 +6,11 @@ Only items in **Now** are executable after their stated decision gate. A branch ## Now -1. **Approve one holistic routing model.** After the user confirms this truth/TODO convergence, write a held proposal covering ordinary topology, SFU/TURN transport roles, fallback ordering, temporary overlap, resource admission, recovery, and the user-supplied constrained-network scenarios. Preserve the accepted endpoint cap and route safety invariants, then stop again for route-semantics confirmation. -2. **Migrate routing from accepted truth.** After route semantics are confirmed and their owning truth is merged, rebuild the scoped runtime change from exact canonical `main`: every non-server endpoint defaults to ordinary downstream cap `2`, deployment accepts `1/2/3`, upstream receive is free, and no Browser tier exists. Replace policy magic numbers with named configuration and shared invariants. -3. **Validate and release.** Run focused state-machine tests, cap `1/2/3` topology and recovery matrices, independent routing review, build/full gates, bounded production preflight, deployment, postflight, and rollback verification. +1. **Merge accepted routing truth.** Reconcile the accepted holistic model across requirements, design, ADR, memory, status, and this ledger, then merge that truth into canonical `main` before dependent implementation. +2. **Migrate routing from accepted truth.** Rebuild the scoped runtime change from that exact `main`: every non-server endpoint defaults to steady outbound media-copy cap `2`, deployment accepts `1/2/3`, a peer child or Host publication consumes one slot, upstream receive is free, selected TURN replaces the same copy's transport, and no Browser tier exists. Replace split topology/route/transport authority and policy magic numbers with one controller, one committed ingress graph, typed reservations, and shared invariants. Keep `EdgeHealth` child-scoped; let the quality owner supply finite correlated windows and independent-edge corroboration for `RelayEligibility`, with no IP-ranking or room-wide score. +3. **Validate and release.** Run focused state-machine tests, child-vs-parent quality ownership, cap `1/2/3` topology and recovery matrices, constrained Host/SFU/TURN paths, independent routing review, build/full gates, bounded production preflight, deployment, postflight, and rollback verification. -The deployed #164/#173/#175/#183 routing mechanisms are inputs to these tasks, not four separate TODOs. Keep their safe generation, authorization, make-before-break, and media-proof behavior while reconciling capacity and fallback accounting. +The existing route implementation is an input to these tasks. Preserve its verified generation, authorization, make-before-break, and media-proof invariants while migrating capacity and fallback accounting. ## Product Decisions Needed diff --git a/docs/verification-status.md b/docs/verification-status.md index 68d43be..69b781b 100644 --- a/docs/verification-status.md +++ b/docs/verification-status.md @@ -12,8 +12,8 @@ Capacity and routing entries below describe the exact tested source or release o - Production runs exact `9461e207af62b4f38f6b7a8aa16f8beb49e0e4ee`, release `9461e20`, with `21d5cd9f7139` retained for rollback. - Local/public health return 200; `screener`, LiveKit, coturn, and nginx are active; `screener` reports zero restarts. SQLite v3 has five rooms, SHA-256 `aef724ae52c4107be8ec8791e72f8dcaa11b0ec849fb432d022e2cfb9cfe0974`, owner `screener:screener`, and mode 0600. -- Room admission defaults to eight and accepts explicit limits from one through sixteen. The endpoint-cap environment is unset, so the deployment default yields Host2/ordinary Browser Viewer1; active, provisional, selected, and Host SFU publication edges count. Ordinary peer ICE stays STUN-only, SFU roots stay <=2, and selected-edge UDP authority is bounded to one `peer-selected` lease per room excluding Host ingress. -- The deployed SFU path enforces the controller/token root cap, Host-publication edge accounting, zero-child assignment/subscriber retention, and `dynacast: false`. The tagged local gate covers these invariants; real shaped LiveKit packet flow, BWE downshift/recovery, resources, and root-with-children behavior remain unverified. +- Room admission defaults to eight and accepts explicit limits from one through sixteen. In the exact release, the unset endpoint-cap environment yielded Host2/ordinary Browser Viewer1, active/provisional/selected overlays were charged, SFU roots were limited to two, and one `peer-selected` lease was allowed per room excluding Host ingress. Those are release facts only. The accepted target is steady endpoint cap `2` with static `1/2/3`, Host-publication slot accounting, same-edge TURN transport, and independent bounded server admission. +- The deployed SFU path enforces the release's controller/token root cap, Host-publication edge accounting, zero-child assignment/subscriber retention, and `dynacast: false`. The tagged local gate covers those release invariants; the accepted route migration still requires real shaped LiveKit packet flow, BWE downshift/recovery, resource, and root-with-children validation. - Share audio presets and local-only Host SFU, codec/encoder, A/V, and selected-candidate diagnostics are deployed. Their target-browser field availability and actual media values remain unverified. - The bounded pre-share self-check, privacy-safe local JSON export, signaling-partition recovery, and peer-quality MBB are deployed. An ordinary Viewer with a peer/SFU upstream or the Host may own a separately budgeted provisional child; sibling MBB remains open. Real-route behavior remains part of the routing gate. - Earlier exact-`16f6eab` evidence proves active Host ingress only; it does not prove the current release, initial Viewer ingress, healthy reselection, last-mile quality, or TURN performance. @@ -32,8 +32,8 @@ Capacity and routing entries below describe the exact tested source or release o - Routing: verify current-release initial ingress, `peer-selected` expiry/failure, corroborated bad-relay reassignment, one-root healthy SFU reselection, silent partitions, and bounded failure on real browser/media paths. Silent control partitions may wait 30 to 60 seconds for heartbeat detection before the default five-second grace. - Quality: production startup blur remains unclassified. One same-`balanced` local A/B retained route/PC/SSRC/track while 720p rose to 1080p, but natural ramp prevents causal, production, or SFU conclusions. Correlate Host capture/encode/send with Viewer receive/decode before changing policy. -- Relay resources: production `9461e20` is Host2/ordinary Browser Viewer1 and rejects Viewer child2 regardless of advertised capacity. The next endpoint-cap and corroboration contract is held, while CPU/GPU/upload and heterogeneous-network measurements remain optional evidence for later tuning rather than authority to change endpoint-cap policy. -- SFU: production `9461e20` deploys ordered `q,h`, roots <=2, and Dynacast off. Root count and accounting are held; real shaped packet flow, BWE downshift/recovery, resource cost, and root-with-children behavior remain unverified evidence boundaries. Use Linux `tc` or an authorized public canary for per-leaf shaping, never CDP throttling. +- Relay resources: production `9461e20` is Host2/ordinary Browser Viewer1 and rejects Viewer child2 regardless of advertised capacity. This is a dated implementation divergence; the accepted endpoint-cap and corroboration contract is in ADR-0005 and awaits runtime migration. CPU/GPU/upload and heterogeneous-network measurements remain evidence, not authority to change that contract. +- SFU: production `9461e20` deploys ordered `q,h`, roots <=2, and Dynacast off. Those are release facts; the accepted model has no fixed root-count or room-wide lease rule and still needs real shaped packet flow, BWE downshift/recovery, resource, and root-with-children validation. Use Linux `tc` or an authorized public canary for per-leaf shaping, never CDP throttling. - Audio: verify the 64/128/256 kbps Share settings, negotiated/observed bitrate, audible music quality, route changes, and game A/V synchronization. Bitrate settings are ceilings, not quality proof. - Observability: the UI and local stats plumbing are deployed, including identity-bound relayed-detail retention and export freshness guards, but target-browser availability and actual values remain unverified for Host SFU sender evidence, codec/fmtp and encoder implementation/power efficiency, media-source and encoded FPS, A/V playout/jitter/concealment, selected candidate endpoints, and bounded same-pair STUN-response observation. These fields diagnose and have no recovery authority; they do not by themselves prove hardware encode, synchronization quality, or route quality. - Mobile: Android/iOS remain Viewer-only. Autoplay, rotation, iOS lock-screen/page reclamation, background reconnection, and network changes need device evidence. AirPlay/system mirroring is Viewer-local output, not a portable in-app WebRTC output contract. diff --git a/docs/方案设计.md b/docs/方案设计.md index 73e9d9f..cd363d3 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -2,11 +2,11 @@ ## 2026-08-18 14:45 -状态:首个 PoC 基线及其已接受扩展的当前设计。Production 普通 peer ICE 保持 STUN-only,并已部署自动 route controller;当前路由不变量由 ADR-0005 持有,SFU/TURN 整体资源模型定案后再迁移实现。精确 deployed behavior 只由 deployment 文档记录。 +状态:首个 PoC 基线及其已接受扩展的当前设计。Production 普通 peer ICE 保持 STUN-only,并已部署自动 route controller;完整 assisted-route 语义由 ADR-0005 持有,运行时迁移尚未部署。精确 deployed behavior 只由 deployment 文档记录。 ## 目标与边界 -产品面向一名分享者和小范围可信朋友的低延迟游戏画面分享。观看端使用普通桌面或手机浏览器;每条普通媒体边独立 ICE 并优先 direct/peer UDP。所有非服务器端点共享一个服务端权威 downstream capacity,默认 `2` 且只允许静态配置 `1`、`2` 或 `3`;upstream receive 免费,角色、浏览器、UA、设备和页面可见性不改变该值。活动媒体图无环,每名 Viewer 同时至多一个 active upstream。SFU/TURN publication、subscription、transport、server-resource、temporary overlap 和 exact fallback sequence 仍待整体定案。 +产品面向一名分享者和小范围可信朋友的低延迟游戏画面分享。观看端使用普通桌面或手机浏览器;每条普通媒体边独立 ICE 并优先 direct/peer UDP。所有非服务器端点共享一个服务端权威稳态 outbound media-copy capacity,默认 `2` 且只允许静态配置 `1`、`2` 或 `3`;peer child 或 Host publication 各消耗一份,upstream receive 免费,selected TURN 只替换同一份 edge 的 transport,角色、浏览器、UA、设备和页面可见性不改变该值。活动媒体图无环,每名 Viewer 同时至多一个 active upstream。SFU subscriber egress、TURN allocation、temporary overlap 和 exact fallback sequence 由 ADR-0005 的整体模型统一定义。 本阶段必须形成完整的最小纵向链路: @@ -50,7 +50,7 @@ flowchart LR V -.地址发现.-> T ``` -每个观看者的候选检查和最终路径互不影响;健康 direct/peer 子树不会因另一条弱路径被全房迁移。Node 房间服务不复制、解码或转发媒体。SFU/TURN 只按待定模型中的明确 owner、edge 与 deployment budget 参与,不能由图中的连线推导 fallback 顺序或 server fanout。 +每个观看者的候选检查和最终路径互不影响;健康 direct/peer 子树不会因另一条弱路径被全房迁移。Node 房间服务不复制、解码或转发媒体。SFU/TURN 只按 ADR-0005 的明确 owner、edge 与 deployment budget 参与,不能由图中的连线另行推导 fallback 顺序或 server fanout。 ## 代码布局 @@ -184,7 +184,7 @@ Viewer 本地连接详情只从当前 route 的唯一 inbound screen video/audio 质量自适应采用 ADR-0007 的 built-in-first 路径隔离双表示,而不是全房间按最差链路降档。Direct/peer 保留每条 PeerConnection 的 stock WebRTC GCC。每条 SFU path 从 `HIGH` ceiling 开始;优先发布共享 `HIGH+LOW` 并让 LiveKit BWE 独立降层和恢复。若该 gate 通过,Screener 不实现媒体层 selector。应用证据只决定下述 parent/leaf 拓扑资格与诊断。`LOW` 可以常驻;关闭空闲层只是资源优化。active representation/layer 数量硬限制为二,不能按 viewer 增长。标准浏览器不保证跨 PeerConnection 共用一个物理编码器,inactive 层也不保证释放 GPU;实际层数、实例数、CPU/GPU、游戏 FPS/p1 low、区间 encode cost、上传与释放行为必须实测。若没有合格 hardware/power-efficient 路径或额外表示破坏游戏/上传预算,则 fail closed,保留健康路径并明确显示弱路径降级或失败。 -质量证据保持 edge-local,只能影响证据覆盖的 parent/child authority,不能形成全房综合分数。Current source 已有 Viewer 与 Host provisional-parent、媒体证明后 promotion、失败保留旧边、坏 relay corroboration 和 bounded quarantine 等机制;整体模型必须统一 candidate eligibility、两 child 关联证据、sibling migration、migration mutex、capacity reservation 与 fallback 交互。无论最终策略如何,prepare/commit/rollback 都保留 exact session/share/revision/connection identity,ICE connected 不能替代 live track、正 RTP 和 decoded-frame progress。 +质量证据保持 edge-local,只能影响证据覆盖的 parent/child authority,不能形成全房综合分数。现有实现包含 Viewer 与 Host provisional-parent、媒体证明后 promotion、失败保留旧边、坏 relay corroboration 和 bounded quarantine 等可复用机制;整体模型统一 candidate eligibility、质量 owner 的独立关联证据、sibling migration、migration mutex、capacity reservation 与 fallback 交互。无论策略取值如何,prepare/commit/rollback 都保留 exact session/share/revision/connection/media-binding identity,ICE connected 不能替代 live track、正 RTP 和 decoded-frame progress。 状态决策必须关联同一时间区间和媒体代次的三段证据: @@ -202,9 +202,9 @@ Viewer 可通过已鉴权、绑定当前房间/路径代次的控制面报告需 SVC 的标准能力门禁已经静态关闭为 `no-go-web-svc-cross-path-hardware-contract`。WebRTC-SVC 只配置单个 outgoing sender,direct/peer receiver 没有对应的按层选择 API,独立 PeerConnection 之间也没有 shared-encode 契约;requested/applied codec/`scalabilityMode`、Media Capabilities `powerEfficient` 与 RTCStats encoder 字段不能证明编码器一定是硬件。Pinned LiveKit 虽能在 SFU 内为每个 subscriber 设置最大空间/时间层请求,但实际选择依 codec/selector 而异:server 1.13.5 只为 VP8 安装 temporal selector,当前 H.264 `q,h` simulcast 只有空间层选择。Client 2.22.0 还会把 screen-share SVC 强制成三层 temporal-only `L1T3`,既没有低分辨率 base,也超过最多两层的门槛;这种 SFU 选层也不延伸到 direct/peer descendants。当前静态结论不运行 browser harness。未来只能由能明确锁定硬编、最多两层、跨 direct/peer/SFU 复用同一输出且通过游戏性能/延迟门槛的 native sender 以新决策重开。普通非分层编码包不能只靠转发生成另一档画质,实现路径差异只能用第二表示、合格的 scalable layers 或 relay/SFU 转码。 -质量控制不放宽 ordinary media topology 预算:所有非服务器端点继续受统一静态 downstream capacity 约束;native relay 只转发选定表示的编码包,不解码重编码。后续双树或条带化必须另行验收,不能绕过当前 graph、identity 和 capacity invariant。 +质量控制不放宽稳态 outbound media-copy 预算:所有非服务器端点继续受统一静态 capacity 约束;native relay 只转发选定表示的编码包,不解码重编码。后续双树或条带化必须另行验收,不能绕过当前 graph、identity 和 capacity invariant。 -应用不得稳定保留经交叉证据确认的 `FALLBACK` parent。SFU BWE 可能先自主 downshift;该观测进入 `suspect`,跨有界窗口确认后再迁走 children 并撤销 capacity,不能承诺 packet-level 先迁后降。Root-with-children gate 必须限制 descendants 的短暂影响。计划或显式质量 fallback 只在内建路径失败后考虑,并先在 assignment/connection generation guard 下迁走全部 children;失败或 stale 操作保持原 assignment 与 quality。更长恢复窗口和 cooldown 后才能重新发布 capacity。Viewer 自报不能单独改变拓扑。详细 wire 由后续 routing owner 持有。 +质量证据形成两条正交状态:`EdgeHealth(parent, child, generation)` 控制 child-scoped reparent,`RelayEligibility(parent)` 仅适用于 relay parent,控制 `eligible -> suspect -> draining -> ineligible`。Viewer 的 C 证据和 parent 对同一 edge 的 B 证据必须按 exact route identity(room/share、双方 session、assignment/connection/media-binding generation、route revision,及适用时 publication generation)和 sequence 相关;同一 edge 的 C+B 不直接触发 parent drain。Relay parent 自身 ingress 失败先触发本地 reparent 或 branch-preserving `replaceIngress` 并进入 `suspect`;质量 owner 根据不同 downstream edges 的当前可观察边和端点容量确认 parent 方向失效,修复失败、明确 sender/resource failure 或独立 downstream edges 确认转发仍失效时才 drain。Host source 使用 publication repair/child migration。drain 停止新 child,仍健康的 ingress 保留,失效 ingress 在同一 target 中替换或移除,迁移有目标的 children,并让无目标者进入有界等待或失败;更长稳定证明和 cooldown 才恢复 `eligible`。同一个 controller 每房间只提交一个 target revision,按 hard failure、confirmed parent drain、single-edge soft reparent 的顺序合并事件;drain 吸收覆盖范围内的 child intent,成功的 reparent 只使同一旧 logical edge 的 pending evidence 失效,其他仍匹配且未过期的 sibling evidence 保留。 标准表示能力排在任何自定义双表示媒体实现之前。当前 Web P2P simulcast 缺少 receiver 选 RID 与跨 PeerConnection shared-encode 契约;当前 Web/LiveKit SVC 也缺少 direct/peer 独立选层和可移植硬件保证。Production 的 exactly-two screen-share simulcast 与 `dynacast: false` 只是实现和质量实验事实,不证明 root topology、publication accounting 或 subscriber model。具体表示门槛由 ADR-0007 和[实时质量调研](./research/realtime-quality-adaptation.md)持有,不得绕过 stock WebRTC congestion control。 @@ -235,27 +235,28 @@ candidate 预发给 participant、房间或普通 peer edge。HTTPS/WSS 始终 `PEER_ASSISTED_MEDIA` 启用同一个 process-wide route controller,但每个房间拥有 独立的 assignment、session、revision、generation、connection 和 capacity 状态。 -所有非服务器端点读取同一个 deployment downstream capacity,默认 `2`,只接受 -`1`、`2` 或 `3`;upstream receive 免费。服务端是权威,客户端广告只能收紧, -不能按 Host/Viewer、浏览器、UA、设备或页面状态放宽或分叉。 +所有非服务器端点读取同一个 deployment steady outbound media-copy capacity,默认 +`2`,只接受 `1`、`2` 或 `3`;peer child 或 Host publication 各消耗一份,upstream +receive 免费,committed selected TURN 不重复计数。服务端是权威,客户端广告只能 +收紧,不能按 Host/Viewer、浏览器、UA、设备或页面状态放宽或分叉。 每个 candidate 和异步结果都必须绑定 exact room、share generation、endpoint -sessions、route revision、assignment generation、connection identity,以及存在时的 -publication generation。迁移保留旧 active media;只有新 candidate 的 live track、 +sessions、route revision、assignment generation、connection identity、media-binding +generation,以及存在时的 publication generation。迁移保留旧 active media;只有新 candidate 的 live track、 正 RTP 与 decoded-frame progress 都属于当前代时才原子 commit。ICE `connected`、token 签发或 assignment 本身都不是媒体证明。失败、超时、stale identity 或授权撤销销毁 candidate,保留或恢复最后一个有效 route。 -SFU 与 TURN 是可选、有界、独立验收的 server-assisted resources。以下内容必须在 -ADR-0005 的整体模型中同时定案,本文不预设答案: - -- SFU publisher 位于 Host、relay parent 或其他明确层级时的 owner 与 generation; -- subscriber 是分布式 root、受限 leaf 或其他 endpoint 时的准入; -- selected TURN 承载 ordinary edge、SFU ingress 或其他 edge 的授权边界; -- endpoint ordinary capacity、server ingress/egress、SFU、TURN 与 provisional - overlap 的分别记账;以及 -- Host、relay parent、leaf 受限时的 exact fallback、downgrade、sibling migration - 和 bounded failure sequence。 +SFU 与 TURN 是可选、有界的 server-assisted resources。一个 room controller +统一负责 allocate/distribute、child reparent 和 relay abdicate/drain:Host +拥有唯一 authoritative publication,所有 SFU-fed Viewer 订阅它;具备独立出站 +证明的 SFU-fed Viewer 可以继续 relay,严格受限 Viewer 保持 leaf。TURN 只替换 +授权 logical edge 或 Host-SFU ingress 的 transport,不是拓扑节点。现有 edge +优先保持 direct/STUN,必要时使用 exact selected TURN,再按需要切换到 SFU; +Host 无 direct first-level path 时优先一条可经 TURN 的 Host-SFU publication。 +端点容量、SFU/TURN ingress/egress、临时 overlap、media binding generation、 +server admission 和 bounded failure 在同一 reserve/proof/commit/rollback +事务中记账。 短期 server-assisted credential 必须严格绑定当前 route identity、只存内存、受 TTL 与 quota 限界,并且不进入 URL、日志、browser storage、room row 或 SQLite。Coturn @@ -270,7 +271,7 @@ identity、media-proof 和 bounded-recovery 不变量的 scoped code 与 tests 每两秒在本地采集一次 `getStats()`。首版不上传原始统计。折叠详情可在产生该连接的本机显示 selected local/remote candidate 的 `address`/`port`,但浏览器未暴露时保持未知;地址不得进入信令、Viewer C、日志、诊断导出、持久化或路由/质量决策。在 peer 边中它只是当前连接端点,在 SFU 边中通常是 SFU 端点,不能标成参与者的权威公网 IP。 -Host 与 relay Viewer 复用同一个纯 presentation reducer 保存远端 `ViewerQualityEvidence`。代际 identity 是 viewer、parent、connection 与 route revision;只有同 identity 且 sequence 严格递增才可承接旧字段,其他输入从新 evidence 重建。每个非空 metric 保存自己的本地观测时间并在五秒边界清空,页面 timer 总是指向最早边界;整块详情不因某个采样缺字段而卸载。`new`/`connecting`/`disconnected` 保留同 identity shell 但立即标为非 fresh,`failed`/`closed` 或 identity/session/share/route 变化清空。任何依赖当前状态的消费者只读取 fresh presentation;该层不增加 wire 字段、服务端状态或 `mediaGeneration`。 +Host 与 relay Viewer 复用同一个纯 presentation reducer 保存远端 `ViewerQualityEvidence`。代际 identity 是 exact route identity(room/share、viewer/parent、assignment/connection、route revision 和当前可用的 media-binding generation,及适用时 publication generation);只有同 identity 且 sequence 严格递增才可承接旧字段,其他输入从新 evidence 重建。每个非空 metric 保存自己的本地观测时间并在五秒边界清空,页面 timer 总是指向最早边界;整块详情不因某个采样缺字段而卸载。`new`/`connecting`/`disconnected` 保留同 identity shell 但立即标为非 fresh,`failed`/`closed` 或 route identity 变化清空。任何依赖当前状态的消费者只读取 fresh presentation;该层不新增 wire 字段或服务端 route authority。 质量诊断必须能按顺序区分:捕获源设置、sender 实际参数、CPU 或带宽 `qualityLimitationReason`、selected candidate/TURN 路径、outbound 与 inbound 差异,以及接收端 dropped/freeze。当前有界实现显示 sender 参数的 requested/applied 读回;同一个非 `none` 原生 `qualityLimitationReason` 连续出现三个采样周期后显示单条原因提示,原因变化、缺失或恢复为 `none` 即重新计数。这只是去抖后的观察,不组合评分、不触发调档或迁移。完整跨路径分类仍由后续真实矩阵验收;首轮使用浏览器 WebRTC internals 导出和一份脱敏 run manifest,不新增服务端遥测。 @@ -347,8 +348,8 @@ Host 与 relay Viewer 复用同一个纯 presentation reducer 保存远端 `View 2. 完成 RoomStore、建房 API、WebSocket 鉴权和定向信令。 3. 完成屏幕捕获、逐观看者 PeerConnection、观看播放和手动质量档。 4. 完成连接恢复和 stats 面板;随后以 STUN-only snapshot 替换旧全房 TURN credential/refresh wire。 -5. 完成 automatic peer/SFU controller,并让 room `1` 仅作为历史 smoke fixture。 -6. Server-assisted transport 的下一次功能验收在整体路由模型接受后定义;ordinary STUN-only、SFU 与 TURN 的配置和 rollout 保持独立边界。 +5. 现有 automatic peer/SFU controller 作为 legacy deployed input;按 ADR-0005 迁移其 server-assisted route semantics,并让 room `1` 仅作为历史 smoke fixture。 +6. 按 ADR-0005 的整体路由模型迁移 server-assisted transport;ordinary STUN-only、SFU 与 TURN 的配置和 rollout 保持独立边界。 7. Site access、room-scoped Viewer grant 与 SQLite v3 已部署,当前 wire 为 `screener-v5`;以后不兼容的 wire/schema 迁移仍须原子切换并保留迁移前备份。用户名/roster 保持独立。 8. Windows Native fixed-`HIGH` 保持可选且不改变 Web 默认;移动端保持 Viewer-only 且不单独分配 relay capacity。Viewer-to-TV 只采用已验证的平台本地输出,不改变媒体路由。 9. 屏幕音频在一个原子 Web slice 中声明 `sdp-transform`/types direct dependencies,加入 answer helper 与 `ViewerPeer` wiring,并同步设置 SFU stereo options、focused tests 和 owning docs;高级设置只增加 64/128/256 kbps 离散 sender ceiling,不增加 Web Audio、第二表示或自定义降级算法。 @@ -357,17 +358,19 @@ Host 与 relay Viewer 复用同一个纯 presentation reducer 保存远端 `View ## 自动路由设计 -服务端维护每个房间唯一的版本化 route owner。候选 parent 可以形成图,但 committed media graph 必须无环;Host 没有 upstream,每名 Viewer 同时只有零或一个 active upstream。加入、容量释放、节点离开、当前边失败和已接受的 path-health evidence 才能触发一次有界重选,未受影响分支保持 sticky。 +服务端维护每个房间唯一的版本化 route owner。候选 parent 可以形成图,但 committed media graph 必须无环;Host 没有 upstream,每名 Viewer 同时只有零或一个 active upstream,且每个 active Viewer 必须沿 peer edges 到达 Host 或沿当前 authoritative Host publication 到达 source。加入、容量释放、节点离开、当前边失败和已接受的 path-health evidence 才能触发一次有界重选,未受影响分支保持 sticky。 -所有非服务器端点共享一个 server-authoritative ordinary downstream capacity:默认 `2`,只允许静态配置 `1`、`2` 或 `3`,upstream receive 免费。客户端 capability 只能收紧该值,不能按角色、浏览器、UA、设备或页面可见性建立另一套 policy。代码通过一个 named config 和 shared guard 执行,不在 Host、Viewer、active、recovery 或 migration 分支重复 magic number。 +所有非服务器端点共享一个 server-authoritative steady outbound media-copy capacity:默认 `2`,只允许静态配置 `1`、`2` 或 `3`。peer child 或 Host publication 各占一份,upstream receive 免费,committed selected TURN 只替换同一份 edge transport;SFU subscriber egress 与 TURN allocation 使用独立 server admission。客户端 capability 只能收紧该值,不能按角色、浏览器、UA、设备或页面可见性建立另一套 policy。代码通过一个 named config 和 shared guard 执行,不在 Host、Viewer、active、recovery 或 migration 分支重复 magic number。 普通 Web relay 把收到的 remote track 加入独立 downstream `RTCPeerConnection`,因此可能逐跳解码和重编码;浏览器不保证跨连接 shared encode。每条 ordinary child 是独立网络副本,实际 encode time、CPU/GPU、bitrate、loss、frame rate、depth 和 recovery 必须测量。 -迁移使用 exact room/share/session/revision/assignment/connection identity。旧 route 保持 active,candidate 在 active map 外准备;只有当前代 live track、正 RTP 与 decoded-frame progress 同时成立时才原子 promotion。失败、超时、代次变化或授权撤销销毁 candidate 并保留或恢复旧 route。Make-before-break 是否有 overlap 空间由统一端点容量和待定 server-resource model 同时决定。 +迁移使用 exact room/share/session/revision/assignment/connection/media-binding identity。旧 route 保持 active,candidate 在 active map 外准备;只有当前代 live track、正 RTP 与 decoded-frame progress 同时成立时才原子 promotion。失败、超时、代次变化或授权撤销销毁 candidate 并保留或恢复旧 route。Make-before-break 的 overlap 由统一端点容量、单个有界 `TransitionOverlapSlot` 和 typed server-resource admission 同时决定。 -SFU/TURN 的 publisher、subscriber、selected transport、central admission、resource accounting、temporary overlap 和 exact fallback sequence 仍是一个不可拆分的设计项。普通端点 child count 不能隐式成为 server cap,兜底也不能形成无界 whole-room fanout。Host、relay parent 或 leaf 受限时,只执行 ADR-0005 最终接受的分支;定案前不从旧 root 数、publication 公式或 lease 规则推导实现。 +SFU/TURN 的 publisher、subscriber、selected transport、central admission、resource accounting、temporary overlap 和 exact fallback sequence 已由 ADR-0005 作为一个不可拆分的设计项接受。普通端点 child count 不能隐式成为 server cap,兜底也不能形成无界 whole-room fanout。一个 Host publication 服务所有 SFU subscriptions;SFU-fed Viewer 只有在独立出站证据成立时才可 relay。TURN 只替换授权 edge 的 transport,Host 无 direct first-level path 时可作为单条 Host-SFU ingress 的 transport。 -ADR-0004 只保留 browser relay、re-encode 和测量门槛的历史证据。Production `9461e20` 是 rollback reference;新实现只按当前 invariant 迁移可复用的 exact-generation、media-proof 和 bounded-recovery 代码。 +质量事件按影响范围分工,而不是按谁先发现分工。Viewer 对自己 upstream 的 C 证据与 parent 对同一 edge 的 B 证据相关后,只触发该 child 的换父;不同 downstream edges 达到质量 owner 的独立 corroboration 门槛,或出现明确 sender/resource failure、修复失败的 relay-parent ingress 或独立 downstream edges 确认转发仍失效,才把 parent 标为 directional-ineligible 并退位排空。同一 edge 的 C+B 不等价于 parent corroboration。Host source 走 publication repair/child migration,不走 relay-parent reparent。节点同时是 child 和 relay parent 时,先用 reparent 或 branch-preserving `replaceIngress` 修复其自身 ingress、停止新 child;新 ingress 恢复下游时保留子树,否则由同一 controller 合并下游 drain。C+B 不产生第二个 controller 或第二个 revision。 + +实现迁移以当前 ADR-0005 invariants 为准;生产 release `9461e20` 仅是部署快照,代码迁移只保留仍满足当前 identity、media-proof 和 bounded-recovery 约束的部分。 ## 官方与参考资料 访问日期为 2026-08-18 至 2026-08-21: diff --git a/docs/需求理解.md b/docs/需求理解.md index 277659c..9a4bb5a 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -6,11 +6,11 @@ ### 当前路由不变量 -`PEER_ASSISTED_MEDIA=true` 是旗舰部署的单一总开关;每个房间独立保存 route revision、会话代次、失败状态和容量预算。系统优先使用 direct/peer UDP,普通 peer 连接保持 STUN-only。所有非服务器端点使用同一个服务端权威下游容量:默认 `2`,只允许静态配置 `1`、`2` 或 `3`;上游接收不消耗该预算,角色、浏览器、UA、设备或页面可见性不产生特例。 +`PEER_ASSISTED_MEDIA=true` 是旗舰部署的单一总开关;每个房间独立保存 route revision、会话代次、失败状态和容量预算。系统优先使用 direct/peer UDP,普通 peer 连接保持 STUN-only。所有非服务器端点使用同一个服务端权威稳态 outbound media-copy capacity:默认 `2`,只允许静态配置 `1`、`2` 或 `3`;peer child 或 Host 的唯一 SFU publication 各消耗一份,上游接收不消耗该预算,selected TURN 只替换同一份 edge 的 transport,角色、浏览器、UA、设备或页面可见性不产生特例。 构建一个面向熟人游戏场景的低延迟屏幕共享工具。分享者可以在私密房间内分享游戏、应用窗口或显示器画面及声音,多名朋友可以即时观看。中心服务负责房间、身份、确定性路由和建立连接;每名 Viewer 同时至多一个 active upstream,活动媒体图保持无环。切换必须绑定精确身份与代次,保留旧边直到新边出现当前代 RTP、解码帧和 live track,所有路线最终进入有效媒体、明确等待或有界失败。系统自动完成分配和恢复,用户不手动选择拓扑。分享端 Web 优先并允许后续 Electron/原生增强;观看端使用普通桌面或手机浏览器。 -SFU/TURN 的 publication、subscription、transport、server-resource accounting、临时 overlap 和具体 fallback 序列必须作为一个整体另行定案,不能从普通端点 child 数量推导。SFU 与 TURN 只能是有界兜底资源,不能把产品改成无界或 always-SFU 的整房转发。当前 PoC 的观看人数限制是实现事实,不是性能声明;任何扩容目标必须以真实资源、质量、深度和恢复矩阵另行验收。产品不承担公开直播或大规模观看。 +SFU/TURN 的 publication、subscription、transport、server-resource accounting、临时 overlap 和具体 fallback 序列由 ADR-0005 的整体模型统一定义,不能从普通端点 child 数量推导。SFU 与 TURN 只能是有界兜底资源,不能把产品改成无界或 always-SFU 的整房转发。当前 PoC 的观看人数限制是实现事实,不是性能声明;任何扩容目标必须以真实资源、质量、深度和恢复矩阵另行验收。产品不承担公开直播或大规模观看。 ## 功能需求 @@ -24,13 +24,13 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - 观看端本地音量:播放控制栏提供 `0%` 至 `100%` 音量条和静音按钮,只改变当前浏览器的媒体元素,不修改 Host 音轨、编码、房间状态或其他观看者。切流、换父和 ICE 恢复保留当前页面内的值;刷新后不持久化。静音保留上次非零音量,拖动到非零值自动取消静音。 - 本地昵称与房间成员:用户可设置一个只保存在当前浏览器同源 `localStorage` 的显示名,界面统一称为“昵称”;默认以只读文本显示,铅笔编辑按钮紧贴昵称,点击后才进入编辑。Viewer 顶部标题直接显示“{Host 名称} 的屏幕”,不得在下方重复一行 Host 名称。信令只在当前在线 socket 会话内保存和广播 NFC 规范化、最多 24 个 Unicode code points、拒绝控制/换行/双向覆盖符与孤立 surrogate 的纯文本标签,不写 SQLite 或日志。空值使用通用角色 fallback。Web Host 与 Viewer 都必须看到全部当前在线 Viewer 的权威总数与 roster,不能拿最多两条 Host 媒体直连数冒充在线人数;presence 更新也不得创建媒体连接。展示身份默认只显示名称;仅当房内出现同名时追加当前房间 `peerId` 的至少六字符短后缀,并只在同名项后缀碰撞时有界延长。显示名、后缀和 `clientId` 都不是账号、授权身份或全局 UUID,重复名称允许且不得参与授权、路由或质量决策。Web Host 与 Viewer 通过同一 opt-in participant roster 看到 Host 名称;Native 使用同一版本化鉴权 wire,但不发送 presence capability,也不接收 Web roster。 - 一对多观看:房间 admission 与端点下游容量分别配置,超额加入必须明确拒绝。Production 当前默认 8、显式接受 1 至 16;未来默认值和上限是独立产品决策,必须经过相应资源、质量、深度和恢复验收,不能从端点 cap 或一次短跑推导。 -- 端点下游容量:所有非服务器端点使用同一个服务端权威值,默认 `2`,只允许静态配置 `1`、`2` 或 `3`;上游接收不消耗该预算。客户端只能声明不高于部署值的可用容量,角色、浏览器、UA、设备类型和页面可见性不产生不同上限。active ordinary children 与获准的 provisional ordinary children 必须在切换全过程受同一规则约束。SFU/TURN publication、subscription、transport、server-resource 和临时 overlap 另由整体路由模型定义,不能从普通 child 预算推断,也不能以替换名义无界超额。 +- 端点发送容量:所有非服务器端点使用同一个服务端权威稳态 outbound media-copy capacity,默认 `2`,只允许静态配置 `1`、`2` 或 `3`;peer child 或 Host 的唯一 SFU publication 各消耗一份,上游接收不消耗该预算,committed selected TURN 只替换同一份 logical edge 的 transport。客户端只能声明不高于部署值的可用容量,角色、浏览器、UA、设备类型和页面可见性不产生不同上限。并行产生媒体的 candidate 必须持有唯一的 `TransitionOverlapSlot`,并在 commit 后回到稳态上限;SFU subscriber egress 与 TURN allocation 由独立 server admission 记账。 - P2P 优先:每条普通逻辑媒体边先尝试 direct/peer UDP,服务器不得默认中转可直连媒体。更多观看者优先沿满足统一端点容量的无环 peer 图分布;每名 Viewer 同时至多一个 active upstream,失败时只影响需要重选的分支。 -- NAT 与传输边界:STUN 必须可用,所有 ordinary peer `RTCPeerConnection` 只接收 STUN ICE servers,普通边不得预发 TURN candidate。所有恢复和候选授权必须绑定 exact room/share generation、endpoint session、route/assignment generation 与 connection identity;成功即停止,失败、超时或绑定失效即撤销,全部可用路径耗尽后明确等待或失败。SFU/TURN 的具体 fallback 顺序、edge 类型、lease multiplicity 和资源记账待整体路由模型定案。凭据不得进入 URL、日志、浏览器持久化或 SQLite;HTTPS/WSS 继续独立使用 TLS/TCP。 +- NAT 与传输边界:STUN 必须可用,所有 ordinary peer `RTCPeerConnection` 只接收 STUN ICE servers,普通边不得预发 TURN candidate。所有恢复和候选授权必须绑定 exact room/share generation、endpoint session、route/assignment generation 与 connection identity;成功即停止,失败、超时或绑定失效即撤销,全部可用路径耗尽后明确等待或失败。已接受的顺序是保持既有 logical edge,必要时 direct/STUN 后使用 exact selected TURN;只有 logical ingress 不可用时才使用当前 Host publication 的 SFU subscription。Host 无 direct first-level path 时优先一条可经 TURN 的 Host-SFU publication,而不是逐 Viewer 建 Host-TURN peer 边。凭据不得进入 URL、日志、浏览器持久化或 SQLite;HTTPS/WSS 继续独立使用 TLS/TCP。 - 质量控制:保留 1080p60(8 Mbps)、1080p30(5 Mbps)、720p30(3 Mbps)三档推荐上限,三档与高级设置初始值统一采用 `balanced`(平衡)与 `automatic`(自动编码),`maintain-resolution`(清晰优先)、`maintain-framerate`(流畅优先)和 H.264/VP8 保留为显式选择;分享期间可直接换档或应用有界高级设置,不重新选择来源或重建健康连接,codec 在直播中锁定并于下一次分享生效。Peer-assisted relay 与已配置的 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,具体降级由浏览器决定;必须读回实际 sender 参数并通过 stats 观测结果、显示参数拒绝、改写和持续受限原因。不做自定义动态 FPS、综合评分或周期性全房间调档。 - 路径隔离双表示:direct/peer 继续使用每条 PeerConnection 独立的 stock WebRTC GCC,不把任何 viewer 反馈合成全房目标。每条 SFU path 从 `HIGH` ceiling 开始;优先用 pinned LiveKit 2.22.0/1.13.5 的标准两层 screen-share simulcast,在 Dynacast off 下常驻 exactly `HIGH+LOW`,让内建 per-subscriber BWE 独立降层和恢复。若该 gate 通过,Screener 不实现媒体层 selector。首 gate 使用 zero-descendant SFU leaf;root-with-children 的自动 downshift 与有界迁移是 default-on 前独立硬门。只有内建选择不满足门槛时才用标准 subscriber quality 显式限制,常驻成本不合格时再测标准 sender 手动启停。`LOW` 停编只是资源优化;active representation/layer 硬上限为二,禁止按 viewer 增长。必须实测实际接收层、硬件编码证据、游戏 FPS/p1 low、CPU/GPU、区间 encode cost、上传和分层 bytes。当前 Web P2P simulcast 缺少 receiver 选 RID 和跨 PeerConnection shared-encode 契约;当前 Web/LiveKit SVC 也缺少 direct/peer 独立选层与可移植硬件保证,且 pinned screen share 强制三层 temporal-only `L1T3`,二者按现契约静态 no-go。全部标准路径失败后才考虑 custom/native dual encode。没有合格 hardware/power-efficient 路径或额外表示破坏游戏/上传预算时 fail closed:保护健康路径并明确显示弱路径降级或不可用。 -- 路径级质量状态:应用的 `HIGH`/`FALLBACK` 非对称窗口只决定拓扑资格和诊断,不命令正常的媒体层切换,也不计算综合分数。SFU BWE downshift 先进入 `suspect`;多个连续窗口的 sender/viewer 关联证据确认 `FALLBACK` 后,应用不得稳定保留该 parent,并把 downstream capacity 降为 0。计划或显式质量 fallback 仅是内建路径失败后的候选:已有 children 时先在 connection/assignment generation guard 下全部迁走,失败或代次过期则保持原 assignment/quality。Autonomous downshift 无法 packet-level 预先迁移,default-on 前必须验收 descendants 的短暂影响。只有更长的稳定恢复窗口和 cooldown 后才能重新声明 relay capacity。Viewer 请求必须鉴权、限频、去重,并由 sender 的 GCC/Transport-CC、实际码率、RTT、丢包、重传、编码限制、协商 codec/profile/parameters 与 viewer 解码、丢帧、冻结证据交叉验证;不能仅凭自报生效,也不得上传 raw SDP、candidate 地址或原始设备/网络标识。 -- 断线恢复:临时网络变化只触发整体路由模型授权的有限恢复动作;任一步成功即停止,失败最终进入明确等待或有界失败。`connecting`/`checking` 先显示不改变拓扑的慢连接状态,硬超时必须由真实移动网络证据确定。所有异步结果受 session、route revision、assignment/connection generation 约束,未受影响分支保持健康,Host 只看到不含 URL、地址、token 或原始错误的阶段信息。具体 peer/SFU/TURN 次序在整体模型定案前不写入当前需求。 +- 路径级质量状态:应用的 `HIGH`/`FALLBACK` 非对称窗口只决定拓扑资格和诊断,不命令正常的媒体层切换,也不计算综合分数。`EdgeHealth(parent, child)` 只控制该 child 的换父;`RelayEligibility(parent)` 仅适用于 relay parent,按 `eligible -> suspect -> draining -> ineligible` 控制其是否接收新 child 以及是否排空现有 children。质量 owner 根据不同 downstream edges 的当前可观察证据和端点容量要求足够的独立关联;同一 edge 的 C+B 只处理该 child。relay parent 自身 ingress 失败先触发本地修复并进入 `suspect`,只有修复失败、明确 sender/resource failure 或独立 downstream edges 确认转发仍失效时才 drain,不等待不存在的第二个 child;Host source 走 publication repair/child migration,不走 parent reparent。退位时仍健康的 ingress 保留,失效 ingress 在同一 bounded target 中替换或移除;无目标 child 进入有界等待或失败,更长的稳定证明和 cooldown 后才恢复 `eligible`。Viewer 请求必须鉴权、限频、去重,并由 sender 的 GCC/Transport-CC、实际码率、RTT、丢包、重传、编码限制、协商 codec/profile/parameters 与 viewer 解码、丢帧、冻结证据交叉验证;不能仅凭自报生效,也不得上传 raw SDP、candidate 地址或原始设备/网络标识。 +- 断线恢复:临时网络变化只触发整体路由模型授权的有限恢复动作;任一步成功即停止,失败最终进入明确等待或有界失败。`connecting`/`checking` 先显示不改变拓扑的慢连接状态,硬超时必须由真实移动网络证据确定。所有异步结果受 session、route revision、assignment/connection generation 约束,未受影响分支保持健康,Host 只看到不含 URL、地址、token 或原始错误的阶段信息。质量证据只改变 edge 或 parent eligibility,不创建第二个 route controller。 - 可观测性:按目标 RTP transport 与不透明连接/ICE 代次关联 selected path、`iceGatheringState`/ICE/connection state、事件推导的建立时长、generation-baselined pair-change delta,以及 RTT、RTP 丢包率、抖动、码率、帧率、编码耗时和受限原因。候选与错误的 wire、日志和导出只使用[低服务器成本媒体路由](./research/low-server-media-routes.md)的脱敏字段和 code bucket,禁止 raw SDP、candidate、地址、URL、`errorText` 或网络标识。只有创建该 `RTCPeerConnection` 的本机可在折叠详情中显示浏览器对 selected local/remote candidate 实际暴露的地址与端口;该值不上传、不持久化、不用于地理/NAT 推断,缺失或被隐私保护时显示未知。 - 渐进式诊断:分享者和观看者默认只看到房间、画面、核心状态和常用控制;Host 的 Viewer 卡片只把本机连接或当前接收证据称为“已连接”,其余状态保持中性的“线路分配中”或具体连接状态,不把 active assignment 当作连接成功。展开连接详情也只在当前媒体证据成立后显示媒体方式:`P2P` 使用绿色与直观图标,`SFU` 使用黄色的 `SFU fallback`;实际 relay 时另标 `TURN`,只有本地 relay protocol 可见时才附加 `/UDP` 等实际值。尚未连接时不预告 P2P、SFU 或未知 ICE,只保留中性状态。Host/Peer 上下游仍只在独立的权威拓扑视图中展示。传输协议、候选路径、RTT、视频/音频 RTP 区间丢包率、实际编解码器、音频区间码率/jitter 和视频编码耗时等技术详情由一个明确的“显示连接详情”开关展开。远端 Viewer evidence 的详情 shell 在同一 viewer/parent/connection/route identity 内保持稳定;非空字段从各自最后一次实际观测起精确保留五秒,`connecting`/`disconnected` 只降为非 fresh,`failed`/`closed`、identity/session/share/route 变化或非单调 sequence 则清空/换代。非 fresh evidence 不得继续证明“已连接”或被其他 current-state 消费者使用。音频 codec MIME、RTP clock、channels 与 fmtp 只能来自当前 RTP 对象引用的 `RTCCodecStats`,缺失即未知;`opus/48000/2` 不得写成 48 kHz 音源或实际立体声。隐藏诊断不能隐藏失败原因、音频缺失、持续画质受限或需要用户操作的告警。 - 站点访问:生产部署必须配置独立的 `SITE_ACCESS_PASSWORD` 控制建房、Host 信令角色和所有 code-only Viewer 入口;只有本地开发/测试可显式关闭。非空值接受 8 至 128 个可见 ASCII bytes,不能复用 TURN、LiveKit、TLS 或其他部署 secret。`POST /api/site-access` 验证成功签发 12 小时、无服务端会话状态的 HttpOnly、`SameSite=Strict` site-access cookie。有效 room-scoped fragment grant 可绕过站点 gate 直接进入对应房间;其余 `/r/{code}`、`/join` 和 public-watch 必须先持有效站点 cookie,之后才检查房间 public/private policy 和房间密码。失败统一且不得泄露房间是否存在或 policy;建房 HTTP 只认该 cookie。已移除的旧环境变量、endpoint 和 cookie 不保留兼容。不引入账号、JWT、服务端 session Map 或通用 logout。 @@ -41,19 +41,19 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - 私密房间双入口:private room 接受现有 `/r/{code}#v={grant}` 合法邀请直接进入,或“站点访问 + 房间号 + 该房间观看密码”。没有 grant 的 `/join` 或 `/r/{code}` 先显示明确标为“站点访问”的整站验证,通过后才显示明确标为“加入房间”的当前房间密码输入;有效 site-access cookie 直接跳过第一步,但两类密码不得复用、代填或互相替代。Host 可设置、轮换或移除房间观看密码。密码接受 1 至 64 个可见 ASCII 字符;异步 scrypt 全局最多执行 2 个、等待 16 个派生。站点口令和房间密码都不得进入 URL/query/storage/log/SQLite;不存在、错误密码、队满和正确密码后的满房都返回通用结果。 -- 统一自动路由:旗舰部署开启 `PEER_ASSISTED_MEDIA` 后,所有正常房间使用同一个服务端 route controller,并各自保存 assignment、session、route revision、assignment generation、connection identity 和容量状态。合格父节点可形成候选图,但 committed media graph 必须无环且每名 Viewer 同时至多一个 active upstream。加入、容量释放、节点离开、当前边失败或已接受的 path-health evidence 只开启一次有界重选;其余健康分支保持 sticky,不做常驻多源、周期评分或全房重平衡。迁移保留旧边,只有 exact current candidate 取得 live track、正 RTP 和 decoded-frame progress 后才原子 commit;失败、超时、代次变化或授权撤销销毁候选并保留或恢复旧权威。普通 peer 始终 STUN-only。SFU/TURN 参与迁移时的 overlap 和资源准入由待定整体模型约束。该能力不增加 UI、用户选择、百分比发布或第二套路由器。 +- 统一自动路由:旗舰部署开启 `PEER_ASSISTED_MEDIA` 后,所有正常房间使用同一个服务端 route controller,并各自保存 assignment、session、route revision、assignment generation、connection identity、media-binding generation 和容量状态。controller 只执行 allocate/distribute、child reparent 和 relay abdicate/drain;合格父节点可形成候选图,但 committed media graph 必须无环、每名 Viewer 同时至多一个 active upstream,且每个 active Viewer 必须沿 peer edges 到达 Host 或沿当前 authoritative Host publication 到达 source。加入、容量释放、节点离开、当前边失败或已接受的 path-health evidence 只开启一次有界重选;其余健康分支保持 sticky,不做常驻多源、周期评分或全房重平衡。迁移保留旧边,只有 exact current candidate 取得 live track、正 RTP 和 decoded-frame progress 后才原子 commit;失败、超时、代次变化或授权撤销销毁候选并保留或恢复旧权威。普通 peer 始终 STUN-only。SFU/TURN overlap 和资源准入由同一 typed ledger 与事务约束。该能力不增加 UI、用户选择、百分比发布或第二套路由器。 - 质量控制扩展:推荐档位仍是默认入口;“分享高级设置”只允许 720p/1080p/1440p 分辨率 ceiling、15 至 60 的整数 FPS ceiling、2 至 12 Mbps 视频码率 ceiling、清晰/平衡/流畅三种偏好、`自动 | H.264 | VP8` 编码选择,以及离散的 `64 kbps 省流 | 128 kbps 音乐默认 | 256 kbps 极高音质` 屏幕音频 sender ceiling;初始值采用 `balanced`、`automatic` 与 128 kbps。视频档位在直播中显式应用;codec 与音频档位在直播中锁定并于下一次分享生效,避免控件产生连续控制流量。所有当前及以后建立的 Web Host、Browser relay 与 SFU sender 必须请求并读回同一房间锁定的 64/128/256 kbps 上限,实际码率仍由浏览器拥塞控制和 stats 决定。Peer 只在唯一明确的 active Opus audio 上做结构化、幂等的 Viewer answer `stereo=1;maxaveragebitrate=256000` 变换,使同一接收上限覆盖三档;非目标形态或 parser/writer 异常必须原样保留浏览器 answer,不得阻断可继续的 mono/video edge。不得用宽泛字符串/正则改 SDP,也不得改写浏览器协商的 FEC。SFU 将三档映射到 pinned SDK 的 music stereo preset 或显式 256 kbps ceiling,统一保留 `forceStereo = true`、`dtx = false` 与 pinned room default `red = true`。不得暴露任意码率、声道、采样率、audio codec、stereo、DTX、RED 或 FEC 控件;256 kbps 只是显式较高上限,不是无损、高保真或最低码率保证。实际质量持续低于所选上限时必须显示可解释的限制原因;在真实拥塞、编码器和接收端证据完成前,不把任一组合称为“完美平衡”。未来 microphone voice 若进入范围,必须是独立 track/处理链,不能把 AEC/降噪/DTX 套到电影或游戏音频。 - SFU 质量设置生命周期:直播中修改档位时,浏览器 sender 的 applied readback、LiveKit `LocalVideoTrack` 保存的 degradation preference、track restart options 与 `LocalTrackPublication` 的 republish options 必须作为同一次串行操作更新;任一步失败恢复旧 sender 与旧 SDK 保存态,旧 publication generation 不得回写当前状态。LiveKit full reconnect/republish 不得恢复初次发布的旧偏好。该修复不改变 codec、码率上限、层数或路由,也不能单独解释即时低帧。 - 本地预览性能候选:在相同动态场景中 A/B 测量 Host 动态预览开/关的 CPU、GPU、encode time 和实际 FPS。只有稳定收益且捕获、编码和远端画面不受影响时,才允许直播开始后默认显示静止缩略图或隐藏动态预览并提供按需展开;未测前不宣称收益。 -- Peer-assisted 媒体路径:所有非服务器 parent 都受同一个服务端权威下游容量约束,默认 `2` 且可静态配置为 `1`、`2` 或 `3`。每名 Viewer 任一时刻只有一个 active upstream,活动媒体图保持无环;只在离散事件中做有界分配或换父。浏览器把收到的标准 remote track 加入独立下游 `RTCPeerConnection` 时会逐跳解码并分别重编码,必须测量而不能称为 shared encode。服务端只在有界房间内存中保存严格校验的质量设置,不写入 SQLite。 +- Peer-assisted 媒体路径:所有非服务器 endpoint 都受同一个服务端权威稳态 outbound media-copy capacity 约束,默认 `2` 且可静态配置为 `1`、`2` 或 `3`;peer child 与 Host publication 都按实际发送副本计数。每名 Viewer 任一时刻只有一个 active upstream,活动媒体图保持无环;只在离散事件中做有界分配或换父。浏览器把收到的标准 remote track 加入独立下游 `RTCPeerConnection` 时会逐跳解码并分别重编码,必须测量而不能称为 shared encode。服务端只在有界房间内存中保存严格校验的质量设置,不写入 SQLite。 - 原生 shared encode 分享端:后续独立的 Electron/原生分享端必须让每个 active representation 只产生一份兼容编码结果,并在 direct、peer 与 SFU transport 间复用;健康路径选择共享 `HIGH`,最多另有一份弱路径共用的 `LOW`,是否常驻由硬件、游戏和上传门槛决定。每条连接仍保留独立 WebRTC packetization、SSRC、pacing、加密和反馈,且各自消耗上传带宽。浏览器不保证跨 `RTCPeerConnection` 复用编码器,因此当前 Web spike 不能宣称满足该保证。 - 原生 shared encode relay:具备原生 relay 能力的非服务器端点同样受统一服务端权威下游容量约束,并应复用兼容编码输出而不再次编码。当前普通浏览器 relay 使用逐边重编码的标准 WebRTC 路径,不伪称 shared encode。 - SVC 重开条件:当前标准 Web/LiveKit SVC 已静态拒绝为 `no-go-web-svc-cross-path-hardware-contract`。sender `scalabilityMode` 没有 direct/peer receiver 对应的按层选择或跨 PeerConnection shared-encode 契约;requested/applied mode、Media Capabilities `powerEfficient` 和 RTCStats encoder 字段也不能证明没有静默软件 fallback。Pinned client 2.22.0 还会把 screen-share SVC 覆盖为三层 temporal-only `L1T3`,不满足低分辨率 base 与最多两层。只有未来 native sender 能明确选择硬件编码器、最多发布两层、将同一编码输出复用于 direct/peer/SFU、保持每条路径独立选择并通过真实游戏和亚秒延迟门槛时,才可用新的 capability decision 重开 SVC;不能把浏览器 API 支持或 UA 推断当成采用理由。 - 分包/分层多树实验:把编码包、对象或可独立解码层拆到多个 peer 路径以分散上传,只能在独立 spike 中验证。必须保持亚秒级,处理多父重组、排序、丢包恢复、关键帧、同步和节点退出;未通过门槛前不进入产品路由控制器。 -- SFU/TURN 中央兜底:产品部署可以提供经 canary 验收的有界 SFU 与可选 TURN 资源,但不要求用户选模式,也不得默认变成整房 always-SFU。publication owner、subscriber 选择、受限 Host/relay/leaf 的路径、TURN edge 类型、central egress/admission、temporary overlap 和 exact fallback sequence 必须在同一个路由模型中定案并测量,不能由固定 root 数或普通端点 child 数代替。 +- SFU/TURN 中央兜底:产品部署可以提供有界 SFU 与可选 TURN 资源,但不要求用户选模式,也不得默认变成整房 always-SFU。只有 Host 拥有一个 authoritative publication;SFU-fed Viewer 具备独立出站证明时才可继续 relay,严格受限 Viewer 保持 leaf。TURN 是某条授权 logical edge 或 Host-SFU ingress 的 selected transport,不是拓扑节点。central egress/admission、temporary overlap 和 exact fallback 由同一个 route controller 与资源账本约束,不能由固定 root 数或普通端点 child 数代替。 - 分享端桌面封装:用 Electron 固定 Chromium 版本、改善选源/热键/系统音频体验,同时保留 Web 观看端。 - Windows 应用音频:Windows 11 Native sender 使用本地 opaque window target 绑定 HWND、PID 与进程创建时间,只捕获所选游戏及其子进程的音频,避免把语音聊天或通知声一并分享;“全部系统声音”只能由分享者显式选择。普通 Windows 10 不具备当前 process-loopback API 门槛,必须明确标为未解决/不支持而不是伪兼容。浏览器无法证明或隔离任意进程音频时必须如实提示,原生捕获失败、无声或进程退出不得静默扩大为整机捕获。 -- 受限网络增强:短期鉴权 TURN 只有在完整、默认关闭的部署配置和当前 route controller 明确授权时可用;普通 peer PC 从创建起保持 STUN-only,不得向整个 participant、房间或所有 peer connections 预发 TURN candidates。凭据必须绑定 exact room/share/session/route/connection generation,短期有效、内存态、受配额约束且不进入 URL、日志、浏览器持久化或 SQLite。它服务的 edge 类型、相对 SFU 的顺序和资源记账由整体路由模型定案;TURN 不得被描述为更高清的传输方式。 +- 受限网络增强:短期鉴权 TURN 只有在完整、默认关闭的部署配置和当前 route controller 明确授权时可用;普通 peer PC 从创建起保持 STUN-only,不得向整个 participant、房间或所有 peer connections 预发 TURN candidates。凭据必须绑定 exact room/share/session/route/connection generation,短期有效、内存态、受配额约束且不进入 URL、日志、浏览器持久化或 SQLite。TURN 只替换授权 edge 的 transport;Host 无 direct first-level path 时可作为单条 Host-SFU ingress 的 transport。TURN 不得被描述为更高清的传输方式。 - Web 编码选择:分享高级设置提供 `自动 | H.264 | VP8`,默认自动使用浏览器/LiveKit 协商;显式选择只在下一次分享前用标准 codec preference 生效。直播中控件锁定并明确提示停止分享后可修改。P2P/浏览器 relay 保留完整 repair 与其他 codec 回退列表,SFU 保持 `backupCodec = false`,不维护第二套应用编码链。实际 codec/profile、encoder implementation 与 stats 是结果真相;H.264 可选不等于浏览器必走硬编,AV1 仍不得仅凭压缩率推断默认启用。 ### P2 - 可选功能 @@ -84,7 +84,7 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - Production 当前八名观看者和 1 至 16 配置范围只是接入边界,不承诺任意机器、档位、网络或跨洲路径都稳定承载;任何未来 admission 变化另行决策和验收。 - 浏览器规范要求每次由用户主动选择分享源,权限不能永久保存;纯网页不能保证指定游戏窗口或始终获得系统音频。 - 1080p60 是能力目标,实际分辨率、帧率和硬件编码由浏览器、GPU、驱动、画面复杂度及网络共同决定。 -- 旗舰 release 的正常房间都可使用同一自动 controller;部署未启用总开关的轻量实例才保持标准 WebRTC P2P。Peer relay 只复用标准 remote-track WebRTC,不加入 Encoded Transform/DataChannel/WebCodecs 媒体协议,也不自研 UDP、编解码器、拥塞控制、FEC/RTX、多树、转码、codec ladder 或综合评分器。独立 native shared encode sender 不在本实验内;跨模式迁移受 ADR-0005 的 identity、generation、media proof 和待定资源准入共同约束。 +- 旗舰 release 的正常房间都可使用同一自动 controller;部署未启用总开关的轻量实例才保持标准 WebRTC P2P。Peer relay 只复用标准 remote-track WebRTC,不加入 Encoded Transform/DataChannel/WebCodecs 媒体协议,也不自研 UDP、编解码器、拥塞控制、FEC/RTX、多树、转码、codec ladder 或综合评分器。独立 native shared encode sender 不在本实验内;跨模式迁移受 ADR-0005 的 identity、generation、media proof 和 typed resource admission 共同约束。 - GPL/AGPL 项目在许可证策略确认前仅用于研究,不能直接复制到计划闭源的产品中。 ## 验收标准 @@ -109,9 +109,9 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - Given 两端网络允许 UDP 打洞,When 观看者加入,Then `getStats()` 显示选中的候选对不是 `relay`,且服务器没有承载媒体流量。 - Given ordinary peer edge 被创建或重建,When 检查其 ICE 配置,Then 只包含部署 STUN,不包含预发给 participant、房间或普通 peer 的 TURN candidate。 -- Given route candidate、signal、recovery 或 commit 的 room/share/session/revision/assignment/connection identity 任一不匹配,When 旧消息或异步结果到达,Then 该操作 fail closed 且不能替换当前媒体。 +- Given route candidate、signal、recovery 或 commit 的 room/share/session/revision/assignment/connection/media-binding identity,及适用时 publication generation,任一不匹配,When 旧消息或异步结果到达,Then 该操作 fail closed 且不能替换当前媒体。 - Given 新路线正在准备,When 尚未同时证明当前代 live track、正 RTP 与 decoded-frame progress,Then 旧路线保持权威;证明后只原子提交 exact candidate,失败或超时销毁候选并保留或恢复旧路线。 -- Given 所有已接受且已配置的媒体路径耗尽,When 有界连接窗口结束,Then 界面显示通用等待或连接失败,不静默黑屏、不长时间假连接,也不尝试未授权路径。SFU/TURN 的精确 path order 和 resource accounting 由整体路由模型的独立验收条款补充。 +- Given 所有已接受且已配置的媒体路径耗尽,When 有界连接窗口结束,Then 界面显示通用等待或连接失败,不静默黑屏、不长时间假连接,也不尝试未授权路径。SFU/TURN 的精确 path order 和 resource accounting 按 ADR-0005 验证。 ### 浏览器与移动端观看 @@ -125,7 +125,7 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting ### 一对多与带宽保护 -- Given 部署的非服务器端点下游容量为 `C`,其中 `C` 只能是 `1`、`2` 或 `3` 且默认 `2`,When Host 或任一 Viewer 建立、恢复、重鉴权或迁移 ordinary downstream edge,Then 服务端和客户端执行边界都不超过 `C`,upstream receive 不计数,角色、浏览器和 UA 不改变 `C`。 +- Given 部署的非服务器端点稳态 outbound media-copy capacity 为 `C`,其中 `C` 只能是 `1`、`2` 或 `3` 且默认 `2`,When Host 或任一 Viewer 建立、恢复、重鉴权或迁移媒体路径,Then peer child 与 Host publication 的稳态实际发送副本总数不超过 `C`,upstream receive 不计数,committed selected TURN 不重复计数,角色、浏览器和 UA 不改变 `C`;并行候选只有持有 `TransitionOverlapSlot` 时才可达到 `min(C + 1, 3)`,并在 commit/abort 后释放。 - Given peer-assisted 路径分别运行 1、3、5、8 名观看者,When 用户加入、容量释放、节点离开、重鉴权或当前 edge 失效,Then 服务端只为新 Viewer、等待 Viewer 或失去 parent 的 subtree root 选择一个 active parent;每名 Viewer 同时至多一个 active upstream,活动图无环,其余健康边保持 sticky。没有可用 parent 时明确等待或拒绝,不主动重平衡或引入常驻多源媒体。 - Given 一名分享者和八名观看者,When 在受控网络中运行已记录的质量档位,Then 记录每条边的拓扑深度、候选类型、码率、编码/解码负载、分享端与 relay 上行、首帧和玻璃到玻璃延迟;该条在真实矩阵完成前保持未验收。 - Given candidate 改变房间 admission 默认值或上限,When 在目标数量与代表性路径上运行规定时长并注入 relay departure、重连和一个弱路径,Then 每名观看者持续解码、每个非服务器端点不超过配置容量、深度与故障半径有界、未受影响 subtree 不迁移,并记录端点与中央服务的 CPU、GPU、内存、上行、出口、帧率、丢包率、冻结和恢复;任一门槛失败时不得切换默认值。 @@ -134,15 +134,17 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - Given ADR-0004 的任一硬门槛只有在引入 Encoded Transform/DataChannel/WebCodecs 媒体、自定义拥塞控制、FEC/RTX、多树、综合评分、转码、codec ladder、浏览器 RTP 注入 hack 或放宽 endpoint capacity 后才能满足,When 结束 bounded spike,Then 放弃并删除该浏览器 relay 实验路径,保留标准 P2P 与整体路由模型已接受的 fallback;后续 native shared encode sender 仍按独立 ADR 推进,但不能据此把失败的 relay 拓扑判为通过。 - Given 新观看者会使预计总上行超过安全预算,When 其请求加入,Then 系统只选择整体路由模型已授权且满足配置容量与资源准入的路径;没有合格路线时明确等待或拒绝,而不是创建超过配置容量的 ordinary edge 或持续堆积发送队列。各 transport 的拥塞控制适应该路径,应用证据只改变拓扑资格,不能合成全房降档。 - Given SFU 或 TURN 参与路线,When 计算准入、迁移 overlap 或失败恢复,Then 同时检查统一端点容量和独立的 server ingress/egress、SFU、TURN 资源预算;不得把普通端点 child 数当作 server cap,也不得借兜底形成无界整房 fanout。 -- Given Host、relay parent 或 leaf 处于受限网络,When direct/peer 路径不足,Then 只执行整体路由模型已明确授权的 publisher、subscriber、selected transport 和 fallback sequence;模型尚未定案的分支不得靠历史 root 数或 lease 规则猜测实现。 +- Given Host、relay parent 或 leaf 处于受限网络,When direct/peer 路径不足,Then 只执行 ADR-0005 授权的 publisher、subscriber、selected transport 和 fallback sequence;不得靠未授权的固定数量或静态 lease 假设猜测实现。 ### 质量和恢复 - Given 用户选择任一推荐档位或高级质量参数,When 接收端实际分辨率、帧率或码率持续显著低于该目标,Then 诊断数据能区分采集约束、发送编码、带宽/拥塞、TURN 路径和接收端限制;产品不得只改变档位标签却继续隐藏实际降级。 - Given 一个健康 SFU leaf 和一个受控弱 SFU leaf 都以 `HIGH` ceiling 订阅,When 内建 LiveKit BWE 运行,Then 弱 leaf 自动使用唯一共享 `LOW` 并在恢复后回到 `HIGH`,健康 leaf 不受影响,且 Screener 没有实现媒体层 selector;direct/peer 继续使用各自的 stock WebRTC GCC。 -- Given 一个路径的 sender/viewer 证据跨连续窗口确认 `FALLBACK`,When 应用更新状态,Then 只改变其 parent/leaf 拓扑资格和诊断;不得重复命令内建媒体层。更长稳定恢复窗口和 cooldown 后才可重新声明 relay capacity。 -- Given 内建路径失败后才启用显式质量 fallback,When 一个当前承载 children 的 relay 准备切换,Then 必须先在匹配 generation 下迁走全部 children;失败或代次过期保持原状态,且 Viewer 自报不能单独触发该过程。 -- Given SFU BWE 在应用确认前把一个仍有 children 的 root 瞬时降层,When 收到该观测,Then 先标记 `suspect`;只有跨有界连续窗口的关联证据确认后才迁走 children 并进入 `FALLBACK`,不得稳定保留 confirmed `FALLBACK` parent。默认启用前必须在 root-with-children gate 中记录并限制这段不可避免的 descendants 短暂画质影响,不能声称 packet-level 先迁后降。 +- Given 一个 Viewer 观察到自己当前 upstream 的接收效果差,When 当前 Viewer 的 `viewer-quality-evidence` 与 parent 对同一 edge 返回的 `parent-edge-quality-evidence` 按 exact route identity(room/share、双方 session、assignment/connection/media-binding generation、route revision,及适用时 publication generation)和 sequence 连续相关,Then 只为该 Viewer 创建 child-scoped reparent intent,排除旧 parent,按 reserve/proof/commit/rollback 换父;不得直接把 parent 全局判坏。 +- Given 一个 relay parent 的不同 downstream edges 达到质量 owner 针对当前可观察边数和端点容量定义的独立关联证据门槛,或该 parent 出现明确的 sender/resource failure、修复失败的自身 ingress 或独立 downstream edges 确认转发失效,When 统一 controller 确认 directional ineligibility,Then parent 进入 `draining`,停止新 child;仍健康的 ingress 保留,失效 ingress 在同一有界 target 中替换或移除,并迁移可行 children;无候选 child 进入 waiting/bounded-failure,不得阻塞其他 child,也不得用固定的“两个 child”替代质量策略。Host source 走 publication repair/child migration,不进入 relay-parent reparent。 +- Given 一个路径的 sender/viewer 证据跨连续窗口确认 `FALLBACK`,When 应用更新状态,Then 只改变其 edge/parent 拓扑资格和诊断;不得重复命令内建媒体层。更长稳定恢复窗口和 cooldown 后才可重新声明 relay capacity。 +- Given 同一 Viewer 同时是某人的 child 和其他人的 parent,When 其自身 ingress 发生 hard/confirmed failure,Then child-scoped reparent 或 branch-preserving `replaceIngress` 优先修复它,同时将其 relay eligibility 置为 `suspect` 并停止新分配;新 ingress 恢复下游媒体时保留子树,只有修复失败或不同 downstream edges 仍确认转发失效时才执行 parent drain。C+B 可同时贡献证据,但 controller 只提交一个 target revision;成功的 child reparent 只使同一旧 logical edge 的 pending evidence 失效,其他仍匹配且未过期的 sibling evidence 保留。 +- Given SFU BWE 在应用确认前把一个仍有 children 的 root 瞬时降层,When 收到该观测,Then 先标记 `suspect`;随后由 ADR-0005 的 exact scope 选择 local repair、child reparent 或 parent drain。只有进入 parent-drain 分支才迁走 children 并进入 `FALLBACK`,不得稳定保留 confirmed `FALLBACK` parent。默认启用前必须在 root-with-children gate 中记录并限制这段不可避免的 descendants 短暂画质影响,不能声称 packet-level 先迁后降。 - Given exactly-two publication 的实测 encoder/CPU/GPU 或上传成本超过游戏预算,或没有合格 hardware/power-efficient 路径,When 评估该候选,Then 保留健康路径并明确显示弱路径降级或不可用,不得退回全房间最低目标或静默软件编码;若 supported sender cohort 无法以常驻或按需方式提供共享 `LOW`,Then 质量验收失败,必须优化编码路径或明确标记不支持。 - Given 同一时刻采集、host outbound 与 viewer inbound 证据,When 进行根因分类,Then 关联派生后的实际协商视频 codec/profile/parameters、适用时的 `scalabilityMode` 与真实解码表现,依次区分 Capture 低、Capture 高但 outbound 因 CPU 低、Capture 高但 outbound 因 bandwidth 低、outbound 正常但 inbound 低、以及 inbound 正常但肉眼模糊五类;不用 UA 或设备型号代替运行时证据,也不上传 raw SDP。 - Given Viewer 发送低档请求,When 请求未鉴权、过频、重复、过期或没有匹配的 sender/viewer 统计,Then 请求不改变表示或路径状态;有效请求也只能作为交叉验证输入。