@torrent-tv/proxy 2.87.6 → 2.87.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +4 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +101 -94
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -0
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +100 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +129 -129
- package/routes/api/transcode-sessions/post.js +160 -160
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +361 -361
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/server.js +4 -0
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +332 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +755 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2162 -2162
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1083 -1083
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +266 -266
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +245 -212
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +131 -59
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1146 -1146
- package/services/server/ViewerRequests.js +522 -522
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -36
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +983 -983
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/data-channel-handler.js +1356 -1356
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +657 -657
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +212 -176
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +35 -35
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +161 -161
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +381 -381
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +103 -103
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +182 -182
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Read usrsctp's live association state, without a rebuild.
|
|
3
|
-
*
|
|
4
|
-
* Roadmap item 11. `node_datachannel.node` ships unstripped — it carries
|
|
5
|
-
* usrsctp's own local symbols, including `system_base_info` and
|
|
6
|
-
* `usrsctp_getsockopt` — so the association's real state (peer receive
|
|
7
|
-
* window, pending data, retransmission timeout, congestion window) can be
|
|
8
|
-
* read out of THIS live process with a short gdb attach. No source rebuild,
|
|
9
|
-
* no SCTP_DEBUG image, no waiting for a packet capture to be read by eye.
|
|
10
|
-
*
|
|
11
|
-
* The walk (hash the association table, call `usrsctp_getsockopt` with
|
|
12
|
-
* `SCTP_STATUS` through the running process) and the healthy baseline it was
|
|
13
|
-
* checked against are in
|
|
14
|
-
* `research/session-2026-08-27-28-freeze-onset-and-sessions.md`, section 1.
|
|
15
|
-
* The script itself is bundled at {@link SCTPSTATE_SCRIPT_PATH} rather than
|
|
16
|
-
* hand-placed on a host, so it ships with every release instead of surviving
|
|
17
|
-
* only as long as someone remembers to copy it back after a container is
|
|
18
|
-
* recreated.
|
|
19
|
-
*
|
|
20
|
-
* **THIS STOPS THE PRODUCT WHILE IT WORKS, so it is off unless asked for.**
|
|
21
|
-
* `gdb` attaches with ptrace and pauses every thread of this process for as
|
|
22
|
-
* long as it is attached. The manual procedure this automates took a fraction
|
|
23
|
-
* of a second; on 2026-09-05 the automatic one held the proxy for four minutes
|
|
24
|
-
* and the viewer was told the proxy had sent no video. Two things follow, and
|
|
25
|
-
* both are in the code below: the deadline is counted by a separate process,
|
|
26
|
-
* because a timer inside a stopped process never fires; and `--usrsctp-state`
|
|
27
|
-
* has to be given for any of this to happen at all.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { spawn } from "node:child_process";
|
|
31
|
-
import path from "node:path";
|
|
32
|
-
import { fileURLToPath } from "node:url";
|
|
33
|
-
|
|
34
|
-
import { shouldStartCapture, WITNESS_COOLDOWN_MS } from "./packet-witness.js";
|
|
35
|
-
|
|
36
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
-
|
|
38
|
-
/** The bundled gdb script that performs the usrsctp state walk. */
|
|
39
|
-
export const SCTPSTATE_SCRIPT_PATH = path.join(HERE, "..", "..", "assets", "diagnostics", "sctpstate.gdb");
|
|
40
|
-
|
|
41
|
-
/** How long gdb may run before it is killed. Generous: this is a rare, one-shot read. */
|
|
42
|
-
export const GDB_TIMEOUT_MS = 15_000;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Create the reader.
|
|
46
|
-
*
|
|
47
|
-
* Single-flight and cooldown-gated the same way the packet witness is
|
|
48
|
-
* ({@link shouldStartCapture}) — a wedge that stays certain for minutes must
|
|
49
|
-
* not spawn a fresh gdb attach on every tick, and two readings ten seconds
|
|
50
|
-
* apart tell the same story a hundred readings would.
|
|
51
|
-
*
|
|
52
|
-
* @param {Object} options
|
|
53
|
-
* @param {(message: string) => void} options.log
|
|
54
|
-
* @param {typeof spawn} [options.spawnProcess] - Seam for tests.
|
|
55
|
-
* @param {number} [options.pid] - Defaults to this process's own pid.
|
|
56
|
-
* @param {string} [options.scriptPath]
|
|
57
|
-
* @param {number} [options.cooldownMs]
|
|
58
|
-
* @returns {{ maybeRead: (reasonText: string) => boolean }}
|
|
59
|
-
*/
|
|
60
|
-
export function createUsrsctpStateReader({
|
|
61
|
-
log,
|
|
62
|
-
spawnProcess = spawn,
|
|
63
|
-
pid = process.pid,
|
|
64
|
-
scriptPath = SCTPSTATE_SCRIPT_PATH,
|
|
65
|
-
cooldownMs = WITNESS_COOLDOWN_MS
|
|
66
|
-
}) {
|
|
67
|
-
/** @type {{ running: boolean, lastStartedAt: number }} */
|
|
68
|
-
const state = { running: false, lastStartedAt: 0 };
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Read the association state now, if the gating rules allow it.
|
|
72
|
-
*
|
|
73
|
-
* @param {string} reasonText - What declared the wedge, for the log line.
|
|
74
|
-
* @returns {boolean} True when a read actually started.
|
|
75
|
-
*/
|
|
76
|
-
const maybeRead = (reasonText) => {
|
|
77
|
-
if (!shouldStartCapture({ ...state, cooldownMs })) {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
state.running = true;
|
|
81
|
-
state.lastStartedAt = Date.now();
|
|
82
|
-
const startedAt = state.lastStartedAt;
|
|
83
|
-
void (async () => {
|
|
84
|
-
let out = "";
|
|
85
|
-
let err = "";
|
|
86
|
-
try {
|
|
87
|
-
await new Promise((resolve) => {
|
|
88
|
-
let settled = false;
|
|
89
|
-
let child;
|
|
90
|
-
const finish = () => {
|
|
91
|
-
if (settled) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
settled = true;
|
|
95
|
-
clearTimeout(killer);
|
|
96
|
-
resolve();
|
|
97
|
-
};
|
|
98
|
-
try {
|
|
99
|
-
// THE DEADLINE IS SET OUTSIDE THE PROCESS THAT IS BEING STOPPED.
|
|
100
|
-
//
|
|
101
|
-
// gdb attaches with ptrace and stops every thread of this process
|
|
102
|
-
// while it works — including whichever one would have counted the
|
|
103
|
-
// seconds. The guard below used to be a `setTimeout` here, and on
|
|
104
|
-
// 2026-09-05 it did not fire: gdb held the proxy for four minutes,
|
|
105
|
-
// the log ended mid-second and the viewer was told the proxy had
|
|
106
|
-
// sent no video. `timeout` is a separate process and keeps counting
|
|
107
|
-
// whatever happens to this one.
|
|
108
|
-
child = spawnProcess(
|
|
109
|
-
"timeout",
|
|
110
|
-
[
|
|
111
|
-
"-s",
|
|
112
|
-
"KILL",
|
|
113
|
-
String(Math.ceil(GDB_TIMEOUT_MS / 1000)),
|
|
114
|
-
"gdb",
|
|
115
|
-
"-q",
|
|
116
|
-
"-batch",
|
|
117
|
-
"-p",
|
|
118
|
-
String(pid),
|
|
119
|
-
"-x",
|
|
120
|
-
scriptPath
|
|
121
|
-
],
|
|
122
|
-
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
123
|
-
);
|
|
124
|
-
} catch (error) {
|
|
125
|
-
err = `could not start gdb: ${error?.message ?? error}`;
|
|
126
|
-
resolve();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
child.stdout?.on("data", (chunk) => {
|
|
130
|
-
out += chunk.toString();
|
|
131
|
-
});
|
|
132
|
-
child.stderr?.on("data", (chunk) => {
|
|
133
|
-
err += chunk.toString();
|
|
134
|
-
});
|
|
135
|
-
child.on("error", (error) => {
|
|
136
|
-
err += `${err ? " " : ""}gdb error: ${error?.message ?? error}`;
|
|
137
|
-
finish();
|
|
138
|
-
});
|
|
139
|
-
child.on("close", finish);
|
|
140
|
-
// A second guard, for the case where `timeout` itself is not on the
|
|
141
|
-
// host: it can only fire while this process is running, which is
|
|
142
|
-
// exactly what cannot be relied on here, so it is a backstop and not
|
|
143
|
-
// the deadline.
|
|
144
|
-
const killer = setTimeout(() => {
|
|
145
|
-
try {
|
|
146
|
-
child.kill("SIGKILL");
|
|
147
|
-
} catch {
|
|
148
|
-
// already gone
|
|
149
|
-
}
|
|
150
|
-
}, GDB_TIMEOUT_MS * 2);
|
|
151
|
-
if (typeof killer.unref === "function") {
|
|
152
|
-
killer.unref();
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
} finally {
|
|
156
|
-
const lines = out.trim().length > 0 ? out.trim().split("\n") : [];
|
|
157
|
-
if (lines.length > 0) {
|
|
158
|
-
log(`usrsctp state (${reasonText}): ${lines.join(" | ")}`);
|
|
159
|
-
} else {
|
|
160
|
-
log(`usrsctp state (${reasonText}): no reading — ${err.trim() || "gdb produced no output"}`);
|
|
161
|
-
}
|
|
162
|
-
state.running = false;
|
|
163
|
-
// Same spacing rule as the packet witness: honour the cooldown even
|
|
164
|
-
// when the read ended quickly.
|
|
165
|
-
const earliestNext = startedAt + cooldownMs;
|
|
166
|
-
if (state.lastStartedAt < earliestNext) {
|
|
167
|
-
state.lastStartedAt = earliestNext;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
})();
|
|
171
|
-
return true;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
return { maybeRead };
|
|
175
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file Read usrsctp's live association state, without a rebuild.
|
|
3
|
+
*
|
|
4
|
+
* Roadmap item 11. `node_datachannel.node` ships unstripped — it carries
|
|
5
|
+
* usrsctp's own local symbols, including `system_base_info` and
|
|
6
|
+
* `usrsctp_getsockopt` — so the association's real state (peer receive
|
|
7
|
+
* window, pending data, retransmission timeout, congestion window) can be
|
|
8
|
+
* read out of THIS live process with a short gdb attach. No source rebuild,
|
|
9
|
+
* no SCTP_DEBUG image, no waiting for a packet capture to be read by eye.
|
|
10
|
+
*
|
|
11
|
+
* The walk (hash the association table, call `usrsctp_getsockopt` with
|
|
12
|
+
* `SCTP_STATUS` through the running process) and the healthy baseline it was
|
|
13
|
+
* checked against are in
|
|
14
|
+
* `research/session-2026-08-27-28-freeze-onset-and-sessions.md`, section 1.
|
|
15
|
+
* The script itself is bundled at {@link SCTPSTATE_SCRIPT_PATH} rather than
|
|
16
|
+
* hand-placed on a host, so it ships with every release instead of surviving
|
|
17
|
+
* only as long as someone remembers to copy it back after a container is
|
|
18
|
+
* recreated.
|
|
19
|
+
*
|
|
20
|
+
* **THIS STOPS THE PRODUCT WHILE IT WORKS, so it is off unless asked for.**
|
|
21
|
+
* `gdb` attaches with ptrace and pauses every thread of this process for as
|
|
22
|
+
* long as it is attached. The manual procedure this automates took a fraction
|
|
23
|
+
* of a second; on 2026-09-05 the automatic one held the proxy for four minutes
|
|
24
|
+
* and the viewer was told the proxy had sent no video. Two things follow, and
|
|
25
|
+
* both are in the code below: the deadline is counted by a separate process,
|
|
26
|
+
* because a timer inside a stopped process never fires; and `--usrsctp-state`
|
|
27
|
+
* has to be given for any of this to happen at all.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { spawn } from "node:child_process";
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
|
|
34
|
+
import { shouldStartCapture, WITNESS_COOLDOWN_MS } from "./packet-witness.js";
|
|
35
|
+
|
|
36
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
|
|
38
|
+
/** The bundled gdb script that performs the usrsctp state walk. */
|
|
39
|
+
export const SCTPSTATE_SCRIPT_PATH = path.join(HERE, "..", "..", "assets", "diagnostics", "sctpstate.gdb");
|
|
40
|
+
|
|
41
|
+
/** How long gdb may run before it is killed. Generous: this is a rare, one-shot read. */
|
|
42
|
+
export const GDB_TIMEOUT_MS = 15_000;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create the reader.
|
|
46
|
+
*
|
|
47
|
+
* Single-flight and cooldown-gated the same way the packet witness is
|
|
48
|
+
* ({@link shouldStartCapture}) — a wedge that stays certain for minutes must
|
|
49
|
+
* not spawn a fresh gdb attach on every tick, and two readings ten seconds
|
|
50
|
+
* apart tell the same story a hundred readings would.
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} options
|
|
53
|
+
* @param {(message: string) => void} options.log
|
|
54
|
+
* @param {typeof spawn} [options.spawnProcess] - Seam for tests.
|
|
55
|
+
* @param {number} [options.pid] - Defaults to this process's own pid.
|
|
56
|
+
* @param {string} [options.scriptPath]
|
|
57
|
+
* @param {number} [options.cooldownMs]
|
|
58
|
+
* @returns {{ maybeRead: (reasonText: string) => boolean }}
|
|
59
|
+
*/
|
|
60
|
+
export function createUsrsctpStateReader({
|
|
61
|
+
log,
|
|
62
|
+
spawnProcess = spawn,
|
|
63
|
+
pid = process.pid,
|
|
64
|
+
scriptPath = SCTPSTATE_SCRIPT_PATH,
|
|
65
|
+
cooldownMs = WITNESS_COOLDOWN_MS
|
|
66
|
+
}) {
|
|
67
|
+
/** @type {{ running: boolean, lastStartedAt: number }} */
|
|
68
|
+
const state = { running: false, lastStartedAt: 0 };
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Read the association state now, if the gating rules allow it.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} reasonText - What declared the wedge, for the log line.
|
|
74
|
+
* @returns {boolean} True when a read actually started.
|
|
75
|
+
*/
|
|
76
|
+
const maybeRead = (reasonText) => {
|
|
77
|
+
if (!shouldStartCapture({ ...state, cooldownMs })) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
state.running = true;
|
|
81
|
+
state.lastStartedAt = Date.now();
|
|
82
|
+
const startedAt = state.lastStartedAt;
|
|
83
|
+
void (async () => {
|
|
84
|
+
let out = "";
|
|
85
|
+
let err = "";
|
|
86
|
+
try {
|
|
87
|
+
await new Promise((resolve) => {
|
|
88
|
+
let settled = false;
|
|
89
|
+
let child;
|
|
90
|
+
const finish = () => {
|
|
91
|
+
if (settled) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
settled = true;
|
|
95
|
+
clearTimeout(killer);
|
|
96
|
+
resolve();
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
// THE DEADLINE IS SET OUTSIDE THE PROCESS THAT IS BEING STOPPED.
|
|
100
|
+
//
|
|
101
|
+
// gdb attaches with ptrace and stops every thread of this process
|
|
102
|
+
// while it works — including whichever one would have counted the
|
|
103
|
+
// seconds. The guard below used to be a `setTimeout` here, and on
|
|
104
|
+
// 2026-09-05 it did not fire: gdb held the proxy for four minutes,
|
|
105
|
+
// the log ended mid-second and the viewer was told the proxy had
|
|
106
|
+
// sent no video. `timeout` is a separate process and keeps counting
|
|
107
|
+
// whatever happens to this one.
|
|
108
|
+
child = spawnProcess(
|
|
109
|
+
"timeout",
|
|
110
|
+
[
|
|
111
|
+
"-s",
|
|
112
|
+
"KILL",
|
|
113
|
+
String(Math.ceil(GDB_TIMEOUT_MS / 1000)),
|
|
114
|
+
"gdb",
|
|
115
|
+
"-q",
|
|
116
|
+
"-batch",
|
|
117
|
+
"-p",
|
|
118
|
+
String(pid),
|
|
119
|
+
"-x",
|
|
120
|
+
scriptPath
|
|
121
|
+
],
|
|
122
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
123
|
+
);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
err = `could not start gdb: ${error?.message ?? error}`;
|
|
126
|
+
resolve();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
child.stdout?.on("data", (chunk) => {
|
|
130
|
+
out += chunk.toString();
|
|
131
|
+
});
|
|
132
|
+
child.stderr?.on("data", (chunk) => {
|
|
133
|
+
err += chunk.toString();
|
|
134
|
+
});
|
|
135
|
+
child.on("error", (error) => {
|
|
136
|
+
err += `${err ? " " : ""}gdb error: ${error?.message ?? error}`;
|
|
137
|
+
finish();
|
|
138
|
+
});
|
|
139
|
+
child.on("close", finish);
|
|
140
|
+
// A second guard, for the case where `timeout` itself is not on the
|
|
141
|
+
// host: it can only fire while this process is running, which is
|
|
142
|
+
// exactly what cannot be relied on here, so it is a backstop and not
|
|
143
|
+
// the deadline.
|
|
144
|
+
const killer = setTimeout(() => {
|
|
145
|
+
try {
|
|
146
|
+
child.kill("SIGKILL");
|
|
147
|
+
} catch {
|
|
148
|
+
// already gone
|
|
149
|
+
}
|
|
150
|
+
}, GDB_TIMEOUT_MS * 2);
|
|
151
|
+
if (typeof killer.unref === "function") {
|
|
152
|
+
killer.unref();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
} finally {
|
|
156
|
+
const lines = out.trim().length > 0 ? out.trim().split("\n") : [];
|
|
157
|
+
if (lines.length > 0) {
|
|
158
|
+
log(`usrsctp state (${reasonText}): ${lines.join(" | ")}`);
|
|
159
|
+
} else {
|
|
160
|
+
log(`usrsctp state (${reasonText}): no reading — ${err.trim() || "gdb produced no output"}`);
|
|
161
|
+
}
|
|
162
|
+
state.running = false;
|
|
163
|
+
// Same spacing rule as the packet witness: honour the cooldown even
|
|
164
|
+
// when the read ended quickly.
|
|
165
|
+
const earliestNext = startedAt + cooldownMs;
|
|
166
|
+
if (state.lastStartedAt < earliestNext) {
|
|
167
|
+
state.lastStartedAt = earliestNext;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
})();
|
|
171
|
+
return true;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return { maybeRead };
|
|
175
|
+
}
|