@ceralive/modem-control 1.0.0 → 1.2.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 (480) hide show
  1. package/README.md +341 -0
  2. package/dist/backend/at-lease.d.ts +59 -0
  3. package/dist/backend/at-lease.js +118 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +101 -0
  61. package/dist/backend/transition-preconditions.js +132 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +216 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +20 -0
  146. package/dist/index.js +27 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +61 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operation-ids.d.ts +2 -0
  196. package/dist/operation-ids.js +26 -0
  197. package/dist/operations/contracts.d.ts +72 -0
  198. package/dist/operations/contracts.js +1 -0
  199. package/dist/operations/index.d.ts +1 -0
  200. package/dist/operations/index.js +1 -0
  201. package/dist/operations/operation-engine.d.ts +16 -0
  202. package/dist/operations/operation-engine.js +194 -0
  203. package/dist/ports/index.d.ts +12 -0
  204. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  205. package/dist/ports/location.d.ts +87 -0
  206. package/dist/ports/location.js +36 -0
  207. package/dist/ports/modem-manager.d.ts +89 -0
  208. package/dist/ports/modem-manager.js +9 -0
  209. package/dist/ports/mutation-admission.d.ts +27 -0
  210. package/dist/ports/mutation-admission.js +9 -0
  211. package/dist/ports/network-manager.d.ts +68 -0
  212. package/dist/ports/network-manager.js +13 -0
  213. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  214. package/dist/ports/observation.js +7 -0
  215. package/dist/ports/ops.d.ts +44 -0
  216. package/dist/ports/ops.js +16 -0
  217. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  218. package/dist/ports/receipts.js +9 -0
  219. package/dist/ports/reconcile.d.ts +33 -0
  220. package/dist/ports/reconcile.js +200 -0
  221. package/dist/ports/resource-ownership.d.ts +29 -0
  222. package/dist/ports/resource-ownership.js +1 -0
  223. package/dist/ports/router.d.ts +19 -0
  224. package/dist/ports/router.js +7 -0
  225. package/dist/ports/sms.d.ts +64 -0
  226. package/dist/ports/sms.js +24 -0
  227. package/dist/ports/uhubctl.d.ts +6 -0
  228. package/dist/ports/uhubctl.js +1 -0
  229. package/dist/providers/contracts.d.ts +124 -0
  230. package/dist/providers/contracts.js +10 -0
  231. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  232. package/dist/providers/huawei-hilink/index.js +2 -0
  233. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  234. package/dist/providers/huawei-hilink/operations.js +56 -0
  235. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  236. package/dist/providers/huawei-hilink/provider.js +76 -0
  237. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  238. package/dist/providers/huawei-hilink/runtime.js +171 -0
  239. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  240. package/dist/providers/huawei-hilink/session.js +120 -0
  241. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  242. package/dist/providers/huawei-hilink/transport.js +1 -0
  243. package/dist/providers/index.d.ts +8 -0
  244. package/dist/providers/index.js +8 -0
  245. package/dist/providers/matcher.d.ts +3 -0
  246. package/dist/providers/matcher.js +205 -0
  247. package/dist/providers/modem-manager/errors.d.ts +7 -0
  248. package/dist/providers/modem-manager/errors.js +37 -0
  249. package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
  250. package/dist/providers/modem-manager/generic-operations.js +209 -0
  251. package/dist/providers/modem-manager/index.d.ts +4 -0
  252. package/dist/providers/modem-manager/index.js +4 -0
  253. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  254. package/dist/providers/modem-manager/module-operations.js +118 -0
  255. package/dist/providers/modem-manager/provider.d.ts +41 -0
  256. package/dist/providers/modem-manager/provider.js +155 -0
  257. package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
  258. package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
  259. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  260. package/dist/providers/modem-manager/snapshot.js +204 -0
  261. package/dist/providers/modem-manager/types.d.ts +137 -0
  262. package/dist/providers/modem-manager/types.js +1 -0
  263. package/dist/providers/network-manager/adapter.d.ts +71 -0
  264. package/dist/providers/network-manager/adapter.js +348 -0
  265. package/dist/providers/network-manager/index.d.ts +2 -0
  266. package/dist/providers/network-manager/index.js +2 -0
  267. package/dist/providers/network-manager/types.d.ts +171 -0
  268. package/dist/providers/network-manager/types.js +77 -0
  269. package/dist/providers/registry.d.ts +13 -0
  270. package/dist/providers/registry.js +33 -0
  271. package/dist/providers/ufi-himi/index.d.ts +6 -0
  272. package/dist/providers/ufi-himi/index.js +6 -0
  273. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  274. package/dist/providers/ufi-himi/operations.js +66 -0
  275. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  276. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  277. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  278. package/dist/providers/ufi-himi/provider.js +204 -0
  279. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  280. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  281. package/dist/providers/ufi-himi/session.d.ts +46 -0
  282. package/dist/providers/ufi-himi/session.js +92 -0
  283. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  284. package/dist/providers/ufi-himi/transport.js +25 -0
  285. package/dist/providers/zte-goform/index.d.ts +2 -0
  286. package/dist/providers/zte-goform/index.js +2 -0
  287. package/dist/providers/zte-goform/provider.d.ts +56 -0
  288. package/dist/providers/zte-goform/provider.js +101 -0
  289. package/dist/providers/zte-goform/session.d.ts +23 -0
  290. package/dist/providers/zte-goform/session.js +197 -0
  291. package/dist/providers/zte-goform/transport.d.ts +16 -0
  292. package/dist/providers/zte-goform/transport.js +1 -0
  293. package/dist/radio/band-truth.d.ts +50 -0
  294. package/dist/radio/band-truth.js +92 -0
  295. package/dist/radio/index.d.ts +3 -0
  296. package/dist/radio/index.js +10 -0
  297. package/dist/radio/mode-combinations.d.ts +88 -0
  298. package/dist/radio/mode-combinations.js +198 -0
  299. package/dist/radio/mode-truth.d.ts +67 -0
  300. package/dist/radio/mode-truth.js +112 -0
  301. package/dist/redact.d.ts +15 -0
  302. package/dist/redact.js +189 -0
  303. package/dist/safety/composition-root.d.ts +28 -0
  304. package/dist/safety/composition-root.js +65 -0
  305. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  306. package/dist/safety/flock-resource-ownership.js +138 -0
  307. package/dist/safety/index.d.ts +2 -0
  308. package/dist/safety/index.js +2 -0
  309. package/dist/sms/dbus-messaging.d.ts +17 -0
  310. package/dist/sms/dbus-messaging.js +185 -0
  311. package/dist/sms/inbox-store.d.ts +10 -0
  312. package/dist/sms/inbox-store.js +82 -0
  313. package/dist/sms/index.d.ts +4 -0
  314. package/dist/sms/index.js +10 -0
  315. package/dist/sms/mmcli-parse.d.ts +54 -0
  316. package/dist/sms/mmcli-parse.js +224 -0
  317. package/dist/sms/normalize.d.ts +42 -0
  318. package/dist/sms/normalize.js +95 -0
  319. package/dist/testing/domain-fakes.d.ts +58 -0
  320. package/dist/testing/domain-fakes.js +98 -0
  321. package/dist/testing/index.d.ts +2 -0
  322. package/dist/testing/index.js +17 -0
  323. package/dist/testing/provider-fakes.d.ts +45 -0
  324. package/dist/testing/provider-fakes.js +64 -0
  325. package/dist/transport/calls.d.ts +9 -0
  326. package/dist/transport/calls.js +88 -0
  327. package/dist/transport/codec.d.ts +3 -0
  328. package/dist/transport/codec.js +207 -0
  329. package/dist/transport/dbus-native.d.ts +57 -0
  330. package/dist/transport/dbus-native.js +17 -0
  331. package/dist/transport/errors.d.ts +21 -0
  332. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  333. package/dist/transport/index.d.ts +4 -0
  334. package/dist/transport/index.js +9 -0
  335. package/dist/transport/signals.d.ts +15 -0
  336. package/dist/transport/signals.js +123 -0
  337. package/dist/transport/signature.d.ts +7 -0
  338. package/dist/transport/signature.js +94 -0
  339. package/dist/transport/transport.d.ts +2 -0
  340. package/dist/transport/transport.js +202 -0
  341. package/dist/transport/types.d.ts +61 -0
  342. package/dist/transport/types.js +19 -0
  343. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  344. package/dist/usb-mode/catalog-schema.js +97 -0
  345. package/dist/usb-mode/catalog.d.ts +21 -0
  346. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  347. package/dist/usb-mode/certified-catalog.json +67 -0
  348. package/dist/usb-mode/index.d.ts +6 -0
  349. package/dist/usb-mode/index.js +16 -0
  350. package/dist/usb-mode/ingestion.d.ts +111 -0
  351. package/dist/usb-mode/ingestion.js +187 -0
  352. package/dist/usb-mode/promotion-review.d.ts +21 -0
  353. package/dist/usb-mode/promotion-review.js +87 -0
  354. package/dist/usb-mode/runtime-capability.d.ts +59 -0
  355. package/dist/usb-mode/runtime-capability.js +157 -0
  356. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  357. package/dist/usb-mode/usb-devices-parse.js +157 -0
  358. package/dist/ussd/calls.d.ts +32 -0
  359. package/dist/ussd/calls.js +96 -0
  360. package/dist/ussd/index.d.ts +5 -0
  361. package/dist/ussd/index.js +12 -0
  362. package/dist/ussd/mm-ussd.d.ts +37 -0
  363. package/dist/ussd/mm-ussd.js +205 -0
  364. package/dist/ussd/refusal.d.ts +53 -0
  365. package/dist/ussd/refusal.js +154 -0
  366. package/dist/ussd/registration.d.ts +20 -0
  367. package/dist/ussd/registration.js +101 -0
  368. package/dist/ussd/session.d.ts +106 -0
  369. package/dist/ussd/session.js +163 -0
  370. package/package.json +38 -4
  371. package/src/backend/at-lease.test.ts +0 -106
  372. package/src/backend/at-lease.ts +0 -158
  373. package/src/backend/cell-info.test.ts +0 -154
  374. package/src/backend/cell-info.ts +0 -160
  375. package/src/backend/device-classifier.test.ts +0 -168
  376. package/src/backend/device-classifier.ts +0 -248
  377. package/src/backend/enrichment.ts +0 -96
  378. package/src/backend/features.test.ts +0 -162
  379. package/src/backend/features.ts +0 -179
  380. package/src/backend/identity-ladder.test.ts +0 -117
  381. package/src/backend/identity-ladder.ts +0 -221
  382. package/src/backend/identity-registry.test.ts +0 -89
  383. package/src/backend/identity-registry.ts +0 -151
  384. package/src/backend/index.ts +0 -236
  385. package/src/backend/lifecycle-interlock.ts +0 -38
  386. package/src/backend/managed-objects.ts +0 -108
  387. package/src/backend/mapping.ts +0 -160
  388. package/src/backend/mm-backend.ts +0 -191
  389. package/src/backend/mm-mutations.ts +0 -228
  390. package/src/backend/modem-actor.test.ts +0 -95
  391. package/src/backend/modem-actor.ts +0 -112
  392. package/src/backend/nm-auto-apn.ts +0 -161
  393. package/src/backend/nm-gsm-fields.ts +0 -122
  394. package/src/backend/nmcli-nm-port.ts +0 -228
  395. package/src/backend/nmcli-runner.ts +0 -52
  396. package/src/backend/observer.ts +0 -297
  397. package/src/backend/power-contract.test.ts +0 -40
  398. package/src/backend/power-contract.ts +0 -83
  399. package/src/backend/recovery-attribution.test.ts +0 -102
  400. package/src/backend/recovery-attribution.ts +0 -86
  401. package/src/backend/recovery-budget.test.ts +0 -64
  402. package/src/backend/recovery-budget.ts +0 -84
  403. package/src/backend/recovery-ladder.test.ts +0 -257
  404. package/src/backend/recovery-ladder.ts +0 -249
  405. package/src/backend/router-ethernet.test.ts +0 -71
  406. package/src/backend/router-ethernet.ts +0 -90
  407. package/src/backend/row-store.ts +0 -105
  408. package/src/backend/signal-setup.ts +0 -112
  409. package/src/backend/sim-unlock.ts +0 -193
  410. package/src/backend/transition-preconditions.ts +0 -149
  411. package/src/backend/uhubctl-power-hook.test.ts +0 -274
  412. package/src/backend/uhubctl-power-hook.ts +0 -377
  413. package/src/backend/usage/accounting.test.ts +0 -147
  414. package/src/backend/usage/accounting.ts +0 -123
  415. package/src/backend/usage/billing-cycle.test.ts +0 -62
  416. package/src/backend/usage/billing-cycle.ts +0 -45
  417. package/src/backend/usage/index.ts +0 -60
  418. package/src/backend/usage/policy-store.test.ts +0 -164
  419. package/src/backend/usage/policy-store.ts +0 -216
  420. package/src/backend/usage/policy-write.test.ts +0 -198
  421. package/src/backend/usage/policy-write.ts +0 -207
  422. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  423. package/src/backend/usage/sampler.test.ts +0 -327
  424. package/src/backend/usage/sampler.ts +0 -282
  425. package/src/backend/usage/store.test.ts +0 -148
  426. package/src/backend/usage/store.ts +0 -177
  427. package/src/backend/usb-enumerator.test.ts +0 -87
  428. package/src/backend/usb-enumerator.ts +0 -181
  429. package/src/backend/usb-mode-transition.test.ts +0 -323
  430. package/src/backend/usb-mode-transition.ts +0 -253
  431. package/src/domain/brand.ts +0 -29
  432. package/src/domain/errors.ts +0 -77
  433. package/src/domain/guards.test.ts +0 -218
  434. package/src/domain/guards.ts +0 -144
  435. package/src/domain/identity.test.ts +0 -83
  436. package/src/domain/identity.ts +0 -165
  437. package/src/domain/index.ts +0 -12
  438. package/src/domain/snapshot.test.ts +0 -266
  439. package/src/domain/snapshot.ts +0 -120
  440. package/src/index.test.ts +0 -6
  441. package/src/index.ts +0 -15
  442. package/src/ports/README.md +0 -61
  443. package/src/ports/forbidden-surface.test.ts +0 -241
  444. package/src/ports/modem-manager.ts +0 -72
  445. package/src/ports/network-manager.ts +0 -87
  446. package/src/ports/ops.ts +0 -60
  447. package/src/ports/ops.type-test.ts +0 -39
  448. package/src/ports/receipts.test.ts +0 -153
  449. package/src/ports/reconcile.test.ts +0 -152
  450. package/src/ports/reconcile.ts +0 -338
  451. package/src/ports/router.ts +0 -29
  452. package/src/redact.test.ts +0 -82
  453. package/src/redact.ts +0 -73
  454. package/src/transport/README.md +0 -65
  455. package/src/transport/calls.ts +0 -113
  456. package/src/transport/characterization.test.ts +0 -260
  457. package/src/transport/codec.test.ts +0 -118
  458. package/src/transport/codec.ts +0 -240
  459. package/src/transport/conformance-python.test.ts +0 -152
  460. package/src/transport/conformance-same-lib.test.ts +0 -115
  461. package/src/transport/dbus-native-lib.d.ts +0 -19
  462. package/src/transport/dbus-native.ts +0 -85
  463. package/src/transport/index.ts +0 -30
  464. package/src/transport/no-library-leak.test.ts +0 -61
  465. package/src/transport/reliability.test.ts +0 -173
  466. package/src/transport/signals.ts +0 -150
  467. package/src/transport/signature.ts +0 -110
  468. package/src/transport/test-support/fake-service.ts +0 -168
  469. package/src/transport/test-support/independent-producer.py +0 -110
  470. package/src/transport/test-support/private-bus.ts +0 -66
  471. package/src/transport/transport.ts +0 -250
  472. package/src/transport/types.ts +0 -118
  473. package/src/usb-mode/catalog-schema.test.ts +0 -181
  474. package/src/usb-mode/catalog-schema.ts +0 -113
  475. package/src/usb-mode/certified-catalog.json +0 -67
  476. package/src/usb-mode/index.ts +0 -58
  477. package/src/usb-mode/ingestion.test.ts +0 -268
  478. package/src/usb-mode/ingestion.ts +0 -297
  479. package/src/usb-mode/promotion-review.ts +0 -117
  480. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -0,0 +1,161 @@
1
+ // Durable persistence for the operator's data-usage POLICY (cycle day + advisory
2
+ // threshold), the write-side counterpart of `store.ts`'s counter persistence.
3
+ //
4
+ // WHY THIS IS LOCAL STATE AND NOT A MODEM WRITE. ModemManager has no data-usage
5
+ // API at all. Verified against a live MM 1.24.2 (`mmcli --help-all`, plus a D-Bus
6
+ // introspection of a real `…/ModemManager1/Modem/N`): the only `Setup`/threshold
7
+ // surface on the whole object is `Modem.Signal.Setup` /
8
+ // `Modem.Signal.SetupThresholds`, whose keys are `rssi-threshold` and
9
+ // `error-rate-threshold` — RADIO QUALITY, not bytes. The only byte counters MM
10
+ // offers are the per-BEARER read-only `Stats` (`rx-bytes`/`tx-bytes`), which
11
+ // reset with every connection and therefore cannot carry a monthly cycle.
12
+ //
13
+ // That is exactly why the sampler in this directory counts `/proc/net/dev`
14
+ // instead, and why `ports/README.md`'s ownership table records usage policy as
15
+ // LOCAL-CONTROLLER owned. So the write path is a local, versioned, fail-soft
16
+ // file — never a D-Bus mutation. (`Modem.Signal.Setup` is additionally forbidden
17
+ // outright by the shadow-mode mutation-freedom contract; nothing here goes near
18
+ // it.)
19
+ //
20
+ // The two hard guarantees are the SAME ones `store.ts` makes, and deliberately
21
+ // implemented the same way so the pair can be read side by side:
22
+ // - MODE 0600 via temp → chmod → atomic rename, regardless of umask.
23
+ // - FAIL-SOFT ON CORRUPTION: an unparseable/incompatible file logs METADATA
24
+ // ONLY (byte length + a classification reason, never the content) and is
25
+ // replaced by a fresh empty 0600 file rather than throwing.
26
+ //
27
+ // A policy row carries ONLY an opaque slot id and two numbers. By construction
28
+ // there is no subscriber or device identity here (no ICCID/IMSI/IMEI, no
29
+ // operator, no model) — the same no-PII property the counter store holds.
30
+ import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
31
+ import { dirname } from 'node:path';
32
+ /** The current on-disk schema version. Bump when the persisted shape changes. */
33
+ export const USAGE_POLICY_SCHEMA_VERSION = 1;
34
+ function defaultLogger(event) {
35
+ console.warn(`[usage-policy] ${event.kind}: bytes=${event.bytes} reason=${event.reason}`);
36
+ }
37
+ function freshState(nowMs) {
38
+ return { schemaVersion: USAGE_POLICY_SCHEMA_VERSION, savedAtMs: nowMs, slots: [] };
39
+ }
40
+ /** A schema violation naming only the offending FIELD (never file content). */
41
+ class PolicySchemaError extends Error {
42
+ constructor(field) {
43
+ super(`schema-mismatch: ${field}`);
44
+ }
45
+ }
46
+ /** True for a value that is a legal cycle day (integer 1–31). */
47
+ export function isValidCycleDay(value) {
48
+ return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 31;
49
+ }
50
+ /** True for a value that is a legal advisory threshold (non-negative integer). */
51
+ export function isValidThresholdBytes(value) {
52
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0;
53
+ }
54
+ function validateSlot(raw) {
55
+ if (typeof raw !== 'object' || raw === null) {
56
+ throw new PolicySchemaError('slot');
57
+ }
58
+ const slot = raw;
59
+ if (typeof slot.logicalSlotId !== 'string' || slot.logicalSlotId.length === 0) {
60
+ throw new PolicySchemaError('logicalSlotId');
61
+ }
62
+ if (slot.cycleDay !== undefined && !isValidCycleDay(slot.cycleDay)) {
63
+ throw new PolicySchemaError('cycleDay');
64
+ }
65
+ if (slot.thresholdBytes !== undefined && !isValidThresholdBytes(slot.thresholdBytes)) {
66
+ throw new PolicySchemaError('thresholdBytes');
67
+ }
68
+ return {
69
+ logicalSlotId: slot.logicalSlotId,
70
+ ...(slot.cycleDay !== undefined ? { cycleDay: slot.cycleDay } : {}),
71
+ ...(slot.thresholdBytes !== undefined ? { thresholdBytes: slot.thresholdBytes } : {}),
72
+ };
73
+ }
74
+ /** Parse + validate the document. Throws `PolicySchemaError` (metadata-only). */
75
+ function validate(raw) {
76
+ if (typeof raw !== 'object' || raw === null) {
77
+ throw new PolicySchemaError('document');
78
+ }
79
+ const doc = raw;
80
+ if (doc.schemaVersion !== USAGE_POLICY_SCHEMA_VERSION) {
81
+ throw new PolicySchemaError('schemaVersion');
82
+ }
83
+ if (typeof doc.savedAtMs !== 'number' || !Number.isFinite(doc.savedAtMs)) {
84
+ throw new PolicySchemaError('savedAtMs');
85
+ }
86
+ if (!Array.isArray(doc.slots)) {
87
+ throw new PolicySchemaError('slots');
88
+ }
89
+ return {
90
+ schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
91
+ savedAtMs: doc.savedAtMs,
92
+ slots: doc.slots.map(validateSlot),
93
+ };
94
+ }
95
+ /** Classify a load failure into a metadata-only reason string (no raw content). */
96
+ function classifyFailure(error) {
97
+ if (error instanceof PolicySchemaError) {
98
+ return error.message;
99
+ }
100
+ if (error instanceof SyntaxError) {
101
+ const offset = /position (\d+)/.exec(error.message)?.[1];
102
+ return offset !== undefined ? `invalid-json at offset ${offset}` : 'invalid-json';
103
+ }
104
+ return 'unreadable';
105
+ }
106
+ /**
107
+ * Read one slot's policy out of a loaded document.
108
+ *
109
+ * This is the read half the composition root uses to build each slot's
110
+ * `UsageObservation.usage`, so the persisted file — not an in-memory guess — is
111
+ * what the sampler accounts against. An unknown slot answers `{}`, i.e. "no
112
+ * policy set", which is exactly what `defaultCellularPolicy` starts from.
113
+ */
114
+ export function selectUsagePolicy(state, logicalSlotId) {
115
+ const slot = state.slots.find((entry) => entry.logicalSlotId === logicalSlotId);
116
+ if (slot === undefined) {
117
+ return {};
118
+ }
119
+ return {
120
+ ...(slot.cycleDay !== undefined ? { cycleDay: slot.cycleDay } : {}),
121
+ ...(slot.thresholdBytes !== undefined ? { thresholdBytes: slot.thresholdBytes } : {}),
122
+ };
123
+ }
124
+ export function createUsagePolicyFileStore(options) {
125
+ const logger = options.logger ?? defaultLogger;
126
+ const { path } = options;
127
+ async function writeAtomic(state) {
128
+ await mkdir(dirname(path), { recursive: true });
129
+ const tmp = `${path}.tmp`;
130
+ await writeFile(tmp, JSON.stringify(state));
131
+ // chmod AFTER the write (not an open flag) so mode is 0600 regardless of umask.
132
+ await chmod(tmp, 0o600);
133
+ await rename(tmp, path);
134
+ }
135
+ return {
136
+ async load(nowMs) {
137
+ let text;
138
+ try {
139
+ text = await readFile(path, 'utf8');
140
+ }
141
+ catch {
142
+ // Absent (or unreadable) → start empty; the first save lays down a 0600 file.
143
+ return freshState(nowMs);
144
+ }
145
+ try {
146
+ return validate(JSON.parse(text));
147
+ }
148
+ catch (error) {
149
+ logger({
150
+ kind: 'corrupt-policy',
151
+ bytes: Buffer.byteLength(text, 'utf8'),
152
+ reason: classifyFailure(error),
153
+ });
154
+ const fresh = freshState(nowMs);
155
+ await writeAtomic(fresh);
156
+ return fresh;
157
+ }
158
+ },
159
+ save: writeAtomic,
160
+ };
161
+ }
@@ -0,0 +1,65 @@
1
+ import type { DesiredUsage } from '../../domain/index.js';
2
+ import { type UsagePolicyStore } from './policy-store.js';
3
+ /**
4
+ * The live-apply seam. `UsageSampler` implements it; a caller with no running
5
+ * sampler simply omits it and the write is persistence-only.
6
+ */
7
+ export interface UsagePolicyTarget {
8
+ applyUsagePolicy(logicalSlotId: string, usage: DesiredUsage, atMs?: number): UsagePolicyApplication;
9
+ }
10
+ /** What a live apply did to the slot's accounting window. */
11
+ export interface UsagePolicyApplication {
12
+ /** The UTC start of the cycle the slot is now accruing into. */
13
+ readonly cycleStartMs: number;
14
+ /** True when the cycle ANCHOR moved, so the per-cycle total restarted at 0. */
15
+ readonly cycleReset: boolean;
16
+ }
17
+ export interface SetUsagePolicyDeps {
18
+ readonly store: UsagePolicyStore;
19
+ /** Optional live sampler to apply the change to immediately. */
20
+ readonly sampler?: UsagePolicyTarget;
21
+ /** Injectable clock (defaults to `Date.now`). */
22
+ readonly now?: () => number;
23
+ }
24
+ /**
25
+ * The requested change.
26
+ *
27
+ * Tri-state per field, and the distinction is the whole point: `undefined`
28
+ * leaves the persisted value ALONE (so a caller changing only the threshold
29
+ * cannot silently drop a cycle day it never mentioned), while an explicit `null`
30
+ * CLEARS it. A caller that cannot express `null` can never unset a policy.
31
+ */
32
+ export interface SetUsagePolicyRequest {
33
+ readonly logicalSlotId: string;
34
+ readonly cycleDay?: number | null;
35
+ readonly thresholdBytes?: number | null;
36
+ }
37
+ export type SetUsagePolicyRejection = 'invalid-slot-id' | 'invalid-cycle-day' | 'invalid-threshold-bytes';
38
+ export type SetUsagePolicyResult = {
39
+ readonly status: 'applied';
40
+ readonly logicalSlotId: string;
41
+ /** The policy now persisted for this slot (post-merge). */
42
+ readonly usage: DesiredUsage;
43
+ /** Present only when a live sampler was supplied. */
44
+ readonly applied?: UsagePolicyApplication;
45
+ } | {
46
+ readonly status: 'rejected';
47
+ readonly logicalSlotId: string;
48
+ readonly reason: SetUsagePolicyRejection;
49
+ } | {
50
+ readonly status: 'failed';
51
+ readonly logicalSlotId: string;
52
+ readonly reason: string;
53
+ };
54
+ /**
55
+ * Persist a slot's usage policy and, when a live sampler is supplied, apply it
56
+ * to that sampler in the same call.
57
+ *
58
+ * ORDER IS LOAD → VALIDATE → PERSIST → APPLY, and it is deliberate. The store is
59
+ * the source of truth (the composition root rebuilds every `UsageObservation`
60
+ * from it), so a live apply that landed while the write failed would leave the
61
+ * running process disagreeing with what a restart would restore.
62
+ */
63
+ export declare function setUsagePolicy(deps: SetUsagePolicyDeps, request: SetUsagePolicyRequest): Promise<SetUsagePolicyResult>;
64
+ /** Read one slot's persisted policy. The read counterpart of `setUsagePolicy`. */
65
+ export declare function getUsagePolicy(deps: Pick<SetUsagePolicyDeps, 'store' | 'now'>, logicalSlotId: string): Promise<DesiredUsage>;
@@ -0,0 +1,112 @@
1
+ // `setUsagePolicy` — the WRITE half of the data-usage surface.
2
+ //
3
+ // The read half already existed (`UsageSampler.snapshot()` reports `cycleBytes`,
4
+ // `thresholdBytes` and `thresholdExceeded`), but nothing could SET the two
5
+ // numbers those readings are computed against: `DesiredUsage` was a shape the
6
+ // planner echoed into a receipt, with no persistence and no apply path. This
7
+ // module closes that, mirroring the read side's file-store idiom exactly.
8
+ //
9
+ // It is a LOCAL write, not a modem write — see `policy-store.ts`'s header for the
10
+ // ModemManager API evidence. Nothing here touches D-Bus, `mmcli`, or any bearer.
11
+ //
12
+ // TYPED RESULTS, NEVER THROWS ON BAD INPUT. Following the `PowerHook` precedent
13
+ // (`power-contract.ts`: `applied` / `unsupported` / `failed`), an out-of-range
14
+ // day is a `rejected` result carrying a named reason rather than an exception —
15
+ // this is called from an RPC boundary where a throw becomes an opaque 500.
16
+ import { isValidCycleDay, isValidThresholdBytes, selectUsagePolicy, USAGE_POLICY_SCHEMA_VERSION, } from './policy-store.js';
17
+ function validateRequest(request) {
18
+ if (typeof request.logicalSlotId !== 'string' || request.logicalSlotId.length === 0) {
19
+ return 'invalid-slot-id';
20
+ }
21
+ if (request.cycleDay !== undefined &&
22
+ request.cycleDay !== null &&
23
+ !isValidCycleDay(request.cycleDay)) {
24
+ return 'invalid-cycle-day';
25
+ }
26
+ if (request.thresholdBytes !== undefined &&
27
+ request.thresholdBytes !== null &&
28
+ !isValidThresholdBytes(request.thresholdBytes)) {
29
+ return 'invalid-threshold-bytes';
30
+ }
31
+ return undefined;
32
+ }
33
+ /** Fold the request onto the currently-persisted policy (tri-state merge). */
34
+ function mergePolicy(current, request) {
35
+ const cycleDay = request.cycleDay === undefined ? current.cycleDay : (request.cycleDay ?? undefined);
36
+ const thresholdBytes = request.thresholdBytes === undefined
37
+ ? current.thresholdBytes
38
+ : (request.thresholdBytes ?? undefined);
39
+ return {
40
+ ...(cycleDay !== undefined ? { cycleDay } : {}),
41
+ ...(thresholdBytes !== undefined ? { thresholdBytes } : {}),
42
+ };
43
+ }
44
+ function toSlot(logicalSlotId, usage) {
45
+ return {
46
+ logicalSlotId,
47
+ ...(usage.cycleDay !== undefined ? { cycleDay: usage.cycleDay } : {}),
48
+ ...(usage.thresholdBytes !== undefined ? { thresholdBytes: usage.thresholdBytes } : {}),
49
+ };
50
+ }
51
+ /**
52
+ * Persist a slot's usage policy and, when a live sampler is supplied, apply it
53
+ * to that sampler in the same call.
54
+ *
55
+ * ORDER IS LOAD → VALIDATE → PERSIST → APPLY, and it is deliberate. The store is
56
+ * the source of truth (the composition root rebuilds every `UsageObservation`
57
+ * from it), so a live apply that landed while the write failed would leave the
58
+ * running process disagreeing with what a restart would restore.
59
+ */
60
+ export async function setUsagePolicy(deps, request) {
61
+ const logicalSlotId = typeof request.logicalSlotId === 'string' ? request.logicalSlotId : '';
62
+ const rejection = validateRequest(request);
63
+ if (rejection !== undefined) {
64
+ return { status: 'rejected', logicalSlotId, reason: rejection };
65
+ }
66
+ const now = deps.now ?? Date.now;
67
+ const at = now();
68
+ let usage;
69
+ try {
70
+ const state = await deps.store.load(at);
71
+ usage = mergePolicy(selectUsagePolicy(state, logicalSlotId), request);
72
+ const others = state.slots.filter((slot) => slot.logicalSlotId !== logicalSlotId);
73
+ // An empty policy is REMOVED rather than stored as an empty row: "no policy"
74
+ // and "a policy that sets nothing" are the same fact, and keeping the row
75
+ // would grow the file by one entry per slot an operator ever cleared.
76
+ const slots = usage.cycleDay === undefined && usage.thresholdBytes === undefined
77
+ ? others
78
+ : [...others, toSlot(logicalSlotId, usage)];
79
+ await deps.store.save({
80
+ schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
81
+ savedAtMs: at,
82
+ slots,
83
+ });
84
+ }
85
+ catch (error) {
86
+ return {
87
+ status: 'failed',
88
+ logicalSlotId,
89
+ reason: error instanceof Error ? error.message : 'persist-failed',
90
+ };
91
+ }
92
+ if (deps.sampler === undefined) {
93
+ return { status: 'applied', logicalSlotId, usage };
94
+ }
95
+ try {
96
+ const applied = deps.sampler.applyUsagePolicy(logicalSlotId, usage, at);
97
+ return { status: 'applied', logicalSlotId, usage, applied };
98
+ }
99
+ catch (error) {
100
+ return {
101
+ status: 'failed',
102
+ logicalSlotId,
103
+ reason: error instanceof Error ? error.message : 'apply-failed',
104
+ };
105
+ }
106
+ }
107
+ /** Read one slot's persisted policy. The read counterpart of `setUsagePolicy`. */
108
+ export async function getUsagePolicy(deps, logicalSlotId) {
109
+ const now = deps.now ?? Date.now;
110
+ const state = await deps.store.load(now());
111
+ return selectUsagePolicy(state, logicalSlotId);
112
+ }
@@ -0,0 +1,18 @@
1
+ /** A source of cumulative rx+tx byte counters keyed by interface name. */
2
+ export interface CounterSource {
3
+ /** Read the current cumulative rx+tx byte total for every interface. */
4
+ read(): Promise<ReadonlyMap<string, number>>;
5
+ }
6
+ /**
7
+ * Parse `/proc/net/dev` text into a map of interface name → cumulative rx+tx bytes.
8
+ * The two-line header is skipped; malformed or short rows are ignored rather than
9
+ * throwing, so a single odd line can never break sampling.
10
+ */
11
+ export declare function parseProcNetDev(text: string): Map<string, number>;
12
+ /**
13
+ * The production counter source: reads and parses the real `/proc/net/dev`. The
14
+ * path is injectable so tests can point at a fixture, but the default is the live
15
+ * kernel file. A missing/unreadable file yields an empty map (fail-soft — a read
16
+ * error never crashes the sampler; that pass simply attributes no new bytes).
17
+ */
18
+ export declare function procNetDevCounterSource(path?: string): CounterSource;
@@ -11,67 +11,59 @@
11
11
  // accounting layer clamps). We parse the proc text ourselves rather than shelling
12
12
  // out to `ip`/`ifconfig` because the raw file is the most reliable, consistent
13
13
  // source and matches exactly what the plan names.
14
-
15
- /** A source of cumulative rx+tx byte counters keyed by interface name. */
16
- export interface CounterSource {
17
- /** Read the current cumulative rx+tx byte total for every interface. */
18
- read(): Promise<ReadonlyMap<string, number>>;
19
- }
20
-
21
14
  const HEADER_LINE_COUNT = 2;
22
15
  const RX_BYTES_COLUMN = 0;
23
16
  const TX_BYTES_COLUMN = 8;
24
17
  const MIN_COLUMNS = TX_BYTES_COLUMN + 1;
25
-
26
18
  /**
27
19
  * Parse `/proc/net/dev` text into a map of interface name → cumulative rx+tx bytes.
28
20
  * The two-line header is skipped; malformed or short rows are ignored rather than
29
21
  * throwing, so a single odd line can never break sampling.
30
22
  */
31
- export function parseProcNetDev(text: string): Map<string, number> {
32
- const counters = new Map<string, number>();
33
- const lines = text.split('\n').slice(HEADER_LINE_COUNT);
34
- for (const line of lines) {
35
- const colon = line.indexOf(':');
36
- if (colon < 0) {
37
- continue;
38
- }
39
- const ifname = line.slice(0, colon).trim();
40
- if (ifname.length === 0) {
41
- continue;
42
- }
43
- const columns = line
44
- .slice(colon + 1)
45
- .trim()
46
- .split(/\s+/);
47
- if (columns.length < MIN_COLUMNS) {
48
- continue;
49
- }
50
- const rx = Number(columns[RX_BYTES_COLUMN]);
51
- const tx = Number(columns[TX_BYTES_COLUMN]);
52
- if (!Number.isFinite(rx) || !Number.isFinite(tx) || rx < 0 || tx < 0) {
53
- continue;
54
- }
55
- counters.set(ifname, rx + tx);
56
- }
57
- return counters;
23
+ export function parseProcNetDev(text) {
24
+ const counters = new Map();
25
+ const lines = text.split('\n').slice(HEADER_LINE_COUNT);
26
+ for (const line of lines) {
27
+ const colon = line.indexOf(':');
28
+ if (colon < 0) {
29
+ continue;
30
+ }
31
+ const ifname = line.slice(0, colon).trim();
32
+ if (ifname.length === 0) {
33
+ continue;
34
+ }
35
+ const columns = line
36
+ .slice(colon + 1)
37
+ .trim()
38
+ .split(/\s+/);
39
+ if (columns.length < MIN_COLUMNS) {
40
+ continue;
41
+ }
42
+ const rx = Number(columns[RX_BYTES_COLUMN]);
43
+ const tx = Number(columns[TX_BYTES_COLUMN]);
44
+ if (!Number.isFinite(rx) || !Number.isFinite(tx) || rx < 0 || tx < 0) {
45
+ continue;
46
+ }
47
+ counters.set(ifname, rx + tx);
48
+ }
49
+ return counters;
58
50
  }
59
-
60
51
  /**
61
52
  * The production counter source: reads and parses the real `/proc/net/dev`. The
62
53
  * path is injectable so tests can point at a fixture, but the default is the live
63
54
  * kernel file. A missing/unreadable file yields an empty map (fail-soft — a read
64
55
  * error never crashes the sampler; that pass simply attributes no new bytes).
65
56
  */
66
- export function procNetDevCounterSource(path = '/proc/net/dev'): CounterSource {
67
- return {
68
- async read(): Promise<ReadonlyMap<string, number>> {
69
- try {
70
- const text = await Bun.file(path).text();
71
- return parseProcNetDev(text);
72
- } catch {
73
- return new Map();
74
- }
75
- },
76
- };
57
+ export function procNetDevCounterSource(path = '/proc/net/dev') {
58
+ return {
59
+ async read() {
60
+ try {
61
+ const text = await Bun.file(path).text();
62
+ return parseProcNetDev(text);
63
+ }
64
+ catch {
65
+ return new Map();
66
+ }
67
+ },
68
+ };
77
69
  }
@@ -0,0 +1,75 @@
1
+ import { type DesiredUsage, type LogicalSlotId } from '../../domain/index.js';
2
+ import type { CounterSource } from './proc-net-dev.js';
3
+ import type { UsageStore } from './store.js';
4
+ /** One slot's observation for a sampling pass — identity + mapping + local policy. */
5
+ export interface UsageObservation {
6
+ readonly logicalSlotId: LogicalSlotId;
7
+ /** Bumped by A4.2 whenever the ifname-to-slot mapping changes (→ zero-delta rebase). */
8
+ readonly mappingGeneration: number;
9
+ readonly ifname: string;
10
+ /** A3.2 identity-ladder confidence; `low` pauses sampling for this slot. */
11
+ readonly confidence: 'high' | 'medium' | 'low';
12
+ /** Local-controller-owned usage policy (cycle day + advisory threshold). */
13
+ readonly usage: DesiredUsage;
14
+ }
15
+ /** A queryable per-slot usage figure — consumed by the A6.1 bench CLI `usage` command. */
16
+ export interface SlotUsageSnapshot {
17
+ readonly logicalSlotId: string;
18
+ readonly cycleBytes: number;
19
+ readonly cycleStartMs: number;
20
+ readonly paused: boolean;
21
+ /** The cycle day in force for this slot, when the operator set one. */
22
+ readonly cycleDay?: number;
23
+ readonly thresholdBytes?: number;
24
+ /** Advisory-only: `cycleBytes > thresholdBytes`. Never gates the connection. */
25
+ readonly thresholdExceeded: boolean;
26
+ }
27
+ /** The sampler's current state, per slot, at a point in time. */
28
+ export interface UsageSnapshot {
29
+ readonly bootId: string;
30
+ readonly generatedAtMs: number;
31
+ readonly slots: readonly SlotUsageSnapshot[];
32
+ }
33
+ export interface UsageSamplerOptions {
34
+ /** The kernel boot id (see `readBootId`) — the session identity. */
35
+ readonly bootId: string;
36
+ readonly source: CounterSource;
37
+ readonly store: UsageStore;
38
+ /** Injectable clock (defaults to `Date.now`). */
39
+ readonly now?: () => number;
40
+ /** Minimum spacing between persists (default 60_000 ms = the ≤1-min loss bound). */
41
+ readonly persistIntervalMs?: number;
42
+ /** Cycle day used when a slot's policy omits `cycleDay` (default 1 = 1st of month). */
43
+ readonly defaultCycleDay?: number;
44
+ }
45
+ export declare class UsageSampler {
46
+ #private;
47
+ private constructor();
48
+ /** Load persisted state (recreating a fresh file if absent/corrupt) then build the sampler. */
49
+ static create(options: UsageSamplerOptions): Promise<UsageSampler>;
50
+ /** Take one sampling pass over the current counters for the given observations. */
51
+ sample(observations: readonly UsageObservation[]): Promise<void>;
52
+ /** Current per-slot usage — the queryable snapshot the CLI and platform read. */
53
+ snapshot(): UsageSnapshot;
54
+ /**
55
+ * Apply an operator's usage policy to this slot immediately, without waiting
56
+ * for the next sampling pass.
57
+ *
58
+ * A CHANGED CYCLE ANCHOR RESTARTS THE WINDOW AT ZERO, and keeps the counter
59
+ * BASELINE. Those two halves are the honest answer to a question with no
60
+ * truthful one: bytes already accrued were measured under the OLD window, so
61
+ * carrying them into the new one over-reports it, and there is no record of
62
+ * how they were distributed within it. Starting fresh states plainly that the
63
+ * new window began now; keeping `lastObserved` means the next sample still
64
+ * attributes only genuinely new bytes, never a jump. A threshold-only change
65
+ * moves no anchor and therefore resets nothing.
66
+ */
67
+ applyUsagePolicy(logicalSlotId: string, usage: DesiredUsage, atMs?: number): {
68
+ cycleStartMs: number;
69
+ cycleReset: boolean;
70
+ };
71
+ /** Flush unpersisted state immediately — the shutdown hook (bounds loss to ≤1 min). */
72
+ flush(): Promise<void>;
73
+ }
74
+ /** Load persisted state and build a ready sampler. See `UsageSampler.create`. */
75
+ export declare function createUsageSampler(options: UsageSamplerOptions): Promise<UsageSampler>;