@ceralive/modem-control 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/README.md +341 -0
  2. package/dist/backend/at-lease.d.ts +59 -0
  3. package/dist/backend/at-lease.js +118 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +101 -0
  61. package/dist/backend/transition-preconditions.js +132 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +216 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +20 -0
  146. package/dist/index.js +27 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +61 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operation-ids.d.ts +2 -0
  196. package/dist/operation-ids.js +26 -0
  197. package/dist/operations/contracts.d.ts +72 -0
  198. package/dist/operations/contracts.js +1 -0
  199. package/dist/operations/index.d.ts +1 -0
  200. package/dist/operations/index.js +1 -0
  201. package/dist/operations/operation-engine.d.ts +16 -0
  202. package/dist/operations/operation-engine.js +194 -0
  203. package/dist/ports/index.d.ts +12 -0
  204. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  205. package/dist/ports/location.d.ts +87 -0
  206. package/dist/ports/location.js +36 -0
  207. package/dist/ports/modem-manager.d.ts +89 -0
  208. package/dist/ports/modem-manager.js +9 -0
  209. package/dist/ports/mutation-admission.d.ts +27 -0
  210. package/dist/ports/mutation-admission.js +9 -0
  211. package/dist/ports/network-manager.d.ts +68 -0
  212. package/dist/ports/network-manager.js +13 -0
  213. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  214. package/dist/ports/observation.js +7 -0
  215. package/dist/ports/ops.d.ts +44 -0
  216. package/dist/ports/ops.js +16 -0
  217. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  218. package/dist/ports/receipts.js +9 -0
  219. package/dist/ports/reconcile.d.ts +33 -0
  220. package/dist/ports/reconcile.js +200 -0
  221. package/dist/ports/resource-ownership.d.ts +29 -0
  222. package/dist/ports/resource-ownership.js +1 -0
  223. package/dist/ports/router.d.ts +19 -0
  224. package/dist/ports/router.js +7 -0
  225. package/dist/ports/sms.d.ts +64 -0
  226. package/dist/ports/sms.js +24 -0
  227. package/dist/ports/uhubctl.d.ts +6 -0
  228. package/dist/ports/uhubctl.js +1 -0
  229. package/dist/providers/contracts.d.ts +124 -0
  230. package/dist/providers/contracts.js +10 -0
  231. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  232. package/dist/providers/huawei-hilink/index.js +2 -0
  233. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  234. package/dist/providers/huawei-hilink/operations.js +56 -0
  235. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  236. package/dist/providers/huawei-hilink/provider.js +76 -0
  237. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  238. package/dist/providers/huawei-hilink/runtime.js +171 -0
  239. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  240. package/dist/providers/huawei-hilink/session.js +120 -0
  241. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  242. package/dist/providers/huawei-hilink/transport.js +1 -0
  243. package/dist/providers/index.d.ts +8 -0
  244. package/dist/providers/index.js +8 -0
  245. package/dist/providers/matcher.d.ts +3 -0
  246. package/dist/providers/matcher.js +205 -0
  247. package/dist/providers/modem-manager/errors.d.ts +7 -0
  248. package/dist/providers/modem-manager/errors.js +37 -0
  249. package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
  250. package/dist/providers/modem-manager/generic-operations.js +209 -0
  251. package/dist/providers/modem-manager/index.d.ts +4 -0
  252. package/dist/providers/modem-manager/index.js +4 -0
  253. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  254. package/dist/providers/modem-manager/module-operations.js +118 -0
  255. package/dist/providers/modem-manager/provider.d.ts +41 -0
  256. package/dist/providers/modem-manager/provider.js +155 -0
  257. package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
  258. package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
  259. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  260. package/dist/providers/modem-manager/snapshot.js +204 -0
  261. package/dist/providers/modem-manager/types.d.ts +137 -0
  262. package/dist/providers/modem-manager/types.js +1 -0
  263. package/dist/providers/network-manager/adapter.d.ts +71 -0
  264. package/dist/providers/network-manager/adapter.js +348 -0
  265. package/dist/providers/network-manager/index.d.ts +2 -0
  266. package/dist/providers/network-manager/index.js +2 -0
  267. package/dist/providers/network-manager/types.d.ts +171 -0
  268. package/dist/providers/network-manager/types.js +77 -0
  269. package/dist/providers/registry.d.ts +13 -0
  270. package/dist/providers/registry.js +33 -0
  271. package/dist/providers/ufi-himi/index.d.ts +6 -0
  272. package/dist/providers/ufi-himi/index.js +6 -0
  273. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  274. package/dist/providers/ufi-himi/operations.js +66 -0
  275. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  276. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  277. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  278. package/dist/providers/ufi-himi/provider.js +204 -0
  279. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  280. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  281. package/dist/providers/ufi-himi/session.d.ts +46 -0
  282. package/dist/providers/ufi-himi/session.js +92 -0
  283. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  284. package/dist/providers/ufi-himi/transport.js +25 -0
  285. package/dist/providers/zte-goform/index.d.ts +2 -0
  286. package/dist/providers/zte-goform/index.js +2 -0
  287. package/dist/providers/zte-goform/provider.d.ts +56 -0
  288. package/dist/providers/zte-goform/provider.js +101 -0
  289. package/dist/providers/zte-goform/session.d.ts +23 -0
  290. package/dist/providers/zte-goform/session.js +197 -0
  291. package/dist/providers/zte-goform/transport.d.ts +16 -0
  292. package/dist/providers/zte-goform/transport.js +1 -0
  293. package/dist/radio/band-truth.d.ts +50 -0
  294. package/dist/radio/band-truth.js +92 -0
  295. package/dist/radio/index.d.ts +3 -0
  296. package/dist/radio/index.js +10 -0
  297. package/dist/radio/mode-combinations.d.ts +88 -0
  298. package/dist/radio/mode-combinations.js +198 -0
  299. package/dist/radio/mode-truth.d.ts +67 -0
  300. package/dist/radio/mode-truth.js +112 -0
  301. package/dist/redact.d.ts +15 -0
  302. package/dist/redact.js +189 -0
  303. package/dist/safety/composition-root.d.ts +28 -0
  304. package/dist/safety/composition-root.js +65 -0
  305. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  306. package/dist/safety/flock-resource-ownership.js +138 -0
  307. package/dist/safety/index.d.ts +2 -0
  308. package/dist/safety/index.js +2 -0
  309. package/dist/sms/dbus-messaging.d.ts +17 -0
  310. package/dist/sms/dbus-messaging.js +185 -0
  311. package/dist/sms/inbox-store.d.ts +10 -0
  312. package/dist/sms/inbox-store.js +82 -0
  313. package/dist/sms/index.d.ts +4 -0
  314. package/dist/sms/index.js +10 -0
  315. package/dist/sms/mmcli-parse.d.ts +54 -0
  316. package/dist/sms/mmcli-parse.js +224 -0
  317. package/dist/sms/normalize.d.ts +42 -0
  318. package/dist/sms/normalize.js +95 -0
  319. package/dist/testing/domain-fakes.d.ts +58 -0
  320. package/dist/testing/domain-fakes.js +98 -0
  321. package/dist/testing/index.d.ts +2 -0
  322. package/dist/testing/index.js +17 -0
  323. package/dist/testing/provider-fakes.d.ts +45 -0
  324. package/dist/testing/provider-fakes.js +64 -0
  325. package/dist/transport/calls.d.ts +9 -0
  326. package/dist/transport/calls.js +88 -0
  327. package/dist/transport/codec.d.ts +3 -0
  328. package/dist/transport/codec.js +207 -0
  329. package/dist/transport/dbus-native.d.ts +57 -0
  330. package/dist/transport/dbus-native.js +17 -0
  331. package/dist/transport/errors.d.ts +21 -0
  332. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  333. package/dist/transport/index.d.ts +4 -0
  334. package/dist/transport/index.js +9 -0
  335. package/dist/transport/signals.d.ts +15 -0
  336. package/dist/transport/signals.js +123 -0
  337. package/dist/transport/signature.d.ts +7 -0
  338. package/dist/transport/signature.js +94 -0
  339. package/dist/transport/transport.d.ts +2 -0
  340. package/dist/transport/transport.js +202 -0
  341. package/dist/transport/types.d.ts +61 -0
  342. package/dist/transport/types.js +19 -0
  343. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  344. package/dist/usb-mode/catalog-schema.js +97 -0
  345. package/dist/usb-mode/catalog.d.ts +21 -0
  346. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  347. package/dist/usb-mode/certified-catalog.json +67 -0
  348. package/dist/usb-mode/index.d.ts +6 -0
  349. package/dist/usb-mode/index.js +16 -0
  350. package/dist/usb-mode/ingestion.d.ts +111 -0
  351. package/dist/usb-mode/ingestion.js +187 -0
  352. package/dist/usb-mode/promotion-review.d.ts +21 -0
  353. package/dist/usb-mode/promotion-review.js +87 -0
  354. package/dist/usb-mode/runtime-capability.d.ts +59 -0
  355. package/dist/usb-mode/runtime-capability.js +157 -0
  356. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  357. package/dist/usb-mode/usb-devices-parse.js +157 -0
  358. package/dist/ussd/calls.d.ts +32 -0
  359. package/dist/ussd/calls.js +96 -0
  360. package/dist/ussd/index.d.ts +5 -0
  361. package/dist/ussd/index.js +12 -0
  362. package/dist/ussd/mm-ussd.d.ts +37 -0
  363. package/dist/ussd/mm-ussd.js +205 -0
  364. package/dist/ussd/refusal.d.ts +53 -0
  365. package/dist/ussd/refusal.js +154 -0
  366. package/dist/ussd/registration.d.ts +20 -0
  367. package/dist/ussd/registration.js +101 -0
  368. package/dist/ussd/session.d.ts +106 -0
  369. package/dist/ussd/session.js +163 -0
  370. package/package.json +38 -4
  371. package/src/backend/at-lease.test.ts +0 -106
  372. package/src/backend/at-lease.ts +0 -158
  373. package/src/backend/cell-info.test.ts +0 -154
  374. package/src/backend/cell-info.ts +0 -160
  375. package/src/backend/device-classifier.test.ts +0 -168
  376. package/src/backend/device-classifier.ts +0 -248
  377. package/src/backend/enrichment.ts +0 -96
  378. package/src/backend/features.test.ts +0 -162
  379. package/src/backend/features.ts +0 -179
  380. package/src/backend/identity-ladder.test.ts +0 -117
  381. package/src/backend/identity-ladder.ts +0 -221
  382. package/src/backend/identity-registry.test.ts +0 -89
  383. package/src/backend/identity-registry.ts +0 -151
  384. package/src/backend/index.ts +0 -236
  385. package/src/backend/lifecycle-interlock.ts +0 -38
  386. package/src/backend/managed-objects.ts +0 -108
  387. package/src/backend/mapping.ts +0 -160
  388. package/src/backend/mm-backend.ts +0 -191
  389. package/src/backend/mm-mutations.ts +0 -228
  390. package/src/backend/modem-actor.test.ts +0 -95
  391. package/src/backend/modem-actor.ts +0 -112
  392. package/src/backend/nm-auto-apn.ts +0 -161
  393. package/src/backend/nm-gsm-fields.ts +0 -122
  394. package/src/backend/nmcli-nm-port.ts +0 -228
  395. package/src/backend/nmcli-runner.ts +0 -52
  396. package/src/backend/observer.ts +0 -297
  397. package/src/backend/power-contract.test.ts +0 -40
  398. package/src/backend/power-contract.ts +0 -83
  399. package/src/backend/recovery-attribution.test.ts +0 -102
  400. package/src/backend/recovery-attribution.ts +0 -86
  401. package/src/backend/recovery-budget.test.ts +0 -64
  402. package/src/backend/recovery-budget.ts +0 -84
  403. package/src/backend/recovery-ladder.test.ts +0 -257
  404. package/src/backend/recovery-ladder.ts +0 -249
  405. package/src/backend/router-ethernet.test.ts +0 -71
  406. package/src/backend/router-ethernet.ts +0 -90
  407. package/src/backend/row-store.ts +0 -105
  408. package/src/backend/signal-setup.ts +0 -112
  409. package/src/backend/sim-unlock.ts +0 -193
  410. package/src/backend/transition-preconditions.ts +0 -149
  411. package/src/backend/uhubctl-power-hook.test.ts +0 -274
  412. package/src/backend/uhubctl-power-hook.ts +0 -377
  413. package/src/backend/usage/accounting.test.ts +0 -147
  414. package/src/backend/usage/accounting.ts +0 -123
  415. package/src/backend/usage/billing-cycle.test.ts +0 -62
  416. package/src/backend/usage/billing-cycle.ts +0 -45
  417. package/src/backend/usage/index.ts +0 -60
  418. package/src/backend/usage/policy-store.test.ts +0 -164
  419. package/src/backend/usage/policy-store.ts +0 -216
  420. package/src/backend/usage/policy-write.test.ts +0 -198
  421. package/src/backend/usage/policy-write.ts +0 -207
  422. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  423. package/src/backend/usage/sampler.test.ts +0 -327
  424. package/src/backend/usage/sampler.ts +0 -282
  425. package/src/backend/usage/store.test.ts +0 -148
  426. package/src/backend/usage/store.ts +0 -177
  427. package/src/backend/usb-enumerator.test.ts +0 -87
  428. package/src/backend/usb-enumerator.ts +0 -181
  429. package/src/backend/usb-mode-transition.test.ts +0 -323
  430. package/src/backend/usb-mode-transition.ts +0 -253
  431. package/src/domain/brand.ts +0 -29
  432. package/src/domain/errors.ts +0 -77
  433. package/src/domain/guards.test.ts +0 -218
  434. package/src/domain/guards.ts +0 -144
  435. package/src/domain/identity.test.ts +0 -83
  436. package/src/domain/identity.ts +0 -165
  437. package/src/domain/index.ts +0 -12
  438. package/src/domain/snapshot.test.ts +0 -266
  439. package/src/domain/snapshot.ts +0 -120
  440. package/src/index.test.ts +0 -6
  441. package/src/index.ts +0 -15
  442. package/src/ports/README.md +0 -61
  443. package/src/ports/forbidden-surface.test.ts +0 -241
  444. package/src/ports/modem-manager.ts +0 -72
  445. package/src/ports/network-manager.ts +0 -87
  446. package/src/ports/ops.ts +0 -60
  447. package/src/ports/ops.type-test.ts +0 -39
  448. package/src/ports/receipts.test.ts +0 -153
  449. package/src/ports/reconcile.test.ts +0 -152
  450. package/src/ports/reconcile.ts +0 -338
  451. package/src/ports/router.ts +0 -29
  452. package/src/redact.test.ts +0 -82
  453. package/src/redact.ts +0 -73
  454. package/src/transport/README.md +0 -65
  455. package/src/transport/calls.ts +0 -113
  456. package/src/transport/characterization.test.ts +0 -260
  457. package/src/transport/codec.test.ts +0 -118
  458. package/src/transport/codec.ts +0 -240
  459. package/src/transport/conformance-python.test.ts +0 -152
  460. package/src/transport/conformance-same-lib.test.ts +0 -115
  461. package/src/transport/dbus-native-lib.d.ts +0 -19
  462. package/src/transport/dbus-native.ts +0 -85
  463. package/src/transport/index.ts +0 -30
  464. package/src/transport/no-library-leak.test.ts +0 -61
  465. package/src/transport/reliability.test.ts +0 -173
  466. package/src/transport/signals.ts +0 -150
  467. package/src/transport/signature.ts +0 -110
  468. package/src/transport/test-support/fake-service.ts +0 -168
  469. package/src/transport/test-support/independent-producer.py +0 -110
  470. package/src/transport/test-support/private-bus.ts +0 -66
  471. package/src/transport/transport.ts +0 -250
  472. package/src/transport/types.ts +0 -118
  473. package/src/usb-mode/catalog-schema.test.ts +0 -181
  474. package/src/usb-mode/catalog-schema.ts +0 -113
  475. package/src/usb-mode/certified-catalog.json +0 -67
  476. package/src/usb-mode/index.ts +0 -58
  477. package/src/usb-mode/ingestion.test.ts +0 -268
  478. package/src/usb-mode/ingestion.ts +0 -297
  479. package/src/usb-mode/promotion-review.ts +0 -117
  480. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -0,0 +1,237 @@
1
+ // The `Modem.Location` adapter — GNSS status, enable/disable, and the current fix.
2
+ //
3
+ // Three decisions here are load-bearing and easy to undo by accident:
4
+ //
5
+ // 1. `Setup`'s `signal_location` argument is ALWAYS false. Passing true makes MM
6
+ // broadcast the `Location` property over `PropertiesChanged`, which would put
7
+ // the operator's coordinates on the system bus for every listener — including
8
+ // this package's own observer, whose snapshots are logged. The fix is fetched
9
+ // by an explicit `GetLocation` call instead, so a coordinate only ever exists
10
+ // where someone asked for it.
11
+ // 2. GNSS runs through `actor.run`, NOT `actor.runQuiesced`. Quiescing exists to
12
+ // stop NetworkManager racing a disruptive change, and it costs a bearer
13
+ // deactivation — on a bonded device, dropping a link to switch a GPS receiver
14
+ // on would be an absurd trade. `Modem.Location.Setup` touches no bearer, so
15
+ // serialization per modem is all that is needed.
16
+ // 3. Disable CLEARS only the GNSS bits. `3gpp-lac-ci` is the cell-info module's
17
+ // source; blanking the whole mask would silently switch off a neighbouring
18
+ // feature the operator never touched.
19
+ import { epochMillis } from '../domain/index.js';
20
+ import { parseNmeaFix } from '../location/nmea.js';
21
+ import { hasGnssSource } from '../ports/index.js';
22
+ import { MM_BUS_NAME, MODEM_LOCATION_IFACE } from './constants.js';
23
+ import { fetchManagedObjects, findInterface, propValue, } from './managed-objects.js';
24
+ /** MMModemLocationSource bits, by decoded source name. */
25
+ const SOURCE_BIT = {
26
+ '3gpp-lac-ci': 1 << 0,
27
+ 'gps-raw': 1 << 1,
28
+ 'gps-nmea': 1 << 2,
29
+ 'cdma-bs': 1 << 3,
30
+ 'gps-unmanaged': 1 << 4,
31
+ 'agps-msa': 1 << 5,
32
+ 'agps-msb': 1 << 6,
33
+ };
34
+ const GPS_RAW_BIT = SOURCE_BIT['gps-raw'];
35
+ const GPS_NMEA_BIT = SOURCE_BIT['gps-nmea'];
36
+ const GNSS_MASK = GPS_RAW_BIT |
37
+ GPS_NMEA_BIT |
38
+ SOURCE_BIT['gps-unmanaged'] |
39
+ SOURCE_BIT['agps-msa'] |
40
+ SOURCE_BIT['agps-msb'];
41
+ /** Decode an MMModemLocationSource bitmask into source names. */
42
+ export function decodeLocationSources(mask) {
43
+ const sources = new Set();
44
+ for (const [name, bit] of Object.entries(SOURCE_BIT)) {
45
+ if ((mask & bit) !== 0) {
46
+ sources.add(name);
47
+ }
48
+ }
49
+ return sources;
50
+ }
51
+ export function encodeLocationSources(sources) {
52
+ let mask = 0;
53
+ for (const source of sources) {
54
+ mask |= SOURCE_BIT[source] ?? 0;
55
+ }
56
+ return mask;
57
+ }
58
+ export class MmLocation {
59
+ #transport;
60
+ #actor;
61
+ #destination;
62
+ #resolveStableKey;
63
+ #now;
64
+ constructor(deps) {
65
+ this.#transport = deps.transport;
66
+ this.#actor = deps.actor;
67
+ this.#destination = deps.destination ?? MM_BUS_NAME;
68
+ this.#resolveStableKey = deps.resolveStableKey;
69
+ this.#now = deps.now ?? Date.now;
70
+ }
71
+ async getLocationStatus(modem) {
72
+ const props = await this.#readLocationProps(modem);
73
+ if (props === undefined) {
74
+ return { ok: false, reason: 'the modem does not expose a Location interface' };
75
+ }
76
+ return { ok: true, status: statusOf(props) };
77
+ }
78
+ enableGnss(modem, sources) {
79
+ return this.#actor.run(this.#resolveStableKey(modem), async () => {
80
+ const props = await this.#readLocationProps(modem);
81
+ if (props === undefined) {
82
+ return refused('the modem does not expose a Location interface');
83
+ }
84
+ const status = statusOf(props);
85
+ if (!status.gnssCapable) {
86
+ return refused('the modem advertises no GNSS source', status.enabledSources);
87
+ }
88
+ const requested = encodeLocationSources(sources) & maskOfNames(status.capabilities);
89
+ if (requested === 0) {
90
+ return refused('none of the requested GNSS sources is advertised by this modem', status.enabledSources);
91
+ }
92
+ const target = maskOfNames(status.enabledSources) | requested;
93
+ return this.#setup(modem, target, 'GNSS enabled');
94
+ });
95
+ }
96
+ disableGnss(modem) {
97
+ return this.#actor.run(this.#resolveStableKey(modem), async () => {
98
+ const props = await this.#readLocationProps(modem);
99
+ if (props === undefined) {
100
+ return refused('the modem does not expose a Location interface');
101
+ }
102
+ const status = statusOf(props);
103
+ const target = maskOfNames(status.enabledSources) & ~GNSS_MASK;
104
+ return this.#setup(modem, target, 'GNSS disabled');
105
+ });
106
+ }
107
+ async readFix(modem) {
108
+ const props = await this.#readLocationProps(modem);
109
+ if (props === undefined) {
110
+ return { outcome: 'unsupported', reason: 'the modem does not expose a Location interface' };
111
+ }
112
+ const status = statusOf(props);
113
+ if (!status.gnssCapable) {
114
+ return { outcome: 'unsupported', reason: 'the modem advertises no GNSS source' };
115
+ }
116
+ if (!status.gnssEnabled) {
117
+ return { outcome: 'disabled', reason: 'GNSS is switched off on this modem' };
118
+ }
119
+ let reply;
120
+ try {
121
+ reply = await this.#transport.callMethod({
122
+ destination: this.#destination,
123
+ path: modem,
124
+ interface: MODEM_LOCATION_IFACE,
125
+ member: 'GetLocation',
126
+ });
127
+ }
128
+ catch (error) {
129
+ return { outcome: 'error', reason: `GetLocation failed: ${describe(error)}` };
130
+ }
131
+ const fix = decodeFix(reply.body[0], epochMillis(this.#now()));
132
+ return fix === undefined
133
+ ? { outcome: 'no-fix', reason: 'the receiver has not acquired a position' }
134
+ : { outcome: 'fix', fix };
135
+ }
136
+ async #setup(modem, mask, appliedReason) {
137
+ try {
138
+ await this.#transport.callMethod({
139
+ destination: this.#destination,
140
+ path: modem,
141
+ interface: MODEM_LOCATION_IFACE,
142
+ member: 'Setup',
143
+ signature: 'ub',
144
+ args: [mask, false],
145
+ });
146
+ }
147
+ catch (error) {
148
+ return {
149
+ outcome: 'failed',
150
+ reason: `Location.Setup failed: ${describe(error)}`,
151
+ enabledSources: new Set(),
152
+ };
153
+ }
154
+ return {
155
+ outcome: 'applied',
156
+ reason: appliedReason,
157
+ enabledSources: decodeLocationSources(mask),
158
+ };
159
+ }
160
+ async #readLocationProps(modem) {
161
+ try {
162
+ const tree = await fetchManagedObjects(this.#transport, this.#destination);
163
+ return findInterface(tree, modem, MODEM_LOCATION_IFACE);
164
+ }
165
+ catch {
166
+ return undefined;
167
+ }
168
+ }
169
+ }
170
+ function maskOfNames(names) {
171
+ return encodeLocationSources(names);
172
+ }
173
+ function refused(reason, enabled = new Set()) {
174
+ return { outcome: 'unsupported', reason, enabledSources: enabled };
175
+ }
176
+ function statusOf(props) {
177
+ const capabilities = decodeLocationSources(maskProp(props, 'Capabilities'));
178
+ const enabledSources = decodeLocationSources(maskProp(props, 'Enabled'));
179
+ return {
180
+ capabilities,
181
+ enabledSources,
182
+ gnssCapable: hasGnssSource(capabilities),
183
+ gnssEnabled: hasGnssSource(enabledSources),
184
+ };
185
+ }
186
+ function maskProp(props, name) {
187
+ const value = propValue(props, name);
188
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
189
+ }
190
+ function entryValue(payload, bit) {
191
+ if (!Array.isArray(payload)) {
192
+ return undefined;
193
+ }
194
+ for (const entry of payload) {
195
+ if (Number(entry[0]) === bit) {
196
+ return entry[1]?.value;
197
+ }
198
+ }
199
+ return undefined;
200
+ }
201
+ function numberField(props, name) {
202
+ const value = propValue(props, name);
203
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
204
+ }
205
+ /**
206
+ * Prefer MM's pre-decoded `gps-raw` dict; fall back to parsing the `gps-nmea`
207
+ * sentences. A raw entry that is present but carries no usable coordinate pair is
208
+ * NOT treated as a fix — MM populates the key as soon as the source is on.
209
+ */
210
+ function decodeFix(payload, observedAt) {
211
+ const raw = entryValue(payload, GPS_RAW_BIT);
212
+ if (Array.isArray(raw)) {
213
+ const props = raw;
214
+ const latitude = numberField(props, 'latitude');
215
+ const longitude = numberField(props, 'longitude');
216
+ if (latitude !== undefined && longitude !== undefined) {
217
+ const altitude = numberField(props, 'altitude');
218
+ const utcTime = propValue(props, 'utc-time');
219
+ return {
220
+ latitude,
221
+ longitude,
222
+ ...(altitude === undefined ? {} : { altitude }),
223
+ ...(typeof utcTime === 'string' ? { utcTime } : {}),
224
+ observedAt,
225
+ };
226
+ }
227
+ }
228
+ const nmea = entryValue(payload, GPS_NMEA_BIT);
229
+ if (typeof nmea !== 'string') {
230
+ return undefined;
231
+ }
232
+ const parsed = parseNmeaFix(nmea);
233
+ return parsed === undefined ? undefined : { ...parsed, observedAt };
234
+ }
235
+ function describe(error) {
236
+ return error instanceof Error ? error.message : String(error);
237
+ }
@@ -0,0 +1,42 @@
1
+ import type { DesiredRadio } from '../domain/index.js';
2
+ import type { BandReadResult, InhibitLease, ModemRef, NetworkScanResult, Receipt, SimPukUnlockResult, SimUnlockResult } from '../ports/index.js';
3
+ import type { DbusTransport } from '../transport/index.js';
4
+ import type { ModemActor } from './modem-actor.js';
5
+ export interface MmMutationsDeps {
6
+ readonly transport: DbusTransport;
7
+ readonly actor: ModemActor;
8
+ readonly destination?: string;
9
+ /** Map a live modem path to its stable actor key (survives replug). */
10
+ readonly resolveStableKey: (modem: ModemRef) => string;
11
+ readonly scanTimeoutMs?: number;
12
+ readonly now?: () => number;
13
+ }
14
+ /** The disruptive + SIM mutations of `ModemManagerPort`, serialized per modem. */
15
+ export declare class MmMutations {
16
+ #private;
17
+ constructor(deps: MmMutationsDeps);
18
+ setRadioModes(modem: ModemRef, preference: DesiredRadio): Promise<Receipt>;
19
+ /**
20
+ * `SetCurrentModes` over the RAW `(uu)` masks the modem itself advertised.
21
+ *
22
+ * `setRadioModes` above speaks the reconciler's vocabulary — an ordered RAT
23
+ * preference — and structurally cannot express `MM_MODEM_MODE_NONE`: its preferred
24
+ * mask is derived from `preferenceOrdered[0]`, so "allow this set and prefer
25
+ * nothing within it" has no spelling. That is the combination the bench FM350-GL
26
+ * actually advertises, so a caller selecting an advertised combination verbatim
27
+ * needs this entry point. It quiesces for the same reason `setRadioModes` does: a
28
+ * mode change re-registers the radio and drops the bearer underneath NM.
29
+ *
30
+ * An `allowed` mask of 0 is refused — that is not a selection, it is a radio with
31
+ * nothing switched on — while a `preferred` mask of 0 is passed through untouched.
32
+ */
33
+ setModeCombination(modem: ModemRef, allowed: number, preferred: number): Promise<Receipt>;
34
+ readBands(modem: ModemRef): Promise<BandReadResult>;
35
+ setCurrentBands(modem: ModemRef, bands: readonly string[]): Promise<Receipt>;
36
+ setPrimarySimSlot(modem: ModemRef, slotIndex: number): Promise<Receipt>;
37
+ sendPin(modem: ModemRef, pin: string): Promise<SimUnlockResult>;
38
+ sendPuk(modem: ModemRef, puk: string, newPin: string): Promise<SimPukUnlockResult>;
39
+ scanNetworks(modem: ModemRef): Promise<NetworkScanResult>;
40
+ inhibit(uid: string): Promise<InhibitLease>;
41
+ uninhibit(lease: InhibitLease): Promise<void>;
42
+ }
@@ -0,0 +1,259 @@
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
+ import { decodeBandList, encodeBandList, isResetSelection } from '../band/index.js';
10
+ import { epochMillis } from '../domain/index.js';
11
+ import { receipt } from '../ports/index.js';
12
+ import { MM_BUS_NAME, MM_MANAGER_IFACE, MM_ROOT_PATH, MODEM_IFACE, MODEM3GPP_IFACE, } from './constants.js';
13
+ import { fetchManagedObjects, findInterface, numberProp, propValue, stringProp, } from './managed-objects.js';
14
+ import { sendSimPin, sendSimPuk } from './sim-unlock.js';
15
+ /** MMModemMode bit per RAT family (2G/3G/4G/5G). */
16
+ const MODE_BIT = { gsm: 2, umts: 4, lte: 8, '5gnr': 16 };
17
+ /** MMModem3gppNetworkAvailability → the port's availability. */
18
+ const AVAILABILITY = {
19
+ 0: 'unknown',
20
+ 1: 'available',
21
+ 2: 'current',
22
+ 3: 'forbidden',
23
+ };
24
+ /** A network scan can take a long time — MM's own default is not enough. */
25
+ const DEFAULT_SCAN_TIMEOUT_MS = 300_000;
26
+ /** The disruptive + SIM mutations of `ModemManagerPort`, serialized per modem. */
27
+ export class MmMutations {
28
+ #transport;
29
+ #actor;
30
+ #destination;
31
+ #resolveStableKey;
32
+ #scanTimeoutMs;
33
+ #now;
34
+ constructor(deps) {
35
+ this.#transport = deps.transport;
36
+ this.#actor = deps.actor;
37
+ this.#destination = deps.destination ?? MM_BUS_NAME;
38
+ this.#resolveStableKey = deps.resolveStableKey;
39
+ this.#scanTimeoutMs = deps.scanTimeoutMs ?? DEFAULT_SCAN_TIMEOUT_MS;
40
+ this.#now = deps.now ?? Date.now;
41
+ }
42
+ setRadioModes(modem, preference) {
43
+ const allowed = maskOf(preference.allowedSet ?? new Set(preference.preferenceOrdered));
44
+ const preferred = preference.preferenceOrdered[0];
45
+ const preferredMask = preferred !== undefined ? MODE_BIT[preferred] : 0;
46
+ if (allowed === 0) {
47
+ return Promise.resolve(receipt('radio', 'failed', 'no radio modes were requested'));
48
+ }
49
+ return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
50
+ try {
51
+ await this.#transport.callMethod({
52
+ destination: this.#destination,
53
+ path: modem,
54
+ interface: MODEM_IFACE,
55
+ member: 'SetCurrentModes',
56
+ signature: '(uu)',
57
+ args: [[allowed, preferredMask]],
58
+ });
59
+ return receipt('radio', 'applied', 'radio mode preference applied');
60
+ }
61
+ catch (error) {
62
+ return receipt('radio', 'failed', `SetCurrentModes failed: ${describe(error)}`);
63
+ }
64
+ });
65
+ }
66
+ /**
67
+ * `SetCurrentModes` over the RAW `(uu)` masks the modem itself advertised.
68
+ *
69
+ * `setRadioModes` above speaks the reconciler's vocabulary — an ordered RAT
70
+ * preference — and structurally cannot express `MM_MODEM_MODE_NONE`: its preferred
71
+ * mask is derived from `preferenceOrdered[0]`, so "allow this set and prefer
72
+ * nothing within it" has no spelling. That is the combination the bench FM350-GL
73
+ * actually advertises, so a caller selecting an advertised combination verbatim
74
+ * needs this entry point. It quiesces for the same reason `setRadioModes` does: a
75
+ * mode change re-registers the radio and drops the bearer underneath NM.
76
+ *
77
+ * An `allowed` mask of 0 is refused — that is not a selection, it is a radio with
78
+ * nothing switched on — while a `preferred` mask of 0 is passed through untouched.
79
+ */
80
+ setModeCombination(modem, allowed, preferred) {
81
+ if (allowed === 0) {
82
+ return Promise.resolve(receipt('radio', 'failed', 'no radio modes were requested'));
83
+ }
84
+ return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
85
+ try {
86
+ await this.#transport.callMethod({
87
+ destination: this.#destination,
88
+ path: modem,
89
+ interface: MODEM_IFACE,
90
+ member: 'SetCurrentModes',
91
+ signature: '(uu)',
92
+ args: [[allowed, preferred]],
93
+ });
94
+ return receipt('radio', 'applied', 'radio mode combination applied');
95
+ }
96
+ catch (error) {
97
+ return receipt('radio', 'failed', `SetCurrentModes failed: ${describe(error)}`);
98
+ }
99
+ });
100
+ }
101
+ async readBands(modem) {
102
+ try {
103
+ const tree = await fetchManagedObjects(this.#transport, this.#destination);
104
+ const props = findInterface(tree, modem, MODEM_IFACE);
105
+ if (props === undefined) {
106
+ return { ok: false, reason: 'the modem exports no Modem interface' };
107
+ }
108
+ return {
109
+ ok: true,
110
+ bands: {
111
+ supported: decodeBandList(propValue(props, 'SupportedBands')),
112
+ current: decodeBandList(propValue(props, 'CurrentBands')),
113
+ },
114
+ };
115
+ }
116
+ catch (error) {
117
+ return { ok: false, reason: `reading bands failed: ${describe(error)}` };
118
+ }
119
+ }
120
+ // Quiesced like `setRadioModes`, and for the same reason: a band change
121
+ // re-registers the radio, so NM must stand down before the bearer drops
122
+ // underneath it rather than after.
123
+ setCurrentBands(modem, bands) {
124
+ if (bands.length === 0) {
125
+ return Promise.resolve(receipt('band', 'failed', 'no bands were requested'));
126
+ }
127
+ const encoded = encodeBandList(bands);
128
+ if (!encoded.ok) {
129
+ return Promise.resolve(receipt('band', 'unsupported', `this build does not know the band "${encoded.unknown}"`));
130
+ }
131
+ const values = encoded.values;
132
+ return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
133
+ try {
134
+ await this.#transport.callMethod({
135
+ destination: this.#destination,
136
+ path: modem,
137
+ interface: MODEM_IFACE,
138
+ member: 'SetCurrentBands',
139
+ signature: 'au',
140
+ args: [values],
141
+ });
142
+ return receipt('band', 'applied', isResetSelection(bands) ? 'band lock released' : `bands set to ${bands.join(', ')}`);
143
+ }
144
+ catch (error) {
145
+ return receipt('band', 'failed', `SetCurrentBands failed: ${describe(error)}`);
146
+ }
147
+ });
148
+ }
149
+ async setPrimarySimSlot(modem, slotIndex) {
150
+ const slots = await this.#readSlotCount(modem);
151
+ if (slots === undefined) {
152
+ return receipt('simSlot', 'failed', 'could not read the modem SIM-slot list');
153
+ }
154
+ if (slots <= 1) {
155
+ return receipt('simSlot', 'unsupported', 'single-slot modem has no primary slot to select');
156
+ }
157
+ if (slotIndex < 1 || slotIndex > slots) {
158
+ return receipt('simSlot', 'failed', `slot ${slotIndex} is out of range (1..${slots})`);
159
+ }
160
+ return this.#actor.runQuiesced({ stableKey: this.#resolveStableKey(modem) }, async () => {
161
+ try {
162
+ await this.#transport.callMethod({
163
+ destination: this.#destination,
164
+ path: modem,
165
+ interface: MODEM_IFACE,
166
+ member: 'SetPrimarySimSlot',
167
+ signature: 'u',
168
+ args: [slotIndex],
169
+ });
170
+ return receipt('simSlot', 'applied', `primary SIM slot set to ${slotIndex}`);
171
+ }
172
+ catch (error) {
173
+ return receipt('simSlot', 'failed', `SetPrimarySimSlot failed: ${describe(error)}`);
174
+ }
175
+ });
176
+ }
177
+ sendPin(modem, pin) {
178
+ return this.#actor.run(this.#resolveStableKey(modem), () => sendSimPin(this.#transport, this.#destination, modem, pin));
179
+ }
180
+ sendPuk(modem, puk, newPin) {
181
+ return this.#actor.run(this.#resolveStableKey(modem), () => sendSimPuk(this.#transport, this.#destination, modem, puk, newPin));
182
+ }
183
+ scanNetworks(modem) {
184
+ return this.#actor.run(this.#resolveStableKey(modem), async () => {
185
+ try {
186
+ const reply = await this.#transport.callMethod({
187
+ destination: this.#destination,
188
+ path: modem,
189
+ interface: MODEM3GPP_IFACE,
190
+ member: 'Scan',
191
+ timeoutMs: this.#scanTimeoutMs,
192
+ });
193
+ return { ok: true, networks: parseScan(reply.body[0]) };
194
+ }
195
+ catch (error) {
196
+ return { ok: false, reason: `network scan failed: ${describe(error)}` };
197
+ }
198
+ });
199
+ }
200
+ async inhibit(uid) {
201
+ await this.#inhibitDevice(uid, true);
202
+ return { uid, acquiredAt: epochMillis(this.#now()) };
203
+ }
204
+ async uninhibit(lease) {
205
+ await this.#inhibitDevice(lease.uid, false);
206
+ }
207
+ #inhibitDevice(uid, inhibit) {
208
+ return this.#transport.callMethod({
209
+ destination: this.#destination,
210
+ path: MM_ROOT_PATH,
211
+ interface: MM_MANAGER_IFACE,
212
+ member: 'InhibitDevice',
213
+ signature: 'sb',
214
+ args: [uid, inhibit],
215
+ });
216
+ }
217
+ async #readSlotCount(modem) {
218
+ try {
219
+ const tree = await fetchManagedObjects(this.#transport, this.#destination);
220
+ const slots = propValue(findInterface(tree, modem, MODEM_IFACE), 'SimSlots');
221
+ return Array.isArray(slots) ? slots.length : undefined;
222
+ }
223
+ catch {
224
+ return undefined;
225
+ }
226
+ }
227
+ }
228
+ /** OR of the MMModemMode bits for a set of RATs. */
229
+ function maskOf(rats) {
230
+ let mask = 0;
231
+ for (const rat of rats) {
232
+ mask |= MODE_BIT[rat];
233
+ }
234
+ return mask;
235
+ }
236
+ /** Parse a `Modem3gpp.Scan` reply (`aa{sv}` → dicts) into scanned networks. */
237
+ function parseScan(value) {
238
+ if (!Array.isArray(value)) {
239
+ return [];
240
+ }
241
+ const networks = [];
242
+ for (const entry of value) {
243
+ const operatorCode = stringProp(entry, 'operator-code');
244
+ if (operatorCode === undefined) {
245
+ continue;
246
+ }
247
+ const name = stringProp(entry, 'operator-long') ?? stringProp(entry, 'operator-short');
248
+ const availability = AVAILABILITY[numberProp(entry, 'status') ?? 0] ?? 'unknown';
249
+ networks.push({
250
+ operatorCode,
251
+ ...(name !== undefined ? { operatorName: name } : {}),
252
+ availability,
253
+ });
254
+ }
255
+ return networks;
256
+ }
257
+ function describe(error) {
258
+ return error instanceof Error ? error.message : String(error);
259
+ }
@@ -0,0 +1,41 @@
1
+ import type { ConnectionId, DeviceIfname } from '../ports/index.js';
2
+ /** What a quiesce lease is taken over — the modem, and (once A4.1 knows them) its
3
+ * NM connection + device. In A3.3 only `stableKey` is populated. */
4
+ export interface QuiesceTarget {
5
+ readonly stableKey: string;
6
+ readonly connectionId?: ConnectionId;
7
+ readonly deviceIfname?: DeviceIfname;
8
+ }
9
+ /** A held quiesce lease — released (reactivating the connection) when the op ends. */
10
+ export interface QuiesceLeaseHandle {
11
+ release(): Promise<void>;
12
+ }
13
+ /**
14
+ * The NM-quiesce coordination hook the actor calls around a disruptive mode/slot
15
+ * change. A4.1 wires the real `nmcli` lease (verify-active → `device disconnect` →
16
+ * hold → reactivate); A3.3 ships `NO_OP_QUIESCE`, whose call site + shape are the
17
+ * exact seam A4.1 fills in.
18
+ */
19
+ export interface QuiesceHook {
20
+ acquire(target: QuiesceTarget): Promise<QuiesceLeaseHandle>;
21
+ }
22
+ /** The A3.3 default quiesce hook — acquires nothing, releases nothing. */
23
+ export declare const NO_OP_QUIESCE: QuiesceHook;
24
+ /**
25
+ * A per-modem serialized actor. `run` serializes a task behind every prior task for
26
+ * the same stable key; `runQuiesced` additionally holds an NM quiesce lease for the
27
+ * task's duration. Different stable keys never block one another.
28
+ */
29
+ export declare class ModemActor {
30
+ #private;
31
+ constructor(quiesce?: QuiesceHook);
32
+ /** Run `task` serialized behind every prior task for `stableKey`. */
33
+ run<T>(stableKey: string, task: () => Promise<T>): Promise<T>;
34
+ /**
35
+ * Run `task` serialized for `target.stableKey` with an NM quiesce lease held for
36
+ * its whole duration — the lease is always released, even if `task` throws.
37
+ */
38
+ runQuiesced<T>(target: QuiesceTarget, task: () => Promise<T>): Promise<T>;
39
+ /** Number of modems with a live queue — exposed for leak / idle assertions. */
40
+ get activeKeyCount(): number;
41
+ }
@@ -0,0 +1,78 @@
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
+ const NO_OP_LEASE = {
20
+ release() {
21
+ return Promise.resolve();
22
+ },
23
+ };
24
+ /** The A3.3 default quiesce hook — acquires nothing, releases nothing. */
25
+ export const NO_OP_QUIESCE = {
26
+ acquire() {
27
+ return Promise.resolve(NO_OP_LEASE);
28
+ },
29
+ };
30
+ /**
31
+ * A per-modem serialized actor. `run` serializes a task behind every prior task for
32
+ * the same stable key; `runQuiesced` additionally holds an NM quiesce lease for the
33
+ * task's duration. Different stable keys never block one another.
34
+ */
35
+ export class ModemActor {
36
+ #quiesce;
37
+ // Tail of each modem's queue — a promise that settles when the last-enqueued task
38
+ // for that key finishes (errors swallowed so the chain never breaks).
39
+ #tails = new Map();
40
+ constructor(quiesce = NO_OP_QUIESCE) {
41
+ this.#quiesce = quiesce;
42
+ }
43
+ /** Run `task` serialized behind every prior task for `stableKey`. */
44
+ run(stableKey, task) {
45
+ const prior = this.#tails.get(stableKey) ?? Promise.resolve();
46
+ // `then(task, task)` runs `task` whether the prior task resolved OR rejected,
47
+ // so one failure never stalls the queue.
48
+ const result = prior.then(task, task);
49
+ const settled = result.then(() => undefined, () => undefined);
50
+ this.#tails.set(stableKey, settled);
51
+ // Drop the entry once this task was the last one, to bound the map.
52
+ void settled.then(() => {
53
+ if (this.#tails.get(stableKey) === settled) {
54
+ this.#tails.delete(stableKey);
55
+ }
56
+ });
57
+ return result;
58
+ }
59
+ /**
60
+ * Run `task` serialized for `target.stableKey` with an NM quiesce lease held for
61
+ * its whole duration — the lease is always released, even if `task` throws.
62
+ */
63
+ runQuiesced(target, task) {
64
+ return this.run(target.stableKey, async () => {
65
+ const lease = await this.#quiesce.acquire(target);
66
+ try {
67
+ return await task();
68
+ }
69
+ finally {
70
+ await lease.release();
71
+ }
72
+ });
73
+ }
74
+ /** Number of modems with a live queue — exposed for leak / idle assertions. */
75
+ get activeKeyCount() {
76
+ return this.#tails.size;
77
+ }
78
+ }