docs: align recovery authority truth (#280)

This commit is contained in:
AAA authored and GitHub committed 2026-08-26 23:28:10 +08:00
1 parent 06c154a080
commit c0c5b1e661
8 files changed
+68 -46

No files matched your search

+2 -2
View File
@@ -18,7 +18,7 @@
- [Native H.264 hardware decision](./research/native-h264-hardware-decision.md): bounded WebCodecs no-go, proved Media Foundation/NVIDIA hardware encode, the default-fmtp `42c01f` stop, and the retained interop boundary.
- [Native H.264 opt-in path](./research/native-h264-opt-in-path.md): explicit H.264 sender mode, exact Pion profile registration, and one bounded Host/Viewer loopback with hardware caveats.
- [Realtime quality adaptation](./research/realtime-quality-adaptation.md): Browser codec and `motion` evidence, manual quality ceilings, path-quality authority, and LiveKit-owned SFU representation adaptation.
- [Cross-restart room recovery](./research/cross-restart-room-recovery.md): pending lineage, authority-epoch, invitation-replay, and bounded media-rebuild evidence without reopening durable room storage.
- [Cross-restart room recovery](./research/cross-restart-room-recovery.md): room-authority lineage, optional SQLite stable mode, invitation continuity, and process-fresh media rebuild evidence.
- [Browser background capture diagnostics](./research/browser-background-capture.md): browser lifecycle evidence, a privacy-bounded capture/send/receive experiment, interpretation gates, and non-goal keepalive mechanisms.
- [Browser screen-audio quality](./research/browser-screen-audio-quality.md): capture/source compatibility, live sender-ceiling readback, Opus negotiation boundaries, and remaining audible/device evidence.
- [Display name and presence boundaries](./research/display-name-presence.md): browser-local storage failure behavior, Unicode normalization, control-character policy, and the Web-only presence capability boundary.
@@ -28,7 +28,7 @@
- [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 P2P-first single-reconcile route model, including deployed Browser SFU ICE-server isolation; remaining real-network validation is tracked in verification status.
- [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-path-isolated-representation-quality.md): accepted LiveKit-owned pinned-default VP8 SFU adaptation, pinned evidence, and application-controller/SVC boundaries.
- [ADR-0007](./adr/0007-path-isolated-representation-quality.md): accepted LiveKit-owned SFU representation adaptation, pinned evidence, and application-controller/SVC boundaries.
- [ADR-0008](./adr/0008-window-scoped-audio-capture.md): browser window-audio hint and the Windows WASAPI process-loopback boundary.
Documentation and project memory are part of the product source of truth. Update them in the same change that alters the corresponding requirement or architecture.
@@ -236,6 +236,9 @@ second mutable graph.
removed naturally.
- 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.
The Host page owns the canonical capture tracks. LiveKit publisher teardown,
remote room deletion, and transport recovery may release only their SFU
publication and must never stop those capture tracks.
Any source-reachable endpoint with effective capacity may be a parent
candidate; SFU-fed and peer-fed endpoints use the same provisional-child
transaction and each child edge commits independently. Viewer republishing
+8 -8
View File
@@ -3,7 +3,7 @@
Last verified: 2026-08-26.
This page records production running exact deployed application/runtime revision
`3037c0a8f0d16e0df791eb8e64003a7a6660d521`, release `3037c0a`, and the single
`8164102af8083d55632cf5de10197b5823b3d140`, release `8164102`, and the single
Browser `screener-v12` contract. Product direction and pending work are owned by
[project memory](./project-memory.md) and [the TODO ledger](./todo.md).
@@ -25,16 +25,16 @@ ICE/UDP only. Ordinary peer ICE remains STUN-only and production coturn uses the
tracked STUN-only configuration with TCP/TLS disabled. The source and production
configure no TURN, ICE/TCP, media TCP, or TLS-relayed media.
Production runs exact `3037c0a8f0d16e0df791eb8e64003a7a6660d521`, release
`3037c0a`, from `/opt/screener/releases/3037c0a`. The immutable runtime tar
Production runs exact `8164102af8083d55632cf5de10197b5823b3d140`, release
`8164102`, from `/opt/screener/releases/8164102`. The immutable runtime tar
SHA-256 is
`f84cb7f7acbfbd950ecfdac13da9a0d6e99a4c10eb221c6948ce81b47ce2b5f6`.
`1f61395702afaabfda8c178a08c7896af77e1c4750dcc9f29ffaafbd56bba80f`.
Its 41-file path/size/hash manifest SHA-256 is
`e2fc8115c9cb7d6455027dc2af6f29229834774ed12de334832a8d53d6fc6262`.
`55b9779fdaf4fe2ba98d5612b16e398fbf2ad6b9ebcfeeeef6e0ce5c3595ac8d`.
Local and public `/healthz` return 200; Screener, LiveKit, coturn, and nginx are
active with `NRestarts=0`. The public main Browser asset is
`assets/index-Y4pFjW_S.js` with SHA-256
`a3df27613dfb10fb45297197afdf2500e2953c2b5330b13e5bb49eafdc3e1447`.
`assets/index-CPECbwjN.js` with SHA-256
`dfcbed5d4178afbd7f6713f1ae348a70921b936199fa56d87fa05c785564559e`.
The release deploys the single Browser `screener-v12` wire and one internal,
content-independent H.264 sender gate with VP8 fallback across Browser direct,
@@ -296,7 +296,7 @@ must fail or wait before a fourth endpoint copy is issued.
Supplying the removed `MAX_PEER_RELAY_DOWNSTREAM_EDGES`, even blank, fails
startup.
Production release `3037c0a` runs the deployed server and Browser assets
Production release `8164102` runs the deployed server and Browser assets
atomically on `screener-v12`; every stale Browser or executable-sender wire fails
before room authority. Native senders and helpers are outside this release.
+24 -17
View File
@@ -8,22 +8,23 @@ This is the current execution index. Git history owns completed timelines;
## Production
- `https://share.bonfire.icu` runs exact application/runtime revision
`3037c0a8f0d16e0df791eb8e64003a7a6660d521`, release `3037c0a`, wire
`screener-v12`, from `/opt/screener/releases/3037c0a`. The immutable runtime
`8164102af8083d55632cf5de10197b5823b3d140`, release `8164102`, wire
`screener-v12`, from `/opt/screener/releases/8164102`. The immutable runtime
tar SHA-256 is
`f84cb7f7acbfbd950ecfdac13da9a0d6e99a4c10eb221c6948ce81b47ce2b5f6`;
`1f61395702afaabfda8c178a08c7896af77e1c4750dcc9f29ffaafbd56bba80f`;
its 41-file manifest SHA-256 is
`e2fc8115c9cb7d6455027dc2af6f29229834774ed12de334832a8d53d6fc6262`.
- The served Browser entry references `assets/index-Y4pFjW_S.js`; the public
`55b9779fdaf4fe2ba98d5612b16e398fbf2ad6b9ebcfeeeef6e0ce5c3595ac8d`.
- The served Browser entry references `assets/index-CPECbwjN.js`; the public
asset SHA-256 is
`a3df27613dfb10fb45297197afdf2500e2953c2b5330b13e5bb49eafdc3e1447`.
`dfcbed5d4178afbd7f6713f1ae348a70921b936199fa56d87fa05c785564559e`.
Public `/healthz` returns 200. The release postflight found Screener, LiveKit,
coturn, and nginx active with zero restarts.
- Production enables SQLite room authority at
`/var/lib/screener/rooms.sqlite`; live participants, routes and media remain
process-only. A controlled restart retained a temporary room's exact Host
authority and normal room deletion removed it afterward. Lightweight mode
remains available when `ROOM_DATABASE_PATH` is unset.
process-only. A controlled restart retained the room authority and Host-owned
capture while the Viewer rebuilt media without a page refresh. Lightweight
mode remains available when `ROOM_DATABASE_PATH` is unset and reacquires a
room rather than persisting the old authority.
- Public media listeners remain STUN-only UDP 3478 and LiveKit UDP 7882. Web
ingress is TCP 80/443; Node 8787 and LiveKit control/signaling 7880 are
private. TURN, ICE/TCP, media TCP, TLS relay, port 5349, and relay ranges are
@@ -41,10 +42,11 @@ This is the current execution index. Git history owns completed timelines;
## Current Source
- Canonical `main` contains the same application tree deployed from `3037c0a`.
- Canonical `main` contains the same application tree deployed from `8164102`.
Optional SQLite stable authority, non-expiring local preferred code, atomic
room replacement, graceful service-restart presentation and the Host codec
selector are implemented; production selects stable storage.
room replacement and the Host codec selector are implemented; production
selects stable storage. Graceful restart, crash, timeout and network loss use
one reconnect state. LiveKit room teardown cannot stop Host-owned capture.
- One event-driven controller owns the committed graph and one room-serial child
operation. Initial direct acquisition uses a five-second foreground window;
exact transport-connected progress may retain that candidate through the
@@ -62,11 +64,15 @@ This is the current execution index. Git history owns completed timelines;
## Current Milestone
1. Implement presentation-authoritative freeze/pause evidence with decoded
1. Close the confirmed authority/recovery P1s: typed KDF gate outcomes, exact
route-terminal ownership, assignment-fenced child creation, bounded SFU
refresh, exact Viewer authentication outcomes, and reconnect-safe Host
quality intent.
2. Implement presentation-authoritative freeze/pause evidence with decoded
progress and the controller-owned shadow aggregate; collect production
samples without active route changes.
2. Finish the Android Chrome and iOS Safari Viewer lifecycle matrix.
3. Finish representative public-network direct, peer-relay, SFU, recovery,
3. Finish the Android Chrome and iOS Safari Viewer lifecycle matrix.
4. Finish representative public-network direct, peer-relay, SFU, recovery,
Pause/Resume, screen-audio, real-game A/V, and all-UDP-blocked acceptance
without adding another transport or quality controller.
@@ -85,5 +91,6 @@ This is the current execution index. Git history owns completed timelines;
## Current Hold
No source or deployment P0 is open. Representative real-network and mobile
physical evidence remains required before route acceptance is complete.
No source or deployment P0 is open. The recovery/authority P1 milestone blocks
active quality-route behavior; representative real-network and mobile physical
evidence remains required before route acceptance is complete.
+15 -4
View File
@@ -7,7 +7,15 @@ later topic is not implementation authority by itself.
## Now
1. **Quality-route evidence and shadow calibration.** Make current Viewer
1. **Recovery and authority convergence.** Close the confirmed P1 owner gaps
before active quality work: return distinct KDF `busy | cancelled | value`
outcomes without committing a passwordless room; return the exact exhausted
demand identity from the route controller and emit its terminal once; fence
every Host child retry to the current assignment; make SFU refresh end in
typed config or failure; preserve exact Viewer authentication outcomes; and
retain one Host quality intent until the server confirms or rejects it. These
are existing owner corrections, not new retry loops or fallback controllers.
2. **Quality-route evidence and shadow calibration.** Make current Viewer
quality evidence presentation-authoritative with standard freeze/pause
deltas, continuing decoded progress, exact presentation epochs and baseline
fencing. Keep one bounded
@@ -15,12 +23,12 @@ later topic is not implementation authority by itself.
expose it only through the Host acceptance snapshot. It must not alter facts,
capacity, routes or SFU usage. Collect annotated production samples before
accepting an active trigger, probation, restore or cooldown policy.
2. **Mobile Viewer lifecycle.** Run Android Chrome and iOS Safari matrices for
3. **Mobile Viewer lifecycle.** Run Android Chrome and iOS Safari matrices for
autoplay gesture, foreground/background audio, foreground video recovery,
lock/page reclamation, rotation, Wi-Fi/cellular migration, and assigned-relay
survival or controller recovery. Web does not promise background video or
relay execution after OS suspension.
3. **Finish real-network route and media acceptance.** Exercise direct peer,
4. **Finish real-network route and media acceptance.** Exercise direct peer,
Browser relay, SFU, relay-ingress recovery with subtree retention,
disconnect/capacity drain, Pause/Resume, source replacement, screen-audio
continuity, and real-game A/V sync across representative IPv4/IPv6,
@@ -58,7 +66,10 @@ later topic is not implementation authority by itself.
physical-media checkpoints settle, inventory components, configuration,
migrations, timers, compatibility paths, tests, and truth duplication using
the [maintenance review](./maintenance.md#机制减负审查). Begin read-only and do
not turn it into a broad refactor.
not turn it into a broad refactor. The current read-only scan confirmed
coarse `mediaAssignment` wire residue, legacy SFU room-name acceptance,
and duplicated teardown/test/browser-gate owners as the bounded starting
set; it did not authorize unrelated cleanup.
## Product Decision Needed
+10 -9
View File
@@ -8,14 +8,14 @@ may be interpreted. Git and pull requests own routine completed checks.
## Current Production Identity
- Production runs exact application/runtime revision
`3037c0a8f0d16e0df791eb8e64003a7a6660d521`, release `3037c0a`, wire
`screener-v12`, from `/opt/screener/releases/3037c0a`.
`8164102af8083d55632cf5de10197b5823b3d140`, release `8164102`, wire
`screener-v12`, from `/opt/screener/releases/8164102`.
- Runtime tar SHA-256:
`f84cb7f7acbfbd950ecfdac13da9a0d6e99a4c10eb221c6948ce81b47ce2b5f6`.
`1f61395702afaabfda8c178a08c7896af77e1c4750dcc9f29ffaafbd56bba80f`.
The 41-file manifest SHA-256 is
`e2fc8115c9cb7d6455027dc2af6f29229834774ed12de334832a8d53d6fc6262`.
- Public Browser asset `assets/index-Y4pFjW_S.js` has SHA-256
`a3df27613dfb10fb45297197afdf2500e2953c2b5330b13e5bb49eafdc3e1447`.
`55b9779fdaf4fe2ba98d5612b16e398fbf2ad6b9ebcfeeeef6e0ce5c3595ac8d`.
- Public Browser asset `assets/index-CPECbwjN.js` has SHA-256
`dfcbed5d4178afbd7f6713f1ae348a70921b936199fa56d87fa05c785564559e`.
Public `/healthz` returns 200. Release postflight found Screener, LiveKit,
coturn, and nginx active with zero restarts.
@@ -26,8 +26,9 @@ may be interpreted. Git and pull requests own routine completed checks.
room-lived Viewer grants, `open | private` code entry, invitation-only private
rooms, password entry, grant rotate/revoke, atomic room replacement and
`ROOM_NOT_FOUND` have passed source gates. Production stable mode retained an
exact Host authority across a controlled restart and then deleted the test
room through the normal lifecycle.
exact Host authority across a controlled restart. A current-production
restart also preserved the Host-owned capture and restored Viewer media
without refreshing either page.
- Endpoint capacity `1/2/3`, default `2`, one active upstream, acyclicity,
source reachability, deterministic parent order, exact candidate identity,
transport-connected wake extension, first-decoded-frame commit, strictly newer
@@ -59,7 +60,7 @@ may be interpreted. Git and pull requests own routine completed checks.
## Current Source Evidence
- Canonical source contains the same application tree deployed from `3037c0a`:
- Canonical source contains the same application tree deployed from `8164102`:
the adaptive H.264/VP8 sender decision with `contentHint = "motion"` reapplies the
selected video profile after answer negotiation, leaves SFU representation
construction to pinned LiveKit, keeps Dynacast/send-side BWE, disables
+5 -5
View File
@@ -2,7 +2,7 @@
## 2026-08-18 14:45
状态:production 与 canonical source 使用严格 `screener-v12`,均已实现每分享代次的 H.264 sender gate 与 VP8 fallback;两者都使用 video `contentHint = "motion"` 和 audio `music`。本阶段接受但尚未实现 Host 本地 pre-share `VP8 | 自动 | H264` selector,以及 SQLite stable room authority、无期限 preferred code 和换房控制;这些不新增 codec wire、自定义 SFU layer list或应用媒体质量 controller。SFU 由 pinned LiveKit 默认 representations、Dynacast、server send-side BWE 和 disabled AdaptiveStream 拥有质量适配;screen audio 使用 64/128/192 kbps ceiling、stereo、DTX off 与 RED off。精确执行状态由 `docs/status.md` 持有。
状态:production 与 canonical source 使用严格 `screener-v12`,均已实现每分享代次的 H.264 sender gate 与 VP8 fallback、本地 pre-share `VP8 | 自动 | H264` selector、可选 SQLite stable room authority、无期限 preferred code 和换房控制;这些不新增 codec wire、自定义 SFU layer list或应用媒体质量 controller。Video 使用 `contentHint = "motion"`,audio 使用 `music`。SFU 由 pinned LiveKit 默认 representations、Dynacast、server send-side BWE 和 disabled AdaptiveStream 拥有质量适配;screen audio 使用 64/128/192 kbps ceiling、stereo、DTX off 与 RED off。精确执行状态由 `docs/status.md` 持有。
## 目标与边界
@@ -11,14 +11,14 @@
本阶段必须形成完整的最小纵向链路:
- 分享者主动选择屏幕、窗口或标签页,并请求可用音频;
- 创建四位随机数字房间;production site access 密码严格控制建房/发布和 code-only 观看,默认 room-scoped capability link 让受邀 Viewer 直接观看,房间控制状态只驻留当前进程内存;
- 创建四位随机数字房间;production site access 密码严格控制建房/发布和 code-only 观看,默认 room-scoped capability link 让受邀 Viewer 直接观看;轻量模式把房间权威留在当前进程,可选 SQLite stable 模式只持久化房间权威 aggregate;
- 每名观看者拥有独立的 `RTCPeerConnection`;
- 当前 production 要求 STUN,普通 peer ICE 只发 STUN servers;source 与 production 的 Browser SFU PC 使用空 ICE-server 列表、保留 LiveKit signaling 的 UDP candidates 并由标准 ICE 选择;
- 展示连接状态、direct/relay、RTT、实际码率、帧率和丢包等数据;
- 超过部署配置上限的观看者被明确拒绝,不静默超载;
- 在代码、测试和真实浏览器验证完成前,不宣称达到延迟或画质目标。
首个基线不实现账号数据库、通用业务数据库、房间数据库、Redis、语音聊天、Electron、原生捕获、PWA、录制、遥测平台或多地区调度。房间控制状态只驻留当前应用进程;peer-assisted/SFU candidate 只通过 ADR-0004/0005 的现有窄 owner 扩展,不引入第二套路由器或通用优化框架。
当前不实现账号数据库、通用业务数据库、Redis、语音聊天、Electron、原生捕获、PWA、录制、遥测平台或多地区调度。可选 SQLite 只保存 ADR-0002 定义的房间权威,不保存参与者、路由或媒体;peer-assisted/SFU candidate 只通过 ADR-0004/0005 的现有窄 owner 扩展,不引入第二套路由器或通用优化框架。
## 技术栈决策
@@ -160,7 +160,7 @@ accepted 的唯一 wire 是 `screener-v12`,不做版本协商、旧 parser、
| 1080p30 | 1920x1080、30 fps | 5 Mbps/peer |
| 1080p60 | 1920x1080、60 fps | 8 Mbps/peer |
浏览器可返回更低设置,界面以 `track.getSettings()` 显示实际捕获结果。推荐区始终只有表中的三档并默认选择中档 `1080p30`;高级分辨率额外提供 `480p`(854x480)、720p、1080p 与 1440p,FPS 继续独立接受 15 至 60 整数,码率继续独立接受 2 至 12 Mbps,因此不建立第四个推荐档或 `480p` preset ID。视频 track 使用标准 `contentHint = "motion"` 表达游戏动态意图;返回的音频 track 使用 `contentHint = "music"`。默认 `1080p30` 与折叠高级区初始值采用 `degradationPreference = "maintain-resolution"`,1080p60 与 720p30 推荐档采用 `balanced`,并允许显式选择 balanced 或 fluid。“分享高级设置”不提供 codec 选择,并提供离散的 `96 kbps 普通默认 | 128 kbps 音乐 | 192 kbps 保真` 屏幕音频 sender ceiling。音频 ceiling 可在直播中串行更新。这些设置只是 ceiling 和浏览器偏好,不保证实际分辨率、帧率、码率或声道。
浏览器可返回更低设置,界面以 `track.getSettings()` 显示实际捕获结果。推荐区始终只有表中的三档并默认选择中档 `1080p30`;高级分辨率额外提供 `480p`(854x480)、720p、1080p 与 1440p,FPS 继续独立接受 15 至 60 整数,码率继续独立接受 2 至 12 Mbps,因此不建立第四个推荐档或 `480p` preset ID。视频 track 使用标准 `contentHint = "motion"` 表达游戏动态意图;返回的音频 track 使用 `contentHint = "music"`。默认 `1080p30` 与折叠高级区初始值采用 `degradationPreference = "balanced"`,并允许显式选择清晰、均衡或流畅。高级设置在未分享时提供 `VP8 | 自动 | H264`,分享期间锁定;屏幕音频提供离散的 `64 kbps 普通 | 128 kbps 音乐默认 | 192 kbps 保真` sender ceiling。音频 ceiling 可在直播中串行更新。这些设置只是 ceiling 和浏览器偏好,不保证实际分辨率、帧率、码率或声道。
每次 `setParameters()` 都必须从当次 `getParameters()` 派生更新,然后读回 requested/applied `maxBitrate`、`maxFramerate`、`scaleResolutionDownBy` 和 degradation preference。浏览器负责具体降级,实际结果由 stats 观测;拒绝或浏览器改写必须进入可见状态,不能只写 `console.warn`。
@@ -365,7 +365,7 @@ Token-free SFU standby prewarm 保持可选且不创建 participant、publicatio
## 当前交付边界
- Production 与 canonical source 使用严格单一 `screener-v12` wire。每分享代 actual-sender gate 在证据通过时让未来 direct/relay 和 Host 单一 SFU publication 使用 H.264,否则使用 VP8。当前 production 尚无手动 selector;本阶段实现本地 pre-share `VP8 | 自动 | H264` 并保持 quality-state/wire、自定义 SFU layer list、backup publication与健康 edge codec 切换不存在。Browser SFU 使用 pinned LiveKit 默认 representations、Dynacast、server send-side BWE、disabled AdaptiveStream 与 RED off;精确执行状态由 `docs/status.md` 持有。
- Production 与 canonical source 使用严格单一 `screener-v12` wire。每分享代 actual-sender gate 在证据通过时让未来 direct/relay 和 Host 单一 SFU publication 使用 H.264,否则使用 VP8。本地 pre-share `VP8 | 自动 | H264` selector 已实现并在分享期间锁定;quality-state/wire、自定义 SFU layer list、backup publication与健康 edge codec 切换不存在。Browser SFU 使用 pinned LiveKit 默认 representations、Dynacast、server send-side BWE、disabled AdaptiveStream 与 RED off;精确执行状态由 `docs/status.md` 持有。
- Pause/Resume 由当前 Host session 和 `shareGeneration` 约束。Host 后台性能和 VP8 hardware acceleration 分别按受控根因证据诊断;标准 ICE/SFU 按真实异构网络验收。
- 当前发布面只包含 Web Host、Viewer 和 browser relay。Native sender、capture helper、shared-encode executable 与两个分发包仍由各自后续决策和物理证据驱动,不属于当前 Browser release。
+1 -1
View File
@@ -27,7 +27,7 @@ SFU 的 publication、subscription、server-resource accounting、临时 overlap
- 端点发送容量:所有非服务器端点使用同一个服务端权威稳态 outbound media-copy capacity,由唯一部署项 `ENDPOINT_MEDIA_COPY_CAPACITY` 配置,默认 `2` 且只接受 `1`、`2` 或 `3`;不读取旧键或保留第二套 tier。peer child 或 Host 的唯一 SFU publication 各消耗一份,上游接收不消耗该预算。客户端只能声明不高于部署值的可用容量,角色、浏览器、UA、设备类型和页面可见性不产生不同上限。服务端在每次 `authenticated` 权威快照中返回最终 `endpointMediaCopyCapacity`;普通非 peer-assisted 模式也只授权稳定加入顺序中的至多 `C` 个 Host child,超额 Viewer 保持入房等待并在槽位释放后提升,信令不能绕过该集合。Browser Host 与 Viewer 严格校验该值并用它约束稳态 assignment;并行产生媒体的 candidate 必须持有唯一 `TransitionOverlapSlot`,active+pending 物理 copy 只可达到 `min(C + 1, 3)` 并在 commit/abort 后释放。`C=3` 满载时必须先收到 active bounded-gap retirement 再 prepare,绝不产生第四份 copy。SFU subscriber egress 由独立 server admission 记账。当前不兼容房间合同以单一 release 原子升级;缺失/非法 cap、旧 wire 与 executable sender都在取得room/route authority前fail closed。
- P2P 优先:每条普通逻辑媒体边先尝试 direct/peer UDP,服务器不得默认中转可直连媒体。更多观看者优先沿满足统一端点容量的无环 peer 图分布;每名 Viewer 同时至多一个 active upstream,失败时只影响需要重选的 child。任何 source-reachable endpoint 在 effective capacity 有空位时都可作为 parent candidate;Peer-fed 与 SFU-fed endpoint 使用同一个 provisional-child transaction,并在 candidate child 解码出第一帧新画面后独立提交该 edge。Host 已满且首次需要 SFU 时,同一 controller 用有限 carrier cursor 建立唯一 publication;carrier 必须能在既有 overlap 上限内完成,不能先切断健康分支。终态归原始等待需求,publication 成功后该需求先取得 SFU 画面,剩余 direct candidate 再在后台收敛;完整事务由 ADR-0005 定义。
- NAT 与传输边界:STUN 必须可用,所有 ordinary peer `RTCPeerConnection` 只接收 STUN ICE servers。Browser LiveKit publisher/subscriber PC 必须显式使用空 `iceServers`,保留 LiveKit signaling 交付的授权 SFU UDP candidates,并由标准 ICE 选择 nominated non-relay pair;不得把 ordinary-peer STUN、LiveKit join response 中的 STUN 或默认公共 STUN混入该 PC,也不得把某一种 candidate type 或数量写成成功条件。部署 STUN 仍供 ordinary peers 和 LiveKit server-side 公网地址发现使用。浏览器 ICE connectivity checks 是 direct UDP 可达性的权威;Screener 不合成 remote candidate、不预测端口、不分类 NAT,也不使用 TCP 探测推断 UDP 媒体可达性。所有恢复和候选授权必须绑定 authenticated room/endpoint session、share generation、base/pending route revision 与 candidate connection identity;成功即停止,失败、超时或绑定失效即撤销,direct 阶段耗尽后在同一 operation deadline 内进入 SFU/UDP,全部可用路径耗尽后明确等待或失败。已接受的完整顺序只有保持或尝试 direct/STUN peers,再复用或建立 direct-ingress Host publication/SFU subscription。Screener 不配置 TURN、ICE/TCP、media TCP 或 TLS-relayed media;当前 LiveKit 也保持 UDP-only。未来若真实严格防火墙矩阵证明需要另一种传输,必须把它作为 LiveKit 内部能力重新决策,不能增加第三种应用 route candidate。LiveKit join JWT 仅按协议进入不得记录的 WebSocket request target;HTTPS/WSS 继续独立使用 TLS/TCP。
- 质量控制:推荐档位始终只有 720p30(3 Mbps)、1080p30(5 Mbps)、1080p60(8 Mbps)三档,默认选择中档 `1080p30`。高级设置的分辨率选项增加 `480p`(854x480),其 FPS 与码率仍由高级控件独立选择;不增加 `480p` 推荐档、preset ID 或固定的 480p FPS/码率组合。三档与高级设置初始值统一采用中档 `balanced`(均衡),`maintain-resolution`(清晰)、`maintain-framerate`(流畅)保留为显式选择。Browser Host 在每个新分享代次运行一次有界实际 sender 预检,Relay 在该代首次取得 upstream source 且尚无 child 时运行同一预检;H.264 源帧与编码帧进度通过时,未来 direct/relay offer 原生排序 H.264 后接 VP8,Host 的唯一 SFU publication 也使用 H.264;不支持、证据不足、超时或表现不达标时只使用 VP8。界面、质量状态和 wire 不提供 codec 选择,结果不持久化,也不因质量设置、换源或换父重复探测或切换已连接线路。分享期间可直接换视频档位和 64/128/192 kbps 音频 sender ceiling,音频默认选择中档 128 kbps,不重新选择来源或重建健康连接。Peer-assisted relay 与已配置的 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,具体降级由 WebRTC/LiveKit 决定;必须读回实际 sender 参数并通过 stats 观测结果、显示参数拒绝、改写和持续受限原因。Host 资源不足时由 Host 主动选择较低的现有档位。Screener 不设置自定义 SFU layer list、动态 FPS、综合评分或周期性全房间调档。
- 质量控制:推荐档位始终只有 720p30(3 Mbps)、1080p30(5 Mbps)、1080p60(8 Mbps)三档,默认选择中档 `1080p30`。高级设置的分辨率选项增加 `480p`(854x480),其 FPS 与码率仍由高级控件独立选择;不增加 `480p` 推荐档、preset ID 或固定的 480p FPS/码率组合。三档与高级设置初始值统一采用中档 `balanced`(均衡),`maintain-resolution`(清晰)、`maintain-framerate`(流畅)保留为显式选择。Browser Host 在未分享时提供本地 `VP8 | 自动 | H264` 选择并在分享期间锁定;自动模式每个新分享代次运行一次有界实际 sender 预检,Relay 在该代首次取得 upstream source 且尚无 child 时运行同一预检。H.264 源帧与编码帧进度通过时,未来 direct/relay offer 原生排序 H.264 后接 VP8,Host 的唯一 SFU publication 也使用 H.264;不支持、证据不足、超时或表现不达标时只使用 VP8。Resolved codec 不进入质量状态或 wire、不持久化,也不因质量设置、换源或换父重复探测或切换已连接线路。分享期间可直接换视频档位和 64/128/192 kbps 音频 sender ceiling,音频默认选择中档 128 kbps,不重新选择来源或重建健康连接。Peer-assisted relay 与已配置的 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,具体降级由 WebRTC/LiveKit 决定;必须读回实际 sender 参数并通过 stats 观测结果、显示参数拒绝、改写和持续受限原因。Host 资源不足时由 Host 主动选择较低的现有档位。Screener 不设置自定义 SFU layer list、动态 FPS、综合评分或周期性全房间调档。
- 路径隔离表示:direct/peer 继续使用每条 PeerConnection 独立的 stock WebRTC GCC,不把 Viewer 反馈合成全房目标。Browser Host 的唯一 SFU publication 不设置 `screenShareSimulcastLayers`,由锁定的 LiveKit client 默认行为拥有当前 H.264 或 VP8 source decision 对应的 screen-share representations、source replacement 与 republish;LiveKit Dynacast 和启用 send-side BWE 的 server stream allocator 负责需求聚合与每个 subscriber 的实际下行层。Screener 不实现质量评分、带宽估计、周期调档或应用媒体层 selector。所有当前 Viewer 都可能承担 peer children,因此 subscriber 保持 `adaptiveStream: false`,不得让本地元素尺寸或可见性控制 relay ingress。表示选择不改变拓扑、端点容量或路由失效权威。
- 路由失效输入:应用不推断 parent-wide 质量,也不计算综合分数。当前 authenticated child 只在 PeerConnection `failed/closed`,或非权威暂停时现有 periodic stats 在统一 deadline 内没有新 decoded frame 时,使自己的 exact edge 失效并唤醒同一个 reconcile。decoded progress 只保存 route-keyed `lastDecodedAt`,route/connection变化或新帧重置、暂停抑制,同一 edge只报告一次,不增加轮询器。Active SFU route 暂时没有 video track、stats report 为空或 stats 读取失败时,现有采样 cadence 仍把该次作为无 decoded progress 推进 deadline;它不得停表、伪造连接指标或立即判定 edge 失效,只有 route 停用或 teardown 才停止该 cadence。该 operation 把失败的 exact direct parent 在当前 fact version 视为已尝试;foreground direct window结束后用SFU取得可用画面,剩余direct candidate只在画面后方收敛,新的外部事实才允许本轮失败tuple重新进入。Track availability、低码率、低 FPS、模糊、sender/viewer stats 与 SFU layer只用于stock WebRTC/LiveKit恢复和诊断。relay自身ingress失效时,它作为child换父并保留subtree;disconnect或effective downstream capacity降低只让direct/overflow children逐个走同一事务。
- 断线恢复:临时网络变化先交给 WebRTC/LiveKit 的 ICE、consent、重连和流状态。Direct edge 可在 exact current parent/child sessions 内采用 framework rebuild 的新 connection ID而不改变topology。应用只在上述exact edge失效后运行一次有界reconcile;任一步成功即停止。候选耗尽时hard-invalid、confirmed-departed或overflow edge被退休并进入明确waiting,healthy bootstrap edge保留。所有异步结果受authenticated session、share generation、base/pending route revision与candidate connection identity约束,未受影响分支保持sticky。权威暂停abort未提交candidate、保留active graph、抑制stall,并在resume后重新reconcile。