@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
@@ -1,191 +0,0 @@
1
- // MmDbusBackend — the real D-Bus `ModemManagerPort`, composed from the A3 parts.
2
- //
3
- // It fulfils the whole port: the read side delegates to A3.1's epoch-scoped observer;
4
- // the mutations run through `MmMutations` (each serialized on the shared per-modem
5
- // `ModemActor`, keyed on A3.2's STABLE key so serialization survives replug); the
6
- // Signal.Setup lifecycle is driven off the observer's `onEpochRefresh` hook; and the
7
- // read-only enrichment (firmware revision, eSIM, signal cadence, normalized cell info)
8
- // is assembled on demand. One transport is shared by all of them.
9
- //
10
- // The `onEpochRefresh` hook is the single integration seam: on every current-epoch
11
- // authoritative snapshot it (1) rebuilds the live path → stable-key map (so a mutation
12
- // keys the actor by the durable identity, not the transient path) and (2) re-drives
13
- // Signal.Setup for that epoch — applying to new modems, re-applying to survivors after
14
- // an owner change, and never firing for a superseded epoch.
15
-
16
- import type { DesiredRadio } from '../domain';
17
- import { epochMillis } from '../domain';
18
- import type {
19
- InhibitLease,
20
- ModemManagerPort,
21
- ModemRef,
22
- NetworkScanResult,
23
- ObservationList,
24
- ObservationListener,
25
- Receipt,
26
- SimPukUnlockResult,
27
- SimUnlockResult,
28
- Unsubscribe,
29
- } from '../ports';
30
- import type { DbusTransport } from '../transport';
31
- import { type CellReading, normalizeCellInfo } from './cell-info';
32
- import { MM_BUS_NAME, MODEM_IFACE } from './constants';
33
- import { buildEnrichment, type ModemEnrichment } from './enrichment';
34
- import { modemIdentityFactsFromTree, resolveModemIdentities } from './identity-ladder';
35
- import { type DecodedProps, fetchManagedObjects, pathsWithInterface } from './managed-objects';
36
- import { MmMutations } from './mm-mutations';
37
- import { ModemActor, type QuiesceHook } from './modem-actor';
38
- import { createMmDbusObserver, type EpochRefreshEvent, type MmDbusObserver } from './observer';
39
- import { type SignalCadence, SignalSetupManager } from './signal-setup';
40
-
41
- export interface MmDbusBackendOptions {
42
- readonly transport: DbusTransport;
43
- readonly destination?: string;
44
- /** NM quiesce hook for disruptive mode/slot changes (A3.3 default: no-op). */
45
- readonly quiesce?: QuiesceHook;
46
- /** Signal.Setup reporting interval in seconds. */
47
- readonly signalIntervalSeconds?: number;
48
- readonly scanTimeoutMs?: number;
49
- readonly now?: () => number;
50
- }
51
-
52
- /** The real D-Bus ModemManager backend: observation + mutations + Signal.Setup. */
53
- export class MmDbusBackend implements ModemManagerPort {
54
- readonly #transport: DbusTransport;
55
- readonly #destination: string;
56
- readonly #now: () => number;
57
- readonly #observer: MmDbusObserver;
58
- readonly #mutations: MmMutations;
59
- readonly #signalSetup: SignalSetupManager;
60
- // Live modem path → durable stable key; rebuilt on every epoch snapshot.
61
- readonly #stableKeyByPath = new Map<string, string>();
62
-
63
- constructor(options: MmDbusBackendOptions) {
64
- this.#transport = options.transport;
65
- this.#destination = options.destination ?? MM_BUS_NAME;
66
- this.#now = options.now ?? Date.now;
67
- const actor = new ModemActor(options.quiesce);
68
- this.#signalSetup = new SignalSetupManager({
69
- transport: this.#transport,
70
- destination: this.#destination,
71
- ...(options.signalIntervalSeconds !== undefined
72
- ? { intervalSeconds: options.signalIntervalSeconds }
73
- : {}),
74
- });
75
- this.#observer = createMmDbusObserver({
76
- transport: this.#transport,
77
- destination: this.#destination,
78
- onEpochRefresh: (event) => this.#onEpochRefresh(event),
79
- });
80
- this.#mutations = new MmMutations({
81
- transport: this.#transport,
82
- actor,
83
- destination: this.#destination,
84
- resolveStableKey: (modem) => this.#stableKeyByPath.get(modem) ?? modem,
85
- ...(options.scanTimeoutMs !== undefined ? { scanTimeoutMs: options.scanTimeoutMs } : {}),
86
- now: this.#now,
87
- });
88
- }
89
-
90
- // ── observation (delegated to the A3.1 observer) ────────────────────────────────
91
-
92
- start(): Promise<ObservationList> {
93
- return this.#observer.start();
94
- }
95
-
96
- observe(listener: ObservationListener): Unsubscribe {
97
- return this.#observer.observe(listener);
98
- }
99
-
100
- stop(): Promise<void> {
101
- return this.#observer.stop();
102
- }
103
-
104
- // ── mutations (serialized per modem through the shared actor) ────────────────────
105
-
106
- setRadioModes(modem: ModemRef, preference: DesiredRadio): Promise<Receipt> {
107
- return this.#mutations.setRadioModes(modem, preference);
108
- }
109
-
110
- setPrimarySimSlot(modem: ModemRef, slotIndex: number): Promise<Receipt> {
111
- return this.#mutations.setPrimarySimSlot(modem, slotIndex);
112
- }
113
-
114
- sendPin(modem: ModemRef, pin: string): Promise<SimUnlockResult> {
115
- return this.#mutations.sendPin(modem, pin);
116
- }
117
-
118
- sendPuk(modem: ModemRef, puk: string, newPin: string): Promise<SimPukUnlockResult> {
119
- return this.#mutations.sendPuk(modem, puk, newPin);
120
- }
121
-
122
- scanNetworks(modem: ModemRef): Promise<NetworkScanResult> {
123
- return this.#mutations.scanNetworks(modem);
124
- }
125
-
126
- inhibit(uid: string): Promise<InhibitLease> {
127
- return this.#mutations.inhibit(uid);
128
- }
129
-
130
- uninhibit(lease: InhibitLease): Promise<void> {
131
- return this.#mutations.uninhibit(lease);
132
- }
133
-
134
- // ── enrichment (additive, read-only, never gating) ──────────────────────────────
135
-
136
- /** Whether periodic signal reporting is configured for a modem (Signal.Setup). */
137
- signalCadence(modem: ModemRef): SignalCadence {
138
- return this.#signalSetup.cadenceFor(modem);
139
- }
140
-
141
- /** Read the normalized visible-cell list for a modem via `Modem.GetCellInfo`. */
142
- async readCellInfo(modem: ModemRef): Promise<readonly CellReading[]> {
143
- const provenance = { source: modem, observedAt: epochMillis(this.#now()) };
144
- try {
145
- const reply = await this.#transport.callMethod({
146
- destination: this.#destination,
147
- path: modem,
148
- interface: MODEM_IFACE,
149
- member: 'GetCellInfo',
150
- });
151
- const cells = Array.isArray(reply.body[0])
152
- ? (reply.body[0] as unknown as DecodedProps[])
153
- : [];
154
- return normalizeCellInfo(cells, provenance);
155
- } catch {
156
- return [];
157
- }
158
- }
159
-
160
- /** Assemble the full read-only enrichment (revision, eSIM, cadence, cell info). */
161
- async readEnrichment(modem: ModemRef): Promise<ModemEnrichment> {
162
- const [tree, cellInfo] = await Promise.all([
163
- fetchManagedObjects(this.#transport, this.#destination),
164
- this.readCellInfo(modem),
165
- ]);
166
- return buildEnrichment(tree, modem, this.#signalSetup.cadenceFor(modem), cellInfo);
167
- }
168
-
169
- #onEpochRefresh(event: EpochRefreshEvent): void {
170
- this.#refreshStableKeys(event);
171
- this.#signalSetup.applyForEpoch(event.epoch, event.tree);
172
- }
173
-
174
- #refreshStableKeys(event: EpochRefreshEvent): void {
175
- const paths = pathsWithInterface(event.tree, MODEM_IFACE);
176
- const facts = paths.map((path) => modemIdentityFactsFromTree(event.tree, path));
177
- const resolved = resolveModemIdentities(facts);
178
- this.#stableKeyByPath.clear();
179
- paths.forEach((path, index) => {
180
- const entry = resolved[index];
181
- if (entry !== undefined) {
182
- this.#stableKeyByPath.set(path, entry.stableKey);
183
- }
184
- });
185
- }
186
- }
187
-
188
- /** Construct the real D-Bus ModemManager backend over an A2.4 transport. */
189
- export function createMmDbusBackend(options: MmDbusBackendOptions): MmDbusBackend {
190
- return new MmDbusBackend(options);
191
- }
@@ -1,228 +0,0 @@
1
- // The ModemManager mutations — the disruptive-and-SIM half of `ModemManagerPort`.
2
- //
3
- // Every disruptive op runs through the shared per-modem `ModemActor` (serialized on
4
- // the STABLE key, so two ops on one modem never interleave and a replug keeps the
5
- // same queue). Mode and slot changes additionally run QUIESCED (NM briefly stands
6
- // down first, via the actor's quiesce hook). PIN / PUK / scan serialize too but do
7
- // NOT quiesce — they don't touch the bearer. NONE of these methods can reach a bearer
8
- // or connect verb: the port has none, and the fake's tripwire proves it at test time.
9
-
10
- import type { DesiredRadio, RadioAccessTechnology } from '../domain';
11
- import { epochMillis } from '../domain';
12
- import type {
13
- InhibitLease,
14
- ModemRef,
15
- NetworkScanResult,
16
- Receipt,
17
- ScannedNetwork,
18
- SimPukUnlockResult,
19
- SimUnlockResult,
20
- } from '../ports';
21
- import { receipt } from '../ports';
22
- import type { DbusTransport } from '../transport';
23
- import {
24
- MM_BUS_NAME,
25
- MM_MANAGER_IFACE,
26
- MM_ROOT_PATH,
27
- MODEM_IFACE,
28
- MODEM3GPP_IFACE,
29
- } from './constants';
30
- import {
31
- type DecodedProps,
32
- fetchManagedObjects,
33
- findInterface,
34
- numberProp,
35
- propValue,
36
- stringProp,
37
- } from './managed-objects';
38
- import type { ModemActor } from './modem-actor';
39
- import { sendSimPin, sendSimPuk } from './sim-unlock';
40
-
41
- /** MMModemMode bit per RAT family (2G/3G/4G/5G). */
42
- const MODE_BIT: Record<RadioAccessTechnology, number> = { gsm: 2, umts: 4, lte: 8, '5gnr': 16 };
43
-
44
- /** MMModem3gppNetworkAvailability → the port's availability. */
45
- const AVAILABILITY: Record<number, ScannedNetwork['availability']> = {
46
- 0: 'unknown',
47
- 1: 'available',
48
- 2: 'current',
49
- 3: 'forbidden',
50
- };
51
-
52
- /** A network scan can take a long time — MM's own default is not enough. */
53
- const DEFAULT_SCAN_TIMEOUT_MS = 300_000;
54
-
55
- export interface MmMutationsDeps {
56
- readonly transport: DbusTransport;
57
- readonly actor: ModemActor;
58
- readonly destination?: string;
59
- /** Map a live modem path to its stable actor key (survives replug). */
60
- readonly resolveStableKey: (modem: ModemRef) => string;
61
- readonly scanTimeoutMs?: number;
62
- readonly now?: () => number;
63
- }
64
-
65
- /** The disruptive + SIM mutations of `ModemManagerPort`, serialized per modem. */
66
- export class MmMutations {
67
- readonly #transport: DbusTransport;
68
- readonly #actor: ModemActor;
69
- readonly #destination: string;
70
- readonly #resolveStableKey: (modem: ModemRef) => string;
71
- readonly #scanTimeoutMs: number;
72
- readonly #now: () => number;
73
-
74
- constructor(deps: MmMutationsDeps) {
75
- this.#transport = deps.transport;
76
- this.#actor = deps.actor;
77
- this.#destination = deps.destination ?? MM_BUS_NAME;
78
- this.#resolveStableKey = deps.resolveStableKey;
79
- this.#scanTimeoutMs = deps.scanTimeoutMs ?? DEFAULT_SCAN_TIMEOUT_MS;
80
- this.#now = deps.now ?? Date.now;
81
- }
82
-
83
- setRadioModes(modem: ModemRef, preference: DesiredRadio): Promise<Receipt> {
84
- const allowed = maskOf(preference.allowedSet ?? new Set(preference.preferenceOrdered));
85
- const preferred = preference.preferenceOrdered[0];
86
- const preferredMask = preferred !== undefined ? MODE_BIT[preferred] : 0;
87
- if (allowed === 0) {
88
- return Promise.resolve(receipt('radio', 'failed', 'no radio modes were requested'));
89
- }
90
- return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
91
- try {
92
- await this.#transport.callMethod({
93
- destination: this.#destination,
94
- path: modem,
95
- interface: MODEM_IFACE,
96
- member: 'SetCurrentModes',
97
- signature: '(uu)',
98
- args: [[allowed, preferredMask]],
99
- });
100
- return receipt('radio', 'applied', 'radio mode preference applied');
101
- } catch (error) {
102
- return receipt('radio', 'failed', `SetCurrentModes failed: ${describe(error)}`);
103
- }
104
- });
105
- }
106
-
107
- async setPrimarySimSlot(modem: ModemRef, slotIndex: number): Promise<Receipt> {
108
- const slots = await this.#readSlotCount(modem);
109
- if (slots === undefined) {
110
- return receipt('simSlot', 'failed', 'could not read the modem SIM-slot list');
111
- }
112
- if (slots <= 1) {
113
- return receipt('simSlot', 'unsupported', 'single-slot modem has no primary slot to select');
114
- }
115
- if (slotIndex < 1 || slotIndex > slots) {
116
- return receipt('simSlot', 'failed', `slot ${slotIndex} is out of range (1..${slots})`);
117
- }
118
- return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
119
- try {
120
- await this.#transport.callMethod({
121
- destination: this.#destination,
122
- path: modem,
123
- interface: MODEM_IFACE,
124
- member: 'SetPrimarySimSlot',
125
- signature: 'u',
126
- args: [slotIndex],
127
- });
128
- return receipt('simSlot', 'applied', `primary SIM slot set to ${slotIndex}`);
129
- } catch (error) {
130
- return receipt('simSlot', 'failed', `SetPrimarySimSlot failed: ${describe(error)}`);
131
- }
132
- });
133
- }
134
-
135
- sendPin(modem: ModemRef, pin: string): Promise<SimUnlockResult> {
136
- return this.#actor.run(this.#resolveStableKey(modem), () =>
137
- sendSimPin(this.#transport, this.#destination, modem, pin),
138
- );
139
- }
140
-
141
- sendPuk(modem: ModemRef, puk: string, newPin: string): Promise<SimPukUnlockResult> {
142
- return this.#actor.run(this.#resolveStableKey(modem), () =>
143
- sendSimPuk(this.#transport, this.#destination, modem, puk, newPin),
144
- );
145
- }
146
-
147
- scanNetworks(modem: ModemRef): Promise<NetworkScanResult> {
148
- return this.#actor.run(this.#resolveStableKey(modem), async () => {
149
- try {
150
- const reply = await this.#transport.callMethod({
151
- destination: this.#destination,
152
- path: modem,
153
- interface: MODEM3GPP_IFACE,
154
- member: 'Scan',
155
- timeoutMs: this.#scanTimeoutMs,
156
- });
157
- return { ok: true, networks: parseScan(reply.body[0]) };
158
- } catch (error) {
159
- return { ok: false, reason: `network scan failed: ${describe(error)}` };
160
- }
161
- });
162
- }
163
-
164
- async inhibit(uid: string): Promise<InhibitLease> {
165
- await this.#inhibitDevice(uid, true);
166
- return { uid, acquiredAt: epochMillis(this.#now()) };
167
- }
168
-
169
- async uninhibit(lease: InhibitLease): Promise<void> {
170
- await this.#inhibitDevice(lease.uid, false);
171
- }
172
-
173
- #inhibitDevice(uid: string, inhibit: boolean): Promise<unknown> {
174
- return this.#transport.callMethod({
175
- destination: this.#destination,
176
- path: MM_ROOT_PATH,
177
- interface: MM_MANAGER_IFACE,
178
- member: 'InhibitDevice',
179
- signature: 'sb',
180
- args: [uid, inhibit],
181
- });
182
- }
183
-
184
- async #readSlotCount(modem: ModemRef): Promise<number | undefined> {
185
- try {
186
- const tree = await fetchManagedObjects(this.#transport, this.#destination);
187
- const slots = propValue(findInterface(tree, modem, MODEM_IFACE), 'SimSlots');
188
- return Array.isArray(slots) ? slots.length : undefined;
189
- } catch {
190
- return undefined;
191
- }
192
- }
193
- }
194
-
195
- /** OR of the MMModemMode bits for a set of RATs. */
196
- function maskOf(rats: ReadonlySet<RadioAccessTechnology>): number {
197
- let mask = 0;
198
- for (const rat of rats) {
199
- mask |= MODE_BIT[rat];
200
- }
201
- return mask;
202
- }
203
-
204
- /** Parse a `Modem3gpp.Scan` reply (`aa{sv}` → dicts) into scanned networks. */
205
- function parseScan(value: unknown): readonly ScannedNetwork[] {
206
- if (!Array.isArray(value)) {
207
- return [];
208
- }
209
- const networks: ScannedNetwork[] = [];
210
- for (const entry of value as DecodedProps[]) {
211
- const operatorCode = stringProp(entry, 'operator-code');
212
- if (operatorCode === undefined) {
213
- continue;
214
- }
215
- const name = stringProp(entry, 'operator-long') ?? stringProp(entry, 'operator-short');
216
- const availability = AVAILABILITY[numberProp(entry, 'status') ?? 0] ?? 'unknown';
217
- networks.push({
218
- operatorCode,
219
- ...(name !== undefined ? { operatorName: name } : {}),
220
- availability,
221
- });
222
- }
223
- return networks;
224
- }
225
-
226
- function describe(error: unknown): string {
227
- return error instanceof Error ? error.message : String(error);
228
- }
@@ -1,95 +0,0 @@
1
- // The shared per-modem disruptive actor — pure, deterministic serialization proof.
2
- //
3
- // Same stable key ⇒ strict serialization (no interleave); different keys ⇒
4
- // independent (overlap); a rejected task never stalls the queue; and a quiesce lease
5
- // is always acquired around and released after a runQuiesced task, even on throw.
6
-
7
- import { describe, expect, test } from 'bun:test';
8
- import {
9
- ModemActor,
10
- type QuiesceHook,
11
- type QuiesceLeaseHandle,
12
- type QuiesceTarget,
13
- } from './modem-actor';
14
-
15
- const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
16
-
17
- const worker = (log: string[], name: string) => async (): Promise<void> => {
18
- log.push(`${name}:start`);
19
- await sleep(15);
20
- log.push(`${name}:end`);
21
- };
22
-
23
- describe('ModemActor — per-key serialization', () => {
24
- test('two tasks on the SAME key run strictly in order, never interleaved', async () => {
25
- const actor = new ModemActor();
26
- const log: string[] = [];
27
- await Promise.all([
28
- actor.run('slot:a', worker(log, 'a')),
29
- actor.run('slot:a', worker(log, 'b')),
30
- ]);
31
- expect(log).toEqual(['a:start', 'a:end', 'b:start', 'b:end']);
32
- });
33
-
34
- test('tasks on DIFFERENT keys run independently (they overlap)', async () => {
35
- const actor = new ModemActor();
36
- const log: string[] = [];
37
- await Promise.all([
38
- actor.run('slot:a', worker(log, 'a')),
39
- actor.run('slot:b', worker(log, 'b')),
40
- ]);
41
- const lastStart = Math.max(log.indexOf('a:start'), log.indexOf('b:start'));
42
- const firstEnd = Math.min(log.indexOf('a:end'), log.indexOf('b:end'));
43
- // Both started before either finished → the two keys did not serialize.
44
- expect(lastStart).toBeLessThan(firstEnd);
45
- });
46
-
47
- test('a rejected task does not stall the queue', async () => {
48
- const actor = new ModemActor();
49
- await expect(actor.run('slot:a', () => Promise.reject(new Error('boom')))).rejects.toThrow(
50
- 'boom',
51
- );
52
- await expect(actor.run('slot:a', () => Promise.resolve('ok'))).resolves.toBe('ok');
53
- });
54
-
55
- test('an idle key drops its queue entry (bounded map)', async () => {
56
- const actor = new ModemActor();
57
- await actor.run('slot:a', () => Promise.resolve(1));
58
- await sleep(0);
59
- expect(actor.activeKeyCount).toBe(0);
60
- });
61
- });
62
-
63
- describe('ModemActor — quiesce lease', () => {
64
- function recordingHook(events: string[]): QuiesceHook {
65
- return {
66
- acquire(target: QuiesceTarget): Promise<QuiesceLeaseHandle> {
67
- events.push(`acquire:${target.stableKey}`);
68
- return Promise.resolve({
69
- release(): Promise<void> {
70
- events.push('release');
71
- return Promise.resolve();
72
- },
73
- });
74
- },
75
- };
76
- }
77
-
78
- test('runQuiesced acquires before, releases after the task', async () => {
79
- const events: string[] = [];
80
- const actor = new ModemActor(recordingHook(events));
81
- await actor.runQuiesced({ stableKey: 'slot:a' }, async () => {
82
- events.push('task');
83
- });
84
- expect(events).toEqual(['acquire:slot:a', 'task', 'release']);
85
- });
86
-
87
- test('the lease is released even when the task throws', async () => {
88
- const events: string[] = [];
89
- const actor = new ModemActor(recordingHook(events));
90
- await expect(
91
- actor.runQuiesced({ stableKey: 'slot:a' }, () => Promise.reject(new Error('x'))),
92
- ).rejects.toThrow('x');
93
- expect(events).toEqual(['acquire:slot:a', 'release']);
94
- });
95
- });
@@ -1,112 +0,0 @@
1
- // The shared per-modem disruptive-operation actor.
2
- //
3
- // EVERY disruptive modem operation funnels through ONE serialization queue PER
4
- // MODEM: radio-mode changes and primary-slot changes (A3.3), the recovery ladder
5
- // (A3.4), and USB-mode / AT transitions (A4.2) all route through the SAME actor so
6
- // two disruptive ops on ONE modem can never interleave. Ops on DIFFERENT modems run
7
- // independently — each modem owns its own queue.
8
- //
9
- // The queue is keyed on the STABLE identity key (A3.2's `stableKey`), NOT the
10
- // transient D-Bus object path. A modem that unplugs and replugs into the same slot
11
- // resolves to the same stable key, so serialization survives the replug (the path
12
- // changed, the queue did not).
13
- //
14
- // Mode and slot changes additionally coordinate with NetworkManager through the
15
- // `QuiesceHook` — before a disruptive change NM briefly deactivates the connection so
16
- // it cannot race MM's own re-activation, then reactivates it on release. In A3.3 the
17
- // hook is a NO-OP stub with the exact lease shape A4.1's real nmcli adapter will fill
18
- // in (`NetworkManagerPort.acquireQuiesceLease`).
19
-
20
- import type { ConnectionId, DeviceIfname } from '../ports';
21
-
22
- /** What a quiesce lease is taken over — the modem, and (once A4.1 knows them) its
23
- * NM connection + device. In A3.3 only `stableKey` is populated. */
24
- export interface QuiesceTarget {
25
- readonly stableKey: string;
26
- readonly connectionId?: ConnectionId;
27
- readonly deviceIfname?: DeviceIfname;
28
- }
29
-
30
- /** A held quiesce lease — released (reactivating the connection) when the op ends. */
31
- export interface QuiesceLeaseHandle {
32
- release(): Promise<void>;
33
- }
34
-
35
- /**
36
- * The NM-quiesce coordination hook the actor calls around a disruptive mode/slot
37
- * change. A4.1 wires the real `nmcli` lease (verify-active → `device disconnect` →
38
- * hold → reactivate); A3.3 ships `NO_OP_QUIESCE`, whose call site + shape are the
39
- * exact seam A4.1 fills in.
40
- */
41
- export interface QuiesceHook {
42
- acquire(target: QuiesceTarget): Promise<QuiesceLeaseHandle>;
43
- }
44
-
45
- const NO_OP_LEASE: QuiesceLeaseHandle = {
46
- release(): Promise<void> {
47
- return Promise.resolve();
48
- },
49
- };
50
-
51
- /** The A3.3 default quiesce hook — acquires nothing, releases nothing. */
52
- export const NO_OP_QUIESCE: QuiesceHook = {
53
- acquire(): Promise<QuiesceLeaseHandle> {
54
- return Promise.resolve(NO_OP_LEASE);
55
- },
56
- };
57
-
58
- /**
59
- * A per-modem serialized actor. `run` serializes a task behind every prior task for
60
- * the same stable key; `runQuiesced` additionally holds an NM quiesce lease for the
61
- * task's duration. Different stable keys never block one another.
62
- */
63
- export class ModemActor {
64
- readonly #quiesce: QuiesceHook;
65
- // Tail of each modem's queue — a promise that settles when the last-enqueued task
66
- // for that key finishes (errors swallowed so the chain never breaks).
67
- readonly #tails = new Map<string, Promise<void>>();
68
-
69
- constructor(quiesce: QuiesceHook = NO_OP_QUIESCE) {
70
- this.#quiesce = quiesce;
71
- }
72
-
73
- /** Run `task` serialized behind every prior task for `stableKey`. */
74
- run<T>(stableKey: string, task: () => Promise<T>): Promise<T> {
75
- const prior = this.#tails.get(stableKey) ?? Promise.resolve();
76
- // `then(task, task)` runs `task` whether the prior task resolved OR rejected,
77
- // so one failure never stalls the queue.
78
- const result = prior.then(task, task);
79
- const settled = result.then(
80
- () => undefined,
81
- () => undefined,
82
- );
83
- this.#tails.set(stableKey, settled);
84
- // Drop the entry once this task was the last one, to bound the map.
85
- void settled.then(() => {
86
- if (this.#tails.get(stableKey) === settled) {
87
- this.#tails.delete(stableKey);
88
- }
89
- });
90
- return result;
91
- }
92
-
93
- /**
94
- * Run `task` serialized for `target.stableKey` with an NM quiesce lease held for
95
- * its whole duration — the lease is always released, even if `task` throws.
96
- */
97
- runQuiesced<T>(target: QuiesceTarget, task: () => Promise<T>): Promise<T> {
98
- return this.run(target.stableKey, async () => {
99
- const lease = await this.#quiesce.acquire(target);
100
- try {
101
- return await task();
102
- } finally {
103
- await lease.release();
104
- }
105
- });
106
- }
107
-
108
- /** Number of modems with a live queue — exposed for leak / idle assertions. */
109
- get activeKeyCount(): number {
110
- return this.#tails.size;
111
- }
112
- }