From 463e6ea7c28784330b9f8aabc5b154a946a51273 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Thu, 20 Aug 2026 03:18:55 +0800 Subject: [PATCH] docs: record access v2 production migration (#57) --- AGENTS.md | 1 + README.md | 7 +++---- docs/deployment.md | 23 ++++++++++++++++++++--- docs/project-memory.md | 15 +++++++-------- docs/status.md | 28 ++++++++++++++-------------- 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7bff4cc..b713ef9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -63,6 +63,7 @@ - Add automated tests in proportion to behavioral risk. Block the current phase on P0 security, privacy, irreversible data, authorization, generation, bounded-resource, and rollback failures, plus P1 failures of the milestone's core user path. Record or cover P2/P3 rare environments, diagnostic detail, and minor UI edges cheaply without turning them into canary blockers unless they can cause P0 harm. Use high-repeat race runs only for a reproduced concurrency failure. For realtime media changes, also document the manual network and browser matrix used. - Batch validation by risk and phase: run narrow checks while iterating, and run expensive full suites, browser matrices, endurance tests, or deployment checks only at an acceptance boundary or when a relevant change invalidates prior evidence. Documentation-only or unrelated changes do not invalidate media-path evidence. - A dedicated deployment subagent owns each staged production deployment and verification while root/mainline continues; root supplies the exact merged SHA, artifact hash, rollback target, and acceptance list, and the deployer must not expand scope. +- Immutable production releases must not share hard-linked dependencies or other permission-mutated files with a rollback release. Copy or reflink an independent tree, verify zero shared regular-file inodes, and only then change ownership or switch the release symlink. - Use comments for non-obvious rationale, invariants, protocol constraints, and workarounds; do not narrate obvious code. - Use WebRTC statistics and reproducible measurements for latency, bitrate, candidate type, packet loss, encode time, and quality limitations. Do not claim performance from assumptions alone. - Keep repository scripts and hooks cross-platform, deterministic, fast, and runnable in CI. A hook must call a tracked script rather than hide project logic in machine-local configuration. diff --git a/README.md b/README.md index 97ba1ba..fff1579 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Screener is a private, low-latency screen-sharing project for one game player an The first measurable Web proof of concept is implemented. A host captures a screen, window, or browser tab and shares a numeric room code. Rooms default to a private fragment invitation whose capability authorizes only one room; a Host may explicitly choose public watching by room code. During a share, the host can change between three ceiling profiles without reopening the source picker: 1080p60 at 8 Mbps, 1080p30 at 5 Mbps, and 720p30 at 3 Mbps. The host can also temporarily pause the picture or switch sources. Rooms are random and temporary by default. A Host-protected deployment can optionally use SQLite-backed room numbers starting at `1`, reusable links, and a waiting state after sharing stops. Default routing gives each viewer an independent direct WebRTC connection; the exact-room controller can instead use bounded peer descendants and LiveKit SFU roots. The default room capacity is eight viewers and deployments may configure 1 through 16, but that admission limit is not a performance promise. -This is not yet a production release. Production runs the bounded SFU failure-stage diagnostic from `9610032`; the repository uses self-hosted STUN-only ordinary ICE plus a separate LiveKit SFU/UDP domain. Real cross-network media, game audio, a sustained 1:8 room, mobile browser lifecycle behavior, and latency targets still require the documented manual test matrix. +This is not yet a broadly validated production release. Production runs `bbe4654` with room-scoped Viewer access and the bounded SFU failure-stage diagnostic; it uses self-hosted STUN-only ordinary ICE plus a separate LiveKit SFU/UDP domain. Real cross-network media, game audio, a sustained 1:8 room, mobile browser lifecycle behavior, and latency targets still require the documented manual test matrix. ## Run locally @@ -26,9 +26,8 @@ must remain on `localhost` or HTTPS because screen capture requires a secure context. The local default has no STUN, so cross-network use still requires HTTPS and the production ICE configuration documented separately. -Before deploying this access migration, take the stopped v1 SQLite backup and -rehearse its restore as documented in [Deployment](./docs/deployment.md). After -deployment, `HOST_ADMISSION_PASSWORD` authorizes only creation and Host role; +Production keeps a stopped v1 SQLite backup and restore boundary as documented +in [Deployment](./docs/deployment.md). `HOST_ADMISSION_PASSWORD` authorizes only creation and Host role; private Viewers use an expiring room-scoped fragment grant, while explicit public-watch accepts a room code. The release has no v1 parser, old cookie, or dual-schema runtime. Ordinary ICE contains only self-hosted STUN, LiveKit serves diff --git a/docs/deployment.md b/docs/deployment.md index e4ebc8a..6d01f55 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -14,9 +14,9 @@ current controller's automatic final media fallback. This capacity is dormant un the peer-assisted experiment, or required STUN discovery. It is configured with ICE/UDP only; the candidate has no ordinary TURN or media-TCP configuration. -Production `d6c8aa06dbd` runs this candidate for exact room `1` on the existing -shared public IP. The unchanged `769de201f7cc` release, environment backup and -coturn relay remain rollback-only. This is a bounded production smoke, not +Production `bbe4654a7a9b` runs this candidate for exact room `1` on the existing +shared public IP. The unchanged `9610032fc5f5` release, v1 database/environment +backup, and coturn relay remain rollback-only. This is a bounded production smoke, not clean-port or broad-rollout acceptance; the media procedures below still apply. ## Topology and prerequisites @@ -47,6 +47,13 @@ npm run check npm start ``` +Each immutable release must own an independent dependency tree. Never hard-link +`node_modules` or another file that deployment may `chown`, `chmod`, replace, or +remove: metadata changes would also mutate the rollback release. A copy or +copy-on-write reflink is acceptable only after an inode audit confirms that the +old and new regular-file sets have zero shared inodes. Do not recursively change +permissions until that check passes. + Run `npm start` under a service supervisor that injects the environment, restarts on failure, and applies bounded logs. For a simple untracked environment file, the equivalent direct launch is @@ -252,6 +259,16 @@ v2 database separately for diagnosis, restore the exact v1 backup with service ownership and mode `0600`, verify integrity and `user_version = 1`, and only then start the old binary. Never point the old binary at the migrated v2 file. +Production completed this migration at 2026-08-20 03:06 +08 on exact +`bbe4654a7a9b`; its artifact SHA-256 is +`3C09AF81BCE68B51DD9E36E1C253A880D8D6F24BB498A090CF815AB196C392AA`. +A stopped, read-only-verified v1 backup retained all four rooms; +the v2 integrity/schema and locked-private digest checks passed, including room +`1`. Host admission and a cookie-free private Viewer denial also passed. An +earlier artifact attempt was rolled back after hard-linked dependencies let a +permission change make the rollback release unreadable for 3m11s; the final release has an +independent dependency tree and zero shared regular-file inodes. + Enabling persistence does not migrate rooms that existed only in memory. The deployment restart invalidates those temporary links; the first subsequently created persistent room receives ID `1`. diff --git a/docs/project-memory.md b/docs/project-memory.md index 38373ef..021d3f7 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -33,21 +33,20 @@ Last updated: 2026-08-20 - Production reports poor film audio and self-echo when system capture includes voice software. Diagnose audio A/B/C and sync; Web cannot isolate arbitrary processes and `maxBitrate` is not quality-up. A Windows 11 native candidate defaults to game-process-tree audio and never widens silently; Windows 10 remains unresolved/unsupported. See `docs/research/browser-screen-audio-quality.md`. - Candidate UI has local Viewer volume/mute, copyable room codes, and a favicon. Names/roster, endpoint details, and RTP loss remain pending; no wire, media, or routing effects. - Do not add scene detection, dynamic-FPS control, or forced AV1 without negotiation, encode, game, CPU/GPU, and sender evidence. -- ADR-0002 access passed independent review/full check and awaits PR/CI before native work: `HOST_ADMISSION_PASSWORD`, `screener-v2`, private grants/public-watch, rotate/revoke, and one nullable digest. No username/roster runtime. +- ADR-0002 access is deployed: `HOST_ADMISSION_PASSWORD`, `screener-v2`, private grants/public-watch, rotate/revoke, and one nullable digest. No username/roster runtime. - Deferred architecture audit: `docs/maintenance.md`. ## Current Implementation - The repository is one npm package using Node.js 24, React, TypeScript, Vite, native WebRTC, `ws`, Zod, Vitest, and separate coturn. -- Production `9610032fc5f55a946853f3687c6ffd15f89a2105` deployed at 2026-08-20 02:26 +08 to `https://share.bonfire.icu` and enables peer/SFU routing only for persistent room `1`; unlisted rooms remain ordinary P2P. Its bounded SFU failure-stage diagnostic is active, while the old release is retained only for rollback. +- Production `bbe4654a7a9b10b1bdcc839665932020b7f31e8b` deployed at 2026-08-20 03:06 +08 to `https://share.bonfire.icu`. It enables peer/SFU routing only for persistent room `1`; unlisted rooms remain ordinary P2P. The bounded SFU failure-stage diagnostic stays active, while `9610032` is rollback-only. - Ordinary ICE is process-wide STUN-only. Room `1` may use one LiveKit publication feeding at most two roots while retaining peer descendants; real 1/2-root media and game quality remain unverified. -- Production still uses optional `ACCESS_PASSWORD`, a 12-hour stateless HMAC HttpOnly Strict cookie for both roles, internal Host token, role-bound signaling, Origin/payload checks, and no accounts/JWT/session map. Its SQLite database stores only the room ID and Host-token digest; room `1` survived deployment. -- Production uses nginx, Node.js 24.19.0, coturn 4.17.2 for STUN/rollback, and pinned LiveKit 1.13.5 on UDP 7882 with TCP fallback disabled. Screener and LiveKit report `NRestarts=0`; local/public health return 200, the deployed asset is `index-DbGEy9hr.js`, and `/rtc`, `/rtc/validate`, and `/rtc/v1` return 404. Public TCP 7880/7881 is blocked; LiveKit has a 192/256 MiB cgroup boundary and no automatic restart. +- Production requires an independent `HOST_ADMISSION_PASSWORD` only for creation/Host role. Default private fragment grants and explicit public-watch authorize Viewers; signaling is role-bound and there are no accounts/JWT/session rows. SQLite v2 stores Host and nullable Viewer-grant digests; all four rooms, including room `1`, survived the stopped migration. +- Production uses nginx, Node.js 24.19.0, coturn 4.17.2 for STUN/rollback, and pinned LiveKit 1.13.5 on UDP 7882 with TCP fallback disabled. Screener and LiveKit report `NRestarts=0`; local/public health and `index-uCv4ooZJ.js` return 200, while `/rtc`, `/rtc/validate`, and unauthenticated `/rtc/v1` return 404 outside the SPA. Public TCP 7880/7881 is blocked; LiveKit has a 192/256 MiB cgroup boundary and no automatic restart. - Old TURN parser/signer/refresh/UI/SNI artifacts are absent; coturn and the old release are rollback-only, not candidate media. -- ADR-0005 is configured with exact `PEER_ASSISTED_ROOM_IDS=1`; unlisted rooms use ordinary P2P. `screener-v1` remains the deployed signaling literal until this separately reviewed access migration is deployed. -- Production logs show two root participants and two short Host participants with no service restart or retained track. Their timing is consistent with, but does not directly prove, the code's one fresh-grant retry and Peer failback. The current candidate exposes only the Host-local current-revision failure stage (`connect|source|video-publish|sender-config|audio-publish|transport`) and uploads no raw error or endpoint data. Browser relays still re-encode; native sharing remains outside product code. -- The repository access candidate requires `HOST_ADMISSION_PASSWORD` in production for creation/Host role only; private Viewer admission is room-scoped, with no accounts/JWT/session map. SQLite v2 stores one nullable Viewer-grant digest beside the Host digest. Its v1 migration locks every prior room private and requires a stopped v1 backup before deployment. -- Candidate signaling is only `screener-v2`; an incompatible first message receives one refresh outcome before room lookup. It has no old parser or translator. Candidate media remains STUN-only ordinary ICE plus separate LiveKit UDP; it does not change the deployed room-1 smoke until the atomic access release is deployed. +- ADR-0005 is configured with exact `PEER_ASSISTED_ROOM_IDS=1`; unlisted rooms use ordinary P2P. `screener-v2` is the only deployed signaling literal; there is no v1 parser or translator. +- Production logs show two root participants and two short Host participants with no service restart or retained track. Their timing is consistent with, but does not directly prove, one fresh-grant retry and Peer failback. The Host exposes only the local current-revision failure stage (`connect|source|video-publish|sender-config|audio-publish|transport`) and uploads no raw error or endpoint data. Browser relays still re-encode; native sharing remains outside product code. +- The retained v1 backup is the only schema rollback path. A failed artifact attempt exposed unsafe hard-linked dependency reuse; the successful immutable release has zero shared regular-file inodes with rollback, and future releases preserve that boundary. - Chrome 151/LiveKit localhost A/B cut failure-to-active/render from 1.481/2.257 seconds to 0.200/0.320; 31 new frames and 25 ms sampling kept host edges at two. It is headless synthetic 720p30 and includes SDK/network prewarm, not public-network evidence. - Native drafts remain experiments: one WebCodecs object is not hardware proof; stock GCC+RTX is no-go. - Chrome 151 synthetic topology/quality-control runs kept fanout 2/1 and all viewers decoding; one relay close recovered in 5.32 seconds. This is control evidence only. diff --git a/docs/status.md b/docs/status.md index 55e9c12..083aa74 100644 --- a/docs/status.md +++ b/docs/status.md @@ -4,21 +4,20 @@ Last updated: 2026-08-20 ## Phase -`https://share.bonfire.icu` runs `9610032fc5f55a946853f3687c6ffd15f89a2105` -since 2026-08-20 02:26 +08 as a shared-IP production smoke. It includes the -bounded SFU failure-stage diagnostic. +`https://share.bonfire.icu` runs `bbe4654a7a9b10b1bdcc839665932020b7f31e8b` +since 2026-08-20 03:06 +08 as a shared-IP production smoke. It includes +room-scoped Viewer access and the bounded SFU failure-stage diagnostic. Ordinary ICE is STUN-only process-wide; persistent room `1` alone enables the automatic peer/SFU-UDP controller with at most two SFU roots. The old -`769de201f7cc` release and coturn relay remain an isolated rollback path. +`9610032fc5f5` release, v1 backup, and coturn relay remain rollback-only. Coherent low-risk milestones may deploy after narrow tests, independent review, one full gate, CI and rollback preflight; protocol/database changes stay atomic. ## Current Snapshot - Capture precedes room creation; live source/quality changes preserve peers and picture pause keeps audio/connections. Quality defaults to `maintain-resolution` with balanced/fluid options. -- Production still uses optional `ACCESS_PASSWORD` and one stateless 12-hour HMAC HttpOnly Strict cookie for both roles; Host auth stays internal and signaling role-bound. Its SQLite database stores only the room ID and Host-token digest. -- This repository candidate atomically replaces that access contract with production-required `HOST_ADMISSION_PASSWORD` for creation/Host role, `screener-v2`, default private fragment grants, explicit public-watch, and generation-bound rotate/revoke. Independent review and the 357-test full check pass; it is ready for PR/CI but remains unmerged and undeployed. -- Candidate SQLite v2 keeps Host and nullable Viewer-grant digests in one checked `STRICT` row; transactional v1 migration locks each old room private without minting a raw grant. Deployment requires a stopped v1 backup and restore rehearsal. +- Production requires `HOST_ADMISSION_PASSWORD` only for creation/Host role and uses `screener-v2`, default private fragment grants, explicit public-watch, and generation-bound rotate/revoke. Viewers never need the Host secret; there is no account/session table or old parser. +- SQLite v2 keeps Host and nullable Viewer-grant digests in one checked `STRICT` row. The stopped migration retained four rooms and locked each old room private without minting a raw grant. - PR #44's exact-room STUN-only/SFU-UDP router is enabled only for production room `1`: host/SFU roots <=2, browser relay <=1, bounded failure. HTTPS/WSS stays TLS/TCP. - Fallback prewarm is token-free and limited to room `1`; mobile/iPad viewers are leaves and healthy edges stay sticky. - Room `1` can publish exactly `HIGH+LOW` with Dynacast/backup codec off and subscriber `HIGH` ceilings; it has no retained real media frame. @@ -26,13 +25,14 @@ one full gate, CI and rollback preflight; protocol/database changes stay atomic. ## Verified Evidence -- `9610032` deployed without changing environment, SQLite, nginx, nftables, LiveKit, or coturn. Screener and LiveKit have `NRestarts=0`; local/public health are 200, `index-DbGEy9hr.js` is active, `/rtc`, `/rtc/validate`, and `/rtc/v1` are 404, and public TCP 7880/7881 is blocked. The preceding release remains rollback-only. +- `bbe4654` passed 365 tests/builds and an atomic v1-to-v2 production migration. Local/public health, `index-uCv4ooZJ.js`, nginx, Host admission, and cookie-free private Viewer denial pass; room `1` remains. LiveKit was not restarted; unversioned `/rtc` paths and unauthenticated `/rtc/v1` return 404 outside the SPA. +- A rejected artifact reused hard-linked dependencies; its permission change made rollback unreadable for 3m11s and restart attempts peaked at 52. Restoring ownership recovered `9610032`; the successful artifact has zero shared regular-file inodes. Immutable releases now forbid this reuse. - On the 960 MiB host, idle LiveKit peaked near 16 MiB with no cgroup high/max/OOM event under `MemoryHigh=192M`, `MemoryMax=256M`, swap disabled and restart disabled. This is containment/idle evidence, not media capacity. - Chrome 151 synthetic `1/3/5/8` and 720p30 quality-change runs kept 2/1 fanout and decoding; slowest first frame was 1.05 seconds and one relay close recovered in 5.32 seconds. This is control evidence only. -- A mobile-network room-1 run produced two root participants for about 4.6 seconds and two short Host participants (about 0.46/0.27 seconds), all client-requested leaves, with zero service restarts and no retained track. This is consistent with the code's one fresh-grant retry and Peer failback, but logs cannot prove those transitions. The next candidate shows only the local failure stage. +- A mobile-network room-1 run produced two root participants for about 4.6 seconds and two short Host participants (about 0.46/0.27 seconds), all client-requested leaves, with zero service restarts and no retained track. This is consistent with the code's one fresh-grant retry and Peer failback, but logs cannot prove those transitions. The deployed Host now shows only the local failure stage. - Host A+B and authenticated P2P Viewer C are sanitized, generation-bound, read-only, and fail closed for stale, ambiguous, or SFU-fed evidence; no raw media metadata is retained. - SVC is `no-go-web-svc-cross-path-hardware-contract`: no direct/peer selection, cross-PC shared encode, or portable hardware proof; pinned screen share is `L1T3`. No browser run was warranted. -- HTTPS/WSS, access and room/WebSocket auth, renewal, public STUN, and authenticated TURN/UDP/TCP relay-only paths pass; TURN/TLS is off. +- HTTPS/WSS, Host admission, room/WebSocket authorization, renewal, and public STUN checks pass. Current media has no TURN credential wire; coturn relay evidence belongs only to rollback. - Draft #16/#18/#22/#23/#25/#28 passed isolated experiments only. ADR-0006 reached host setup/one encoder output, then timed out before viewer 1 decoded/rendered. - Access focused tests pass protocol/config/HTTP/storage/RoomStore/SQLite/signaling, including commit-first teardown, persistence-failure continuity, relay/SFU retirement, grant bounds, and v1 migration rollback. @@ -47,19 +47,19 @@ one full gate, CI and rollback preflight; protocol/database changes stay atomic. - Browser fanout is host two/viewer one; any accepted endpoint relay stays capped at two downstream edges. - PR #49/#50 BWE is unverified/default-off: test zero-child leaves, then root impact. No local per-leaf UDP shaper; resume with Linux `tc` or public canary, never CDP. Web P2P/SVC shortcuts remain no-go. - The corrected standby smoke is localhost/headless/video-only; public DNS/TLS reuse, transport, audio, shaping, load, mobile, endurance, and sub-25 ms overlap remain open. -- Scoped Viewer access is implemented but unmerged/undeployed. Remaining deployment gates are nginx load/rate limit, browser storage/request/log leak inspection, and production-copy v1 backup/migration/restore rehearsal. Accounts stay out. +- Headful fragment-to-sessionStorage consumption, invitation rotation after migration, and request/log leak inspection remain production UX gates. Accounts stay out. ## Next Milestone -Deploy the bounded SFU-stage diagnostic and repeat room 1 once. Fix only the +Repeat room 1 and use the bounded SFU stage to fix only the identified connect/source/video-publish/sender-config/audio/transport layer, then retain selected UDP, a decoded/rendered frame, edge caps and resource deltas. The recovery target uses one ICE restart, one same-parent rebuild, one alternate peer, then SFU; it never runs three identical retries or abandons progressing P2P early. Next gate exactly-two/Dynacast-off BWE on a zero-child SFU leaf, then root -evacuation. In parallel, merge this atomic ADR-0002 access PR after CI and then begin the native staged -gate; username/roster stays separate. Audio A/B/C may run without displacing P0. +evacuation. In parallel, migrate the native staged gate to v2; +username/roster stays separate. Audio A/B/C may run without displacing P0. TURN remains a future selected-edge change, not this canary. ADR-0004 still requires a full-resolution 30-minute `1/3/5/8` network,