@torrent-tv/proxy 2.86.0 → 2.87.0

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 (382) hide show
  1. package/CHANGELOG.md +2138 -1915
  2. package/CLAUDE.md +318 -197
  3. package/assets/calibration/NOTICE.md +2 -2
  4. package/bin/cli.js +684 -616
  5. package/biome.json +409 -233
  6. package/docs/container-architecture.md +218 -213
  7. package/docs/download-architecture.md +7 -7
  8. package/docs/encode-architecture.md +287 -11
  9. package/docs/encode-run-state.md +2 -2
  10. package/docs/logs.md +97 -97
  11. package/docs/{disk-architecture.md → storage-architecture.md} +107 -1
  12. package/openspec/changes/adaptive-bitrate/design.md +2 -2
  13. package/openspec/changes/adaptive-bitrate/proposal.md +1 -1
  14. package/openspec/changes/adaptive-bitrate/tasks.md +1 -1
  15. package/openspec/changes/chunked-request-bodies/design.md +1 -1
  16. package/openspec/changes/chunked-request-bodies/proposal.md +1 -1
  17. package/openspec/changes/chunked-request-bodies/tasks.md +1 -1
  18. package/openspec/changes/cold-start/design.md +1 -1
  19. package/openspec/changes/disk-cap/proposal.md +1 -1
  20. package/openspec/changes/proxy-observability/proposal.md +2 -2
  21. package/openspec/changes/track-selection/proposal.md +1 -1
  22. package/openspec/changes/transcode-quality/proposal.md +1 -1
  23. package/package.json +2 -2
  24. package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
  25. package/research/double-spawn-2026-09-10.md +171 -171
  26. package/routes/api/playback-plan/post.js +2 -2
  27. package/routes/api/sources/files/get.js +1 -1
  28. package/routes/api/sources/stats/get.js +1 -1
  29. package/routes/api/sources/warm/post.js +10 -3
  30. package/routes/api/subtitles/get.js +230 -230
  31. package/routes/api/transcode-sessions/fragment-far/post.js +3 -3
  32. package/routes/api/transcode-sessions/net-report/post.js +129 -94
  33. package/routes/api/transcode-sessions/post.js +160 -139
  34. package/routes/api/transcode-sessions/progress/get.js +3 -3
  35. package/routes/api/transcode-sessions/release/post.js +3 -3
  36. package/routes/api/transcode-sessions/seek/post.js +12 -3
  37. package/routes/stream/get.js +2 -2
  38. package/routes/transcode/audio-file/get.js +15 -6
  39. package/routes/transcode/audio-warm/get.js +8 -5
  40. package/routes/transcode/session-file/get.js +429 -268
  41. package/routes/transcode/variant-file/get.js +47 -7
  42. package/routes/transcode/variant-warm/get.js +18 -6
  43. package/scripts/render-run-graph.js +1 -1
  44. package/server.js +720 -465
  45. package/services/encode/CoverageMap.js +428 -428
  46. package/services/encode/CushionReport.js +345 -0
  47. package/services/encode/EncodeAdmission.js +332 -0
  48. package/services/{orchestrators → encode}/EncodeOrchestrator.js +285 -17
  49. package/services/encode/EncodeRun.js +16 -9
  50. package/services/encode/EncodeRuns.js +1219 -0
  51. package/services/encode/Encoder.js +18 -2
  52. package/services/encode/EncoderSelection.js +38 -0
  53. package/services/encode/LocalObservations.js +359 -0
  54. package/services/encode/NvencEncoder.js +5 -0
  55. package/services/encode/OutputOpening.js +745 -0
  56. package/services/encode/OutputTimes.js +579 -0
  57. package/services/encode/Renditions.js +2162 -0
  58. package/services/encode/RunProgress.js +84 -0
  59. package/services/encode/SoftwareEncoder.js +12 -7
  60. package/services/encode/args.js +402 -186
  61. package/services/encode/calibration.js +361 -0
  62. package/services/encode/contention.js +128 -128
  63. package/services/encode/encode-run-state.js +16 -20
  64. package/services/encode/fingerprint.js +322 -0
  65. package/services/{hwaccel.js → encode/hwaccel.js} +82 -42
  66. package/services/{learned-median.js → encode/learned-median.js} +11 -0
  67. package/services/encode/output/EncodedOutput.js +57 -0
  68. package/services/encode/output/Output.js +131 -0
  69. package/services/{output/LiveOutputs.js → encode/output/OutputCatalog.js} +141 -40
  70. package/services/encode/output/OutputSpec.js +390 -0
  71. package/services/{output → encode/output}/Timeline.js +12 -37
  72. package/services/encode/output/index.js +20 -0
  73. package/services/encode/output/ladder.js +32 -0
  74. package/services/{output → encode/output}/playlists.js +3 -0
  75. package/services/encode/output-key-format.js +21 -0
  76. package/services/encode/quality/EncodeCost.js +1083 -0
  77. package/services/encode/quality/HostLoad.js +525 -0
  78. package/services/encode/quality/HostTimings.js +249 -0
  79. package/services/encode/quality/OutputQualityState.js +28 -0
  80. package/services/encode/quality/QualityController.js +931 -0
  81. package/services/encode/quality/QualityOffer.js +416 -0
  82. package/services/encode/quality/drain-threat.js +47 -0
  83. package/services/encode/quality/link-budget.js +254 -0
  84. package/services/encode/quality/output-format.js +266 -0
  85. package/services/encode/quality/serving-output.js +216 -0
  86. package/services/encode/quality/visible-rung.js +57 -0
  87. package/services/encode/run-command.js +23 -20
  88. package/services/encode/run-inputs.js +42 -0
  89. package/services/{segment-formats → encode/segment-formats}/fmp4.js +63 -0
  90. package/services/{segment-formats → encode/segment-formats}/index.js +3 -3
  91. package/services/encode/segment-formats/init-compat.js +324 -0
  92. package/services/{segment-formats → encode/segment-formats}/mp4-boxes.js +8 -3
  93. package/services/encode/throughput.js +97 -0
  94. package/services/{orchestrators → media}/ContainerOrchestrator.js +45 -7
  95. package/services/media/KeyframeTables.js +316 -0
  96. package/services/{source → media}/SourceFile.js +7 -55
  97. package/services/{torrent-worker/subtitle-cues.js → media/SubtitleCues.js} +103 -132
  98. package/services/media/SubtitleOrchestrator.js +167 -0
  99. package/services/{audio-inventory.js → media/audio-inventory.js} +7 -1
  100. package/services/{container → media/container}/AviContainer.js +266 -266
  101. package/services/{container → media/container}/Container.js +596 -601
  102. package/services/media/container/ContainerFactory.js +56 -0
  103. package/services/media/container/KeyframeTable.js +272 -0
  104. package/services/{container → media/container}/MatroskaContainer.js +1485 -1485
  105. package/services/{container → media/container}/Mp4Container.js +1244 -1244
  106. package/services/{container → media/container}/SubtitleFileContainer.js +322 -322
  107. package/services/media/keyframe-probe.js +157 -0
  108. package/services/media/media-info-probe.js +93 -0
  109. package/services/{playback-planner.js → media/playback-planner.js} +82 -39
  110. package/services/{tracks → media/tracks}/AudioTrack.js +131 -131
  111. package/services/{tracks → media/tracks}/language-detect.js +174 -174
  112. package/services/server/OutputLifecycle.js +418 -0
  113. package/services/server/SegmentServing.js +1146 -0
  114. package/services/server/ViewerRequests.js +522 -0
  115. package/services/server/controllers/PlaybackController.js +36 -0
  116. package/services/{controllers → server/controllers}/SubtitleController.js +17 -33
  117. package/services/server/wire-outputs.js +958 -0
  118. package/services/storage/Diagnostics.js +202 -0
  119. package/services/storage/MachineBudget.js +235 -0
  120. package/services/{piece-store → storage}/allowance.js +107 -107
  121. package/services/{core-dumps.js → storage/core-dumps.js} +1 -1
  122. package/services/{files → storage/files}/CompletedFiles.js +116 -1
  123. package/services/{disk → storage}/free.js +33 -0
  124. package/services/storage/machine-memory.js +56 -0
  125. package/services/{memory-report.js → storage/memory-report.js} +575 -596
  126. package/services/{piece-store → storage/piece-store}/piece-disk-store.js +0 -7
  127. package/services/{piece-store → storage/piece-store}/shared-piece-store.js +28 -21
  128. package/services/{encode → storage/segment-store}/SegmentStore.js +395 -8
  129. package/services/storage/wire.js +175 -0
  130. package/services/{download → torrent/download}/SwarmSelection.js +2 -2
  131. package/services/{download → torrent/download}/registry.js +2 -2
  132. package/services/torrent/files.js +1 -1
  133. package/services/torrent/piece-store-of.js +29 -0
  134. package/services/{torrent-pool.js → torrent/torrent-pool.js} +26 -119
  135. package/services/{torrent-worker → torrent/worker}/background-fill.js +2 -2
  136. package/services/{torrent-worker → torrent/worker}/channel.js +271 -250
  137. package/services/{torrent-worker → torrent/worker}/client.js +77 -58
  138. package/services/torrent/worker/held-bytes.js +155 -0
  139. package/services/{torrent-worker → torrent/worker}/install-webrtc-shim.js +1 -1
  140. package/services/{torrent-worker → torrent/worker}/piece-reader.js +21 -30
  141. package/services/{torrent-worker → torrent/worker}/pool-adapter.js +101 -137
  142. package/services/{torrent-worker → torrent/worker}/protocol.js +26 -30
  143. package/services/torrent/worker/resume-warm.js +107 -0
  144. package/services/{torrent-worker → torrent/worker}/webrtc-shim.js +2 -2
  145. package/services/{torrent-worker → torrent/worker}/worker.js +68 -159
  146. package/services/{data-channel-handler.js → transport/data-channel-handler.js} +1356 -1356
  147. package/services/{delivery-probe.js → transport/delivery-probe.js} +842 -842
  148. package/services/{health-collector.js → transport/health-collector.js} +6 -35
  149. package/services/{packet-witness.js → transport/packet-witness.js} +808 -784
  150. package/services/{port-mapper.js → transport/port-mapper.js} +1 -1
  151. package/services/{usrsctp-state.js → transport/usrsctp-state.js} +1 -1
  152. package/services/{webrtc-manager.js → transport/webrtc-manager.js} +1 -1
  153. package/services/viewer/Assignments.js +340 -0
  154. package/services/{priority → viewer}/PriorityMap.js +390 -390
  155. package/services/{priority → viewer}/PriorityOrchestrator.js +325 -320
  156. package/services/viewer/Viewer.js +657 -483
  157. package/services/viewer/Viewers.js +368 -257
  158. package/services/viewer/active-output.js +37 -54
  159. package/services/viewer/audio-start.js +4 -3
  160. package/services/viewer/buffer-trend.js +81 -0
  161. package/services/viewer/choices.js +647 -0
  162. package/services/viewer/positions.js +126 -120
  163. package/services/viewer/report-intake.js +46 -45
  164. package/test/asset-paths.test.js +28 -0
  165. package/test/audio-inventory.test.js +3 -3
  166. package/test/audio-track-bitrate.test.js +40 -0
  167. package/test/auto-quality-step.test.js +707 -515
  168. package/test/available-share.test.js +1 -1
  169. package/test/background-fill.test.js +1 -1
  170. package/test/client-log-file.test.js +110 -110
  171. package/test/completed-files.test.js +1 -1
  172. package/test/concurrent-cost.test.js +13 -3
  173. package/test/container-media-info.test.js +3 -3
  174. package/test/core-dumps.test.js +1 -1
  175. package/test/coverage-follows-the-disk.test.js +40 -8
  176. package/test/cue-push-carries-what-the-walk-found.test.js +58 -0
  177. package/test/cut-times-timeline.test.js +2 -2
  178. package/test/cuts-follow-published-grid.test.js +9 -8
  179. package/test/data-channel-frame.test.js +1 -1
  180. package/test/dead-torrent-handle.test.js +1 -1
  181. package/test/declared-rates.test.js +2 -2
  182. package/test/declared-tracks.test.js +2 -2
  183. package/test/decode-cost-fit.test.js +1 -1
  184. package/test/decode-cost.test.js +34 -25
  185. package/test/delivery-probe.test.js +1 -1
  186. package/test/demand-register.test.js +4 -4
  187. package/test/dht-bootstrap.test.js +1 -1
  188. package/test/drain-threat.test.js +65 -0
  189. package/test/duplicate-add.test.js +1 -1
  190. package/test/encode-admission.test.js +146 -0
  191. package/test/encode-cost.test.js +199 -10
  192. package/test/encode-orchestrator.test.js +39 -1
  193. package/test/encode-plan-viewers.test.js +2 -2
  194. package/test/encode-run.test.js +2 -2
  195. package/test/encode-slope.test.js +1 -1
  196. package/test/encoded-output.test.js +39 -0
  197. package/test/encoder-readings.test.js +1 -1
  198. package/test/every-claimant-is-told-its-share.test.js +152 -0
  199. package/test/failed-encoder-still-serves-what-exists.test.js +354 -0
  200. package/test/failed-start-breaker.test.js +81 -30
  201. package/test/fastest-wires.test.js +1 -1
  202. package/test/ffmpeg-stream-counts.test.js +1 -1
  203. package/test/file-edges.test.js +3 -3
  204. package/test/fmp4-self-contained.test.js +1 -1
  205. package/test/fmp4-timestamps.test.js +1 -1
  206. package/test/fmp4-track-completeness.test.js +1 -1
  207. package/test/format-keyed-outputs.test.js +78 -0
  208. package/test/health-metrics.test.js +3 -2
  209. package/test/held-request-width.test.js +22 -23
  210. package/test/helpers/encode-run.js +136 -136
  211. package/test/helpers/held-file.js +29 -0
  212. package/test/helpers/manager.js +57 -0
  213. package/test/helpers/output-spec.js +63 -0
  214. package/test/host-load.test.js +1 -1
  215. package/test/index-accuracy-is-about-the-file.test.js +128 -0
  216. package/test/init-compat.test.js +219 -0
  217. package/test/input-lost-quiets-the-plan.test.js +1 -1
  218. package/test/input-unavailable.test.js +1 -1
  219. package/test/keeping-period.test.js +4 -4
  220. package/test/keyframe-index-accuracy.test.js +97 -45
  221. package/test/keyframe-table-budget.test.js +28 -33
  222. package/test/keyframes-belong-to-the-file.test.js +174 -57
  223. package/test/learned-median.test.js +1 -1
  224. package/test/letting-go-asks-who-is-watching.test.js +191 -0
  225. package/test/limit-row-by-area.test.js +60 -0
  226. package/test/link-budget.test.js +111 -0
  227. package/test/link-probe.test.js +56 -56
  228. package/test/local-encoding-data.test.js +273 -0
  229. package/test/look-ahead-coverage.test.js +1 -1
  230. package/test/manager-surface.test.js +294 -0
  231. package/test/matroska-blocks.test.js +0 -0
  232. package/test/matroska-cues-track.test.js +1 -1
  233. package/test/memory-budget.test.js +2 -2
  234. package/test/mp4-composition-times.test.js +0 -0
  235. package/test/mp4-subtitles.test.js +1 -1
  236. package/test/one-authority.test.js +45 -41
  237. package/test/one-budget.test.js +149 -0
  238. package/test/one-output-one-session.test.js +204 -0
  239. package/test/{disk-space.test.js → one-resource-divided.test.js} +30 -10
  240. package/test/one-serving-output-per-height.test.js +458 -0
  241. package/test/orchestrator-wired.test.js +46 -45
  242. package/test/output-catalog.test.js +39 -0
  243. package/test/output-shape.test.js +4 -51
  244. package/test/output-spec.test.js +251 -157
  245. package/test/output-speed.test.js +17 -15
  246. package/test/outputs-being-prepared.test.js +40 -0
  247. package/test/packet-witness-ring.test.js +2 -2
  248. package/test/packet-witness.test.js +1 -1
  249. package/test/piece-disk-store.test.js +2 -2
  250. package/test/piece-from-whole-file.test.js +1 -1
  251. package/test/piece-lru.test.js +1 -1
  252. package/test/piece-reader.test.js +2 -2
  253. package/test/piece-store-eviction.test.js +1 -1
  254. package/test/piece-store-never-refuses.test.js +1 -1
  255. package/test/piece-store-reservations.test.js +1 -1
  256. package/test/piece-store-slow-disk.test.js +1 -1
  257. package/test/piece-tail.test.js +1 -1
  258. package/test/plan-host-timings.test.js +1 -1
  259. package/test/plan-is-steady.test.js +225 -225
  260. package/test/priority-map-download.test.js +4 -4
  261. package/test/priority-map-emptied.test.js +204 -205
  262. package/test/priority-map-per-output.test.js +215 -209
  263. package/test/priority-map.test.js +1 -1
  264. package/test/probe-wedge-certainty.test.js +1 -1
  265. package/test/produced-copy-choice.test.js +15 -60
  266. package/test/quality-offer.test.js +162 -0
  267. package/test/quality-variants.test.js +1229 -1167
  268. package/test/rate-control-in-the-output.test.js +180 -0
  269. package/test/read-survives-withdrawal.test.js +2 -2
  270. package/test/read-window.test.js +10 -3
  271. package/test/resume-warm.test.js +1 -1
  272. package/test/run-inputs.test.js +80 -0
  273. package/test/run-position-follows-published-grid.test.js +13 -11
  274. package/test/run-progress.test.js +65 -0
  275. package/test/same-height-switch.test.js +664 -0
  276. package/test/seek-frees-the-viewers-requests.test.js +217 -0
  277. package/test/seek-landing.test.js +13 -13
  278. package/test/seek-target-not-superseded.test.js +28 -29
  279. package/test/segment-closed-on-both-branches.test.js +96 -0
  280. package/test/segment-cut-times.test.js +1 -1
  281. package/test/segment-hold-supersede.test.js +28 -35
  282. package/test/segment-judged-by-its-own-header.test.js +221 -0
  283. package/test/segment-serve-wiring.test.js +130 -39
  284. package/test/segment-store-eviction.test.js +22 -7
  285. package/test/segment-store.test.js +98 -14
  286. package/test/segment-true-start.test.js +1 -1
  287. package/test/segments-are-shared.test.js +17 -16
  288. package/test/send-chunk-bytes.test.js +1 -1
  289. package/test/serving-existing-output.test.js +102 -0
  290. package/test/serving-output.test.js +102 -0
  291. package/test/shared-piece-store.test.js +1 -1
  292. package/test/source-file.test.js +1 -1
  293. package/test/stale-request-after-seek.test.js +17 -21
  294. package/test/startup-readings.test.js +10 -5
  295. package/test/subtitle-cue-framing.test.js +5 -5
  296. package/test/subtitle-cue-source.test.js +36 -38
  297. package/test/subtitle-cue-walk.test.js +11 -10
  298. package/test/subtitle-defaults.test.js +1 -1
  299. package/test/subtitle-language.test.js +4 -4
  300. package/test/subtitle-track-numbering.test.js +5 -4
  301. package/test/supply-margin.test.js +1 -1
  302. package/test/swarm-follows-readers.test.js +3 -3
  303. package/test/swarm-reach.test.js +1 -1
  304. package/test/swarm-selection.test.js +3 -3
  305. package/test/tail-duplication.test.js +1 -1
  306. package/test/the-channel-has-both-ends.test.js +91 -0
  307. package/test/the-torrent-thread-serves-bytes.test.js +107 -0
  308. package/test/thread-pool.test.js +3 -3
  309. package/test/timeline.test.js +1 -12
  310. package/test/torrent-cost.test.js +1 -1
  311. package/test/tracks-begin-together.test.js +38 -24
  312. package/test/tunnel-renewal.test.js +1 -1
  313. package/test/two-viewers-one-picture.test.js +120 -87
  314. package/test/upload-hurry.test.js +3 -3
  315. package/test/usrsctp-state.test.js +1 -1
  316. package/test/video-facts.test.js +1 -1
  317. package/test/viewer-assignments.test.js +143 -0
  318. package/test/viewer-outputs.test.js +60 -52
  319. package/test/viewer-presence.test.js +22 -25
  320. package/test/viewer-requests-admission.test.js +89 -0
  321. package/test/viewer-states.test.js +189 -202
  322. package/test/viewer-subtitle-subscription.test.js +1 -1
  323. package/test/viewer.test.js +139 -136
  324. package/test/viewing-generations.test.js +390 -0
  325. package/test/visible-rung.test.js +37 -0
  326. package/test/wait-ledger.test.js +1 -1
  327. package/test/webrtc-shim.test.js +3 -3
  328. package/test/wedge-certainty.test.js +2 -2
  329. package/test/withdraw-piece-claim.test.js +2 -2
  330. package/test/worker-channel.test.js +1 -31
  331. package/utils/client-log-file.js +151 -151
  332. package/scripts/read-container-index.mjs +0 -108
  333. package/services/container/ContainerFactory.js +0 -122
  334. package/services/controllers/PlaybackController.js +0 -33
  335. package/services/disk/DiskSpace.js +0 -150
  336. package/services/disk/wire.js +0 -61
  337. package/services/hls-session-manager.js +0 -9608
  338. package/services/orchestrators/SubtitleOrchestrator.js +0 -160
  339. package/services/output/Output.js +0 -94
  340. package/services/output/OutputSpec.js +0 -202
  341. package/services/output/index.js +0 -1
  342. package/services/output/ladder.js +0 -26
  343. package/services/produced-index.js +0 -222
  344. package/services/quality/EncodeCost.js +0 -555
  345. package/services/torrent-worker/container-tracks.js +0 -414
  346. package/services/viewer/link-readings.js +0 -56
  347. package/test/produced-index.test.js +0 -142
  348. /package/{services → bin}/thread-pool.js +0 -0
  349. /package/services/{available-share.js → encode/available-share.js} +0 -0
  350. /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
  351. /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
  352. /package/services/{host-load.js → encode/host-load.js} +0 -0
  353. /package/services/{output → encode/output}/cut-grid.js +0 -0
  354. /package/services/{output → encode/output}/rates.js +0 -0
  355. /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
  356. /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
  357. /package/services/{container → media/container}/ebml-reader.js +0 -0
  358. /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
  359. /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
  360. /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
  361. /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
  362. /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
  363. /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
  364. /package/services/{tracks → media/tracks}/index.js +0 -0
  365. /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
  366. /package/services/{disk → storage}/keep.js +0 -0
  367. /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
  368. /package/services/{disk → storage}/returns.js +0 -0
  369. /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
  370. /package/services/{demand → torrent/demand}/Urgency.js +0 -0
  371. /package/services/{demand → torrent/demand}/Window.js +0 -0
  372. /package/services/{demand → torrent/demand}/index.js +0 -0
  373. /package/services/{demand → torrent/demand}/pieces.js +0 -0
  374. /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
  375. /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
  376. /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
  377. /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
  378. /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
  379. /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
  380. /package/services/{registry-api.js → transport/registry-api.js} +0 -0
  381. /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
  382. /package/services/{priority → viewer}/WaitLedger.js +0 -0
@@ -7,7 +7,7 @@ ceilings, each standing for the whole disk.
7
7
  | what | where | what bounded it before |
8
8
  |---|---|---|
9
9
  | segments an encoder produced | `services/encode/SegmentStore.js` | a quarter of what was free, plus a 2 GB floor — both numbers chosen by hand |
10
- | pieces the memory store spilled | `services/piece-store/piece-disk-store.js` | **nothing at all** |
10
+ | pieces the memory store spilled | `services/storage/piece-store/piece-disk-store.js` | **nothing at all** |
11
11
  | diagnostics kept on purpose | `/data` — core dumps, heap snapshots, packet captures | a count of files, never a size |
12
12
 
13
13
  The middle row is what it cost: one fifty-minute viewing wrote **14 400 MB** to
@@ -159,3 +159,109 @@ distribution.
159
159
  **The diagnostics are not a claimant yet.** They are bounded by a count of files
160
160
  and never by a size, and they cannot simply be thrown away when space is short: a
161
161
  dump is the only evidence of the death it records. That needs a rule of its own.
162
+
163
+ ## One layer, four directories, and under the import rule
164
+
165
+ `SegmentStore` moved out of `services/encode/` on 2026-09-14 — a store of
166
+ finished pieces inside the ENCODING layer was the encoder owning its own disk.
167
+ It did not move into the room's own directory either: it is a CLAIMANT, and a
168
+ claimant inside the owner of the resource is the same fault one floor down.
169
+
170
+ **Four directories, deliberately.** They share one property — bytes on a medium
171
+ with a limit — and that is what makes them one layer. What they are is three
172
+ different things: a torrent piece is born when it is downloaded and addressed by
173
+ `(infohash, number)`; a produced segment is born when an encoder closes it and
174
+ addressed by `(output key, number)`; a whole file is born when its last piece
175
+ lands. Different birth, different death, different name, and two of the three
176
+ live in the torrent WORKER thread while the others are on the main one. Flatten
177
+ them and that thread boundary becomes invisible: code that cannot call code
178
+ looks like a sibling, and no rule catches it.
179
+
180
+ **The rule is written as an exception, not as a list.** `group: ["../**",
181
+ "!../storage/**", "!../piece-store/**", "!../segment-store/**", "!../files/**"]`
182
+ — everything outside the layer, minus the layer. A list of forbidden layers was
183
+ tried first and let `../../utils/logger.js` straight through, because a list
184
+ only catches what somebody remembered to write in it. Checked by breaking it,
185
+ in both directions, in all four directories.
186
+
187
+ **One reader of how much memory is free** (`machine-memory.js`). It had been
188
+ written three times — the health collector, the memory report and the piece
189
+ store — and the copies had already drifted: the piece store was corrected from
190
+ `os.freemem()` to the kernel's `MemAvailable` on 2026-08-27 and the health
191
+ collector went on publishing the wrong quantity until 2026-09-02.
192
+
193
+ ## Every claimant is told its share
194
+
195
+ A CLAIMANT IS WHOEVER HOLDS BYTES, and by 2026-09-14 all of them are registered
196
+ with the owner (`wire.js`): the segments an encoder produced, the pieces the
197
+ memory store spilled, the files downloaded whole, and the evidence. Two of the
198
+ four live on the torrent thread and arrive as a pair of closures over its
199
+ channel.
200
+
201
+ **Whole files had no bound of any kind** until that day — a 2.8 GB film kept
202
+ whole on a host whose disk is often a 32 GB card. They now take a share, and
203
+ over it the whole file nobody has asked for in longest goes. Losing one is not
204
+ losing data: the torrent can fetch it again, and until it does the read falls
205
+ back to the pieces, which is what `piece-from-whole-file.js` exists for. A file
206
+ is also not ASSEMBLED when there is no room for it — writing it and then
207
+ removing it is the same bytes written for nothing.
208
+
209
+ **The torrent pool's own "disk cap" is gone, and it owned nothing.** It capped
210
+ `torrentDownloadedBytes` — a sum over WebTorrent's bitfield — so a piece held
211
+ purely in MEMORY told against a ceiling called disk, while the bytes it meant to
212
+ bound belong to the spill and to the whole files, each of which has an owner.
213
+ Under it, eviction removed whole idle TORRENTS to free bytes that were not
214
+ necessarily on the disk at all.
215
+
216
+ **The evidence is a claimant with a rule of its own** (`Diagnostics.js`), and the
217
+ rule is what makes a bound on it safe:
218
+
219
+ 1. over its share, collection STOPS. Nothing already recorded is deleted to make
220
+ room — a dump is the only evidence of the death it records — and a refusal is
221
+ a LINE with the figures, so an investigation that finds nothing can tell "it
222
+ did not happen" from "there was nowhere to put it";
223
+ 2. except what is superseded, which is not evidence twice. A capture of a
224
+ connection already captured adds nothing: the wedge does not un-wedge. Field
225
+ 2026-09-06, thirteen captures of one wedge in six hours, 74 MB, every one
226
+ saying what the first said;
227
+ 3. what it asks for is measured — what it holds plus one more of the largest
228
+ kind seen — so the claim grows with the evidence and a proxy that has never
229
+ faulted asks for nothing.
230
+
231
+ **Core dumps are not ours to refuse, and that is stated rather than papered
232
+ over.** The kernel writes them, whole address space at a time, and no gate of
233
+ ours is consulted. They are counted, so they take room from what the product may
234
+ hold and the figure is visible; the pruning that keeps the newest two is left
235
+ where it is, because changing it is a decision about evidence and not about
236
+ disk.
237
+
238
+ ## One budget
239
+
240
+ There is one owner, one policy and one place that reads the machine —
241
+ `MachineBudget.js`. What it divides is divided PER RESOURCE, because memory
242
+ cannot be paid for with disk, and "disk" is one resource per DEVICE: measured on
243
+ the addon host 2026-09-05, `/tmp` (the segments and the spill) is the overlay
244
+ and `/data` (the evidence) is ext4 on the nvme. Dividing one figure between
245
+ claimants on both gave each a share of a disk it does not write to, which is
246
+ what the old `DiskSpace` did — it read the free space of the segment root alone.
247
+ The device is read with `statSync(dir).dev`, from the nearest ancestor that
248
+ exists, for the same reason `freeBytesFor` walks up.
249
+
250
+ **Why one owner and not two.** The claimants trade across resources: pieces that
251
+ do not fit in memory are spilled to disk, whole files exist so that pieces need
252
+ not be held, segments exist because making them again is dear. Field 2026-08-31:
253
+ 14 400 MB spilled in fifty minutes while the memory store held 312-424 MB — give
254
+ it memory and there is no spill to bound. Two owners cannot make that trade,
255
+ because neither sees both sides of it. Memory was divided inside the torrent
256
+ thread and disk on the main one until 2026-09-14; the memory share now travels
257
+ the same channel the spill share already did.
258
+
259
+ **The policy is the operator's, and it is the only chosen number here.**
260
+ `--budget adaptive|share|fixed` with `--budget-share` / `--budget-bytes`, and
261
+ floors with `--min-memory-bytes` / `--min-disk-bytes`. The default is
262
+ `adaptive`, which is the measured rule of `allowance.js` and chooses nothing. A
263
+ floor lifts what the policy allows and can never invent room the machine does
264
+ not have; a claimant left below its own minimum says so in a line, because
265
+ working below what it needs is a fault of the machine and invisible otherwise.
266
+
267
+
@@ -1,6 +1,6 @@
1
1
  # Design: Adaptive bitrate (proxy)
2
2
 
3
- Read before coding: `services/hwaccel.js` (`chooseSoftwareEncodeSettings`,
3
+ Read before coding: `services/encode/hwaccel.js` (`chooseSoftwareEncodeSettings`,
4
4
  the software descriptor's `buildVideoArgs`), `services/hls-session-manager.js`
5
5
  (the `BUDGET_*` constants, `#chooseEncodeBudget`, the budget interval check
6
6
  `#enforceRealtimeBudget`, `#classifyTranscodeBound`, `#applyBudgetDownshift`,
@@ -28,7 +28,7 @@ constrained-CRF). Do NOT touch hardware descriptors in this change.
28
28
  ## (b) Net report intake
29
29
 
30
30
  Route: `POST /api/transcode-sessions/:id/net-report` (data-channel path, so
31
- add the prefix to the allowlist regex in `data-channel-handler.js` if not
31
+ add the prefix to the allowlist regex in `transport/data-channel-handler.js` if not
32
32
  covered by the existing `/api/` rule — verify). Body:
33
33
 
34
34
  { linkMbps: number, bufferedAheadSec: number }
@@ -46,7 +46,7 @@ trigger is skipped.
46
46
 
47
47
  ## Impact
48
48
 
49
- - `services/hwaccel.js` — nominal-rate table + caps in the software
49
+ - `services/encode/hwaccel.js` — nominal-rate table + caps in the software
50
50
  descriptor's video args.
51
51
  - `services/hls-session-manager.js` — accept/store net reports per session;
52
52
  link trigger in the budget check; log line.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 1. Caps
4
4
 
5
- - [x] 1.1 `hwaccel.js`: `RUNG_NOMINAL_KBPS` table + nearest-rung lookup;
5
+ - [x] 1.1 `encode/hwaccel.js`: `RUNG_NOMINAL_KBPS` table + nearest-rung lookup;
6
6
  software descriptor's video args emit `-maxrate`/`-bufsize`
7
7
  (1.3× / 1.5× nominal for the actual encode height). Hardware
8
8
  descriptors untouched.
@@ -3,7 +3,7 @@
3
3
  Written to be executed as specified — the wire format, constants, limits
4
4
  and do-NOT list are normative. Read before coding:
5
5
 
6
- - `services/data-channel-handler.js`: the whole file — the wire-protocol
6
+ - `services/transport/data-channel-handler.js`: the whole file — the wire-protocol
7
7
  doc comment (~lines 9–37), `handleChannel` (onMessage string handling),
8
8
  `handleRequest`, `sendChunk` (the response frame writer whose layout the
9
9
  request frames mirror), `send`.
@@ -46,7 +46,7 @@ the server repo's `chunked-request-bodies` change. Release order: proxy
46
46
 
47
47
  ## Impact
48
48
 
49
- - `services/data-channel-handler.js` — binary inbound frame parsing;
49
+ - `services/transport/data-channel-handler.js` — binary inbound frame parsing;
50
50
  per-channel partial-body assembly with caps/TTL; `request-start`
51
51
  handling; shared execution path.
52
52
  - Release: folds into the PENDING proxy 2.9.35 (extend its CHANGELOG entry;
@@ -5,7 +5,7 @@ its top first.
5
5
 
6
6
  ## 1. Implementation
7
7
 
8
- - [ ] 1.1 `data-channel-handler.js`: factor the execution tail of
8
+ - [ ] 1.1 `transport/data-channel-handler.js`: factor the execution tail of
9
9
  `handleRequest` so a legacy `request` message and an assembled
10
10
  chunked request run the SAME function. `node --check`.
11
11
  - [ ] 1.2 `request-start` + binary inbound frames + per-channel assembly
@@ -11,7 +11,7 @@ Written to be executed as specified. Read before coding:
11
11
  ~854), the segment-ready path (where segment 00000 first becomes
12
12
  servable — grep the long-poll the `routes/transcode/session-file/get.js`
13
13
  route uses).
14
- - `services/torrent-pool.js`: `prefetchFileEdges` (~line 608) — signature
14
+ - `services/torrent/torrent-pool.js`: `prefetchFileEdges` (~line 608) — signature
15
15
  `(torrent, fileIndex, { headBytes, tailBytes, timeoutMs })`.
16
16
  - `server.js`: how `playbackPlanner` and `hlsSessionManager` are constructed
17
17
  and injected.
@@ -32,6 +32,6 @@ Assistant itself. This adds the last missing Level 1 piece: a global cap.
32
32
 
33
33
  ## Impact
34
34
 
35
- - `services/torrent-pool.js` (access tracking, cap enforcement),
35
+ - `services/torrent/torrent-pool.js` (access tracking, cap enforcement),
36
36
  `bin/cli.js` (`--max-disk-bytes`), `server.js` (option pass-through);
37
37
  ha-addon bump. Part of the proxy transcode/hygiene batch (2.9.29).
@@ -42,8 +42,8 @@ failures:
42
42
 
43
43
  - `routes/healthz/get.js`, `routes/health/get.js` (or their handlers) —
44
44
  version field.
45
- - `services/torrent-pool.js` — warning/announce logging, stats origin tags.
46
- - `services/port-mapper.js` — SSDP listener fix.
45
+ - `services/torrent/torrent-pool.js` — warning/announce logging, stats origin tags.
46
+ - `services/transport/port-mapper.js` — SSDP listener fix.
47
47
  - CHANGELOG entry at current package.json version + 1 patch (2.9.25);
48
48
  requires the usual ha-addon version bump (0.2.47) after `npm run patch`
49
49
  (npm publish needs the user's npm key).
@@ -40,6 +40,6 @@ subtitle streams were unreachable by the browser.
40
40
 
41
41
  - `services/playback-planner.js`, `services/hls-session-manager.js`,
42
42
  `routes/api/transcode-sessions/post.js`, new `routes/api/subtitles/get.js`,
43
- `server.js` wiring, `services/torrent-pool.js` (log masking).
43
+ `server.js` wiring, `services/torrent/torrent-pool.js` (log masking).
44
44
  - Pairs with the server-side `track-selection-ui` change; requires the usual
45
45
  ha-addon bump.
@@ -44,7 +44,7 @@ manual-quality menu also needs the server UI).
44
44
 
45
45
  ## Impact
46
46
 
47
- - `services/hwaccel.js` (fps, benchmark, tonemap args),
47
+ - `services/encode/hwaccel.js` (fps, benchmark, tonemap args),
48
48
  `services/hls-session-manager.js` (fps probe, runtime speed watch),
49
49
  `services/playback-planner.js` (HDR/fps in the plan);
50
50
  server player UI for the Quality menu; ha-addon bump.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.86.0",
3
+ "version": "2.87.0",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "type": "module",
10
10
  "engines": {
11
- "node": ">=24 <25"
11
+ "node": ">=24"
12
12
  },
13
13
  "bin": {
14
14
  "torrent-tv-proxy": "./bin/cli.js"