@ceralive/modem-control 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/README.md +341 -0
  2. package/dist/backend/at-lease.d.ts +59 -0
  3. package/dist/backend/at-lease.js +118 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +101 -0
  61. package/dist/backend/transition-preconditions.js +132 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +216 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +20 -0
  146. package/dist/index.js +27 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +61 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operation-ids.d.ts +2 -0
  196. package/dist/operation-ids.js +26 -0
  197. package/dist/operations/contracts.d.ts +72 -0
  198. package/dist/operations/contracts.js +1 -0
  199. package/dist/operations/index.d.ts +1 -0
  200. package/dist/operations/index.js +1 -0
  201. package/dist/operations/operation-engine.d.ts +16 -0
  202. package/dist/operations/operation-engine.js +194 -0
  203. package/dist/ports/index.d.ts +12 -0
  204. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  205. package/dist/ports/location.d.ts +87 -0
  206. package/dist/ports/location.js +36 -0
  207. package/dist/ports/modem-manager.d.ts +89 -0
  208. package/dist/ports/modem-manager.js +9 -0
  209. package/dist/ports/mutation-admission.d.ts +27 -0
  210. package/dist/ports/mutation-admission.js +9 -0
  211. package/dist/ports/network-manager.d.ts +68 -0
  212. package/dist/ports/network-manager.js +13 -0
  213. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  214. package/dist/ports/observation.js +7 -0
  215. package/dist/ports/ops.d.ts +44 -0
  216. package/dist/ports/ops.js +16 -0
  217. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  218. package/dist/ports/receipts.js +9 -0
  219. package/dist/ports/reconcile.d.ts +33 -0
  220. package/dist/ports/reconcile.js +200 -0
  221. package/dist/ports/resource-ownership.d.ts +29 -0
  222. package/dist/ports/resource-ownership.js +1 -0
  223. package/dist/ports/router.d.ts +19 -0
  224. package/dist/ports/router.js +7 -0
  225. package/dist/ports/sms.d.ts +64 -0
  226. package/dist/ports/sms.js +24 -0
  227. package/dist/ports/uhubctl.d.ts +6 -0
  228. package/dist/ports/uhubctl.js +1 -0
  229. package/dist/providers/contracts.d.ts +124 -0
  230. package/dist/providers/contracts.js +10 -0
  231. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  232. package/dist/providers/huawei-hilink/index.js +2 -0
  233. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  234. package/dist/providers/huawei-hilink/operations.js +56 -0
  235. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  236. package/dist/providers/huawei-hilink/provider.js +76 -0
  237. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  238. package/dist/providers/huawei-hilink/runtime.js +171 -0
  239. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  240. package/dist/providers/huawei-hilink/session.js +120 -0
  241. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  242. package/dist/providers/huawei-hilink/transport.js +1 -0
  243. package/dist/providers/index.d.ts +8 -0
  244. package/dist/providers/index.js +8 -0
  245. package/dist/providers/matcher.d.ts +3 -0
  246. package/dist/providers/matcher.js +205 -0
  247. package/dist/providers/modem-manager/errors.d.ts +7 -0
  248. package/dist/providers/modem-manager/errors.js +37 -0
  249. package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
  250. package/dist/providers/modem-manager/generic-operations.js +209 -0
  251. package/dist/providers/modem-manager/index.d.ts +4 -0
  252. package/dist/providers/modem-manager/index.js +4 -0
  253. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  254. package/dist/providers/modem-manager/module-operations.js +118 -0
  255. package/dist/providers/modem-manager/provider.d.ts +41 -0
  256. package/dist/providers/modem-manager/provider.js +155 -0
  257. package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
  258. package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
  259. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  260. package/dist/providers/modem-manager/snapshot.js +204 -0
  261. package/dist/providers/modem-manager/types.d.ts +137 -0
  262. package/dist/providers/modem-manager/types.js +1 -0
  263. package/dist/providers/network-manager/adapter.d.ts +71 -0
  264. package/dist/providers/network-manager/adapter.js +348 -0
  265. package/dist/providers/network-manager/index.d.ts +2 -0
  266. package/dist/providers/network-manager/index.js +2 -0
  267. package/dist/providers/network-manager/types.d.ts +171 -0
  268. package/dist/providers/network-manager/types.js +77 -0
  269. package/dist/providers/registry.d.ts +13 -0
  270. package/dist/providers/registry.js +33 -0
  271. package/dist/providers/ufi-himi/index.d.ts +6 -0
  272. package/dist/providers/ufi-himi/index.js +6 -0
  273. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  274. package/dist/providers/ufi-himi/operations.js +66 -0
  275. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  276. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  277. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  278. package/dist/providers/ufi-himi/provider.js +204 -0
  279. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  280. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  281. package/dist/providers/ufi-himi/session.d.ts +46 -0
  282. package/dist/providers/ufi-himi/session.js +92 -0
  283. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  284. package/dist/providers/ufi-himi/transport.js +25 -0
  285. package/dist/providers/zte-goform/index.d.ts +2 -0
  286. package/dist/providers/zte-goform/index.js +2 -0
  287. package/dist/providers/zte-goform/provider.d.ts +56 -0
  288. package/dist/providers/zte-goform/provider.js +101 -0
  289. package/dist/providers/zte-goform/session.d.ts +23 -0
  290. package/dist/providers/zte-goform/session.js +197 -0
  291. package/dist/providers/zte-goform/transport.d.ts +16 -0
  292. package/dist/providers/zte-goform/transport.js +1 -0
  293. package/dist/radio/band-truth.d.ts +50 -0
  294. package/dist/radio/band-truth.js +92 -0
  295. package/dist/radio/index.d.ts +3 -0
  296. package/dist/radio/index.js +10 -0
  297. package/dist/radio/mode-combinations.d.ts +88 -0
  298. package/dist/radio/mode-combinations.js +198 -0
  299. package/dist/radio/mode-truth.d.ts +67 -0
  300. package/dist/radio/mode-truth.js +112 -0
  301. package/dist/redact.d.ts +15 -0
  302. package/dist/redact.js +189 -0
  303. package/dist/safety/composition-root.d.ts +28 -0
  304. package/dist/safety/composition-root.js +65 -0
  305. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  306. package/dist/safety/flock-resource-ownership.js +138 -0
  307. package/dist/safety/index.d.ts +2 -0
  308. package/dist/safety/index.js +2 -0
  309. package/dist/sms/dbus-messaging.d.ts +17 -0
  310. package/dist/sms/dbus-messaging.js +185 -0
  311. package/dist/sms/inbox-store.d.ts +10 -0
  312. package/dist/sms/inbox-store.js +82 -0
  313. package/dist/sms/index.d.ts +4 -0
  314. package/dist/sms/index.js +10 -0
  315. package/dist/sms/mmcli-parse.d.ts +54 -0
  316. package/dist/sms/mmcli-parse.js +224 -0
  317. package/dist/sms/normalize.d.ts +42 -0
  318. package/dist/sms/normalize.js +95 -0
  319. package/dist/testing/domain-fakes.d.ts +58 -0
  320. package/dist/testing/domain-fakes.js +98 -0
  321. package/dist/testing/index.d.ts +2 -0
  322. package/dist/testing/index.js +17 -0
  323. package/dist/testing/provider-fakes.d.ts +45 -0
  324. package/dist/testing/provider-fakes.js +64 -0
  325. package/dist/transport/calls.d.ts +9 -0
  326. package/dist/transport/calls.js +88 -0
  327. package/dist/transport/codec.d.ts +3 -0
  328. package/dist/transport/codec.js +207 -0
  329. package/dist/transport/dbus-native.d.ts +57 -0
  330. package/dist/transport/dbus-native.js +17 -0
  331. package/dist/transport/errors.d.ts +21 -0
  332. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  333. package/dist/transport/index.d.ts +4 -0
  334. package/dist/transport/index.js +9 -0
  335. package/dist/transport/signals.d.ts +15 -0
  336. package/dist/transport/signals.js +123 -0
  337. package/dist/transport/signature.d.ts +7 -0
  338. package/dist/transport/signature.js +94 -0
  339. package/dist/transport/transport.d.ts +2 -0
  340. package/dist/transport/transport.js +202 -0
  341. package/dist/transport/types.d.ts +61 -0
  342. package/dist/transport/types.js +19 -0
  343. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  344. package/dist/usb-mode/catalog-schema.js +97 -0
  345. package/dist/usb-mode/catalog.d.ts +21 -0
  346. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  347. package/dist/usb-mode/certified-catalog.json +67 -0
  348. package/dist/usb-mode/index.d.ts +6 -0
  349. package/dist/usb-mode/index.js +16 -0
  350. package/dist/usb-mode/ingestion.d.ts +111 -0
  351. package/dist/usb-mode/ingestion.js +187 -0
  352. package/dist/usb-mode/promotion-review.d.ts +21 -0
  353. package/dist/usb-mode/promotion-review.js +87 -0
  354. package/dist/usb-mode/runtime-capability.d.ts +59 -0
  355. package/dist/usb-mode/runtime-capability.js +157 -0
  356. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  357. package/dist/usb-mode/usb-devices-parse.js +157 -0
  358. package/dist/ussd/calls.d.ts +32 -0
  359. package/dist/ussd/calls.js +96 -0
  360. package/dist/ussd/index.d.ts +5 -0
  361. package/dist/ussd/index.js +12 -0
  362. package/dist/ussd/mm-ussd.d.ts +37 -0
  363. package/dist/ussd/mm-ussd.js +205 -0
  364. package/dist/ussd/refusal.d.ts +53 -0
  365. package/dist/ussd/refusal.js +154 -0
  366. package/dist/ussd/registration.d.ts +20 -0
  367. package/dist/ussd/registration.js +101 -0
  368. package/dist/ussd/session.d.ts +106 -0
  369. package/dist/ussd/session.js +163 -0
  370. package/package.json +38 -4
  371. package/src/backend/at-lease.test.ts +0 -106
  372. package/src/backend/at-lease.ts +0 -158
  373. package/src/backend/cell-info.test.ts +0 -154
  374. package/src/backend/cell-info.ts +0 -160
  375. package/src/backend/device-classifier.test.ts +0 -168
  376. package/src/backend/device-classifier.ts +0 -248
  377. package/src/backend/enrichment.ts +0 -96
  378. package/src/backend/features.test.ts +0 -162
  379. package/src/backend/features.ts +0 -179
  380. package/src/backend/identity-ladder.test.ts +0 -117
  381. package/src/backend/identity-ladder.ts +0 -221
  382. package/src/backend/identity-registry.test.ts +0 -89
  383. package/src/backend/identity-registry.ts +0 -151
  384. package/src/backend/index.ts +0 -236
  385. package/src/backend/lifecycle-interlock.ts +0 -38
  386. package/src/backend/managed-objects.ts +0 -108
  387. package/src/backend/mapping.ts +0 -160
  388. package/src/backend/mm-backend.ts +0 -191
  389. package/src/backend/mm-mutations.ts +0 -228
  390. package/src/backend/modem-actor.test.ts +0 -95
  391. package/src/backend/modem-actor.ts +0 -112
  392. package/src/backend/nm-auto-apn.ts +0 -161
  393. package/src/backend/nm-gsm-fields.ts +0 -122
  394. package/src/backend/nmcli-nm-port.ts +0 -228
  395. package/src/backend/nmcli-runner.ts +0 -52
  396. package/src/backend/observer.ts +0 -297
  397. package/src/backend/power-contract.test.ts +0 -40
  398. package/src/backend/power-contract.ts +0 -83
  399. package/src/backend/recovery-attribution.test.ts +0 -102
  400. package/src/backend/recovery-attribution.ts +0 -86
  401. package/src/backend/recovery-budget.test.ts +0 -64
  402. package/src/backend/recovery-budget.ts +0 -84
  403. package/src/backend/recovery-ladder.test.ts +0 -257
  404. package/src/backend/recovery-ladder.ts +0 -249
  405. package/src/backend/router-ethernet.test.ts +0 -71
  406. package/src/backend/router-ethernet.ts +0 -90
  407. package/src/backend/row-store.ts +0 -105
  408. package/src/backend/signal-setup.ts +0 -112
  409. package/src/backend/sim-unlock.ts +0 -193
  410. package/src/backend/transition-preconditions.ts +0 -149
  411. package/src/backend/uhubctl-power-hook.test.ts +0 -274
  412. package/src/backend/uhubctl-power-hook.ts +0 -377
  413. package/src/backend/usage/accounting.test.ts +0 -147
  414. package/src/backend/usage/accounting.ts +0 -123
  415. package/src/backend/usage/billing-cycle.test.ts +0 -62
  416. package/src/backend/usage/billing-cycle.ts +0 -45
  417. package/src/backend/usage/index.ts +0 -60
  418. package/src/backend/usage/policy-store.test.ts +0 -164
  419. package/src/backend/usage/policy-store.ts +0 -216
  420. package/src/backend/usage/policy-write.test.ts +0 -198
  421. package/src/backend/usage/policy-write.ts +0 -207
  422. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  423. package/src/backend/usage/sampler.test.ts +0 -327
  424. package/src/backend/usage/sampler.ts +0 -282
  425. package/src/backend/usage/store.test.ts +0 -148
  426. package/src/backend/usage/store.ts +0 -177
  427. package/src/backend/usb-enumerator.test.ts +0 -87
  428. package/src/backend/usb-enumerator.ts +0 -181
  429. package/src/backend/usb-mode-transition.test.ts +0 -323
  430. package/src/backend/usb-mode-transition.ts +0 -253
  431. package/src/domain/brand.ts +0 -29
  432. package/src/domain/errors.ts +0 -77
  433. package/src/domain/guards.test.ts +0 -218
  434. package/src/domain/guards.ts +0 -144
  435. package/src/domain/identity.test.ts +0 -83
  436. package/src/domain/identity.ts +0 -165
  437. package/src/domain/index.ts +0 -12
  438. package/src/domain/snapshot.test.ts +0 -266
  439. package/src/domain/snapshot.ts +0 -120
  440. package/src/index.test.ts +0 -6
  441. package/src/index.ts +0 -15
  442. package/src/ports/README.md +0 -61
  443. package/src/ports/forbidden-surface.test.ts +0 -241
  444. package/src/ports/modem-manager.ts +0 -72
  445. package/src/ports/network-manager.ts +0 -87
  446. package/src/ports/ops.ts +0 -60
  447. package/src/ports/ops.type-test.ts +0 -39
  448. package/src/ports/receipts.test.ts +0 -153
  449. package/src/ports/reconcile.test.ts +0 -152
  450. package/src/ports/reconcile.ts +0 -338
  451. package/src/ports/router.ts +0 -29
  452. package/src/redact.test.ts +0 -82
  453. package/src/redact.ts +0 -73
  454. package/src/transport/README.md +0 -65
  455. package/src/transport/calls.ts +0 -113
  456. package/src/transport/characterization.test.ts +0 -260
  457. package/src/transport/codec.test.ts +0 -118
  458. package/src/transport/codec.ts +0 -240
  459. package/src/transport/conformance-python.test.ts +0 -152
  460. package/src/transport/conformance-same-lib.test.ts +0 -115
  461. package/src/transport/dbus-native-lib.d.ts +0 -19
  462. package/src/transport/dbus-native.ts +0 -85
  463. package/src/transport/index.ts +0 -30
  464. package/src/transport/no-library-leak.test.ts +0 -61
  465. package/src/transport/reliability.test.ts +0 -173
  466. package/src/transport/signals.ts +0 -150
  467. package/src/transport/signature.ts +0 -110
  468. package/src/transport/test-support/fake-service.ts +0 -168
  469. package/src/transport/test-support/independent-producer.py +0 -110
  470. package/src/transport/test-support/private-bus.ts +0 -66
  471. package/src/transport/transport.ts +0 -250
  472. package/src/transport/types.ts +0 -118
  473. package/src/usb-mode/catalog-schema.test.ts +0 -181
  474. package/src/usb-mode/catalog-schema.ts +0 -113
  475. package/src/usb-mode/certified-catalog.json +0 -67
  476. package/src/usb-mode/index.ts +0 -58
  477. package/src/usb-mode/ingestion.test.ts +0 -268
  478. package/src/usb-mode/ingestion.ts +0 -297
  479. package/src/usb-mode/promotion-review.ts +0 -117
  480. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -1,160 +0,0 @@
1
- // Pure mapping: a decoded ModemManager object tree → a `CellularSnapshot`.
2
- //
3
- // A3.1 is lifecycle-only. This mapper is DELIBERATELY conservative: it captures
4
- // identity, presence, source health, radio power, and MM state — the facts the
5
- // observer needs to order and reconcile modems — but leaves the richer 3GPP
6
- // registration + access-technology set as `unknown`/empty. Faithful registration and
7
- // cell-info normalisation are A3.2 (identity ladder) and A3.3 (Signal.Setup / cell
8
- // info); mapping them here would either be a guess or trip the domain guards. The
9
- // snapshot this produces is always guard-valid by construction.
10
-
11
- import {
12
- type CellularSnapshot,
13
- imeiEquipmentId,
14
- type MmState,
15
- subscriptionId as makeSubscriptionId,
16
- type RadioPower,
17
- runtimePath,
18
- type SubscriptionId,
19
- } from '../domain';
20
- import { MODEM_IFACE, MODEM3GPP_IFACE, SIM_IFACE } from './constants';
21
- import {
22
- type DecodedManagedObjects,
23
- findInterface,
24
- followObjectPath,
25
- numberProp,
26
- pathsWithInterface,
27
- stringProp,
28
- } from './managed-objects';
29
-
30
- /** The dimensions of a mapped modem, WITHOUT a revision (the observer stamps that). */
31
- export type MappedModem = Omit<CellularSnapshot, 'revision'>;
32
-
33
- /** MMModemState (`Modem.State`, signed) → the domain `MmState`. */
34
- function mapMmState(state: number | undefined): MmState {
35
- switch (state) {
36
- case -1:
37
- return 'failed';
38
- case 1:
39
- return 'initializing';
40
- case 2:
41
- return 'locked';
42
- case 3:
43
- return 'disabled';
44
- case 4:
45
- return 'disabling';
46
- case 5:
47
- return 'enabling';
48
- case 6:
49
- return 'enabled';
50
- case 7:
51
- return 'searching';
52
- case 8:
53
- return 'registered';
54
- case 9:
55
- return 'disconnecting';
56
- case 10:
57
- return 'connecting';
58
- case 11:
59
- return 'connected';
60
- default:
61
- return 'unknown';
62
- }
63
- }
64
-
65
- /** MMModemPowerState (`Modem.PowerState`) → the domain `RadioPower`. */
66
- function mapRadioPower(power: number | undefined): RadioPower {
67
- switch (power) {
68
- case 1:
69
- return 'off';
70
- case 2:
71
- return 'low';
72
- case 3:
73
- return 'on';
74
- default:
75
- return 'unknown';
76
- }
77
- }
78
-
79
- /** Locked / failed modems keep the radio off; every other state we cannot see as
80
- * off (the guards forbid an active MM state with the radio off), so an unknown
81
- * power reading is treated as `on` when the modem is clearly on the air. */
82
- function reconcilePower(power: RadioPower, state: MmState): RadioPower {
83
- const onTheAir =
84
- state === 'enabled' ||
85
- state === 'searching' ||
86
- state === 'registered' ||
87
- state === 'connecting' ||
88
- state === 'connected' ||
89
- state === 'disconnecting';
90
- if (onTheAir && (power === 'unknown' || power === 'off')) {
91
- return 'on';
92
- }
93
- return power;
94
- }
95
-
96
- /** Read a modem's subscription id (ICCID) from its active SIM object, if any. */
97
- function readSubscriptionId(
98
- tree: DecodedManagedObjects,
99
- modemPath: string,
100
- ): SubscriptionId | undefined {
101
- const modem = findInterface(tree, modemPath, MODEM_IFACE);
102
- const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
103
- const iccid = stringProp(sim, 'SimIdentifier');
104
- return iccid !== undefined && iccid.length > 0 ? makeSubscriptionId(iccid) : undefined;
105
- }
106
-
107
- /** Map ONE modem object to its dimensions. The modem must expose `Modem`. */
108
- export function mapModem(tree: DecodedManagedObjects, modemPath: string): MappedModem {
109
- const modem = findInterface(tree, modemPath, MODEM_IFACE);
110
- const modem3gpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
111
-
112
- const equipment = stringProp(modem, 'EquipmentIdentifier') ?? stringProp(modem3gpp, 'Imei') ?? '';
113
- const mmState = mapMmState(numberProp(modem, 'State'));
114
- const radioPower = reconcilePower(mapRadioPower(numberProp(modem, 'PowerState')), mmState);
115
- const sub = readSubscriptionId(tree, modemPath);
116
-
117
- return {
118
- identity: {
119
- equipmentId: imeiEquipmentId(equipment),
120
- runtimePath: runtimePath(modemPath),
121
- ...(sub !== undefined ? { subscriptionId: sub } : {}),
122
- },
123
- presence: 'present',
124
- sourceHealth: 'live',
125
- simSlots: [],
126
- radioPower,
127
- mmState,
128
- // Conservative: 3GPP registration + RAT set are A3.3's job (see file header).
129
- registration: { status: 'unknown', activeRats: new Set() },
130
- nmActivation: 'unavailable',
131
- dataInterface: { present: false },
132
- reconcileStatus: 'pending',
133
- recoveryState: { stage: 'idle', attempts: 0 },
134
- };
135
- }
136
-
137
- /** Every modem path in the tree (objects exposing the `Modem` interface), in order. */
138
- export function modemPaths(tree: DecodedManagedObjects): string[] {
139
- return pathsWithInterface(tree, MODEM_IFACE);
140
- }
141
-
142
- /** A stable fingerprint of a mapped modem's meaningful dimensions (ignores revision
143
- * and source health), used to suppress redundant revision bumps on identical reads. */
144
- export function fingerprint(mapped: MappedModem): string {
145
- return JSON.stringify({
146
- id: mapped.identity.equipmentId,
147
- sub: mapped.identity.subscriptionId ?? null,
148
- path: mapped.identity.runtimePath,
149
- presence: mapped.presence,
150
- radioPower: mapped.radioPower,
151
- mmState: mapped.mmState,
152
- registration: {
153
- status: mapped.registration.status,
154
- rats: [...mapped.registration.activeRats].sort(),
155
- },
156
- nmActivation: mapped.nmActivation,
157
- dataInterface: mapped.dataInterface,
158
- reconcileStatus: mapped.reconcileStatus,
159
- });
160
- }
@@ -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
- });