@ceralive/modem-control 1.0.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 (474) 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 -236
  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/uhubctl-power-hook.test.ts +0 -274
  406. package/src/backend/uhubctl-power-hook.ts +0 -377
  407. package/src/backend/usage/accounting.test.ts +0 -147
  408. package/src/backend/usage/accounting.ts +0 -123
  409. package/src/backend/usage/billing-cycle.test.ts +0 -62
  410. package/src/backend/usage/billing-cycle.ts +0 -45
  411. package/src/backend/usage/index.ts +0 -60
  412. package/src/backend/usage/policy-store.test.ts +0 -164
  413. package/src/backend/usage/policy-store.ts +0 -216
  414. package/src/backend/usage/policy-write.test.ts +0 -198
  415. package/src/backend/usage/policy-write.ts +0 -207
  416. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  417. package/src/backend/usage/sampler.test.ts +0 -327
  418. package/src/backend/usage/sampler.ts +0 -282
  419. package/src/backend/usage/store.test.ts +0 -148
  420. package/src/backend/usage/store.ts +0 -177
  421. package/src/backend/usb-enumerator.test.ts +0 -87
  422. package/src/backend/usb-enumerator.ts +0 -181
  423. package/src/backend/usb-mode-transition.test.ts +0 -323
  424. package/src/backend/usb-mode-transition.ts +0 -253
  425. package/src/domain/brand.ts +0 -29
  426. package/src/domain/errors.ts +0 -77
  427. package/src/domain/guards.test.ts +0 -218
  428. package/src/domain/guards.ts +0 -144
  429. package/src/domain/identity.test.ts +0 -83
  430. package/src/domain/identity.ts +0 -165
  431. package/src/domain/index.ts +0 -12
  432. package/src/domain/snapshot.test.ts +0 -266
  433. package/src/domain/snapshot.ts +0 -120
  434. package/src/index.test.ts +0 -6
  435. package/src/index.ts +0 -15
  436. package/src/ports/README.md +0 -61
  437. package/src/ports/forbidden-surface.test.ts +0 -241
  438. package/src/ports/modem-manager.ts +0 -72
  439. package/src/ports/network-manager.ts +0 -87
  440. package/src/ports/ops.ts +0 -60
  441. package/src/ports/ops.type-test.ts +0 -39
  442. package/src/ports/receipts.test.ts +0 -153
  443. package/src/ports/reconcile.test.ts +0 -152
  444. package/src/ports/reconcile.ts +0 -338
  445. package/src/ports/router.ts +0 -29
  446. package/src/redact.test.ts +0 -82
  447. package/src/redact.ts +0 -73
  448. package/src/transport/README.md +0 -65
  449. package/src/transport/calls.ts +0 -113
  450. package/src/transport/characterization.test.ts +0 -260
  451. package/src/transport/codec.test.ts +0 -118
  452. package/src/transport/codec.ts +0 -240
  453. package/src/transport/conformance-python.test.ts +0 -152
  454. package/src/transport/conformance-same-lib.test.ts +0 -115
  455. package/src/transport/dbus-native-lib.d.ts +0 -19
  456. package/src/transport/dbus-native.ts +0 -85
  457. package/src/transport/index.ts +0 -30
  458. package/src/transport/no-library-leak.test.ts +0 -61
  459. package/src/transport/reliability.test.ts +0 -173
  460. package/src/transport/signals.ts +0 -150
  461. package/src/transport/signature.ts +0 -110
  462. package/src/transport/test-support/fake-service.ts +0 -168
  463. package/src/transport/test-support/independent-producer.py +0 -110
  464. package/src/transport/test-support/private-bus.ts +0 -66
  465. package/src/transport/transport.ts +0 -250
  466. package/src/transport/types.ts +0 -118
  467. package/src/usb-mode/catalog-schema.test.ts +0 -181
  468. package/src/usb-mode/catalog-schema.ts +0 -113
  469. package/src/usb-mode/certified-catalog.json +0 -67
  470. package/src/usb-mode/index.ts +0 -58
  471. package/src/usb-mode/ingestion.test.ts +0 -268
  472. package/src/usb-mode/ingestion.ts +0 -297
  473. package/src/usb-mode/promotion-review.ts +0 -117
  474. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -1,147 +0,0 @@
1
- // The adversarial accounting matrix — pure, deterministic proofs that per-slot usage
2
- // is attributed correctly across remaps, swaps, reuse, resets, pauses, and rollovers.
3
-
4
- import { describe, expect, test } from 'bun:test';
5
- import { type IdentityConfidence, logicalSlotId } from '../../domain';
6
- import { applySample, type BaselineKey, type SlotAccount } from './accounting';
7
-
8
- const SLOT_A = logicalSlotId('slot-a');
9
- const SLOT_B = logicalSlotId('slot-b');
10
- const SLOT_C = logicalSlotId('slot-c');
11
- const BOOT = 'boot-uuid-1';
12
-
13
- interface Step {
14
- readonly slot: string;
15
- readonly gen: number;
16
- readonly ifname: string;
17
- readonly current: number;
18
- readonly confidence?: IdentityConfidence;
19
- readonly cycleStartMs?: number;
20
- }
21
-
22
- function key(slot: string, gen: number, ifname: string): BaselineKey {
23
- return { logicalSlotId: slot, mappingGeneration: gen, ifname, bootId: BOOT };
24
- }
25
-
26
- function step(prior: SlotAccount | undefined, s: Step): SlotAccount {
27
- return applySample(prior, {
28
- key: key(s.slot, s.gen, s.ifname),
29
- current: s.current,
30
- confidence: s.confidence ?? 'high',
31
- cycleStartMs: s.cycleStartMs ?? 1000,
32
- });
33
- }
34
-
35
- describe('applySample — two modems tracked independently', () => {
36
- test('separate slots never cross-contaminate', () => {
37
- let a = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
38
- let b = step(undefined, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 5000 });
39
- a = step(a, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 300 });
40
- b = step(b, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 5001 });
41
- expect(a.cycleBytes).toBe(200);
42
- expect(b.cycleBytes).toBe(1);
43
- });
44
- });
45
-
46
- describe('applySample — A/B ifname swap follows the slot, not the interface', () => {
47
- test('each slot keeps its own total across a swap and re-baselines zero-delta', () => {
48
- // Prime: A on wwan0, B on wwan1.
49
- let a = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
50
- let b = step(undefined, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 1000 });
51
- a = step(a, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 200 });
52
- b = step(b, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 1100 });
53
- expect(a.cycleBytes).toBe(100);
54
- expect(b.cycleBytes).toBe(100);
55
-
56
- // Swap: A now maps to wwan1, B to wwan0 (mapping generation bumps for both).
57
- // First post-swap sample must be zero-delta despite the large counter change.
58
- a = step(a, { slot: SLOT_A, gen: 1, ifname: 'wwan1', current: 1100 });
59
- b = step(b, { slot: SLOT_B, gen: 1, ifname: 'wwan0', current: 200 });
60
- expect(a.cycleBytes).toBe(100);
61
- expect(b.cycleBytes).toBe(100);
62
-
63
- // Subsequent growth attributes to the correct slot on its new interface.
64
- a = step(a, { slot: SLOT_A, gen: 1, ifname: 'wwan1', current: 1150 });
65
- b = step(b, { slot: SLOT_B, gen: 1, ifname: 'wwan0', current: 260 });
66
- expect(a.cycleBytes).toBe(150);
67
- expect(b.cycleBytes).toBe(160);
68
- });
69
- });
70
-
71
- describe('applySample — old ifname reused by a different slot (no bleed)', () => {
72
- test('a new slot inheriting an old interface starts from zero', () => {
73
- let a = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
74
- a = step(a, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 500 });
75
- expect(a.cycleBytes).toBe(400);
76
-
77
- // Slot C now occupies wwan0. Its own account starts fresh (zero-delta baseline).
78
- let c = step(undefined, { slot: SLOT_C, gen: 0, ifname: 'wwan0', current: 500 });
79
- c = step(c, { slot: SLOT_C, gen: 0, ifname: 'wwan0', current: 700 });
80
- expect(c.cycleBytes).toBe(200);
81
- // A's total is untouched — no bleed either direction.
82
- expect(a.cycleBytes).toBe(400);
83
- });
84
- });
85
-
86
- describe('applySample — rename-first-sample is zero-delta', () => {
87
- test('the first sample after a remap never reports a spurious jump', () => {
88
- let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
89
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 200 });
90
- expect(s.cycleBytes).toBe(100);
91
- // Rename wwan0 → wwan5 with a huge counter: must NOT attribute 9M.
92
- s = step(s, { slot: SLOT_A, gen: 1, ifname: 'wwan5', current: 9_000_000 });
93
- expect(s.cycleBytes).toBe(100);
94
- });
95
- });
96
-
97
- describe('applySample — reset-then-positive', () => {
98
- test('a counter reset clamps to zero, then normal increases resume', () => {
99
- let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 1000 });
100
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 1500 });
101
- expect(s.cycleBytes).toBe(500);
102
- // Interface recreated: counter drops to 50 (decrease) → clamp + rebase.
103
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 50 });
104
- expect(s.cycleBytes).toBe(500);
105
- // Growth from the rebased baseline is tracked again.
106
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 250 });
107
- expect(s.cycleBytes).toBe(700);
108
- });
109
-
110
- test('a decrease never produces a negative delta', () => {
111
- let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 800 });
112
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 300 });
113
- expect(s.cycleBytes).toBe(0);
114
- expect(s.cycleBytes).toBeGreaterThanOrEqual(0);
115
- });
116
- });
117
-
118
- describe('applySample — ambiguous identity pauses sampling', () => {
119
- test('low confidence attributes nothing and re-baselines zero-delta on resume', () => {
120
- let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
121
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 300 });
122
- expect(s.cycleBytes).toBe(200);
123
- // Identity turns ambiguous → paused; the counter keeps climbing unattributed.
124
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 9999, confidence: 'low' });
125
- expect(s.paused).toBe(true);
126
- expect(s.cycleBytes).toBe(200);
127
- // Confidence returns: resume is zero-delta, so the paused-window bytes are NOT
128
- // back-attributed; only growth after resume counts.
129
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 9999 });
130
- expect(s.paused).toBe(false);
131
- expect(s.cycleBytes).toBe(200);
132
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 10_099 });
133
- expect(s.cycleBytes).toBe(300);
134
- });
135
- });
136
-
137
- describe('applySample — cycle rollover resets the per-cycle total', () => {
138
- test('crossing into a newer cycle zeroes cycleBytes but keeps the baseline', () => {
139
- let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
140
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 600 });
141
- expect(s.cycleBytes).toBe(500);
142
- // New cycle boundary — cycleBytes resets, counter continuity preserved.
143
- s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 650, cycleStartMs: 2000 });
144
- expect(s.cycleStartMs).toBe(2000);
145
- expect(s.cycleBytes).toBe(50);
146
- });
147
- });
@@ -1,123 +0,0 @@
1
- // The pure usage-accounting reducer — the heart of the sampler.
2
- //
3
- // For each logical slot we keep a per-cycle byte total plus a BASELINE: the last
4
- // observed cumulative counter value under a specific key. The key is the composite
5
- // `{logicalSlotId, mappingGeneration, ifname, bootId}`. The rules, all encoded here
6
- // with zero I/O so they can be exhaustively unit-tested:
7
- //
8
- // - REMAP (any key field changes — e.g. the ifname-to-slot mapping bumped
9
- // `mappingGeneration`, or a reboot changed `bootId`): rebaseline with a
10
- // ZERO-DELTA sample. We start counting fresh from the current cumulative value
11
- // and attribute nothing for that first sample — never a spurious jump.
12
- // - SAME-KEY DECREASE (the cumulative counter went DOWN — interface re-created /
13
- // counter reset without a reported remap): CLAMP the negative delta to zero and
14
- // REBASE the baseline to the new lower value. Never report negative usage.
15
- // - LOW CONFIDENCE (ambiguous identity, A3.2 ladder): sampling is PAUSED. We drop
16
- // the baseline and attribute nothing; on resume the next sample is zero-delta so
17
- // bytes moved during the ambiguous window are never mis-attributed.
18
- // - CYCLE ROLLOVER (`now` crossed into a new billing cycle): the per-cycle total
19
- // resets to zero. The baseline is kept — the kernel counter is continuous across
20
- // a billing boundary; only OUR accounting window resets.
21
-
22
- import type { IdentityConfidence, LogicalSlotId } from '../../domain';
23
-
24
- /** The composite baseline key. A change in ANY field is a remap (zero-delta rebase). */
25
- export interface BaselineKey {
26
- readonly logicalSlotId: string;
27
- readonly mappingGeneration: number;
28
- readonly ifname: string;
29
- readonly bootId: string;
30
- }
31
-
32
- /** Per-slot accounting state. `key`/`lastObserved` are unset before the first sample. */
33
- export interface SlotAccount {
34
- /** Bytes attributed to this slot within the current cycle. */
35
- readonly cycleBytes: number;
36
- /** UTC start of the cycle `cycleBytes` is accruing into. */
37
- readonly cycleStartMs: number;
38
- /** True when the last observation was low-confidence and sampling is paused. */
39
- readonly paused: boolean;
40
- /** The key the baseline was captured under (undefined = no baseline yet). */
41
- readonly key?: BaselineKey;
42
- /** The cumulative counter value at the last accepted sample. */
43
- readonly lastObserved?: number;
44
- }
45
-
46
- /** One observation fed to the reducer for a single slot in one sampling pass. */
47
- export interface SampleInput {
48
- readonly key: BaselineKey;
49
- /** The current cumulative rx+tx counter for the slot's interface. */
50
- readonly current: number;
51
- readonly confidence: IdentityConfidence;
52
- /** The UTC start of the slot's active cycle (from `cycleStart`). */
53
- readonly cycleStartMs: number;
54
- }
55
-
56
- /** A fresh account for a slot first seen in `cycleStartMs`. */
57
- export function initialAccount(cycleStartMs: number): SlotAccount {
58
- return { cycleBytes: 0, cycleStartMs, paused: false };
59
- }
60
-
61
- function sameKey(a: BaselineKey, b: BaselineKey): boolean {
62
- return (
63
- a.logicalSlotId === b.logicalSlotId &&
64
- a.mappingGeneration === b.mappingGeneration &&
65
- a.ifname === b.ifname &&
66
- a.bootId === b.bootId
67
- );
68
- }
69
-
70
- /** Apply a cycle rollover: if we crossed into a newer cycle, zero the per-cycle total. */
71
- function rollCycle(account: SlotAccount, cycleStartMs: number): SlotAccount {
72
- if (cycleStartMs > account.cycleStartMs) {
73
- return { ...account, cycleBytes: 0, cycleStartMs };
74
- }
75
- return account;
76
- }
77
-
78
- /**
79
- * Fold one observation into a slot's account, returning the NEXT account. Pure —
80
- * no clock, no I/O; the caller supplies `current`, `confidence` and `cycleStartMs`.
81
- */
82
- export function applySample(prior: SlotAccount | undefined, input: SampleInput): SlotAccount {
83
- const base = rollCycle(prior ?? initialAccount(input.cycleStartMs), input.cycleStartMs);
84
-
85
- // Ambiguous identity → pause: attribute nothing and drop the baseline so the
86
- // next confident sample re-baselines zero-delta (no back-attribution).
87
- if (input.confidence === 'low') {
88
- return { cycleBytes: base.cycleBytes, cycleStartMs: base.cycleStartMs, paused: true };
89
- }
90
-
91
- // Remap, first-ever sample, or resuming from a pause → zero-delta rebaseline.
92
- if (base.paused || base.key === undefined || base.lastObserved === undefined) {
93
- return { ...base, paused: false, key: input.key, lastObserved: input.current };
94
- }
95
- if (!sameKey(base.key, input.key)) {
96
- return { ...base, paused: false, key: input.key, lastObserved: input.current };
97
- }
98
-
99
- // Same key: a decrease is a counter reset → clamp the negative delta and rebase.
100
- if (input.current < base.lastObserved) {
101
- return { ...base, paused: false, key: input.key, lastObserved: input.current };
102
- }
103
-
104
- // Normal case: attribute the non-negative delta and advance the baseline.
105
- const delta = input.current - base.lastObserved;
106
- return {
107
- ...base,
108
- paused: false,
109
- key: input.key,
110
- lastObserved: input.current,
111
- cycleBytes: base.cycleBytes + delta,
112
- };
113
- }
114
-
115
- /** Convenience constructor for a `BaselineKey` from its parts. */
116
- export function baselineKey(
117
- logicalSlotId: LogicalSlotId,
118
- mappingGeneration: number,
119
- ifname: string,
120
- bootId: string,
121
- ): BaselineKey {
122
- return { logicalSlotId, mappingGeneration, ifname, bootId };
123
- }
@@ -1,62 +0,0 @@
1
- // UTC billing-cycle math — month-length clamping (cycleDay 31 → Feb 28/29) and the
2
- // before/after-boundary cycle-start selection.
3
-
4
- import { describe, expect, test } from 'bun:test';
5
- import { type EpochMillis, epochMillis } from '../../domain';
6
- import { clampCycleDay, cycleStart, daysInMonth } from './billing-cycle';
7
-
8
- const utc = (y: number, m: number, d: number, h = 0): EpochMillis =>
9
- epochMillis(Date.UTC(y, m, d, h));
10
-
11
- describe('daysInMonth', () => {
12
- test('February is 28 days in a common year, 29 in a leap year', () => {
13
- expect(daysInMonth(2023, 1)).toBe(28);
14
- expect(daysInMonth(2024, 1)).toBe(29); // 2024 is a leap year
15
- expect(daysInMonth(2100, 1)).toBe(28); // century non-leap
16
- expect(daysInMonth(2000, 1)).toBe(29); // 400-divisible leap
17
- });
18
-
19
- test('30- and 31-day months', () => {
20
- expect(daysInMonth(2024, 3)).toBe(30); // April
21
- expect(daysInMonth(2024, 0)).toBe(31); // January
22
- });
23
- });
24
-
25
- describe('clampCycleDay', () => {
26
- test('cycleDay 31 clamps to the last day of a short month', () => {
27
- expect(clampCycleDay(31, 2023, 1)).toBe(28); // Feb non-leap
28
- expect(clampCycleDay(31, 2024, 1)).toBe(29); // Feb leap
29
- expect(clampCycleDay(31, 2024, 3)).toBe(30); // April
30
- expect(clampCycleDay(31, 2024, 0)).toBe(31); // January — no clamp
31
- });
32
-
33
- test('a valid in-range day is unchanged; a floor of 1 is enforced', () => {
34
- expect(clampCycleDay(15, 2024, 5)).toBe(15);
35
- expect(clampCycleDay(0, 2024, 5)).toBe(1);
36
- });
37
- });
38
-
39
- describe('cycleStart — month-length clamp with before/after selection', () => {
40
- test('cycleDay 31 in February resolves to the clamped Feb boundary', () => {
41
- // 2023-02-15 with cycleDay 31 → cycle started 2023-01-31 (Feb boundary Feb 28
42
- // is still ahead of the 15th).
43
- expect(cycleStart(epochMillis(utc(2023, 1, 15)), 31)).toBe(utc(2023, 0, 31));
44
- // 2023-03-01 with cycleDay 31 → the active cycle began at clamped Feb 28.
45
- expect(cycleStart(epochMillis(utc(2023, 2, 1)), 31)).toBe(utc(2023, 1, 28));
46
- // Leap year: 2024-03-01 → Feb 29.
47
- expect(cycleStart(epochMillis(utc(2024, 2, 1)), 31)).toBe(utc(2024, 1, 29));
48
- });
49
-
50
- test('now exactly on the boundary starts a new cycle', () => {
51
- expect(cycleStart(epochMillis(utc(2024, 5, 10)), 10)).toBe(utc(2024, 5, 10));
52
- });
53
-
54
- test('now before this month boundary rolls back to the previous month', () => {
55
- // cycleDay 20, now is the 5th → cycle began on the 20th of the prior month.
56
- expect(cycleStart(epochMillis(utc(2024, 6, 5)), 20)).toBe(utc(2024, 5, 20));
57
- });
58
-
59
- test('January rollback wraps to December of the prior year', () => {
60
- expect(cycleStart(epochMillis(utc(2024, 0, 5)), 20)).toBe(utc(2023, 11, 20));
61
- });
62
- });
@@ -1,45 +0,0 @@
1
- // UTC billing-cycle computation with month-length clamping.
2
- //
3
- // A usage cycle resets on a configured day of the month (`cycleDay`, 1–31) in UTC.
4
- // Months are not all the same length, so a `cycleDay` past the end of a short month
5
- // must clamp to that month's LAST day rather than rolling into the next month or
6
- // throwing: `cycleDay: 31` resolves to Feb 28 (or Feb 29 in a leap year), Apr 30,
7
- // etc. All arithmetic is UTC — the device clock's local zone never shifts a cycle.
8
-
9
- import type { EpochMillis } from '../../domain';
10
-
11
- /** Days in a given UTC month. `month` is 0-based (0 = January). Handles leap Feb. */
12
- export function daysInMonth(year: number, month: number): number {
13
- // Day 0 of the next month is the last day of this month; UTC avoids DST drift.
14
- return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
15
- }
16
-
17
- /** Clamp a requested cycle day to the last valid day of the given month. */
18
- export function clampCycleDay(cycleDay: number, year: number, month: number): number {
19
- const floor = Math.max(1, Math.trunc(cycleDay));
20
- return Math.min(floor, daysInMonth(year, month));
21
- }
22
-
23
- /** The UTC epoch-ms of the cycle boundary in a specific month (clamped). */
24
- function boundaryFor(cycleDay: number, year: number, month: number): number {
25
- return Date.UTC(year, month, clampCycleDay(cycleDay, year, month));
26
- }
27
-
28
- /**
29
- * The UTC start (`EpochMillis`) of the cycle that `now` falls in for a given
30
- * `cycleDay`. If `now` is before this month's (clamped) boundary the active cycle
31
- * began on the previous month's boundary; otherwise it began on this month's.
32
- */
33
- export function cycleStart(now: EpochMillis, cycleDay: number): EpochMillis {
34
- const at = new Date(now);
35
- const year = at.getUTCFullYear();
36
- const month = at.getUTCMonth();
37
- const thisBoundary = boundaryFor(cycleDay, year, month);
38
- if (now >= thisBoundary) {
39
- return thisBoundary as EpochMillis;
40
- }
41
- // Roll back one month (December wraps to the prior year), re-clamping there.
42
- const prevMonth = month === 0 ? 11 : month - 1;
43
- const prevYear = month === 0 ? year - 1 : year;
44
- return boundaryFor(cycleDay, prevYear, prevMonth) as EpochMillis;
45
- }
@@ -1,60 +0,0 @@
1
- // Data-usage sampler — internal all-interface sampler over `/proc/net/dev`
2
- // CUMULATIVE counters (A4.3). Public surface consumed by the A6.1 bench CLI `usage`
3
- // command (via `UsageSnapshot`) and the composition root that wires the sampler.
4
-
5
- export {
6
- applySample,
7
- type BaselineKey,
8
- baselineKey,
9
- initialAccount,
10
- type SampleInput,
11
- type SlotAccount,
12
- } from './accounting';
13
- export { clampCycleDay, cycleStart, daysInMonth } from './billing-cycle';
14
- export { readBootId } from './boot-id';
15
- export {
16
- createUsagePolicyFileStore,
17
- isValidCycleDay,
18
- isValidThresholdBytes,
19
- type PersistedUsagePolicy,
20
- type PersistedUsagePolicySlot,
21
- selectUsagePolicy,
22
- USAGE_POLICY_SCHEMA_VERSION,
23
- type UsagePolicyFileStoreOptions,
24
- type UsagePolicyLogEvent,
25
- type UsagePolicyLogger,
26
- type UsagePolicyStore,
27
- } from './policy-store';
28
- export {
29
- getUsagePolicy,
30
- type SetUsagePolicyDeps,
31
- type SetUsagePolicyRejection,
32
- type SetUsagePolicyRequest,
33
- type SetUsagePolicyResult,
34
- setUsagePolicy,
35
- type UsagePolicyApplication,
36
- type UsagePolicyTarget,
37
- } from './policy-write';
38
- export {
39
- type CounterSource,
40
- parseProcNetDev,
41
- procNetDevCounterSource,
42
- } from './proc-net-dev';
43
- export {
44
- createUsageSampler,
45
- type SlotUsageSnapshot,
46
- type UsageObservation,
47
- UsageSampler,
48
- type UsageSamplerOptions,
49
- type UsageSnapshot,
50
- } from './sampler';
51
- export {
52
- createUsageFileStore,
53
- type PersistedSlot,
54
- type PersistedUsage,
55
- USAGE_SCHEMA_VERSION,
56
- type UsageFileStoreOptions,
57
- type UsageLogEvent,
58
- type UsageLogger,
59
- type UsageStore,
60
- } from './store';
@@ -1,164 +0,0 @@
1
- // Usage-policy persistence contract — mode 0600 (real fs.stat), fail-soft
2
- // corruption recovery with METADATA-ONLY logging, and the per-slot selector.
3
-
4
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
5
- import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
6
- import { tmpdir } from 'node:os';
7
- import { join } from 'node:path';
8
- import {
9
- createUsagePolicyFileStore,
10
- isValidCycleDay,
11
- isValidThresholdBytes,
12
- type PersistedUsagePolicy,
13
- selectUsagePolicy,
14
- USAGE_POLICY_SCHEMA_VERSION,
15
- type UsagePolicyLogEvent,
16
- } from './policy-store';
17
-
18
- let dir: string;
19
- let path: string;
20
-
21
- beforeEach(async () => {
22
- dir = await mkdtemp(join(tmpdir(), 'usage-policy-'));
23
- path = join(dir, 'policy.json');
24
- });
25
-
26
- afterEach(async () => {
27
- await rm(dir, { recursive: true, force: true });
28
- });
29
-
30
- const sample: PersistedUsagePolicy = {
31
- schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
32
- savedAtMs: 1_700_000_000_000,
33
- slots: [
34
- { logicalSlotId: 'slot-a', cycleDay: 15, thresholdBytes: 5_000_000_000 },
35
- { logicalSlotId: 'slot-b', cycleDay: 1 },
36
- { logicalSlotId: 'slot-c', thresholdBytes: 0 },
37
- ],
38
- };
39
-
40
- describe('UsagePolicyStore — versioned round-trip', () => {
41
- test('save then load returns identical, schema-versioned state', async () => {
42
- const store = createUsagePolicyFileStore({ path });
43
- await store.save(sample);
44
- expect(await store.load(1)).toEqual(sample);
45
- });
46
-
47
- test('an absent file loads as a fresh empty document and writes nothing', async () => {
48
- const store = createUsagePolicyFileStore({ path });
49
- const loaded = await store.load(4242);
50
- expect(loaded).toEqual({
51
- schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
52
- savedAtMs: 4242,
53
- slots: [],
54
- });
55
- expect(readFile(path, 'utf8')).rejects.toThrow();
56
- });
57
-
58
- test('the written file is mode 0600 regardless of umask', async () => {
59
- const store = createUsagePolicyFileStore({ path });
60
- await store.save(sample);
61
- expect((await stat(path)).mode & 0o777).toBe(0o600);
62
- });
63
- });
64
-
65
- describe('UsagePolicyStore — fail-soft corruption', () => {
66
- test('invalid JSON is replaced by a fresh 0600 file and logged as METADATA ONLY', async () => {
67
- const events: UsagePolicyLogEvent[] = [];
68
- await writeFile(path, '{"schemaVersion":1,"slots":');
69
- const store = createUsagePolicyFileStore({ path, logger: (e) => events.push(e) });
70
-
71
- const loaded = await store.load(7);
72
-
73
- expect(loaded.slots).toEqual([]);
74
- expect(events).toHaveLength(1);
75
- expect(events[0]?.kind).toBe('corrupt-policy');
76
- expect(events[0]?.reason).toContain('invalid-json');
77
- expect((await stat(path)).mode & 0o777).toBe(0o600);
78
- });
79
-
80
- test.each([
81
- [
82
- 'schemaVersion',
83
- JSON.stringify({ ...sample, schemaVersion: 99 }),
84
- 'schema-mismatch: schemaVersion',
85
- ],
86
- ['savedAtMs', JSON.stringify({ ...sample, savedAtMs: 'soon' }), 'schema-mismatch: savedAtMs'],
87
- ['slots', JSON.stringify({ ...sample, slots: {} }), 'schema-mismatch: slots'],
88
- [
89
- 'logicalSlotId',
90
- JSON.stringify({ ...sample, slots: [{ cycleDay: 3 }] }),
91
- 'schema-mismatch: logicalSlotId',
92
- ],
93
- [
94
- 'cycleDay',
95
- JSON.stringify({ ...sample, slots: [{ logicalSlotId: 'a', cycleDay: 32 }] }),
96
- 'schema-mismatch: cycleDay',
97
- ],
98
- [
99
- 'thresholdBytes',
100
- JSON.stringify({ ...sample, slots: [{ logicalSlotId: 'a', thresholdBytes: -1 }] }),
101
- 'schema-mismatch: thresholdBytes',
102
- ],
103
- ])('a bad %s is rejected by name and never throws', async (_field, text, reason) => {
104
- const events: UsagePolicyLogEvent[] = [];
105
- await writeFile(path, text);
106
- const store = createUsagePolicyFileStore({ path, logger: (e) => events.push(e) });
107
-
108
- expect((await store.load(9)).slots).toEqual([]);
109
- expect(events[0]?.reason).toBe(reason);
110
- });
111
-
112
- test('the corruption log carries a byte count and NEVER the file content', async () => {
113
- const secretish = JSON.stringify({ schemaVersion: 1, slots: 'iccid-8991101200003204514' });
114
- const events: UsagePolicyLogEvent[] = [];
115
- await writeFile(path, secretish);
116
- const store = createUsagePolicyFileStore({ path, logger: (e) => events.push(e) });
117
-
118
- await store.load(11);
119
-
120
- expect(events[0]?.bytes).toBe(Buffer.byteLength(secretish, 'utf8'));
121
- expect(JSON.stringify(events[0])).not.toContain('8991101200003204514');
122
- });
123
- });
124
-
125
- describe('selectUsagePolicy', () => {
126
- test('returns the slot policy, omitting fields the slot never set', () => {
127
- expect(selectUsagePolicy(sample, 'slot-a')).toEqual({
128
- cycleDay: 15,
129
- thresholdBytes: 5_000_000_000,
130
- });
131
- expect(selectUsagePolicy(sample, 'slot-b')).toEqual({ cycleDay: 1 });
132
- expect(selectUsagePolicy(sample, 'slot-c')).toEqual({ thresholdBytes: 0 });
133
- });
134
-
135
- test('an unknown slot answers "no policy set", not a default', () => {
136
- expect(selectUsagePolicy(sample, 'nope')).toEqual({});
137
- });
138
- });
139
-
140
- describe('validators', () => {
141
- test.each([
142
- [1, true],
143
- [31, true],
144
- [15, true],
145
- [0, false],
146
- [32, false],
147
- [1.5, false],
148
- [Number.NaN, false],
149
- ['3', false],
150
- ])('isValidCycleDay(%p) === %p', (value, expected) => {
151
- expect(isValidCycleDay(value)).toBe(expected);
152
- });
153
-
154
- test.each([
155
- [0, true],
156
- [5_000_000_000, true],
157
- [-1, false],
158
- [1.5, false],
159
- [Number.POSITIVE_INFINITY, false],
160
- ['5', false],
161
- ])('isValidThresholdBytes(%p) === %p', (value, expected) => {
162
- expect(isValidThresholdBytes(value)).toBe(expected);
163
- });
164
- });