diff --git a/.github/workflows/native-sender-package.yml b/.github/workflows/native-sender-package.yml deleted file mode 100644 index 0fed7a3..0000000 --- a/.github/workflows/native-sender-package.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Native sender release package (manual) - -on: - workflow_dispatch: - -permissions: - contents: read - -jobs: - package: - name: Package native release candidate - runs-on: windows-latest - steps: - - name: Check out repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: 1.26.6 - cache-dependency-path: native/sender/go.sum - - name: Build and verify package - shell: pwsh - run: | - ./scripts/package-native-sender.ps1 ` - -OutputDirectory (Join-Path $env:RUNNER_TEMP 'screener-native-package') ` - -Revision '${{ github.sha }}' - - name: Upload evaluation artifact - uses: actions/upload-artifact@v7 - with: - name: screener-native-windows-x64-${{ github.sha }} - path: | - ${{ runner.temp }}/screener-native-package/*.zip - ${{ runner.temp }}/screener-native-package/*.zip.sha256 - if-no-files-found: error - retention-days: 7 - compression-level: 0 diff --git a/docs/adr/0005-automatic-hybrid-media-routing.md b/docs/adr/0005-automatic-hybrid-media-routing.md index 0342510..07734f5 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 and deployed; native-edge convergence defaults on per share +- Status: Accepted - Date: 2026-08-20 - Last updated: 2026-08-28 @@ -62,11 +62,13 @@ itself reparented while its descendants remain attached. ### Deterministic Candidates -Each child operation owns one candidate list and cursor, one current candidate -and reservations, one route-demand owner, one fact version, and one total -deadline. Candidate creation first rejects stale sessions, unreachable sources, -cycles, insufficient steady/overlap capacity, tuples already consumed by that -operation, and unavailable SFU resources. +Each child operation owns one immutable purpose, one candidate list and cursor, +one current candidate and reservations, one route-demand owner, one fact version, +and one total deadline. Availability work preempts a background operation by +aborting it and creating the required operation; the old operation is never +relabelled. Candidate creation first rejects stale sessions, unreachable +sources, cycles, insufficient steady/overlap capacity, tuples already consumed +by that operation, and unavailable SFU resources. Eligible Peer parents are ordered by: @@ -87,8 +89,12 @@ root. A fresh healthy candidate sender proof is required before make-before-brea commit. Success moves one branch toward even fanout; failure consumes that one-shot intent. No timer, continuous rebalance or general load score is introduced. -A failed exact tuple is consumed only for its current operation. A later external -fact may make it eligible again; there is no persistent parent blacklist. +A failed tuple is consumed for its exact candidate opportunity: tuple, relevant +endpoint sessions or publication generation, and endpoint-transition strength. +Only a new session or generation, or a strictly better transition, makes that +opportunity new. Candidate removal, worsening, reordering, and unrelated facts +do not retry it. An external SFU-resource wake may reopen SFU opportunities but +never Peer opportunities. There is no persistent parent blacklist. ### Prepare, Commit, And Rollback @@ -156,6 +162,12 @@ space times the per-room Viewer limit; neither has an independent tuning key. Exact publication and subscription handles remain charged while reserved, committed, or draining. Reactivating the same handle does not double-charge it. +Grant revocation, Viewer leave, and SFU-to-P2P replacement remove the exact +LiveKit Viewer participant and confirm its absence. This control-plane drain is +not token revocation or ledger release: an already issued self-hosted LiveKit +token remains usable until expiry, so that subscription's egress stays charged +until the whole publication generation is deleted and confirmed absent. + The application explicitly creates a managed LiveKit room before issuing media tokens. Replacement, abort, timeout, confirmed media-participant loss, share rollover, stop, and room deletion transfer the resources they own into the same typed drain path. @@ -225,9 +237,13 @@ candidate receive windows. A P2P candidate commits only after first decoded frame and three consecutive windows with no freeze/pause, no lower pixel area or rounded FPS, and a strict improvement in at least one of those dimensions. Bitrate does not rank P2P candidates because codec and content phase make it -non-monotonic. Old-edge recovery, missing/incomparable evidence, authority -change, or deadline aborts the experiment. A successful commit clears and -rebaselines the affected subtree. +non-monotonic. Three consecutive comparable non-improving windows reject the +candidate and advance the existing cursor without resetting the deadline. A +persistently limited exact candidate sender is also vetoed even when receiver +comparison approves it. Unknown or incomparable evidence waits only within the +existing deadline; none of these outcomes creates a score. Old-edge recovery, +authority change, or deadline aborts the experiment. A successful commit clears +and rebaselines the affected subtree. The same rule supplies both active parent change and relay abdication. A bad relay ingress reparents that relay while retaining its subtree. A bad exact diff --git a/docs/adr/0007-path-isolated-representation-quality.md b/docs/adr/0007-path-isolated-representation-quality.md index 7eaeac8..2d7be93 100644 --- a/docs/adr/0007-path-isolated-representation-quality.md +++ b/docs/adr/0007-path-isolated-representation-quality.md @@ -1,8 +1,8 @@ # ADR-0007: Framework-Owned Media Quality Adaptation -- Status: Accepted; adaptive gate and local selector deployed +- Status: Accepted - Date: 2026-08-19 -- Last reviewed: 2026-08-26 +- Last reviewed: 2026-08-28 ## Context @@ -61,11 +61,11 @@ it does not need a second bitrate, resolution, FPS, or layer-control system. remains disabled because a Viewer may relay its received track to peer children; local DOM size or visibility cannot represent that downstream demand. -8. Quality measurements are diagnostic. Bitrate, resolution, FPS, RTT, jitter, - loss, freeze counters, codec, and limitation reason do not trigger parent - selection, relay abdication, periodic rebalancing, or route changes. Only - hard connection failure, the existing non-paused decoded-frame stall, parent - departure, or capacity invalidation can make an active edge unusable. +8. WebRTC and LiveKit remain the media-adaptation owners. ADR-0005's deployed + native-edge convergence may consume only a persistent categorical limitation + from the exact sender and comparative delivery proof from the same Viewer over + a real prepared candidate. It does not set bitrate, resolution, FPS, or layer, + infer a physical bottleneck, or combine quality measurements into a score. 9. A healthy decoded route remains sticky. Manual media reconnect rebuilds the current exact P2P parent or current SFU subscription; it does not search for a better parent. If current-route recovery genuinely exhausts, ADR-0005's @@ -109,8 +109,9 @@ LiveKit HIGH+LOW publication without backup codec. - A Host may need to lower its explicit share profile when encoder or uplink capacity is insufficient; Screener does not silently remove constrained Viewer support. -- ADR-0005's native-edge local convergence remains unimplemented. Weighted or - global topology optimization still requires another route-model decision. +- ADR-0005's native-edge local convergence is deployed and performs bounded + real-candidate comparisons without owning media adaptation. Weighted or global + topology optimization still requires another route-model decision. ## Stop Lines diff --git a/docs/deployment.md b/docs/deployment.md index e85e7ad..685fc51 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -33,12 +33,11 @@ Start from a clean exact revision with Node.js 24 and npm 11: ```sh npm ci npm run check -npm run build node scripts/package-app-release.mjs ``` -The packager refuses a dirty tree, records the full revision, emits a runtime -archive plus path/size/SHA-256 manifest and release descriptor, and +The packager refuses a dirty tree, builds from that exact revision, records the +full revision, emits a runtime archive plus path/size/SHA-256 manifest and release descriptor, and extracts its own artifact to verify it. Upload the archive, manifest, and descriptor together to `/opt/screener/uploads`. diff --git a/docs/product/media-quality.md b/docs/product/media-quality.md index 7a42f3f..c2068c5 100644 --- a/docs/product/media-quality.md +++ b/docs/product/media-quality.md @@ -94,10 +94,10 @@ are the observable result. Connection detail may show actual path, codec, resolution, FPS, bitrate, loss, jitter, RTT, and encode/decode behavior when the Browser exposes them; missing or reset counters remain unknown. -The current quality shadow accepts only current foreground presentation evidence -and never changes routes or media settings. ADR-0005 accepts native-edge local -topology convergence that is not implemented or deployed; weighted/global -optimization remains parked in [TODO](../todo.md). +WebRTC and LiveKit remain the media-adaptation owners. ADR-0005's deployed +native-edge convergence consumes only a persistent categorical sender limitation +and proof from the same Viewer over a real prepared candidate; it does not turn +quality metrics into media settings or a weighted/global route score. Web pages cannot guarantee Host background capture, mobile background playback, or relay execution after Browser/OS suspension. Those remain physical lifecycle diff --git a/docs/product/rooms-access.md b/docs/product/rooms-access.md index 76d052f..527c217 100644 --- a/docs/product/rooms-access.md +++ b/docs/product/rooms-access.md @@ -57,7 +57,10 @@ remain separate. Rotating or revoking the Viewer grant is a strong authorization change. The new generation is committed before old Viewers, signaling grace, routes, and sender -edges are closed. Changing code-entry policy or password affects later +edges are closed. An affected SFU Viewer is removed by its exact LiveKit +identity and confirmed absent. This does not revoke an already issued +self-hosted LiveKit token: its subscription stays charged until that publication +generation drains. Changing code-entry policy or password affects later code-only attempts and does not silently revoke invitations. ## Persistence Modes diff --git a/docs/product/routing-transport.md b/docs/product/routing-transport.md index 8460ba6..5eed20f 100644 --- a/docs/product/routing-transport.md +++ b/docs/product/routing-transport.md @@ -59,7 +59,8 @@ route stays active until the exact candidate child decodes a first new frame; ICE connected alone is not commit proof. Availability repair may instead use the explicit bounded-gap plan defined by ADR-0005 when no overlap slot exists. Failure or stale authority releases the candidate and preserves unaffected -branches. +branches. An operation's purpose is immutable; availability work preempts a +background operation by replacing it rather than changing its meaning. Direct peers receive the first bounded opportunity, with SFU used for availability when direct paths cannot provide media. Remaining direct paths may @@ -97,7 +98,10 @@ configures no TURN, ICE/TCP, media TCP, or TLS-relayed media path. HTTPS/WSS is separate control transport and remains TLS/TCP. SFU resources remain bounded throughout reservation, use, and cleanup; stale -credentials cannot recreate off-ledger media. ADR-0005 owns that lifecycle. +credentials cannot recreate off-ledger media. Grant revocation, Viewer leave, +and SFU-to-P2P replacement remove the exact LiveKit Viewer and confirm absence, +but the subscription remains charged until its publication generation drains. +ADR-0005 owns that lifecycle. ## Quality And Privacy Boundaries @@ -107,8 +111,9 @@ per-share convergence gate is enabled, Screener uses an exact persistent native sender limitation only to trigger one measured experiment through the existing serial operation. The same Viewer must then prove a P2P candidate strictly improves delivered pixel area or rounded FPS without regressing either dimension -or producing a freeze/pause across three fresh paired windows. Ordinary quality -moves remain Peer-to-Peer. SFU may +or producing a freeze/pause across three fresh paired windows. A persistently +limited exact candidate sender cannot commit. Ordinary quality moves remain +Peer-to-Peer. SFU may join quality work only as Host fanout relief when every current Host-origin Peer edge, with at least two such edges, is persistently degraded; one Viewer moves before the controller observes the new topology again. During that overlap, the diff --git a/docs/status.md b/docs/status.md index 77edcb6..4befbc4 100644 --- a/docs/status.md +++ b/docs/status.md @@ -74,6 +74,19 @@ This is the current execution index. Git history owns completed timelines; pagehide no longer triggers LiveKit's automatic disconnect. Native Viewer controls still own playback, and manual reconnect stays on the current route. +## Source/Production Delta + +- Current source replaces aggregate route-fact retries with exact session, + publication and transition opportunities; advances proved non-improving + candidates; binds stall and recovery reports to physical media identities; + and removes exact retired LiveKit Viewers while retaining generation-owned + resource charges. +- Current source also keeps lease authority in `RoomStore`, batches stable-room + expiry persistence, serializes share start with room replacement, binds Web + release artifacts to their source revision, and retains Native sender code as + research rather than a distribution surface. These corrections are not + deployed; production remains exact release `24cb780` above. + ## Current Milestone Measured route convergence, generation-owned Viewer presentation and @@ -97,5 +110,6 @@ subscribers back to P2P, and retired the Host SFU publication. ## Current Hold -No source or deployment P0/P1 is open. Sanitized route logging remains enabled -for the pre-release canary. +No source P0/P1 is known. Production does not contain the current source +corrections until an explicitly authorized deployment. Sanitized route logging +remains enabled for the pre-release canary. diff --git a/docs/todo.md b/docs/todo.md index 8799f94..5665804 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,23 +1,32 @@ # Current TODO Ledger -Last reviewed: 2026-08-27 +Last reviewed: 2026-08-28 Only **Now** is executable. Observations, old branches, experiments, and parked topics are not implementation authority. ## Now -No implementation task is currently authorized. Continue bounded production -observation of the deployed route canary; new code requires new evidence. +No implementation task is currently authorized. The audit root-cause +corrections are integrated and verified in current source but remain undeployed; +deployment requires explicit authority. ## Parked Repository Work 1. **Bounded mechanism simplification.** Remove only the proved zero-consumer or duplicate owners: coarse `mediaAssignment` wire residue, legacy SFU room-name acceptance, orphan `scripts/browser-codec-preflight.ts`, and the access gate's - test-title scan. Keep the production + test-title scan. The same bounded pass may remove the unused SFU publisher + snapshot callback, test-only production helpers, unused SFU token allowlist + input, debug ordinals after participant retirement, and share-unowned sender + evidence caches. Keep the production `src/client/webrtc/video-codec-preflight.ts`, live internal media-assignment helpers, and destructive-path safety gates. +2. **Tooling ownership correction.** Give every CDP call a deadline, move shared + Browser harness code out of the Native owner, type-check the access/privacy + gate, run repository hygiene under the pinned Node version, ignore untracked + drafts in canonical-document gates, and scope rejected Native checks to + Native changes or explicit research runs. ## Parked Product Work @@ -25,27 +34,42 @@ observation of the deployed route canary; new code requires new evidence. parent-wide prediction, startup-limited inference, and alternative SFU policies remain unaccepted. Reopen only from exact path evidence; do not add a score, all-pairs probe, periodic rebalancer, or ordinary SFU preference. -2. **Mobile Viewer lifecycle.** Run Android Chrome and iOS Safari matrices for +2. **Startup codec budget.** Reconcile the bounded Auto codec probe with the + first-frame target from measured Browser evidence; do not silently weaken + Auto or move an unproved result into the availability path. +3. **Control-plane resource fairness.** Calibrate one deployment-wide LiveKit + control queue and simple HTTP/WebSocket ingress budgets before implementation; + prove a self-hosted stale-token revocation boundary before independently + releasing subscription egress; do not introduce accounts, polling, risk + scoring, or an independent policy framework. +4. **Client ownership and diagnostic rendering.** Batch Host-only diagnostic + snapshots when a 20-Viewer measurement proves rendering cost, and extract + media-session owners from the Host/Viewer pages only alongside reachable + behavior work. File length alone does not authorize a page rewrite. +5. **C=3 structural intent retention.** Revisit multiple simultaneous one-shot + Host-root convergence intents only with an exact C=3 reproduction; do not add + periodic balancing or another topology queue for the default path. +6. **Mobile Viewer lifecycle.** Run Android Chrome and iOS Safari matrices for autoplay, background audio, foreground recovery, lock/page reclamation, rotation, network migration, and relay survival. -3. **Representative network acceptance.** Complete public-network direct, +7. **Representative network acceptance.** Complete public-network direct, Browser relay, SFU, recovery, Pause/Resume, screen-audio, real-game A/V, two-room SFU, 20-Viewer endurance, and all-UDP-blocked bounded failure. -4. **Whole-product UI and bilingual decision.** Review copy, responsive +8. **Whole-product UI and bilingual decision.** Review copy, responsive hierarchy, visual consistency, restrained motion, rendering cost, terminal navigation, and Chinese/English scope once media behavior stabilizes. -5. **Client-input security review.** Audit HTTP/WebSocket schema, auth, +9. **Client-input security review.** Audit HTTP/WebSocket schema, auth, authorization, rate/body/resource bounds, errors, logs, and secrets without adding a parallel security framework. -6. **Public-server package.** Package the exact Web/signaling, STUN/SFU, proxy, +10. **Public-server package.** Package the exact Web/signaling, STUN/SFU, proxy, secrets, health, and recovery contract for a user-owned server. -7. **Fully local package.** Package Host capture, application server, and local +11. **Fully local package.** Package Host capture, application server, and local state for Windows/macOS/Linux with honest TLS, gateway, NAT, and firewall limits. -8. **Native Host and shared encode.** Revisit Windows first only after capture, +12. **Native Host and shared encode.** Revisit Windows first only after capture, hardware encode, audio, RTP feedback, resources, packaging, licensing, and Browser interoperability are proved. -9. **Platform output.** Revisit AirPlay/Cast only when a target Browser and +13. **Platform output.** Revisit AirPlay/Cast only when a target Browser and physical receiver prove the live `MediaStream` contract. ## Decision Needed diff --git a/native/sender/README.md b/native/sender/README.md index ef59d8d..33bd3b4 100644 --- a/native/sender/README.md +++ b/native/sender/README.md @@ -1,43 +1,16 @@ -# Screener Native Sender for Windows x64 +# Screener Native Sender Research -This evaluation package contains the current Native sender and its Windows 11 -window-capture helper. It has no installer, updater, code signature, or bundled -server. +This directory retains Native sender and shared-encode evidence. It is not a +current product or distribution surface and its private wire is not compatible +with the current Browser/server contract. Do not package or distribute it. -## Run +[ADR-0006](../../docs/adr/0006-fixed-high-native-sender-canary.md) owns the stop +line; [Native sender research](../../docs/research/native-sender.md) owns the +retained evidence and the gates required before this work can resume. -1. Keep `screener-sender.exe` and `screener-window-capture.exe` in the same - directory. -2. Run `screener-sender.exe`. It opens a loopback-only UI in the default - browser; current Chrome or Edge is required. -3. Enter the Screener server and site access password. Browser VP8 remains the - default; browser H.264 and Native window H.264 are explicit opt-ins. -4. For a browser video source, choose the browser screen/window and optionally - select the same window target for process-tree audio. Native window H.264 - requires one target and captures its video and process-tree audio together. -5. Close the console window to stop the sender. +The retained Go checks remain available from `native/sender`: -Native capture requires Windows 11 build 22000 or newer. It binds the selected -window to its process identity, never falls back to the system mix, and never -falls back from the hardware-only Media Foundation H.264 encoder to software. -Browser H.264 still requests browser hardware preference without proving which -physical encoder Chrome or Edge selected. Viewers use the normal Web client. - -Verify the downloaded ZIP against its adjacent `.sha256` file, then verify the -files inside it with `SHA256SUMS.txt`. The package is unsigned, so Windows may -show an unknown-publisher warning. - -The project distribution license is not yet selected. This short-lived Actions -artifact is for evaluation and does not grant redistribution rights. Exact -third-party license texts for the linked Go runtime and modules are under -`licenses/`. - -## Build - -From a clean Git checkout on Windows with Go 1.26.6 and Visual Studio C++ Build -Tools installed: - -```powershell -$output = Join-Path ([IO.Path]::GetTempPath()) "screener-native-package-$([Guid]::NewGuid())" -./scripts/package-native-sender.ps1 -OutputDirectory $output +```sh +go test ./... +go vet ./... ``` diff --git a/scripts/package-app-release.mjs b/scripts/package-app-release.mjs index a49faf2..1f5d3e2 100644 --- a/scripts/package-app-release.mjs +++ b/scripts/package-app-release.mjs @@ -36,6 +36,30 @@ function run(command, args, cwd) { return result.stdout.trim(); } +function buildRuntime(cwd) { + if (process.platform === "win32") { + run( + process.env.ComSpec || "cmd.exe", + ["/d", "/s", "/c", "npm run build"], + cwd, + ); + return; + } + run("npm", ["run", "build"], cwd); +} + +function assertCleanRevision(repositoryRoot, expectedRevision) { + const revision = run("git", ["rev-parse", "HEAD"], repositoryRoot).toLowerCase(); + if (!/^[0-9a-f]{40}$/.test(revision)) fail("Git did not return a full revision"); + if (expectedRevision && revision !== expectedRevision) { + fail("Repository revision changed during release packaging"); + } + if (run("git", ["status", "--porcelain"], repositoryRoot) !== "") { + fail("Application releases require a clean Git checkout"); + } + return revision; +} + function sha256(path) { return createHash("sha256").update(readFileSync(path)).digest("hex"); } @@ -155,13 +179,14 @@ if (relativeOutput === "" || (!relativeOutput.startsWith("..") && !isAbsolute(re } if (existsSync(outputRoot)) fail("Output directory must not already exist"); -const revision = run("git", ["rev-parse", "HEAD"], repositoryRoot).toLowerCase(); -if (!/^[0-9a-f]{40}$/.test(revision)) fail("Git did not return a full revision"); -if (run("git", ["status", "--porcelain"], repositoryRoot) !== "") { - fail("Application releases require a clean Git checkout"); -} +const revision = assertCleanRevision(repositoryRoot); -for (const required of ["package.json", "package-lock.json", "dist/client", "dist/server"]) { +for (const required of ["package.json", "package-lock.json"]) { + if (!existsSync(join(repositoryRoot, required))) fail(`Missing release input: ${required}`); +} +buildRuntime(repositoryRoot); +assertCleanRevision(repositoryRoot, revision); +for (const required of ["dist/client", "dist/server"]) { if (!existsSync(join(repositoryRoot, required))) fail(`Missing built runtime path: ${required}`); } @@ -179,6 +204,7 @@ try { ]) { copyRuntimeFile(repositoryRoot, runtimeRoot, source); } + assertCleanRevision(repositoryRoot, revision); writeFileSync(join(runtimeRoot, "REVISION"), `${revision}\n`, "ascii"); const records = recordsFor(runtimeRoot); diff --git a/scripts/package-native-sender.ps1 b/scripts/package-native-sender.ps1 deleted file mode 100644 index 551d5b2..0000000 --- a/scripts/package-native-sender.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -[CmdletBinding()] -param( - [Parameter(Mandatory = $true)] - [string]$OutputDirectory, - - [string]$Revision -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -$scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path -$repositoryRoot = [IO.Path]::GetFullPath((Join-Path $scriptDirectory '..')) -$outputRoot = [IO.Path]::GetFullPath($OutputDirectory) -$repositoryPrefix = $repositoryRoot.TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar -if ($outputRoot.Equals($repositoryRoot, [StringComparison]::OrdinalIgnoreCase) -or - $outputRoot.StartsWith($repositoryPrefix, [StringComparison]::OrdinalIgnoreCase)) { - throw 'OutputDirectory must be outside the repository.' -} -if (Test-Path -LiteralPath $outputRoot) { - throw 'OutputDirectory must not already exist.' -} - -$headRevision = (& git -C $repositoryRoot rev-parse HEAD).Trim() -if ($LASTEXITCODE -ne 0) { throw 'Unable to resolve the Git revision.' } -if ([string]::IsNullOrWhiteSpace($Revision)) { $Revision = $headRevision } -$Revision = $Revision.Trim().ToLowerInvariant() -if ($Revision -notmatch '^[0-9a-f]{40}$' -or $Revision -ne $headRevision) { - throw 'Revision must be the full SHA-1 of the checked-out commit.' -} -$dirty = @(git -C $repositoryRoot status --porcelain) -if ($LASTEXITCODE -ne 0 -or $dirty.Count -ne 0) { - throw 'Native packages must be built from a clean Git checkout.' -} - -$go = Get-Command go.exe -ErrorAction Stop -$senderDirectory = Join-Path $repositoryRoot 'native\sender' -$packageName = "screener-native-windows-x64-$($Revision.Substring(0, 12))" -$packageDirectory = Join-Path $outputRoot $packageName -$licenseDirectory = Join-Path $packageDirectory 'licenses' -$senderExecutable = Join-Path $packageDirectory 'screener-sender.exe' -$zipPath = Join-Path $outputRoot "$packageName.zip" -New-Item -ItemType Directory -Path $licenseDirectory -Force | Out-Null - -$previousGoOS = $env:GOOS -$previousGoArch = $env:GOARCH -$previousCGO = $env:CGO_ENABLED -Push-Location $senderDirectory -try { - $env:GOOS = 'windows' - $env:GOARCH = 'amd64' - $env:CGO_ENABLED = '0' - $linkerFlags = "-X main.buildRevision=$Revision" - & $go.Source build -mod=readonly -trimpath -buildvcs=true -ldflags $linkerFlags -o $senderExecutable ./cmd/screener-sender - if ($LASTEXITCODE -ne 0) { throw 'Native sender build failed.' } - - $moduleLines = @(& $go.Source list -deps -f '{{with .Module}}{{if not .Main}}{{.Path}}|{{.Version}}|{{.Dir}}{{end}}{{end}}' ./cmd/screener-sender | - Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Sort-Object -Unique) - if ($LASTEXITCODE -ne 0 -or $moduleLines.Count -eq 0) { - throw 'Unable to enumerate linked Go modules.' - } - $goVersion = (& $go.Source version).Trim() - $goRoot = (& $go.Source env GOROOT).Trim() -} finally { - Pop-Location - $env:GOOS = $previousGoOS - $env:GOARCH = $previousGoArch - $env:CGO_ENABLED = $previousCGO -} - -& (Join-Path $repositoryRoot 'native\window-capture-helper\build.ps1') -OutputDirectory $packageDirectory | Out-Null -@('window-capture.obj', 'process-audio.obj') | ForEach-Object { - $helperObject = Join-Path $packageDirectory $_ - if (Test-Path -LiteralPath $helperObject) { Remove-Item -LiteralPath $helperObject -Force } -} - -$moduleManifest = @() -foreach ($line in $moduleLines) { - $modulePath, $moduleVersion, $moduleDirectory = $line -split '\|', 3 - if ([string]::IsNullOrWhiteSpace($moduleDirectory) -or -not (Test-Path -LiteralPath $moduleDirectory -PathType Container)) { - throw "Linked module source is unavailable: $modulePath@$moduleVersion" - } - $licenseFiles = @(Get-ChildItem -LiteralPath $moduleDirectory -File | Where-Object { - $_.Name -match '^(LICENSE|LICENCE|COPYING|NOTICE)(\.|$)' - }) - if ($licenseFiles.Count -eq 0) { throw "Linked module has no recognized license file: $modulePath@$moduleVersion" } - $moduleLicenseDirectory = Join-Path $licenseDirectory (($modulePath + '@' + $moduleVersion) -replace '[^A-Za-z0-9._@+-]', '_') - New-Item -ItemType Directory -Path $moduleLicenseDirectory | Out-Null - $licenseFiles | Copy-Item -Destination $moduleLicenseDirectory - $moduleManifest += "$modulePath $moduleVersion" -} - -$goLicense = Join-Path $goRoot 'LICENSE' -if (-not (Test-Path -LiteralPath $goLicense -PathType Leaf)) { throw 'Go toolchain license was not found.' } -$goLicenseDirectory = Join-Path $licenseDirectory 'go-toolchain' -New-Item -ItemType Directory -Path $goLicenseDirectory | Out-Null -Copy-Item -LiteralPath $goLicense -Destination (Join-Path $goLicenseDirectory 'LICENSE') -$moduleManifest | Set-Content -LiteralPath (Join-Path $licenseDirectory 'MODULES.txt') -Encoding ascii -Copy-Item -LiteralPath (Join-Path $senderDirectory 'README.md') -Destination (Join-Path $packageDirectory 'README.md') -@( - "revision=$Revision" - 'platform=windows/amd64' - "toolchain=$goVersion" -) | Set-Content -LiteralPath (Join-Path $packageDirectory 'REVISION.txt') -Encoding ascii - -$versionOutput = (& $senderExecutable --version).Trim() -if ($LASTEXITCODE -ne 0 -or $versionOutput -ne "Screener sender $Revision") { - throw 'Packaged sender revision smoke failed.' -} - -$packagePrefix = $packageDirectory.TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar -$checksums = Get-ChildItem -LiteralPath $packageDirectory -File -Recurse | Sort-Object FullName | ForEach-Object { - $relativePath = $_.FullName.Substring($packagePrefix.Length).Replace('\', '/') - "$((Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()) $relativePath" -} -$checksums | Set-Content -LiteralPath (Join-Path $packageDirectory 'SHA256SUMS.txt') -Encoding ascii - -Compress-Archive -LiteralPath $packageDirectory -DestinationPath $zipPath -CompressionLevel Optimal -$zipHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant() -"$zipHash $([IO.Path]::GetFileName($zipPath))" | - Set-Content -LiteralPath "$zipPath.sha256" -Encoding ascii - -Write-Output $zipPath -Write-Output "$zipPath.sha256" diff --git a/scripts/required-project-paths.txt b/scripts/required-project-paths.txt index 4fb35fa..0d89c8c 100644 --- a/scripts/required-project-paths.txt +++ b/scripts/required-project-paths.txt @@ -15,7 +15,6 @@ tsconfig.json .githooks/pre-commit .github/pull_request_template.md .github/workflows/repository-hygiene.yml -.github/workflows/native-sender-package.yml docs/README.md docs/project-memory.md docs/status.md @@ -44,7 +43,6 @@ scripts/required-project-paths.txt scripts/check-project-state.sh scripts/check-project-state.ps1 scripts/check-docs.mjs -scripts/package-native-sender.ps1 scripts/install-hooks.sh scripts/install-hooks.ps1 deploy/coturn/turnserver.conf.example diff --git a/src/client/components/StageEntryActions.tsx b/src/client/components/StageEntryActions.tsx index a1c58b4..112a7c0 100644 --- a/src/client/components/StageEntryActions.tsx +++ b/src/client/components/StageEntryActions.tsx @@ -4,12 +4,14 @@ import { RoomCodeEntry } from "./RoomCodeEntry"; interface StageEntryActionsProps { joiningRoom: boolean; + startSharingDisabled?: boolean; onJoinToggle: () => void; onStartSharing: () => void; } export function StageEntryActions({ joiningRoom, + startSharingDisabled = false, onJoinToggle, onStartSharing, }: StageEntryActionsProps) { @@ -19,6 +21,7 @@ export function StageEntryActions({