@torrent-tv/proxy 2.89.5 → 2.89.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.
Files changed (447) hide show
  1. package/.claude/commands/opsx/apply.md +155 -155
  2. package/.claude/commands/opsx/archive.md +160 -160
  3. package/.claude/commands/opsx/explore.md +174 -174
  4. package/.claude/commands/opsx/propose.md +109 -109
  5. package/.claude/commands/opsx/sync.md +143 -143
  6. package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
  7. package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
  8. package/.claude/skills/openspec-explore/SKILL.md +289 -289
  9. package/.claude/skills/openspec-propose/SKILL.md +113 -113
  10. package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
  11. package/.dockerignore +5 -5
  12. package/AGENTS.md +338 -338
  13. package/CHANGELOG.md +16 -0
  14. package/Dockerfile +30 -30
  15. package/LICENSE +16 -16
  16. package/README.md +369 -369
  17. package/assets/calibration/NOTICE.md +100 -100
  18. package/assets/diagnostics/sctpstate.gdb +31 -31
  19. package/bin/cli.js +684 -684
  20. package/bin/thread-pool.js +30 -30
  21. package/docs/download-architecture.md +261 -261
  22. package/docs/encode-architecture.md +765 -765
  23. package/docs/encode-run-state.md +96 -96
  24. package/docs/logs.md +97 -97
  25. package/docs/playback-start-forecast.md +17 -2
  26. package/docs/socks5-egress-research.md +114 -114
  27. package/docs/storage-architecture.md +273 -273
  28. package/knip.json +14 -14
  29. package/openspec/changes/adaptive-bitrate/design.md +92 -92
  30. package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
  31. package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
  32. package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
  33. package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
  34. package/openspec/changes/binary-distribution/proposal.md +53 -53
  35. package/openspec/changes/chunked-request-bodies/design.md +81 -81
  36. package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
  37. package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
  38. package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
  39. package/openspec/changes/cold-start/design.md +98 -98
  40. package/openspec/changes/cold-start/proposal.md +62 -62
  41. package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
  42. package/openspec/changes/cold-start/tasks.md +48 -48
  43. package/openspec/changes/disk-cap/.openspec.yaml +2 -2
  44. package/openspec/changes/disk-cap/proposal.md +37 -37
  45. package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
  46. package/openspec/changes/disk-cap/tasks.md +19 -19
  47. package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
  48. package/openspec/changes/proxy-observability/design.md +38 -38
  49. package/openspec/changes/proxy-observability/proposal.md +49 -49
  50. package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
  51. package/openspec/changes/proxy-observability/tasks.md +21 -21
  52. package/openspec/changes/seek-debounce/design.md +118 -118
  53. package/openspec/changes/seek-debounce/proposal.md +59 -59
  54. package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
  55. package/openspec/changes/seek-debounce/tasks.md +33 -33
  56. package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
  57. package/openspec/changes/subtitle-language/proposal.md +50 -50
  58. package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
  59. package/openspec/changes/subtitle-language/tasks.md +26 -26
  60. package/openspec/changes/track-selection/.openspec.yaml +2 -2
  61. package/openspec/changes/track-selection/design.md +47 -47
  62. package/openspec/changes/track-selection/proposal.md +45 -45
  63. package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
  64. package/openspec/changes/track-selection/tasks.md +20 -20
  65. package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
  66. package/openspec/changes/transcode-quality/proposal.md +51 -51
  67. package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
  68. package/openspec/changes/transcode-quality/tasks.md +63 -63
  69. package/openspec/config.yaml +38 -38
  70. package/package.json +1 -1
  71. package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
  72. package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
  73. package/research/double-spawn-2026-09-10.md +171 -171
  74. package/research/handover-2026-09-13.md +209 -209
  75. package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
  76. package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
  77. package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
  78. package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
  79. package/routes/api/client-logs/post.js +86 -86
  80. package/routes/api/delivery-sink/get.js +72 -72
  81. package/routes/api/link-probe/get.js +51 -51
  82. package/routes/api/playback-plan/audio-tracks/post.js +29 -29
  83. package/routes/api/playback-plan/post.js +53 -53
  84. package/routes/api/sources/post.js +36 -36
  85. package/routes/api/sources/stats/get.js +132 -132
  86. package/routes/api/sources/warm/post.js +217 -217
  87. package/routes/api/subtitles/get.js +230 -230
  88. package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
  89. package/routes/api/transcode-sessions/net-report/post.js +138 -138
  90. package/routes/api/transcode-sessions/post.js +178 -178
  91. package/routes/api/transcode-sessions/progress/get.js +27 -27
  92. package/routes/api/transcode-sessions/release/post.js +41 -41
  93. package/routes/api/transcode-sessions/seek/post.js +63 -63
  94. package/routes/health/get.js +13 -13
  95. package/routes/healthz/get.js +13 -13
  96. package/routes/stream/get.js +416 -416
  97. package/routes/transcode/audio-file/get.js +64 -64
  98. package/routes/transcode/audio-warm/get.js +95 -95
  99. package/routes/transcode/session-file/get.js +52 -52
  100. package/routes/transcode/variant-file/get.js +95 -95
  101. package/routes/transcode/variant-warm/get.js +84 -84
  102. package/scripts/render-run-graph.js +173 -173
  103. package/server.js +748 -748
  104. package/services/encode/CoverageMap.js +428 -428
  105. package/services/encode/CushionReport.js +345 -345
  106. package/services/encode/EncodeAdmission.js +376 -376
  107. package/services/encode/EncodeOrchestrator.js +1192 -1192
  108. package/services/encode/EncodePlan.js +1368 -1368
  109. package/services/encode/EncodeRun.js +32 -4
  110. package/services/encode/EncodeRuns.js +19 -7
  111. package/services/encode/Encoder.js +142 -142
  112. package/services/encode/EncoderSelection.js +38 -38
  113. package/services/encode/LocalObservations.js +359 -359
  114. package/services/encode/NvencEncoder.js +50 -50
  115. package/services/encode/OutputTimes.js +579 -579
  116. package/services/encode/QsvEncoder.js +58 -58
  117. package/services/encode/Renditions.js +2283 -2283
  118. package/services/encode/RunClock.js +87 -87
  119. package/services/encode/RunProgress.js +79 -79
  120. package/services/encode/SegmentDemand.js +95 -95
  121. package/services/encode/SoftwareEncoder.js +121 -121
  122. package/services/encode/V4l2m2mEncoder.js +53 -53
  123. package/services/encode/VaapiEncoder.js +66 -66
  124. package/services/encode/args.js +418 -418
  125. package/services/encode/available-share.js +89 -89
  126. package/services/encode/calibration.js +361 -361
  127. package/services/encode/contention.js +128 -128
  128. package/services/encode/decode-cost-fit.js +363 -363
  129. package/services/encode/encode-exit.js +116 -114
  130. package/services/encode/encode-run-state.js +551 -551
  131. package/services/encode/encoder-readings.js +43 -43
  132. package/services/encode/fingerprint.js +322 -322
  133. package/services/encode/host-load.js +174 -174
  134. package/services/encode/index.js +9 -9
  135. package/services/encode/learned-median.js +90 -90
  136. package/services/encode/output/EncodedOutput.js +57 -57
  137. package/services/encode/output/Output.js +131 -131
  138. package/services/encode/output/OutputCatalog.js +414 -414
  139. package/services/encode/output/OutputSpec.js +390 -390
  140. package/services/encode/output/Timeline.js +308 -308
  141. package/services/encode/output/cut-grid.js +107 -107
  142. package/services/encode/output/ladder.js +32 -32
  143. package/services/encode/output/playlists.js +284 -284
  144. package/services/encode/output/rates.js +125 -125
  145. package/services/encode/output-key-format.js +21 -21
  146. package/services/encode/piece-completeness.js +60 -2
  147. package/services/encode/quality/EncodeCost.js +1043 -1043
  148. package/services/encode/quality/HostLoad.js +525 -525
  149. package/services/encode/quality/HostTimings.js +249 -249
  150. package/services/encode/quality/OutputQualityState.js +24 -24
  151. package/services/encode/quality/QualityController.js +758 -758
  152. package/services/encode/quality/QualityOffer.js +416 -416
  153. package/services/encode/quality/drain-threat.js +47 -47
  154. package/services/encode/quality/link-budget.js +337 -337
  155. package/services/encode/quality/output-format.js +319 -319
  156. package/services/encode/quality/serving-output.js +216 -216
  157. package/services/encode/quality/visible-rung.js +57 -57
  158. package/services/encode/run-budget.js +97 -97
  159. package/services/encode/run-command.js +3 -1
  160. package/services/encode/run-costs.js +155 -155
  161. package/services/encode/run-inputs.js +53 -53
  162. package/services/encode/segment-formats/fmp4.js +476 -476
  163. package/services/encode/segment-formats/index.js +1 -0
  164. package/services/encode/segment-formats/init-compat.js +324 -324
  165. package/services/encode/segment-formats/mp4-boxes.js +8 -1
  166. package/services/encode/segment-formats/mpegts.js +134 -134
  167. package/services/encode/start-stop-cost.js +178 -178
  168. package/services/encode/throughput.js +97 -97
  169. package/services/encode/torrent-cost.js +88 -88
  170. package/services/identity/installation-id.js +38 -38
  171. package/services/media/SourceFile.js +298 -298
  172. package/services/media/SubtitleOrchestrator.js +167 -167
  173. package/services/media/container/KeyframeTable.js +272 -272
  174. package/services/media/container/ebml-reader.js +159 -159
  175. package/services/media/ffmpeg-banner.js +260 -260
  176. package/services/media/keyframe-probe.js +157 -157
  177. package/services/media/media-info-probe.js +93 -93
  178. package/services/media/playback-planner.js +931 -931
  179. package/services/media/tracks/AudioTrack.js +143 -143
  180. package/services/media/tracks/ImageSubtitleTrack.js +19 -19
  181. package/services/media/tracks/SubtitleTrack.js +38 -38
  182. package/services/media/tracks/VideoTrack.js +33 -33
  183. package/services/media/tracks/audio-bound.js +233 -233
  184. package/services/media/tracks/index.js +15 -15
  185. package/services/media/tracks/language-detect.js +174 -174
  186. package/services/server/OutputLifecycle.js +418 -418
  187. package/services/server/SegmentServing.js +1152 -1152
  188. package/services/server/ViewerRequests.js +785 -785
  189. package/services/server/WaitLedger.js +142 -142
  190. package/services/server/controllers/PlaybackController.js +40 -40
  191. package/services/server/controllers/SubtitleController.js +109 -109
  192. package/services/server/http-responses.js +60 -60
  193. package/services/server/transcode-session-files.js +325 -325
  194. package/services/server/wire-outputs.js +1029 -1029
  195. package/services/storage/Diagnostics.js +202 -202
  196. package/services/storage/MachineBudget.js +235 -235
  197. package/services/storage/allowance.js +107 -107
  198. package/services/storage/core-dumps.js +106 -106
  199. package/services/storage/files/CompletedFiles.js +391 -391
  200. package/services/storage/files/piece-from-whole-file.js +118 -118
  201. package/services/storage/free.js +71 -71
  202. package/services/storage/keep.js +48 -48
  203. package/services/storage/machine-memory.js +56 -56
  204. package/services/storage/memory-report.js +575 -575
  205. package/services/storage/piece-store/piece-disk-store.js +539 -539
  206. package/services/storage/piece-store/piece-lru.js +495 -495
  207. package/services/storage/returns.js +103 -103
  208. package/services/storage/wire.js +175 -175
  209. package/services/torrent/demand/DemandRegister.js +202 -202
  210. package/services/torrent/demand/Urgency.js +137 -137
  211. package/services/torrent/demand/Window.js +118 -118
  212. package/services/torrent/demand/index.js +8 -8
  213. package/services/torrent/demand/pieces.js +140 -140
  214. package/services/torrent/download/SwarmSelection.js +386 -386
  215. package/services/torrent/download/registry.js +102 -102
  216. package/services/torrent/download/withdraw-claim.js +80 -80
  217. package/services/torrent/files.js +455 -455
  218. package/services/torrent/naming.js +619 -619
  219. package/services/torrent/piece-store-of.js +29 -29
  220. package/services/torrent/supply-margin.js +264 -264
  221. package/services/torrent/worker/background-fill.js +213 -213
  222. package/services/torrent/worker/channel.js +271 -271
  223. package/services/torrent/worker/fastest-wires.js +342 -342
  224. package/services/torrent/worker/handle-state.js +23 -23
  225. package/services/torrent/worker/install-webrtc-shim.js +32 -32
  226. package/services/torrent/worker/piece-reader.js +1078 -1078
  227. package/services/torrent/worker/pool-adapter.js +371 -371
  228. package/services/torrent/worker/protocol.js +184 -184
  229. package/services/torrent/worker/resume-warm.js +107 -107
  230. package/services/torrent/worker/webrtc-shim.js +219 -219
  231. package/services/transport/delivery-probe.js +842 -842
  232. package/services/transport/health-collector.js +53 -53
  233. package/services/transport/nat-classifier.js +231 -231
  234. package/services/transport/packet-witness.js +808 -808
  235. package/services/transport/port-mapper.js +235 -235
  236. package/services/transport/registry-api.js +81 -81
  237. package/services/transport/tunnel-client.js +447 -447
  238. package/services/transport/usrsctp-state.js +175 -175
  239. package/services/transport/webrtc-manager.js +484 -484
  240. package/services/viewer/Assignments.js +340 -340
  241. package/services/viewer/PriorityOrchestrator.js +325 -325
  242. package/services/viewer/Viewer.js +691 -691
  243. package/services/viewer/Viewers.js +368 -368
  244. package/services/viewer/active-output.js +37 -37
  245. package/services/viewer/audio-start.js +46 -46
  246. package/services/viewer/buffer-trend.js +81 -81
  247. package/services/viewer/choices.js +647 -647
  248. package/services/viewer/media-time.js +355 -355
  249. package/services/viewer/playback-readiness.js +635 -635
  250. package/services/viewer/positions.js +126 -126
  251. package/services/viewer/report-intake.js +46 -46
  252. package/store/source-registry.js +71 -71
  253. package/test/asset-paths.test.js +28 -28
  254. package/test/audio-buffer-continuity.test.js +77 -77
  255. package/test/audio-inventory.test.js +212 -212
  256. package/test/audio-track-bitrate.test.js +40 -40
  257. package/test/auto-quality-step.test.js +762 -753
  258. package/test/available-share.test.js +76 -76
  259. package/test/background-fill.test.js +190 -190
  260. package/test/client-log-file.test.js +110 -110
  261. package/test/completed-files.test.js +115 -115
  262. package/test/concurrent-cost.test.js +148 -148
  263. package/test/container-media-info.test.js +228 -228
  264. package/test/contention.test.js +76 -76
  265. package/test/core-dumps.test.js +57 -57
  266. package/test/coverage-follows-the-disk.test.js +228 -228
  267. package/test/coverage-map.test.js +195 -195
  268. package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
  269. package/test/cut-times-timeline.test.js +65 -65
  270. package/test/cuts-follow-published-grid.test.js +109 -109
  271. package/test/data-channel-frame.test.js +63 -63
  272. package/test/data-channel-viewer-lifecycle.test.js +92 -92
  273. package/test/dead-torrent-handle.test.js +52 -52
  274. package/test/declared-rates.test.js +116 -116
  275. package/test/declared-tracks.test.js +43 -43
  276. package/test/decode-cost-fit.test.js +202 -202
  277. package/test/decode-cost.test.js +475 -462
  278. package/test/delivery-probe.test.js +393 -393
  279. package/test/demand-register.test.js +206 -206
  280. package/test/dht-bootstrap.test.js +72 -72
  281. package/test/drain-threat.test.js +65 -65
  282. package/test/duplicate-add.test.js +48 -48
  283. package/test/encode-admission.test.js +146 -146
  284. package/test/encode-cost.test.js +275 -275
  285. package/test/encode-exit.test.js +111 -111
  286. package/test/encode-orchestrator.test.js +426 -426
  287. package/test/encode-plan-viewers.test.js +725 -725
  288. package/test/encode-plan.test.js +545 -545
  289. package/test/encode-run-state.test.js +314 -314
  290. package/test/encode-run.test.js +38 -0
  291. package/test/encode-slope.test.js +46 -46
  292. package/test/encoded-output.test.js +39 -39
  293. package/test/encoder-kinds.test.js +122 -122
  294. package/test/encoder-readings.test.js +68 -68
  295. package/test/every-claimant-is-told-its-share.test.js +152 -152
  296. package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
  297. package/test/failed-start-breaker.test.js +202 -202
  298. package/test/fastest-wires.test.js +143 -143
  299. package/test/ffmpeg-stream-counts.test.js +78 -78
  300. package/test/file-edges.test.js +191 -191
  301. package/test/fixtures/refused-audio-20261002.json +10 -0
  302. package/test/fmp4-presentation-clock.test.js +4 -2
  303. package/test/fmp4-self-contained.test.js +80 -80
  304. package/test/fmp4-timestamps.test.js +140 -140
  305. package/test/fmp4-track-completeness.test.js +41 -41
  306. package/test/format-keyed-outputs.test.js +78 -78
  307. package/test/health-metrics.test.js +38 -38
  308. package/test/held-request-width.test.js +157 -157
  309. package/test/helpers/encode-run.js +240 -240
  310. package/test/helpers/manager.js +62 -57
  311. package/test/helpers/output-spec.js +63 -63
  312. package/test/host-load.test.js +68 -68
  313. package/test/index-accuracy-is-about-the-file.test.js +128 -128
  314. package/test/init-compat.test.js +219 -219
  315. package/test/input-lost-quiets-the-plan.test.js +261 -261
  316. package/test/input-unavailable.test.js +48 -48
  317. package/test/installation-id.test.js +39 -39
  318. package/test/keeping-period.test.js +83 -83
  319. package/test/keyframe-index-accuracy.test.js +188 -188
  320. package/test/keyframes-belong-to-the-file.test.js +249 -249
  321. package/test/learned-median.test.js +37 -37
  322. package/test/letting-go-asks-who-is-watching.test.js +191 -191
  323. package/test/limit-row-by-area.test.js +60 -60
  324. package/test/link-budget.test.js +111 -111
  325. package/test/link-probe.test.js +56 -56
  326. package/test/local-encoding-data.test.js +273 -273
  327. package/test/logger-repeats.test.js +134 -134
  328. package/test/look-ahead-coverage.test.js +56 -56
  329. package/test/manager-surface.test.js +291 -291
  330. package/test/matroska-cues-track.test.js +192 -192
  331. package/test/media-time.test.js +62 -62
  332. package/test/memory-budget.test.js +390 -390
  333. package/test/move-cost.test.js +216 -216
  334. package/test/mp4-subtitles.test.js +173 -173
  335. package/test/one-authority.test.js +285 -285
  336. package/test/one-budget.test.js +149 -149
  337. package/test/one-resource-divided.test.js +185 -185
  338. package/test/one-serving-output-per-height.test.js +458 -458
  339. package/test/orchestrator-wired.test.js +207 -207
  340. package/test/output-catalog.test.js +39 -39
  341. package/test/output-shape.test.js +21 -21
  342. package/test/output-spec.test.js +251 -251
  343. package/test/output-speed.test.js +87 -87
  344. package/test/outputs-being-prepared.test.js +40 -40
  345. package/test/packet-witness-ring.test.js +236 -236
  346. package/test/packet-witness.test.js +148 -148
  347. package/test/piece-completeness.test.js +37 -1
  348. package/test/piece-disk-store.test.js +401 -401
  349. package/test/piece-from-whole-file.test.js +129 -129
  350. package/test/piece-lru.test.js +319 -319
  351. package/test/piece-reader.test.js +162 -162
  352. package/test/piece-selection-offset.test.js +85 -85
  353. package/test/piece-store-eviction.test.js +595 -595
  354. package/test/piece-store-never-refuses.test.js +153 -153
  355. package/test/piece-store-reservations.test.js +403 -403
  356. package/test/piece-store-slow-disk.test.js +159 -159
  357. package/test/piece-tail.test.js +97 -97
  358. package/test/plan-host-timings.test.js +68 -68
  359. package/test/plan-is-steady.test.js +225 -225
  360. package/test/playback-readiness.test.js +518 -518
  361. package/test/playback-start-regressions.test.js +101 -101
  362. package/test/priority-map-download.test.js +174 -174
  363. package/test/priority-map-emptied.test.js +204 -204
  364. package/test/priority-map-per-output.test.js +215 -215
  365. package/test/priority-map.test.js +163 -163
  366. package/test/probe-wedge-certainty.test.js +60 -60
  367. package/test/produced-copy-choice.test.js +213 -213
  368. package/test/published-timeline.test.js +83 -83
  369. package/test/quality-offer.test.js +160 -160
  370. package/test/quality-variants.test.js +1270 -1257
  371. package/test/rate-control-in-the-output.test.js +180 -180
  372. package/test/read-survives-withdrawal.test.js +164 -164
  373. package/test/read-window.test.js +365 -365
  374. package/test/resume-warm.test.js +39 -39
  375. package/test/run-graph-drift.test.js +37 -37
  376. package/test/run-inputs.test.js +80 -80
  377. package/test/run-intervals.test.js +100 -100
  378. package/test/run-position-follows-published-grid.test.js +89 -89
  379. package/test/run-progress.test.js +64 -64
  380. package/test/same-height-switch.test.js +663 -663
  381. package/test/seek-frees-the-viewers-requests.test.js +217 -217
  382. package/test/seek-landing.test.js +109 -109
  383. package/test/seek-target-not-superseded.test.js +195 -195
  384. package/test/segment-cut-times.test.js +63 -63
  385. package/test/segment-demand.test.js +80 -80
  386. package/test/segment-hold-supersede.test.js +91 -91
  387. package/test/segment-judged-by-its-own-header.test.js +222 -221
  388. package/test/segment-serve-wiring.test.js +9 -3
  389. package/test/segment-store-eviction.test.js +247 -247
  390. package/test/segment-store.test.js +352 -352
  391. package/test/segment-true-start.test.js +86 -86
  392. package/test/segments-are-shared.test.js +176 -176
  393. package/test/send-chunk-bytes.test.js +86 -86
  394. package/test/serving-existing-output.test.js +111 -105
  395. package/test/serving-output.test.js +102 -102
  396. package/test/shared-piece-store.test.js +310 -310
  397. package/test/sidecar-naming.test.js +142 -142
  398. package/test/soundtrack-rate-bound.test.js +289 -289
  399. package/test/source-file.test.js +133 -133
  400. package/test/source-registry.test.js +18 -18
  401. package/test/split-a-stretch.test.js +139 -139
  402. package/test/stale-request-after-seek.test.js +158 -158
  403. package/test/startup-readings.test.js +124 -124
  404. package/test/stream-route.test.js +237 -237
  405. package/test/subtitle-cue-framing.test.js +200 -200
  406. package/test/subtitle-cue-source.test.js +102 -102
  407. package/test/subtitle-cursor.test.js +63 -63
  408. package/test/subtitle-defaults.test.js +118 -118
  409. package/test/subtitle-language.test.js +252 -252
  410. package/test/supply-margin.test.js +190 -190
  411. package/test/swarm-follows-readers.test.js +174 -174
  412. package/test/swarm-reach.test.js +130 -130
  413. package/test/swarm-selection.test.js +220 -220
  414. package/test/tail-duplication.test.js +167 -167
  415. package/test/the-channel-has-both-ends.test.js +91 -91
  416. package/test/the-torrent-thread-serves-bytes.test.js +107 -107
  417. package/test/thread-pool.test.js +53 -53
  418. package/test/timeline.test.js +84 -84
  419. package/test/torrent-contents.test.js +235 -235
  420. package/test/torrent-cost.test.js +71 -71
  421. package/test/torrent-files.test.js +221 -221
  422. package/test/torrent-naming.test.js +255 -255
  423. package/test/tracks-begin-together.test.js +209 -209
  424. package/test/tunnel-renewal.test.js +167 -167
  425. package/test/two-viewers-one-picture.test.js +508 -504
  426. package/test/upload-hurry.test.js +186 -186
  427. package/test/usrsctp-state.test.js +131 -131
  428. package/test/video-facts.test.js +102 -102
  429. package/test/viewer-assignments.test.js +143 -143
  430. package/test/viewer-outputs.test.js +285 -285
  431. package/test/viewer-presence.test.js +116 -116
  432. package/test/viewer-requests-admission.test.js +92 -89
  433. package/test/viewer-states.test.js +189 -189
  434. package/test/viewer-subtitle-subscription.test.js +96 -96
  435. package/test/viewer.test.js +139 -139
  436. package/test/viewing-generations.test.js +390 -390
  437. package/test/visible-rung.test.js +37 -37
  438. package/test/wait-ledger.test.js +100 -100
  439. package/test/webrtc-shim.test.js +149 -149
  440. package/test/wedge-certainty.test.js +131 -131
  441. package/test/withdraw-piece-claim.test.js +212 -212
  442. package/test/worker-channel.test.js +158 -158
  443. package/utils/client-log-file.js +151 -151
  444. package/utils/logger.js +312 -312
  445. package/utils/parse-range.js +25 -25
  446. package/utils/perf.js +59 -59
  447. package/utils/torrent-source-key.js +38 -38
@@ -1,62 +1,62 @@
1
- # Proposal: Cold-start reduction (proxy side)
2
-
3
- ## Why
4
-
5
- Field recordings (mobile tester, 2026-07-08) show ~90 seconds from picking an
6
- episode to a picture. The proxy owns two chunks of that:
7
-
8
- 1. **A redundant ffmpeg input scan.** The playback planner probes the input
9
- (`probeStreamCodecs`) and CACHES the plan — then `createSession` in
10
- hls-session-manager immediately probes the SAME input again
11
- (`probeInputMediaInfo`) for duration/resolution/fps/startTime/HDR. Each
12
- probe is a full ffmpeg startup + banner scan over the torrent-backed HTTP
13
- stream — seconds on the HA host, on the critical path, twice.
14
- 2. **First-segment piece latency.** The planner prefetches only the file
15
- EDGES (256 KB head + 2 MB tail — what the codec probe needs). The first
16
- segment's encode then needs the first ~10–20 MB of the file BODY, whose
17
- pieces start downloading only when ffmpeg asks for them — while the swarm
18
- sat mostly idle during the plan poll.
19
-
20
- There is also no per-stage timing in the logs, so field regressions in
21
- session startup are invisible.
22
-
23
- ## What Changes
24
-
25
- - **Reuse the planner's probe in session create.** The planner parses and
26
- caches the full media info (duration, width/height, fps, startTime, isHdr)
27
- from the probe it already runs; hls-session-manager consults that cache
28
- and skips its own `probeInputMediaInfo` on a hit (probe stays as the
29
- fallback). One full ffmpeg scan disappears from the critical path.
30
- - **Prefetch the file-body start once the plan is ready.** After a successful
31
- probe, fire-and-forget prefetch of the first 16 MB of the file, so the
32
- session's ffmpeg reads hit already-downloaded data instead of paying piece
33
- latency at encode time.
34
- - **Stage timings in the log.** Session create logs probe ms (and
35
- cache-hit/probed), keyframe-probe ms, and the time from create to the
36
- first servable segment — so cold-start is measurable per stage in the
37
- field, next to the client-side summary line (server `cold-start` change).
38
-
39
- Client-side counterparts (phase instrumentation, earlier prebuffer start)
40
- live in the server repo's `cold-start` change. The two changes are
41
- independent — no wire-format coupling, either releases alone.
42
-
43
- NOT in this change (candidates for later, kept out deliberately):
44
- next-episode speculative prefetch (bandwidth cost needs field data first);
45
- encoder-side first-segment speed-ups (faster warm-up preset would need a
46
- mid-stream switch — parked with transcode-quality 2.2b).
47
-
48
- ## Capabilities
49
-
50
- ### New Capabilities
51
-
52
- - `cold-start`: proxy-side session startup latency behaviour.
53
-
54
- ## Impact
55
-
56
- - `services/playback-planner.js` — parse + cache full media info; expose
57
- `getCachedMediaInfo`; body-start prefetch after a successful probe.
58
- - `services/hls-session-manager.js` — consult the cache in `createSession`;
59
- stage-timing log lines.
60
- - `server.js` — wire `getCachedMediaInfo` into the HlsSessionManager deps.
61
- - Release: proxy `npm run patch` + ha-addon version bump (standard order:
62
- proxy first, then addon).
1
+ # Proposal: Cold-start reduction (proxy side)
2
+
3
+ ## Why
4
+
5
+ Field recordings (mobile tester, 2026-07-08) show ~90 seconds from picking an
6
+ episode to a picture. The proxy owns two chunks of that:
7
+
8
+ 1. **A redundant ffmpeg input scan.** The playback planner probes the input
9
+ (`probeStreamCodecs`) and CACHES the plan — then `createSession` in
10
+ hls-session-manager immediately probes the SAME input again
11
+ (`probeInputMediaInfo`) for duration/resolution/fps/startTime/HDR. Each
12
+ probe is a full ffmpeg startup + banner scan over the torrent-backed HTTP
13
+ stream — seconds on the HA host, on the critical path, twice.
14
+ 2. **First-segment piece latency.** The planner prefetches only the file
15
+ EDGES (256 KB head + 2 MB tail — what the codec probe needs). The first
16
+ segment's encode then needs the first ~10–20 MB of the file BODY, whose
17
+ pieces start downloading only when ffmpeg asks for them — while the swarm
18
+ sat mostly idle during the plan poll.
19
+
20
+ There is also no per-stage timing in the logs, so field regressions in
21
+ session startup are invisible.
22
+
23
+ ## What Changes
24
+
25
+ - **Reuse the planner's probe in session create.** The planner parses and
26
+ caches the full media info (duration, width/height, fps, startTime, isHdr)
27
+ from the probe it already runs; hls-session-manager consults that cache
28
+ and skips its own `probeInputMediaInfo` on a hit (probe stays as the
29
+ fallback). One full ffmpeg scan disappears from the critical path.
30
+ - **Prefetch the file-body start once the plan is ready.** After a successful
31
+ probe, fire-and-forget prefetch of the first 16 MB of the file, so the
32
+ session's ffmpeg reads hit already-downloaded data instead of paying piece
33
+ latency at encode time.
34
+ - **Stage timings in the log.** Session create logs probe ms (and
35
+ cache-hit/probed), keyframe-probe ms, and the time from create to the
36
+ first servable segment — so cold-start is measurable per stage in the
37
+ field, next to the client-side summary line (server `cold-start` change).
38
+
39
+ Client-side counterparts (phase instrumentation, earlier prebuffer start)
40
+ live in the server repo's `cold-start` change. The two changes are
41
+ independent — no wire-format coupling, either releases alone.
42
+
43
+ NOT in this change (candidates for later, kept out deliberately):
44
+ next-episode speculative prefetch (bandwidth cost needs field data first);
45
+ encoder-side first-segment speed-ups (faster warm-up preset would need a
46
+ mid-stream switch — parked with transcode-quality 2.2b).
47
+
48
+ ## Capabilities
49
+
50
+ ### New Capabilities
51
+
52
+ - `cold-start`: proxy-side session startup latency behaviour.
53
+
54
+ ## Impact
55
+
56
+ - `services/playback-planner.js` — parse + cache full media info; expose
57
+ `getCachedMediaInfo`; body-start prefetch after a successful probe.
58
+ - `services/hls-session-manager.js` — consult the cache in `createSession`;
59
+ stage-timing log lines.
60
+ - `server.js` — wire `getCachedMediaInfo` into the HlsSessionManager deps.
61
+ - Release: proxy `npm run patch` + ha-addon version bump (standard order:
62
+ proxy first, then addon).
@@ -1,47 +1,47 @@
1
- # cold-start — delta spec (proxy)
2
-
3
- ## ADDED Requirements
4
-
5
- ### Requirement: Session create reuses the planner's probe
6
-
7
- Creating an HLS transcode session SHALL NOT re-probe an input whose media
8
- info the playback planner already probed and cached for the same source and
9
- file. The cached info is used only when its critical fields (duration,
10
- width, height) are present and valid; otherwise the session SHALL probe as
11
- before. Cache lifetime and invalidation follow the plan cache exactly.
12
-
13
- #### Scenario: Warm plan, immediate session
14
- - **WHEN** the browser requests a transcode session right after receiving a
15
- playback plan for the same file
16
- - **THEN** the session starts without a second ffmpeg input scan and its
17
- playlist/ladder decisions are identical to what the probe would have
18
- produced
19
-
20
- #### Scenario: Cache cannot serve
21
- - **WHEN** no cached media info exists (e.g. proxy restarted between plan
22
- and session) or a critical field is missing
23
- - **THEN** the session probes the input itself, exactly as before this
24
- change
25
-
26
- ### Requirement: The file-body start is warm before the encoder needs it
27
-
28
- Once a playback plan probe succeeds, the proxy SHALL prefetch the beginning
29
- of the file body (bounded, ~16 MB) in the background, without delaying the
30
- plan response and without competing with a still-running header probe.
31
-
32
- #### Scenario: First segment does not wait for pieces
33
- - **WHEN** the viewer confirms playback within the normal flow (seconds
34
- after the plan)
35
- - **THEN** the encoder's initial reads are served from already-downloaded
36
- data and the first segment's production is not blocked on piece download
37
-
38
- ### Requirement: Session startup is measurable per stage
39
-
40
- Session creation SHALL log the media-info acquisition time (and whether it
41
- was cached or probed), the keyframe-probe time when it runs, and — once per
42
- session — the time from session-create entry to the first servable segment.
43
-
44
- #### Scenario: Field regression triage
45
- - **WHEN** a tester reports a slow start
46
- - **THEN** the proxy log shows, for that session, where the time went:
47
- media info, keyframes, or first-segment production
1
+ # cold-start — delta spec (proxy)
2
+
3
+ ## ADDED Requirements
4
+
5
+ ### Requirement: Session create reuses the planner's probe
6
+
7
+ Creating an HLS transcode session SHALL NOT re-probe an input whose media
8
+ info the playback planner already probed and cached for the same source and
9
+ file. The cached info is used only when its critical fields (duration,
10
+ width, height) are present and valid; otherwise the session SHALL probe as
11
+ before. Cache lifetime and invalidation follow the plan cache exactly.
12
+
13
+ #### Scenario: Warm plan, immediate session
14
+ - **WHEN** the browser requests a transcode session right after receiving a
15
+ playback plan for the same file
16
+ - **THEN** the session starts without a second ffmpeg input scan and its
17
+ playlist/ladder decisions are identical to what the probe would have
18
+ produced
19
+
20
+ #### Scenario: Cache cannot serve
21
+ - **WHEN** no cached media info exists (e.g. proxy restarted between plan
22
+ and session) or a critical field is missing
23
+ - **THEN** the session probes the input itself, exactly as before this
24
+ change
25
+
26
+ ### Requirement: The file-body start is warm before the encoder needs it
27
+
28
+ Once a playback plan probe succeeds, the proxy SHALL prefetch the beginning
29
+ of the file body (bounded, ~16 MB) in the background, without delaying the
30
+ plan response and without competing with a still-running header probe.
31
+
32
+ #### Scenario: First segment does not wait for pieces
33
+ - **WHEN** the viewer confirms playback within the normal flow (seconds
34
+ after the plan)
35
+ - **THEN** the encoder's initial reads are served from already-downloaded
36
+ data and the first segment's production is not blocked on piece download
37
+
38
+ ### Requirement: Session startup is measurable per stage
39
+
40
+ Session creation SHALL log the media-info acquisition time (and whether it
41
+ was cached or probed), the keyframe-probe time when it runs, and — once per
42
+ session — the time from session-create entry to the first servable segment.
43
+
44
+ #### Scenario: Field regression triage
45
+ - **WHEN** a tester reports a slow start
46
+ - **THEN** the proxy log shows, for that session, where the time went:
47
+ media info, keyframes, or first-segment production
@@ -1,48 +1,48 @@
1
- # Tasks: Cold-start reduction (proxy side)
2
-
3
- Execute in order; design.md is normative. Read the code regions listed at
4
- the top of design.md first.
5
-
6
- ## 1. Media-info reuse
7
-
8
- - [ ] 1.1 Make the banner-parse helpers of `probeInputMediaInfo` reusable by
9
- the planner (export or shared module — no duplication). `node --check`
10
- both files.
11
- - [ ] 1.2 playback-planner: build + cache `mediaInfo` alongside the plan on
12
- a codecs-detected probe; expose `getCachedMediaInfo({ sourceKey,
13
- fileIndex })`. Pending probes cache nothing.
14
- - [ ] 1.3 server.js: pass `getCachedMediaInfo` into HlsSessionManager;
15
- hls-session-manager: consult it in `createSession` with the
16
- critical-fields gate (duration/width/height), probe as fallback.
17
- - [ ] 1.4 Verify equivalence: for one file, run plan → session with the
18
- cache and (by temporarily disabling the wiring) without it — the
19
- session log line (duration, segments, ladder/rung) must be identical.
20
-
21
- ## 2. Body-start prefetch
22
-
23
- - [ ] 2.1 playback-planner: fire-and-forget 16 MB head prefetch
24
- (`prefetchFileEdges` with `tailBytes: 0`) at the probe-success point;
25
- never on pending polls; never awaited.
26
- - [ ] 2.2 Verify: on a fresh well-seeded magnet, proxy log/order shows the
27
- prefetch starting right after the plan while the session that follows
28
- produces its first segment without a piece-wait stall (compare
29
- first-segment ms with task 3 timings before/after).
30
-
31
- ## 3. Stage timings
32
-
33
- - [ ] 3.1 `createSession`: one `cold-start …: media-info=<ms> (cached|probed)
34
- keyframes=<ms|skipped> create-total=<ms>` line.
35
- - [ ] 3.2 First-segment-ready line (`+<ms>` since create entry), once per
36
- session (timestamp on the session object + boolean guard).
37
-
38
- ## 4. Verification and release
39
-
40
- - [ ] 4.1 Regression: normal HLS transcode start, video-copy start (keyframe
41
- probe still runs), seek-restart, quality switch — behaviour unchanged;
42
- timings visible in the log.
43
- - [ ] 4.2 Before/after numbers on the dev HA proxy for one cold magnet:
44
- media-info ms (probed → cached) and first-segment ms. Record them in
45
- this file.
46
- - [ ] 4.3 CHANGELOG entry (current version + 1 patch); `npm run patch`; bump
47
- ha-addon `config.yaml` + its CHANGELOG; push; update the addon in HA
48
- (proxy FIRST, then addon — release order per root CLAUDE.md).
1
+ # Tasks: Cold-start reduction (proxy side)
2
+
3
+ Execute in order; design.md is normative. Read the code regions listed at
4
+ the top of design.md first.
5
+
6
+ ## 1. Media-info reuse
7
+
8
+ - [ ] 1.1 Make the banner-parse helpers of `probeInputMediaInfo` reusable by
9
+ the planner (export or shared module — no duplication). `node --check`
10
+ both files.
11
+ - [ ] 1.2 playback-planner: build + cache `mediaInfo` alongside the plan on
12
+ a codecs-detected probe; expose `getCachedMediaInfo({ sourceKey,
13
+ fileIndex })`. Pending probes cache nothing.
14
+ - [ ] 1.3 server.js: pass `getCachedMediaInfo` into HlsSessionManager;
15
+ hls-session-manager: consult it in `createSession` with the
16
+ critical-fields gate (duration/width/height), probe as fallback.
17
+ - [ ] 1.4 Verify equivalence: for one file, run plan → session with the
18
+ cache and (by temporarily disabling the wiring) without it — the
19
+ session log line (duration, segments, ladder/rung) must be identical.
20
+
21
+ ## 2. Body-start prefetch
22
+
23
+ - [ ] 2.1 playback-planner: fire-and-forget 16 MB head prefetch
24
+ (`prefetchFileEdges` with `tailBytes: 0`) at the probe-success point;
25
+ never on pending polls; never awaited.
26
+ - [ ] 2.2 Verify: on a fresh well-seeded magnet, proxy log/order shows the
27
+ prefetch starting right after the plan while the session that follows
28
+ produces its first segment without a piece-wait stall (compare
29
+ first-segment ms with task 3 timings before/after).
30
+
31
+ ## 3. Stage timings
32
+
33
+ - [ ] 3.1 `createSession`: one `cold-start …: media-info=<ms> (cached|probed)
34
+ keyframes=<ms|skipped> create-total=<ms>` line.
35
+ - [ ] 3.2 First-segment-ready line (`+<ms>` since create entry), once per
36
+ session (timestamp on the session object + boolean guard).
37
+
38
+ ## 4. Verification and release
39
+
40
+ - [ ] 4.1 Regression: normal HLS transcode start, video-copy start (keyframe
41
+ probe still runs), seek-restart, quality switch — behaviour unchanged;
42
+ timings visible in the log.
43
+ - [ ] 4.2 Before/after numbers on the dev HA proxy for one cold magnet:
44
+ media-info ms (probed → cached) and first-segment ms. Record them in
45
+ this file.
46
+ - [ ] 4.3 CHANGELOG entry (current version + 1 patch); `npm run patch`; bump
47
+ ha-addon `config.yaml` + its CHANGELOG; push; update the addon in HA
48
+ (proxy FIRST, then addon — release order per root CLAUDE.md).
@@ -1,2 +1,2 @@
1
- schema: spec-driven
2
- created: 2026-07-07
1
+ schema: spec-driven
2
+ created: 2026-07-07
@@ -1,37 +1,37 @@
1
- # Proposal: Global disk cap with LRU eviction (Disk hygiene Level 1, final)
2
-
3
- ## Why
4
-
5
- Downloaded torrent data is already removed on a 300 s idle TTL and at
6
- shutdown, and orphans are swept at startup — but under pressure (several
7
- large files opened within the TTL window, or a fast fill) the total can
8
- still grow unbounded and fill a small Home Assistant host's disk
9
- (SD/eMMC on a Yellow/Pi is often 16–32 GB). A full disk can take down Home
10
- Assistant itself. This adds the last missing Level 1 piece: a global cap.
11
-
12
- ## What Changes
13
-
14
- - The pool tracks total downloaded bytes and, when it exceeds a cap, evicts
15
- whole torrents with NO active reader, least-recently-used first, until
16
- back under the cap (checked every 30 s and reused via the existing
17
- remove-with-store path). A torrent that is currently playing is never
18
- evicted — we cannot delete what is in use.
19
- - The cap defaults to `min(10 GB, half of free disk)` (measured via
20
- `statfs` on the store filesystem), and is overridable with
21
- `--max-disk-bytes` (0 disables).
22
-
23
- ## Capabilities
24
-
25
- ### New Capabilities
26
-
27
- - `disk-cap`: bounded total on-disk footprint via LRU eviction.
28
-
29
- ### Modified Capabilities
30
-
31
- <!-- none -->
32
-
33
- ## Impact
34
-
35
- - `services/torrent/torrent-pool.js` (access tracking, cap enforcement),
36
- `bin/cli.js` (`--max-disk-bytes`), `server.js` (option pass-through);
37
- ha-addon bump. Part of the proxy transcode/hygiene batch (2.9.29).
1
+ # Proposal: Global disk cap with LRU eviction (Disk hygiene Level 1, final)
2
+
3
+ ## Why
4
+
5
+ Downloaded torrent data is already removed on a 300 s idle TTL and at
6
+ shutdown, and orphans are swept at startup — but under pressure (several
7
+ large files opened within the TTL window, or a fast fill) the total can
8
+ still grow unbounded and fill a small Home Assistant host's disk
9
+ (SD/eMMC on a Yellow/Pi is often 16–32 GB). A full disk can take down Home
10
+ Assistant itself. This adds the last missing Level 1 piece: a global cap.
11
+
12
+ ## What Changes
13
+
14
+ - The pool tracks total downloaded bytes and, when it exceeds a cap, evicts
15
+ whole torrents with NO active reader, least-recently-used first, until
16
+ back under the cap (checked every 30 s and reused via the existing
17
+ remove-with-store path). A torrent that is currently playing is never
18
+ evicted — we cannot delete what is in use.
19
+ - The cap defaults to `min(10 GB, half of free disk)` (measured via
20
+ `statfs` on the store filesystem), and is overridable with
21
+ `--max-disk-bytes` (0 disables).
22
+
23
+ ## Capabilities
24
+
25
+ ### New Capabilities
26
+
27
+ - `disk-cap`: bounded total on-disk footprint via LRU eviction.
28
+
29
+ ### Modified Capabilities
30
+
31
+ <!-- none -->
32
+
33
+ ## Impact
34
+
35
+ - `services/torrent/torrent-pool.js` (access tracking, cap enforcement),
36
+ `bin/cli.js` (`--max-disk-bytes`), `server.js` (option pass-through);
37
+ ha-addon bump. Part of the proxy transcode/hygiene batch (2.9.29).
@@ -1,25 +1,25 @@
1
- # disk-cap — delta spec
2
-
3
- ## ADDED Requirements
4
-
5
- ### Requirement: Total torrent data is bounded by a disk cap
6
- The pool SHALL keep the total downloaded torrent footprint under a cap. When
7
- the total exceeds the cap it SHALL evict whole torrents that have no active
8
- file reader, least-recently-used first, removing each with its on-disk store,
9
- until back under the cap or no evictable torrent remains. A torrent with an
10
- active reader SHALL NEVER be evicted. The cap SHALL default to the smaller of
11
- 10 GB and half the free disk, and be overridable (0 disables).
12
-
13
- #### Scenario: Idle torrents evicted under pressure
14
- - **WHEN** the total downloaded data exceeds the cap and some torrents have no
15
- active reader
16
- - **THEN** the least-recently-used idle torrents are removed with their stores
17
- until the total is back under the cap
18
-
19
- #### Scenario: Active torrent protected
20
- - **WHEN** the cap is exceeded but the only large torrent is currently playing
21
- - **THEN** it is not evicted (the cap cannot delete in-use data)
22
-
23
- #### Scenario: Cap disabled
24
- - **WHEN** the cap is set to 0
25
- - **THEN** no eviction occurs
1
+ # disk-cap — delta spec
2
+
3
+ ## ADDED Requirements
4
+
5
+ ### Requirement: Total torrent data is bounded by a disk cap
6
+ The pool SHALL keep the total downloaded torrent footprint under a cap. When
7
+ the total exceeds the cap it SHALL evict whole torrents that have no active
8
+ file reader, least-recently-used first, removing each with its on-disk store,
9
+ until back under the cap or no evictable torrent remains. A torrent with an
10
+ active reader SHALL NEVER be evicted. The cap SHALL default to the smaller of
11
+ 10 GB and half the free disk, and be overridable (0 disables).
12
+
13
+ #### Scenario: Idle torrents evicted under pressure
14
+ - **WHEN** the total downloaded data exceeds the cap and some torrents have no
15
+ active reader
16
+ - **THEN** the least-recently-used idle torrents are removed with their stores
17
+ until the total is back under the cap
18
+
19
+ #### Scenario: Active torrent protected
20
+ - **WHEN** the cap is exceeded but the only large torrent is currently playing
21
+ - **THEN** it is not evicted (the cap cannot delete in-use data)
22
+
23
+ #### Scenario: Cap disabled
24
+ - **WHEN** the cap is set to 0
25
+ - **THEN** no eviction occurs
@@ -1,19 +1,19 @@
1
- # Tasks: Global disk cap with LRU eviction
2
-
3
- ## 1. Implementation
4
-
5
- - [x] 1.1 Access tracking (`#lastAccess`) updated on getTorrent (incl. the
6
- duplicate-infoHash path) and acquireFile
7
- - [x] 1.2 Cap computed at construction (`min(10GB, half free)` via statfs) or
8
- taken from the `maxDiskBytes` option; 0 disables
9
- - [x] 1.3 Periodic (30 s) `#enforceDiskCap`: evict zero-reader torrents
10
- LRU-first via the existing remove-with-store path; clear timer on
11
- destroyAll; drop `#lastAccess` on removal
12
- - [x] 1.4 `--max-disk-bytes` CLI flag → server.js → pool
13
- - [x] 1.5 Syntax checks + unit-test the eviction ordering/active-skip
14
-
15
- ## 2. Release
16
-
17
- - [ ] 2.1 Ship in the proxy batch (2.9.29) + ha-addon bump
18
- - [ ] 2.2 Field-check on the host: open several large files, confirm idle
19
- ones are evicted and playback is never interrupted
1
+ # Tasks: Global disk cap with LRU eviction
2
+
3
+ ## 1. Implementation
4
+
5
+ - [x] 1.1 Access tracking (`#lastAccess`) updated on getTorrent (incl. the
6
+ duplicate-infoHash path) and acquireFile
7
+ - [x] 1.2 Cap computed at construction (`min(10GB, half free)` via statfs) or
8
+ taken from the `maxDiskBytes` option; 0 disables
9
+ - [x] 1.3 Periodic (30 s) `#enforceDiskCap`: evict zero-reader torrents
10
+ LRU-first via the existing remove-with-store path; clear timer on
11
+ destroyAll; drop `#lastAccess` on removal
12
+ - [x] 1.4 `--max-disk-bytes` CLI flag → server.js → pool
13
+ - [x] 1.5 Syntax checks + unit-test the eviction ordering/active-skip
14
+
15
+ ## 2. Release
16
+
17
+ - [ ] 2.1 Ship in the proxy batch (2.9.29) + ha-addon bump
18
+ - [ ] 2.2 Field-check on the host: open several large files, confirm idle
19
+ ones are evicted and playback is never interrupted
@@ -1,2 +1,2 @@
1
- schema: spec-driven
2
- created: 2026-07-06
1
+ schema: spec-driven
2
+ created: 2026-07-06
@@ -1,38 +1,38 @@
1
- # Design: Proxy observability
2
-
3
- ## Context
4
-
5
- Diagnosing the 2026-07-06 failures required docker exec into the addon
6
- container (version) and offered no explanation for zero-peer torrents.
7
- See proposal for the three gaps.
8
-
9
- ## Goals / Non-Goals
10
-
11
- **Goals:** remote version visibility; explainable zero-peer torrents; clean
12
- logs.
13
-
14
- **Non-Goals:** peer-origin tags in `[stats]` (tracker/DHT/PEX attribution is
15
- not cheaply available on WebTorrent's public API — dropped from the original
16
- proposal scope); structured/queryable log output.
17
-
18
- ## Decisions
19
-
20
- 1. **Version via createRequire of package.json** (same pattern as the server
21
- repo), passed as a route dep — no version literal to forget on release.
22
- 2. **Announce results from `torrent.discovery.tracker` ("update" event)** —
23
- internal WebTorrent API, therefore guarded with optional chaining and a
24
- fallback log line when absent; a breaking WebTorrent upgrade degrades to
25
- "not logged", never to a crash. Torrent-level `warning` events carry
26
- tracker rejections/errors and are a public API.
27
- 3. **SSDP fix = setMaxListeners(0) on the one emitter** right after the
28
- first successful map() (the UPnP client is created lazily by it).
29
- Alternatives rejected: raising EventEmitter.defaultMaxListeners (global,
30
- hides real leaks elsewhere); one shared NatAPI for both mappers (couples
31
- TCP/UDP lifecycles for no gain).
32
-
33
- ## Risks / Trade-offs
34
-
35
- - [Announce logging depends on WebTorrent internals] → defensive access +
36
- explicit fallback line; covered by the spec's best-effort clause.
37
- - [Announce lines add log volume] → one line per announce interval
38
- (minutes), negligible next to the existing [stats] cadence.
1
+ # Design: Proxy observability
2
+
3
+ ## Context
4
+
5
+ Diagnosing the 2026-07-06 failures required docker exec into the addon
6
+ container (version) and offered no explanation for zero-peer torrents.
7
+ See proposal for the three gaps.
8
+
9
+ ## Goals / Non-Goals
10
+
11
+ **Goals:** remote version visibility; explainable zero-peer torrents; clean
12
+ logs.
13
+
14
+ **Non-Goals:** peer-origin tags in `[stats]` (tracker/DHT/PEX attribution is
15
+ not cheaply available on WebTorrent's public API — dropped from the original
16
+ proposal scope); structured/queryable log output.
17
+
18
+ ## Decisions
19
+
20
+ 1. **Version via createRequire of package.json** (same pattern as the server
21
+ repo), passed as a route dep — no version literal to forget on release.
22
+ 2. **Announce results from `torrent.discovery.tracker` ("update" event)** —
23
+ internal WebTorrent API, therefore guarded with optional chaining and a
24
+ fallback log line when absent; a breaking WebTorrent upgrade degrades to
25
+ "not logged", never to a crash. Torrent-level `warning` events carry
26
+ tracker rejections/errors and are a public API.
27
+ 3. **SSDP fix = setMaxListeners(0) on the one emitter** right after the
28
+ first successful map() (the UPnP client is created lazily by it).
29
+ Alternatives rejected: raising EventEmitter.defaultMaxListeners (global,
30
+ hides real leaks elsewhere); one shared NatAPI for both mappers (couples
31
+ TCP/UDP lifecycles for no gain).
32
+
33
+ ## Risks / Trade-offs
34
+
35
+ - [Announce logging depends on WebTorrent internals] → defensive access +
36
+ explicit fallback line; covered by the spec's best-effort clause.
37
+ - [Announce lines add log volume] → one line per announce interval
38
+ (minutes), negligible next to the existing [stats] cadence.