@ceralive/modem-control 0.2.0 → 1.1.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 (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -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 +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -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 +196 -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 +19 -0
  146. package/dist/index.js +26 -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 +47 -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/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
@@ -0,0 +1,50 @@
1
+ import type { DesiredUsage } from '../../domain/index.js';
2
+ /** The current on-disk schema version. Bump when the persisted shape changes. */
3
+ export declare const USAGE_POLICY_SCHEMA_VERSION = 1;
4
+ /** One slot's persisted usage policy. Both fields are absent when unset. */
5
+ export interface PersistedUsagePolicySlot {
6
+ readonly logicalSlotId: string;
7
+ /** Day of month (1–31) the cycle resets; UTC, month-length clamped (A4.3). */
8
+ readonly cycleDay?: number;
9
+ /** Advisory threshold in bytes; crossing it raises an advisory, never gates. */
10
+ readonly thresholdBytes?: number;
11
+ }
12
+ /** The full persisted policy document. */
13
+ export interface PersistedUsagePolicy {
14
+ readonly schemaVersion: typeof USAGE_POLICY_SCHEMA_VERSION;
15
+ readonly savedAtMs: number;
16
+ readonly slots: readonly PersistedUsagePolicySlot[];
17
+ }
18
+ /** A metadata-only log event. Corruption NEVER carries the raw file content. */
19
+ export type UsagePolicyLogEvent = {
20
+ readonly kind: 'corrupt-policy';
21
+ readonly bytes: number;
22
+ readonly reason: string;
23
+ };
24
+ /** Sink for policy-store log events. Defaults to a metadata-only `console.warn`. */
25
+ export type UsagePolicyLogger = (event: UsagePolicyLogEvent) => void;
26
+ /** The persistence seam `setUsagePolicy` drives. */
27
+ export interface UsagePolicyStore {
28
+ /** Load persisted policy; recreate a fresh 0600 file if absent or corrupt. */
29
+ load(nowMs: number): Promise<PersistedUsagePolicy>;
30
+ /** Atomically write policy with mode 0600 (temp → chmod → rename). */
31
+ save(state: PersistedUsagePolicy): Promise<void>;
32
+ }
33
+ export interface UsagePolicyFileStoreOptions {
34
+ readonly path: string;
35
+ readonly logger?: UsagePolicyLogger;
36
+ }
37
+ /** True for a value that is a legal cycle day (integer 1–31). */
38
+ export declare function isValidCycleDay(value: unknown): value is number;
39
+ /** True for a value that is a legal advisory threshold (non-negative integer). */
40
+ export declare function isValidThresholdBytes(value: unknown): value is number;
41
+ /**
42
+ * Read one slot's policy out of a loaded document.
43
+ *
44
+ * This is the read half the composition root uses to build each slot's
45
+ * `UsageObservation.usage`, so the persisted file — not an in-memory guess — is
46
+ * what the sampler accounts against. An unknown slot answers `{}`, i.e. "no
47
+ * policy set", which is exactly what `defaultCellularPolicy` starts from.
48
+ */
49
+ export declare function selectUsagePolicy(state: PersistedUsagePolicy, logicalSlotId: string): DesiredUsage;
50
+ export declare function createUsagePolicyFileStore(options: UsagePolicyFileStoreOptions): UsagePolicyStore;
@@ -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>;