@torrent-tv/proxy 2.87.2 → 2.87.4
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 +2157 -2148
- 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 +94 -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 +50 -50
- 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/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/sources/warm/post.js +217 -181
- 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 +721 -720
- 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 +212 -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 +856 -848
- 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/{viewer → server}/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +36 -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/torrent-pool.js +2675 -2635
- package/services/torrent/torrent-source-key.js +38 -38
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -760
- 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/pool-adapter.js +365 -350
- package/services/torrent/worker/protocol.js +184 -182
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/torrent/worker/worker.js +1129 -1116
- 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 +73 -73
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +176 -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 -294
- 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/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
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file The priority map: one number per second of film.
|
|
3
|
-
*
|
|
4
|
-
* The map is literally a map — an array as long as the film, saying how urgently
|
|
5
|
-
* each second is wanted, and beside it how long until somebody plays it. What is
|
|
6
|
-
* checked here is what the numbers MEAN, since only their order is meaningful:
|
|
7
|
-
* in front of a viewer beats behind them, nearer beats further, and two viewers
|
|
8
|
-
* merge to the more urgent of them.
|
|
9
|
-
*
|
|
10
|
-
* Nothing here spawns anything, reads a disk or looks at a clock: this layer is
|
|
11
|
-
* exercised with plain values alone, which is the layer check made executable.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import test from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import {
|
|
17
|
-
emptyMap,
|
|
18
|
-
inWorkingOrder,
|
|
19
|
-
isBehindEverybody,
|
|
20
|
-
mapForViewer,
|
|
21
|
-
mergeMaps,
|
|
22
|
-
runsOf
|
|
23
|
-
} from "../services/viewer/PriorityMap.js";
|
|
24
|
-
|
|
25
|
-
const FILM = 3600;
|
|
26
|
-
const ALLOWANCE = 8;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @param {number} atSeconds
|
|
30
|
-
* @param {boolean} [playing]
|
|
31
|
-
*/
|
|
32
|
-
function viewer(atSeconds, playing = true) {
|
|
33
|
-
return mapForViewer({
|
|
34
|
-
atSeconds,
|
|
35
|
-
durationSeconds: FILM,
|
|
36
|
-
allowanceSeconds: ALLOWANCE,
|
|
37
|
-
playing
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
test("the map is one entry per second of film", () => {
|
|
42
|
-
const map = viewer(600);
|
|
43
|
-
assert.equal(map.durationSeconds, FILM);
|
|
44
|
-
assert.equal(map.priority.length, FILM);
|
|
45
|
-
assert.equal(map.secondsUntilPlayed.length, FILM);
|
|
46
|
-
assert.equal(map.behind.length, FILM);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("the nearer a viewer is to a second, the higher its number", () => {
|
|
50
|
-
// The whole of what a priority means: which of two seconds is wanted first.
|
|
51
|
-
const map = viewer(600);
|
|
52
|
-
assert.ok(map.priority[600] > map.priority[700], "their own second beats one a hundred on");
|
|
53
|
-
assert.ok(map.priority[700] > map.priority[3000], "and that beats one forty minutes on");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("the time is the distance, because a viewer covers a second of film in a second", () => {
|
|
57
|
-
const map = viewer(600);
|
|
58
|
-
assert.equal(map.secondsUntilPlayed[600], 0, "they are there now");
|
|
59
|
-
assert.equal(map.secondsUntilPlayed[660], 60, "a minute of film away is a minute away");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("what is behind a viewer is wanted, and wanted last", () => {
|
|
63
|
-
// Still wanted — a seek back must be cheap — but it yields to everything
|
|
64
|
-
// anybody is walking towards, however far off that is.
|
|
65
|
-
const map = viewer(600);
|
|
66
|
-
assert.equal(map.behind[599], 1);
|
|
67
|
-
assert.equal(map.behind[600], 0);
|
|
68
|
-
assert.ok(isBehindEverybody(map.priority[599]));
|
|
69
|
-
assert.ok(map.priority[599] < map.priority[3599],
|
|
70
|
-
"the last second of the film outranks the second they just watched");
|
|
71
|
-
assert.equal(map.secondsUntilPlayed[599], Number.POSITIVE_INFINITY,
|
|
72
|
-
"nobody is on their way there, so there is no time by which it must exist");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("a viewer who has stopped the picture keeps their position, and loses their times", () => {
|
|
76
|
-
// A pause removes the time, not the direction. Collapsed to one flat value
|
|
77
|
-
// over the whole film, as it was, their position disappeared entirely — and
|
|
78
|
-
// with it the rule that what is in front of them is made first.
|
|
79
|
-
const map = viewer(600, false);
|
|
80
|
-
assert.equal(map.behind[599], 1, "what they have watched is still behind them");
|
|
81
|
-
assert.equal(map.behind[600], 0, "and what they have not is still in front");
|
|
82
|
-
assert.ok(map.priority[600] > map.priority[599], "in front still outranks behind");
|
|
83
|
-
assert.ok(map.priority[600] > map.priority[3000], "and nearer still outranks further");
|
|
84
|
-
for (const second of [599, 600, 3000]) {
|
|
85
|
-
assert.equal(map.secondsUntilPlayed[second], Number.POSITIVE_INFINITY,
|
|
86
|
-
"but nothing has a time, because they are on their way nowhere");
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("anybody who is watching outranks anybody who has stopped", () => {
|
|
91
|
-
// An ordering fact rather than a chosen number: somebody watching needs their
|
|
92
|
-
// next second almost at once, while somebody stopped needs theirs at a time
|
|
93
|
-
// nothing here knows.
|
|
94
|
-
const watching = viewer(600);
|
|
95
|
-
const stopped = viewer(3000, false);
|
|
96
|
-
assert.ok(watching.priority[3599] > stopped.priority[3000],
|
|
97
|
-
"the far tail of a watching viewer beats the very next second of a stopped one");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("two viewers merge to the more urgent of them, second by second", () => {
|
|
101
|
-
const merged = mergeMaps([viewer(600), viewer(1800)]);
|
|
102
|
-
assert.equal(merged.priority[1800], merged.priority[600],
|
|
103
|
-
"each of them is at the top of the scale where they stand");
|
|
104
|
-
assert.equal(merged.secondsUntilPlayed[1800], 0, "the nearer time wins");
|
|
105
|
-
assert.equal(merged.behind[599], 1, "behind both of them is behind");
|
|
106
|
-
assert.equal(merged.behind[700], 0, "and in front of ANYBODY is in front");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("the second viewer's position is not buried under the first viewer's distance", () => {
|
|
110
|
-
// The failure this guards: one viewer's far film outranking another viewer's
|
|
111
|
-
// own second, so the second of them is served by nobody.
|
|
112
|
-
const merged = mergeMaps([viewer(600), viewer(1800)]);
|
|
113
|
-
assert.ok(merged.priority[1800] > merged.priority[1799],
|
|
114
|
-
"where the second viewer stands beats the film just before them");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("a film nobody is watching wants nothing", () => {
|
|
118
|
-
const merged = mergeMaps([]);
|
|
119
|
-
assert.equal(merged.durationSeconds, 0);
|
|
120
|
-
assert.deepEqual(runsOf(merged), []);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("the map as stretches says the same thing in fewer numbers", () => {
|
|
124
|
-
const runs = runsOf(viewer(600));
|
|
125
|
-
assert.ok(runs.length > 1 && runs.length < 40, "a handful of stretches, not thousands");
|
|
126
|
-
assert.equal(runs[0].from, 0, "starting at the beginning of the film");
|
|
127
|
-
assert.equal(runs[0].behind, true, "which is behind them");
|
|
128
|
-
assert.equal(runs[runs.length - 1].to, FILM, "and ending at its end");
|
|
129
|
-
for (let index = 0; index < runs.length - 1; index += 1) {
|
|
130
|
-
assert.equal(runs[index].to, runs[index + 1].from, "with no gaps");
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("the stretches widen with distance, so any film is a handful of them", () => {
|
|
135
|
-
// Near the viewer the difference between now and ten seconds away decides what
|
|
136
|
-
// is made first; twenty minutes out, one more division changes no decision.
|
|
137
|
-
const ahead = runsOf(viewer(0)).filter((run) => run.behind === false);
|
|
138
|
-
for (let index = 1; index < ahead.length; index += 1) {
|
|
139
|
-
assert.ok(ahead[index].to - ahead[index].from >= ahead[index - 1].to - ahead[index - 1].from,
|
|
140
|
-
"each stretch is at least as wide as the one before it");
|
|
141
|
-
}
|
|
142
|
-
assert.ok(ahead[0].to - ahead[0].from <= ALLOWANCE + 1,
|
|
143
|
-
"and the first is as wide as the measured allowance");
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("the working order is most urgent first, earliest film within one priority", () => {
|
|
147
|
-
const ordered = inWorkingOrder(runsOf(viewer(600)));
|
|
148
|
-
for (let index = 0; index < ordered.length - 1; index += 1) {
|
|
149
|
-
const left = ordered[index];
|
|
150
|
-
const right = ordered[index + 1];
|
|
151
|
-
assert.ok(
|
|
152
|
-
left.priority > right.priority || (left.priority === right.priority && left.from < right.from),
|
|
153
|
-
"sorted by priority, then by position"
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
assert.equal(ordered[0].from, 600, "and the first of all is where the viewer is stopped");
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("a map of no length is a statement, and it says nothing is wanted", () => {
|
|
160
|
-
const map = emptyMap(0);
|
|
161
|
-
assert.equal(map.durationSeconds, 0);
|
|
162
|
-
assert.deepEqual(runsOf(map), []);
|
|
163
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file The priority map: one number per second of film.
|
|
3
|
+
*
|
|
4
|
+
* The map is literally a map — an array as long as the film, saying how urgently
|
|
5
|
+
* each second is wanted, and beside it how long until somebody plays it. What is
|
|
6
|
+
* checked here is what the numbers MEAN, since only their order is meaningful:
|
|
7
|
+
* in front of a viewer beats behind them, nearer beats further, and two viewers
|
|
8
|
+
* merge to the more urgent of them.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here spawns anything, reads a disk or looks at a clock: this layer is
|
|
11
|
+
* exercised with plain values alone, which is the layer check made executable.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import test from "node:test";
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import {
|
|
17
|
+
emptyMap,
|
|
18
|
+
inWorkingOrder,
|
|
19
|
+
isBehindEverybody,
|
|
20
|
+
mapForViewer,
|
|
21
|
+
mergeMaps,
|
|
22
|
+
runsOf
|
|
23
|
+
} from "../services/viewer/PriorityMap.js";
|
|
24
|
+
|
|
25
|
+
const FILM = 3600;
|
|
26
|
+
const ALLOWANCE = 8;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {number} atSeconds
|
|
30
|
+
* @param {boolean} [playing]
|
|
31
|
+
*/
|
|
32
|
+
function viewer(atSeconds, playing = true) {
|
|
33
|
+
return mapForViewer({
|
|
34
|
+
atSeconds,
|
|
35
|
+
durationSeconds: FILM,
|
|
36
|
+
allowanceSeconds: ALLOWANCE,
|
|
37
|
+
playing
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("the map is one entry per second of film", () => {
|
|
42
|
+
const map = viewer(600);
|
|
43
|
+
assert.equal(map.durationSeconds, FILM);
|
|
44
|
+
assert.equal(map.priority.length, FILM);
|
|
45
|
+
assert.equal(map.secondsUntilPlayed.length, FILM);
|
|
46
|
+
assert.equal(map.behind.length, FILM);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("the nearer a viewer is to a second, the higher its number", () => {
|
|
50
|
+
// The whole of what a priority means: which of two seconds is wanted first.
|
|
51
|
+
const map = viewer(600);
|
|
52
|
+
assert.ok(map.priority[600] > map.priority[700], "their own second beats one a hundred on");
|
|
53
|
+
assert.ok(map.priority[700] > map.priority[3000], "and that beats one forty minutes on");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("the time is the distance, because a viewer covers a second of film in a second", () => {
|
|
57
|
+
const map = viewer(600);
|
|
58
|
+
assert.equal(map.secondsUntilPlayed[600], 0, "they are there now");
|
|
59
|
+
assert.equal(map.secondsUntilPlayed[660], 60, "a minute of film away is a minute away");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("what is behind a viewer is wanted, and wanted last", () => {
|
|
63
|
+
// Still wanted — a seek back must be cheap — but it yields to everything
|
|
64
|
+
// anybody is walking towards, however far off that is.
|
|
65
|
+
const map = viewer(600);
|
|
66
|
+
assert.equal(map.behind[599], 1);
|
|
67
|
+
assert.equal(map.behind[600], 0);
|
|
68
|
+
assert.ok(isBehindEverybody(map.priority[599]));
|
|
69
|
+
assert.ok(map.priority[599] < map.priority[3599],
|
|
70
|
+
"the last second of the film outranks the second they just watched");
|
|
71
|
+
assert.equal(map.secondsUntilPlayed[599], Number.POSITIVE_INFINITY,
|
|
72
|
+
"nobody is on their way there, so there is no time by which it must exist");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("a viewer who has stopped the picture keeps their position, and loses their times", () => {
|
|
76
|
+
// A pause removes the time, not the direction. Collapsed to one flat value
|
|
77
|
+
// over the whole film, as it was, their position disappeared entirely — and
|
|
78
|
+
// with it the rule that what is in front of them is made first.
|
|
79
|
+
const map = viewer(600, false);
|
|
80
|
+
assert.equal(map.behind[599], 1, "what they have watched is still behind them");
|
|
81
|
+
assert.equal(map.behind[600], 0, "and what they have not is still in front");
|
|
82
|
+
assert.ok(map.priority[600] > map.priority[599], "in front still outranks behind");
|
|
83
|
+
assert.ok(map.priority[600] > map.priority[3000], "and nearer still outranks further");
|
|
84
|
+
for (const second of [599, 600, 3000]) {
|
|
85
|
+
assert.equal(map.secondsUntilPlayed[second], Number.POSITIVE_INFINITY,
|
|
86
|
+
"but nothing has a time, because they are on their way nowhere");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("anybody who is watching outranks anybody who has stopped", () => {
|
|
91
|
+
// An ordering fact rather than a chosen number: somebody watching needs their
|
|
92
|
+
// next second almost at once, while somebody stopped needs theirs at a time
|
|
93
|
+
// nothing here knows.
|
|
94
|
+
const watching = viewer(600);
|
|
95
|
+
const stopped = viewer(3000, false);
|
|
96
|
+
assert.ok(watching.priority[3599] > stopped.priority[3000],
|
|
97
|
+
"the far tail of a watching viewer beats the very next second of a stopped one");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("two viewers merge to the more urgent of them, second by second", () => {
|
|
101
|
+
const merged = mergeMaps([viewer(600), viewer(1800)]);
|
|
102
|
+
assert.equal(merged.priority[1800], merged.priority[600],
|
|
103
|
+
"each of them is at the top of the scale where they stand");
|
|
104
|
+
assert.equal(merged.secondsUntilPlayed[1800], 0, "the nearer time wins");
|
|
105
|
+
assert.equal(merged.behind[599], 1, "behind both of them is behind");
|
|
106
|
+
assert.equal(merged.behind[700], 0, "and in front of ANYBODY is in front");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("the second viewer's position is not buried under the first viewer's distance", () => {
|
|
110
|
+
// The failure this guards: one viewer's far film outranking another viewer's
|
|
111
|
+
// own second, so the second of them is served by nobody.
|
|
112
|
+
const merged = mergeMaps([viewer(600), viewer(1800)]);
|
|
113
|
+
assert.ok(merged.priority[1800] > merged.priority[1799],
|
|
114
|
+
"where the second viewer stands beats the film just before them");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("a film nobody is watching wants nothing", () => {
|
|
118
|
+
const merged = mergeMaps([]);
|
|
119
|
+
assert.equal(merged.durationSeconds, 0);
|
|
120
|
+
assert.deepEqual(runsOf(merged), []);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("the map as stretches says the same thing in fewer numbers", () => {
|
|
124
|
+
const runs = runsOf(viewer(600));
|
|
125
|
+
assert.ok(runs.length > 1 && runs.length < 40, "a handful of stretches, not thousands");
|
|
126
|
+
assert.equal(runs[0].from, 0, "starting at the beginning of the film");
|
|
127
|
+
assert.equal(runs[0].behind, true, "which is behind them");
|
|
128
|
+
assert.equal(runs[runs.length - 1].to, FILM, "and ending at its end");
|
|
129
|
+
for (let index = 0; index < runs.length - 1; index += 1) {
|
|
130
|
+
assert.equal(runs[index].to, runs[index + 1].from, "with no gaps");
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("the stretches widen with distance, so any film is a handful of them", () => {
|
|
135
|
+
// Near the viewer the difference between now and ten seconds away decides what
|
|
136
|
+
// is made first; twenty minutes out, one more division changes no decision.
|
|
137
|
+
const ahead = runsOf(viewer(0)).filter((run) => run.behind === false);
|
|
138
|
+
for (let index = 1; index < ahead.length; index += 1) {
|
|
139
|
+
assert.ok(ahead[index].to - ahead[index].from >= ahead[index - 1].to - ahead[index - 1].from,
|
|
140
|
+
"each stretch is at least as wide as the one before it");
|
|
141
|
+
}
|
|
142
|
+
assert.ok(ahead[0].to - ahead[0].from <= ALLOWANCE + 1,
|
|
143
|
+
"and the first is as wide as the measured allowance");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("the working order is most urgent first, earliest film within one priority", () => {
|
|
147
|
+
const ordered = inWorkingOrder(runsOf(viewer(600)));
|
|
148
|
+
for (let index = 0; index < ordered.length - 1; index += 1) {
|
|
149
|
+
const left = ordered[index];
|
|
150
|
+
const right = ordered[index + 1];
|
|
151
|
+
assert.ok(
|
|
152
|
+
left.priority > right.priority || (left.priority === right.priority && left.from < right.from),
|
|
153
|
+
"sorted by priority, then by position"
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
assert.equal(ordered[0].from, 600, "and the first of all is where the viewer is stopped");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("a map of no length is a statement, and it says nothing is wanted", () => {
|
|
160
|
+
const map = emptyMap(0);
|
|
161
|
+
assert.equal(map.durationSeconds, 0);
|
|
162
|
+
assert.deepEqual(runsOf(map), []);
|
|
163
|
+
});
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
import { probeWedgeIsCertain, readProbeState, PROBE_INTERVAL_MS } from "../services/transport/delivery-probe.js";
|
|
5
|
-
|
|
6
|
-
test("a seen-counter bounded lag is not a wedge, however long it lasts", () => {
|
|
7
|
-
// Session 4dcac61b, field log 2026-08-28: gap held at 6-7 probes for 95+
|
|
8
|
-
// seconds on a backgrounded tab, but `seen` kept climbing right along with
|
|
9
|
-
// `sent` — this connection's own history says gaps up to ~3.5 s (7 probes
|
|
10
|
-
// at 500 ms) are ordinary, so the same 3.5 s stuck must not read as certain.
|
|
11
|
-
const verdict = probeWedgeIsCertain({
|
|
12
|
-
stuckForMs: 3400,
|
|
13
|
-
longestHealthySeenGapMs: 3500
|
|
14
|
-
});
|
|
15
|
-
assert.equal(verdict.isCertain, false);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("a seen-counter frozen past this connection's own worst legitimate gap is a wedge", () => {
|
|
19
|
-
// Session d85ae4f5, the same field log: `seen` frozen at one value for over
|
|
20
|
-
// a minute while `sent` climbed unbounded — this is the shape the detector
|
|
21
|
-
// exists to catch.
|
|
22
|
-
const verdict = probeWedgeIsCertain({
|
|
23
|
-
stuckForMs: 90_000,
|
|
24
|
-
longestHealthySeenGapMs: 3500
|
|
25
|
-
});
|
|
26
|
-
assert.equal(verdict.isCertain, true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("with no healthy history yet, one probe interval is still required", () => {
|
|
30
|
-
const verdict = probeWedgeIsCertain({
|
|
31
|
-
stuckForMs: PROBE_INTERVAL_MS - 1,
|
|
32
|
-
longestHealthySeenGapMs: 0
|
|
33
|
-
});
|
|
34
|
-
assert.equal(verdict.isCertain, false);
|
|
35
|
-
assert.equal(verdict.needMs, PROBE_INTERVAL_MS);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("readProbeState calls association-stopped only when the unreliable channel agrees", () => {
|
|
39
|
-
// Ordered channels behind, but the unordered/no-retransmit one current:
|
|
40
|
-
// head-of-line blocking in one stream, not the association.
|
|
41
|
-
const streamStuck = readProbeState({
|
|
42
|
-
seq: 100,
|
|
43
|
-
seen: { proxy: 90, "proxy-control": 90, "proxy-fast": 99 },
|
|
44
|
-
labels: ["proxy", "proxy-control", "proxy-fast"],
|
|
45
|
-
echoes: 5,
|
|
46
|
-
echoAgeMs: 100,
|
|
47
|
-
allowed: { proxy: 2, "proxy-control": 2, "proxy-fast": 2 }
|
|
48
|
-
});
|
|
49
|
-
assert.equal(streamStuck.verdict, "stream-stuck");
|
|
50
|
-
|
|
51
|
-
const associationStopped = readProbeState({
|
|
52
|
-
seq: 100,
|
|
53
|
-
seen: { proxy: 90, "proxy-control": 90, "proxy-fast": 90 },
|
|
54
|
-
labels: ["proxy", "proxy-control", "proxy-fast"],
|
|
55
|
-
echoes: 5,
|
|
56
|
-
echoAgeMs: 100,
|
|
57
|
-
allowed: { proxy: 2, "proxy-control": 2, "proxy-fast": 2 }
|
|
58
|
-
});
|
|
59
|
-
assert.equal(associationStopped.verdict, "association-stopped");
|
|
60
|
-
});
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { probeWedgeIsCertain, readProbeState, PROBE_INTERVAL_MS } from "../services/transport/delivery-probe.js";
|
|
5
|
+
|
|
6
|
+
test("a seen-counter bounded lag is not a wedge, however long it lasts", () => {
|
|
7
|
+
// Session 4dcac61b, field log 2026-08-28: gap held at 6-7 probes for 95+
|
|
8
|
+
// seconds on a backgrounded tab, but `seen` kept climbing right along with
|
|
9
|
+
// `sent` — this connection's own history says gaps up to ~3.5 s (7 probes
|
|
10
|
+
// at 500 ms) are ordinary, so the same 3.5 s stuck must not read as certain.
|
|
11
|
+
const verdict = probeWedgeIsCertain({
|
|
12
|
+
stuckForMs: 3400,
|
|
13
|
+
longestHealthySeenGapMs: 3500
|
|
14
|
+
});
|
|
15
|
+
assert.equal(verdict.isCertain, false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("a seen-counter frozen past this connection's own worst legitimate gap is a wedge", () => {
|
|
19
|
+
// Session d85ae4f5, the same field log: `seen` frozen at one value for over
|
|
20
|
+
// a minute while `sent` climbed unbounded — this is the shape the detector
|
|
21
|
+
// exists to catch.
|
|
22
|
+
const verdict = probeWedgeIsCertain({
|
|
23
|
+
stuckForMs: 90_000,
|
|
24
|
+
longestHealthySeenGapMs: 3500
|
|
25
|
+
});
|
|
26
|
+
assert.equal(verdict.isCertain, true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("with no healthy history yet, one probe interval is still required", () => {
|
|
30
|
+
const verdict = probeWedgeIsCertain({
|
|
31
|
+
stuckForMs: PROBE_INTERVAL_MS - 1,
|
|
32
|
+
longestHealthySeenGapMs: 0
|
|
33
|
+
});
|
|
34
|
+
assert.equal(verdict.isCertain, false);
|
|
35
|
+
assert.equal(verdict.needMs, PROBE_INTERVAL_MS);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("readProbeState calls association-stopped only when the unreliable channel agrees", () => {
|
|
39
|
+
// Ordered channels behind, but the unordered/no-retransmit one current:
|
|
40
|
+
// head-of-line blocking in one stream, not the association.
|
|
41
|
+
const streamStuck = readProbeState({
|
|
42
|
+
seq: 100,
|
|
43
|
+
seen: { proxy: 90, "proxy-control": 90, "proxy-fast": 99 },
|
|
44
|
+
labels: ["proxy", "proxy-control", "proxy-fast"],
|
|
45
|
+
echoes: 5,
|
|
46
|
+
echoAgeMs: 100,
|
|
47
|
+
allowed: { proxy: 2, "proxy-control": 2, "proxy-fast": 2 }
|
|
48
|
+
});
|
|
49
|
+
assert.equal(streamStuck.verdict, "stream-stuck");
|
|
50
|
+
|
|
51
|
+
const associationStopped = readProbeState({
|
|
52
|
+
seq: 100,
|
|
53
|
+
seen: { proxy: 90, "proxy-control": 90, "proxy-fast": 90 },
|
|
54
|
+
labels: ["proxy", "proxy-control", "proxy-fast"],
|
|
55
|
+
echoes: 5,
|
|
56
|
+
echoAgeMs: 100,
|
|
57
|
+
allowed: { proxy: 2, "proxy-control": 2, "proxy-fast": 2 }
|
|
58
|
+
});
|
|
59
|
+
assert.equal(associationStopped.verdict, "association-stopped");
|
|
60
|
+
});
|