@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,112 +0,0 @@
1
- // The shared per-modem disruptive-operation actor.
2
- //
3
- // EVERY disruptive modem operation funnels through ONE serialization queue PER
4
- // MODEM: radio-mode changes and primary-slot changes (A3.3), the recovery ladder
5
- // (A3.4), and USB-mode / AT transitions (A4.2) all route through the SAME actor so
6
- // two disruptive ops on ONE modem can never interleave. Ops on DIFFERENT modems run
7
- // independently — each modem owns its own queue.
8
- //
9
- // The queue is keyed on the STABLE identity key (A3.2's `stableKey`), NOT the
10
- // transient D-Bus object path. A modem that unplugs and replugs into the same slot
11
- // resolves to the same stable key, so serialization survives the replug (the path
12
- // changed, the queue did not).
13
- //
14
- // Mode and slot changes additionally coordinate with NetworkManager through the
15
- // `QuiesceHook` — before a disruptive change NM briefly deactivates the connection so
16
- // it cannot race MM's own re-activation, then reactivates it on release. In A3.3 the
17
- // hook is a NO-OP stub with the exact lease shape A4.1's real nmcli adapter will fill
18
- // in (`NetworkManagerPort.acquireQuiesceLease`).
19
-
20
- import type { ConnectionId, DeviceIfname } from '../ports';
21
-
22
- /** What a quiesce lease is taken over — the modem, and (once A4.1 knows them) its
23
- * NM connection + device. In A3.3 only `stableKey` is populated. */
24
- export interface QuiesceTarget {
25
- readonly stableKey: string;
26
- readonly connectionId?: ConnectionId;
27
- readonly deviceIfname?: DeviceIfname;
28
- }
29
-
30
- /** A held quiesce lease — released (reactivating the connection) when the op ends. */
31
- export interface QuiesceLeaseHandle {
32
- release(): Promise<void>;
33
- }
34
-
35
- /**
36
- * The NM-quiesce coordination hook the actor calls around a disruptive mode/slot
37
- * change. A4.1 wires the real `nmcli` lease (verify-active → `device disconnect` →
38
- * hold → reactivate); A3.3 ships `NO_OP_QUIESCE`, whose call site + shape are the
39
- * exact seam A4.1 fills in.
40
- */
41
- export interface QuiesceHook {
42
- acquire(target: QuiesceTarget): Promise<QuiesceLeaseHandle>;
43
- }
44
-
45
- const NO_OP_LEASE: QuiesceLeaseHandle = {
46
- release(): Promise<void> {
47
- return Promise.resolve();
48
- },
49
- };
50
-
51
- /** The A3.3 default quiesce hook — acquires nothing, releases nothing. */
52
- export const NO_OP_QUIESCE: QuiesceHook = {
53
- acquire(): Promise<QuiesceLeaseHandle> {
54
- return Promise.resolve(NO_OP_LEASE);
55
- },
56
- };
57
-
58
- /**
59
- * A per-modem serialized actor. `run` serializes a task behind every prior task for
60
- * the same stable key; `runQuiesced` additionally holds an NM quiesce lease for the
61
- * task's duration. Different stable keys never block one another.
62
- */
63
- export class ModemActor {
64
- readonly #quiesce: QuiesceHook;
65
- // Tail of each modem's queue — a promise that settles when the last-enqueued task
66
- // for that key finishes (errors swallowed so the chain never breaks).
67
- readonly #tails = new Map<string, Promise<void>>();
68
-
69
- constructor(quiesce: QuiesceHook = NO_OP_QUIESCE) {
70
- this.#quiesce = quiesce;
71
- }
72
-
73
- /** Run `task` serialized behind every prior task for `stableKey`. */
74
- run<T>(stableKey: string, task: () => Promise<T>): Promise<T> {
75
- const prior = this.#tails.get(stableKey) ?? Promise.resolve();
76
- // `then(task, task)` runs `task` whether the prior task resolved OR rejected,
77
- // so one failure never stalls the queue.
78
- const result = prior.then(task, task);
79
- const settled = result.then(
80
- () => undefined,
81
- () => undefined,
82
- );
83
- this.#tails.set(stableKey, settled);
84
- // Drop the entry once this task was the last one, to bound the map.
85
- void settled.then(() => {
86
- if (this.#tails.get(stableKey) === settled) {
87
- this.#tails.delete(stableKey);
88
- }
89
- });
90
- return result;
91
- }
92
-
93
- /**
94
- * Run `task` serialized for `target.stableKey` with an NM quiesce lease held for
95
- * its whole duration — the lease is always released, even if `task` throws.
96
- */
97
- runQuiesced<T>(target: QuiesceTarget, task: () => Promise<T>): Promise<T> {
98
- return this.run(target.stableKey, async () => {
99
- const lease = await this.#quiesce.acquire(target);
100
- try {
101
- return await task();
102
- } finally {
103
- await lease.release();
104
- }
105
- });
106
- }
107
-
108
- /** Number of modems with a live queue — exposed for leak / idle assertions. */
109
- get activeKeyCount(): number {
110
- return this.#tails.size;
111
- }
112
- }
@@ -1,161 +0,0 @@
1
- // Auto-APN: boot capability probe, the two atomic transition argv builders, and the
2
- // activation-result classifier.
3
- //
4
- // Auto-APN (`apn:"auto"` ⇒ NM `gsm.auto-config yes`) needs NetworkManager >= 1.22
5
- // (bookworm ships 1.42). A boot capability probe reads nmcli's version — replacing any
6
- // implicit/hardcoded gating — and when NM is too old an auto policy resolves to
7
- // `unsupported` up front with the `autoApnUnavailable` advisory (no modify at all).
8
- //
9
- // When NM DOES support auto-config a bad/unknown SIM can still have no MBPI match: the
10
- // connection fails to activate with a GSM_APN_FAILED device-state reason. That is
11
- // classified at ACTIVATION, not guessed ahead of time — reactivate the exact
12
- // (uuid, ifname) pair, await the terminal state, then:
13
- // GSM_APN_FAILED under an auto profile → unsupported + autoApnUnavailable advisory
14
- // any other activation error → failed
15
- // nmcli's own activation wait timed out → pending
16
-
17
- import { type Receipt, receipt } from '../ports';
18
- import { cliArg, passwordFlags } from './nm-gsm-fields';
19
- import { type NmcliResult, type NmcliRunner, runNmcli } from './nmcli-runner';
20
-
21
- /** The single advisory Auto-APN raises when it cannot be honored. */
22
- export const AUTO_APN_ADVISORY = 'autoApnUnavailable';
23
- export type AutoApnAdvisory = typeof AUTO_APN_ADVISORY;
24
-
25
- /** The outcome of an Auto-APN transition: a receipt plus an optional advisory flag. */
26
- export interface AutoApnTransitionResult {
27
- readonly receipt: Receipt;
28
- readonly advisory?: AutoApnAdvisory;
29
- }
30
-
31
- /** Explicit APN + credentials for the manual direction of a transition. */
32
- export interface ManualApn {
33
- readonly apn: string;
34
- readonly username?: string;
35
- readonly password?: string;
36
- }
37
-
38
- const AUTO_CONFIG_MIN_MAJOR = 1;
39
- const AUTO_CONFIG_MIN_MINOR = 22;
40
-
41
- /** nmcli's own `--wait` timeout exit status (nmcli(1): 3 = timeout expired). */
42
- const NMCLI_EXIT_TIMEOUT = 3;
43
-
44
- const APN_FAILED = /gsm[_-]?apn[_-]?failed/i;
45
-
46
- /** Parse `nmcli tool, version 1.42.4` → `{ major, minor }` (null on garbage). */
47
- export function parseNmVersion(output: string): { major: number; minor: number } | null {
48
- const match = /version\s+(\d+)\.(\d+)/i.exec(output);
49
- if (match?.[1] === undefined || match[2] === undefined) {
50
- return null;
51
- }
52
- return { major: Number(match[1]), minor: Number(match[2]) };
53
- }
54
-
55
- /** `true` iff the parsed NM version is >= 1.22 (auto-config support floor). */
56
- export function autoApnSupportedByVersion(version: { major: number; minor: number }): boolean {
57
- if (version.major !== AUTO_CONFIG_MIN_MAJOR) {
58
- return version.major > AUTO_CONFIG_MIN_MAJOR;
59
- }
60
- return version.minor >= AUTO_CONFIG_MIN_MINOR;
61
- }
62
-
63
- /**
64
- * Decide from a `nmcli --version` result whether Auto-APN is available on this install.
65
- * Fail-CLOSED — an unreadable/garbled version is "not capable", so we never claim
66
- * Auto-APN works when we cannot prove it does.
67
- */
68
- export function autoApnCapableFromVersion(result: NmcliResult): boolean {
69
- if (result.exitCode !== 0) {
70
- return false;
71
- }
72
- const version = parseNmVersion(result.stdout);
73
- return version !== null && autoApnSupportedByVersion(version);
74
- }
75
-
76
- /** Boot capability probe: run `nmcli --version` and resolve Auto-APN availability. */
77
- export async function probeAutoApnCapability(runner: NmcliRunner): Promise<boolean> {
78
- return autoApnCapableFromVersion(await runNmcli(runner, ['--version']));
79
- }
80
-
81
- /**
82
- * ONE atomic `connection modify` that flips a profile to Auto-APN: clears every
83
- * credential AND sets `gsm.auto-config yes` in the SAME invocation. Order/atomicity
84
- * matter — NM rejects `auto-config yes` while any credential is still set, so the clear
85
- * and the enable MUST land together (never two modifies).
86
- */
87
- export function toAutoArgs(id: string): string[] {
88
- return [
89
- 'connection',
90
- 'modify',
91
- id,
92
- 'gsm.apn',
93
- '',
94
- 'gsm.username',
95
- '',
96
- 'gsm.password',
97
- '',
98
- 'gsm.password-flags',
99
- '4',
100
- 'gsm.auto-config',
101
- 'yes',
102
- ];
103
- }
104
-
105
- /**
106
- * The exact reverse: ONE atomic `connection modify` restoring explicit APN + creds and
107
- * clearing `gsm.auto-config`, with password-flags matching the (new) password.
108
- */
109
- export function toManualArgs(id: string, creds: ManualApn): string[] {
110
- return [
111
- 'connection',
112
- 'modify',
113
- id,
114
- 'gsm.apn',
115
- cliArg(creds.apn),
116
- 'gsm.username',
117
- cliArg(creds.username),
118
- 'gsm.password',
119
- cliArg(creds.password),
120
- 'gsm.password-flags',
121
- passwordFlags(creds.password),
122
- 'gsm.auto-config',
123
- 'no',
124
- ];
125
- }
126
-
127
- /**
128
- * Classify a `connection up` result into a receipt (+ advisory). `underAuto` marks that
129
- * the profile being activated is an Auto-APN profile, so an APN failure is the "no MBPI
130
- * match for this SIM" case → unsupported + advisory rather than a hard fail.
131
- */
132
- export function classifyActivation(
133
- result: NmcliResult,
134
- options: { readonly underAuto: boolean },
135
- ): AutoApnTransitionResult {
136
- if (result.exitCode === 0) {
137
- return { receipt: receipt('connection', 'applied', 'connection activated') };
138
- }
139
- if (result.exitCode === NMCLI_EXIT_TIMEOUT || /timeout/i.test(result.stderr)) {
140
- return {
141
- receipt: receipt(
142
- 'connection',
143
- 'pending',
144
- 'activation wait timed out; terminal state unknown',
145
- ),
146
- };
147
- }
148
- if (options.underAuto && (APN_FAILED.test(result.stderr) || APN_FAILED.test(result.stdout))) {
149
- return {
150
- receipt: receipt(
151
- 'connection',
152
- 'unsupported',
153
- 'Auto-APN found no operator match for this SIM',
154
- ),
155
- advisory: AUTO_APN_ADVISORY,
156
- };
157
- }
158
- return {
159
- receipt: receipt('connection', 'failed', result.stderr || 'connection activation failed'),
160
- };
161
- }
@@ -1,122 +0,0 @@
1
- // Pure nmcli GSM-connection argv builders — the FULL nine-field write parity today's
2
- // CeraUI carried (modem-registration.ts `sanitizeModemConfigForNetworkManager` +
3
- // `addConnectionForModem`), rebuilt from scratch for the device-exact adapter so the
4
- // greenfield port loses nothing the wrap-first path wrote.
5
- //
6
- // The nine fields and their exact NM conventions (`gsm.*` + `connection.*` setting
7
- // semantics) are the contract A4.1 must not regress:
8
- // gsm.apn / gsm.username / gsm.password — creds ("" when unset)
9
- // gsm.password-flags — "4" NOT_REQUIRED (no password),
10
- // "0" NONE / system-stored (password set)
11
- // gsm.home-only — "yes" ⇒ roaming DISABLED
12
- // gsm.network-id — manual operator PLMN, only while roaming
13
- // gsm.auto-config — "yes" ⇒ Auto-APN (creds MUST be empty)
14
- // connection.autoconnect — always "yes"
15
- // connection.autoconnect-retries — "2"
16
-
17
- import type { ConnectionId, GsmProfile, GsmProfileInput, GsmProfilePatch } from '../ports';
18
-
19
- // Empty-string fallback; Bun runtime limitation with empty CLI args: a bare
20
- // `undefined` reaching `Bun.spawn` argv drops the slot and shifts every following
21
- // token, so every optional nmcli value is coerced to "" first — the same `value || ""`
22
- // convention CeraUI documented on gsm.apn / gsm.username / gsm.password.
23
- export const cliArg = (value: string | undefined): string => value || '';
24
-
25
- /** NM secret-flags: no password ⇒ NOT_REQUIRED ("4"); password set ⇒ NONE ("0"). */
26
- export const passwordFlags = (password: string | undefined): string => (password ? '0' : '4');
27
-
28
- /** `gsm.network-id` — the manual operator id, and only while roaming (home-only off). */
29
- const networkIdField = (input: GsmProfileInput): string =>
30
- input.homeOnly ? '' : cliArg(input.networkId);
31
-
32
- /**
33
- * The nine `gsm.*` + `connection.*` key/value pairs for a profile, in stable order.
34
- * With `autoConfig` set the creds are forced empty (NM rejects `auto-config yes` while
35
- * any credential is present — nm-setting-gsm.c:440-447), so an Auto-APN create is always
36
- * NM-valid by construction.
37
- */
38
- export function gsmFieldPairs(input: GsmProfileInput): Array<[string, string]> {
39
- const auto = input.autoConfig;
40
- return [
41
- ['gsm.apn', auto ? '' : cliArg(input.apn)],
42
- ['gsm.username', auto ? '' : cliArg(input.username)],
43
- ['gsm.password', auto ? '' : cliArg(input.password)],
44
- ['gsm.password-flags', passwordFlags(auto ? undefined : input.password)],
45
- ['gsm.home-only', input.homeOnly ? 'yes' : 'no'],
46
- ['gsm.network-id', networkIdField(input)],
47
- ['gsm.auto-config', auto ? 'yes' : 'no'],
48
- ['connection.autoconnect', 'yes'],
49
- ['connection.autoconnect-retries', '2'],
50
- ];
51
- }
52
-
53
- /** Flatten key/value pairs into the alternating argv nmcli expects. */
54
- export function flattenPairs(pairs: ReadonlyArray<readonly [string, string]>): string[] {
55
- return pairs.flat();
56
- }
57
-
58
- /** `connection add type gsm con-name <name> <nine fields>` — device-exact create argv. */
59
- export function createGsmArgs(input: GsmProfileInput): string[] {
60
- return [
61
- 'connection',
62
- 'add',
63
- 'type',
64
- 'gsm',
65
- 'con-name',
66
- input.connectionName,
67
- ...flattenPairs(gsmFieldPairs(input)),
68
- ];
69
- }
70
-
71
- /**
72
- * The key/value pairs for a `connection modify`. Roaming (home-only) and the manual
73
- * operator id move together — turning roaming off clears `gsm.network-id`, honoring the
74
- * `gsm.network-id = roaming ? id : ""` invariant in the SAME modify.
75
- */
76
- export function patchPairs(patch: GsmProfilePatch): string[] {
77
- const pairs: string[] = [];
78
- if (patch.connectionName !== undefined) {
79
- pairs.push('connection.id', patch.connectionName);
80
- }
81
- if (patch.apn !== undefined) {
82
- pairs.push('gsm.apn', cliArg(patch.apn));
83
- }
84
- if (patch.username !== undefined) {
85
- pairs.push('gsm.username', cliArg(patch.username));
86
- }
87
- if (patch.password !== undefined) {
88
- pairs.push(
89
- 'gsm.password',
90
- cliArg(patch.password),
91
- 'gsm.password-flags',
92
- passwordFlags(patch.password),
93
- );
94
- }
95
- if (patch.autoConfig !== undefined) {
96
- pairs.push('gsm.auto-config', patch.autoConfig ? 'yes' : 'no');
97
- }
98
- if (patch.homeOnly !== undefined) {
99
- pairs.push('gsm.home-only', patch.homeOnly ? 'yes' : 'no');
100
- pairs.push('gsm.network-id', patch.homeOnly ? '' : cliArg(patch.networkId));
101
- } else if (patch.networkId !== undefined) {
102
- pairs.push('gsm.network-id', cliArg(patch.networkId));
103
- }
104
- return pairs;
105
- }
106
-
107
- /** Map an nmcli terse readback back onto a `GsmProfile` (inverse of the write parity). */
108
- export function buildProfile(id: ConnectionId, settings: Map<string, string>): GsmProfile {
109
- const username = settings.get('gsm.username') ?? '';
110
- const password = settings.get('gsm.password') ?? '';
111
- const networkId = settings.get('gsm.network-id') ?? '';
112
- return {
113
- connectionId: id,
114
- connectionName: settings.get('connection.id') ?? '',
115
- apn: settings.get('gsm.apn') ?? '',
116
- homeOnly: settings.get('gsm.home-only') === 'yes',
117
- autoConfig: settings.get('gsm.auto-config') === 'yes',
118
- ...(username !== '' ? { username } : {}),
119
- ...(password !== '' ? { password } : {}),
120
- ...(networkId !== '' ? { networkId } : {}),
121
- };
122
- }
@@ -1,228 +0,0 @@
1
- // The device-exact NetworkManager adapter: a FRESH nmcli-based `NetworkManagerPort`
2
- // (no reused CeraUI code) with the full nine-field GSM write parity, verify-then-
3
- // device-disconnect deactivation (never id-only `connection down`), abandoned-lease
4
- // quiesce, and atomic Auto-APN transitions.
5
-
6
- import { epochMillis } from '../domain';
7
- import {
8
- type ConnectionId,
9
- connectionId,
10
- type DeviceIfname,
11
- type GsmProfile,
12
- type GsmProfileInput,
13
- type GsmProfilePatch,
14
- type NetworkManagerPort,
15
- type QuiesceLease,
16
- type Receipt,
17
- receipt,
18
- } from '../ports';
19
- import {
20
- AUTO_APN_ADVISORY,
21
- type AutoApnTransitionResult,
22
- classifyActivation,
23
- type ManualApn,
24
- toAutoArgs,
25
- toManualArgs,
26
- } from './nm-auto-apn';
27
- import { buildProfile, createGsmArgs, patchPairs } from './nm-gsm-fields';
28
- import { type NmcliResult, type NmcliRunner, parseTerse, runNmcli } from './nmcli-runner';
29
-
30
- const READBACK_FIELDS = [
31
- 'connection.id',
32
- 'gsm.apn',
33
- 'gsm.username',
34
- 'gsm.password',
35
- 'gsm.password-flags',
36
- 'gsm.home-only',
37
- 'gsm.network-id',
38
- 'gsm.auto-config',
39
- 'connection.autoconnect',
40
- 'connection.autoconnect-retries',
41
- ];
42
-
43
- /** Default abandoned-lease TTL: a held quiesce lease older than this auto-releases. */
44
- const DEFAULT_LEASE_TTL_MS = 60_000;
45
-
46
- export interface NmcliNmPortOptions {
47
- readonly runner: NmcliRunner;
48
- /** Resolved once at boot by the capability probe; default `true`. */
49
- readonly autoApnCapable?: boolean;
50
- readonly leaseTtlMs?: number;
51
- readonly now?: () => number;
52
- }
53
-
54
- export class NmcliNmPort implements NetworkManagerPort {
55
- readonly #runner: NmcliRunner;
56
- readonly #autoApnCapable: boolean;
57
- readonly #leaseTtlMs: number;
58
- readonly #now: () => number;
59
- readonly #leases = new Set<QuiesceLease>();
60
-
61
- constructor(options: NmcliNmPortOptions) {
62
- this.#runner = options.runner;
63
- this.#autoApnCapable = options.autoApnCapable ?? true;
64
- this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
65
- this.#now = options.now ?? Date.now;
66
- }
67
-
68
- async createGsmProfile(profile: GsmProfileInput): Promise<GsmProfile> {
69
- const result = await runNmcli(this.#runner, createGsmArgs(profile));
70
- const uuid = /\(([^)]+)\) successfully added/.exec(result.stdout)?.[1];
71
- if (uuid === undefined) {
72
- throw new Error(`nmcli connection add failed: ${result.stderr || result.stdout}`);
73
- }
74
- return this.#requireProfile(connectionId(uuid));
75
- }
76
-
77
- async readGsmProfile(id: ConnectionId): Promise<GsmProfile | undefined> {
78
- const result = await runNmcli(this.#runner, [
79
- '-t',
80
- '-f',
81
- READBACK_FIELDS.join(','),
82
- 'connection',
83
- 'show',
84
- id,
85
- ]);
86
- if (result.exitCode !== 0) {
87
- return undefined;
88
- }
89
- return buildProfile(id, parseTerse(result.stdout));
90
- }
91
-
92
- async updateGsmProfile(id: ConnectionId, patch: GsmProfilePatch): Promise<GsmProfile> {
93
- const result = await runNmcli(this.#runner, ['connection', 'modify', id, ...patchPairs(patch)]);
94
- if (result.exitCode !== 0) {
95
- throw new Error(`nmcli connection modify failed: ${result.stderr}`);
96
- }
97
- return this.#requireProfile(id);
98
- }
99
-
100
- async deleteGsmProfile(id: ConnectionId): Promise<void> {
101
- await runNmcli(this.#runner, ['connection', 'delete', id]);
102
- }
103
-
104
- async activate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt> {
105
- const result = await runNmcli(this.#runner, ['connection', 'up', id, 'ifname', ifname]);
106
- return activationReceipt(result, `activated ${id} on ${ifname}`);
107
- }
108
-
109
- async deactivate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt> {
110
- if ((await this.#activeUuidOn(ifname)) !== id) {
111
- return receipt('enabled', 'applied', `${id} not active on ${ifname}; nothing to deactivate`);
112
- }
113
- const result = await runNmcli(this.#runner, ['device', 'disconnect', ifname]);
114
- return activationReceipt(result, `deactivated ${id} on ${ifname}`);
115
- }
116
-
117
- async acquireQuiesceLease(id: ConnectionId, ifname: DeviceIfname): Promise<QuiesceLease> {
118
- if ((await this.#activeUuidOn(ifname)) === id) {
119
- await runNmcli(this.#runner, ['device', 'disconnect', ifname]);
120
- }
121
- const lease: QuiesceLease = {
122
- connectionId: id,
123
- deviceIfname: ifname,
124
- acquiredAt: epochMillis(this.#now()),
125
- };
126
- this.#leases.add(lease);
127
- return lease;
128
- }
129
-
130
- async releaseQuiesceLease(lease: QuiesceLease): Promise<void> {
131
- if (this.#leases.delete(lease)) {
132
- await this.#reactivate(lease);
133
- }
134
- }
135
-
136
- /** Reactivate every lease held past the TTL — the abandoned-lease watchdog. */
137
- async sweepExpiredLeases(now: number = this.#now()): Promise<void> {
138
- for (const lease of [...this.#leases]) {
139
- if (now - lease.acquiredAt >= this.#leaseTtlMs) {
140
- this.#leases.delete(lease);
141
- await this.#reactivate(lease);
142
- }
143
- }
144
- }
145
-
146
- /** Flip a profile to Auto-APN via ONE atomic modify, then reactivate + classify. */
147
- async transitionToAuto(id: ConnectionId, ifname: DeviceIfname): Promise<AutoApnTransitionResult> {
148
- if (!this.#autoApnCapable) {
149
- return {
150
- receipt: receipt('connection', 'unsupported', 'Auto-APN requires NetworkManager >= 1.22'),
151
- advisory: AUTO_APN_ADVISORY,
152
- };
153
- }
154
- const modify = await runNmcli(this.#runner, toAutoArgs(id));
155
- if (modify.exitCode !== 0) {
156
- return {
157
- receipt: receipt('connection', 'failed', modify.stderr || 'auto-config modify rejected'),
158
- };
159
- }
160
- return this.#reactivateAndClassify(id, ifname, true);
161
- }
162
-
163
- /** Flip a profile to manual APN via ONE atomic modify, then reactivate + classify. */
164
- async transitionToManual(
165
- id: ConnectionId,
166
- ifname: DeviceIfname,
167
- creds: ManualApn,
168
- ): Promise<AutoApnTransitionResult> {
169
- const modify = await runNmcli(this.#runner, toManualArgs(id, creds));
170
- if (modify.exitCode !== 0) {
171
- return {
172
- receipt: receipt('connection', 'failed', modify.stderr || 'manual-apn modify rejected'),
173
- };
174
- }
175
- return this.#reactivateAndClassify(id, ifname, false);
176
- }
177
-
178
- async #reactivateAndClassify(
179
- id: ConnectionId,
180
- ifname: DeviceIfname,
181
- underAuto: boolean,
182
- ): Promise<AutoApnTransitionResult> {
183
- const up = await runNmcli(this.#runner, ['connection', 'up', id, 'ifname', ifname]);
184
- return classifyActivation(up, { underAuto });
185
- }
186
-
187
- async #reactivate(lease: QuiesceLease): Promise<void> {
188
- await runNmcli(this.#runner, [
189
- 'connection',
190
- 'up',
191
- lease.connectionId,
192
- 'ifname',
193
- lease.deviceIfname,
194
- ]);
195
- }
196
-
197
- async #activeUuidOn(ifname: DeviceIfname): Promise<string | undefined> {
198
- const result = await runNmcli(this.#runner, [
199
- '-t',
200
- '-f',
201
- 'UUID,DEVICE',
202
- 'connection',
203
- 'show',
204
- '--active',
205
- ]);
206
- for (const line of result.stdout.split('\n')) {
207
- const [uuid, device] = line.split(':');
208
- if (device === ifname && uuid) {
209
- return uuid;
210
- }
211
- }
212
- return undefined;
213
- }
214
-
215
- async #requireProfile(id: ConnectionId): Promise<GsmProfile> {
216
- const profile = await this.readGsmProfile(id);
217
- if (profile === undefined) {
218
- throw new Error(`nmcli: profile ${id} did not read back`);
219
- }
220
- return profile;
221
- }
222
- }
223
-
224
- function activationReceipt(result: NmcliResult, appliedReason: string): Receipt {
225
- return result.exitCode === 0
226
- ? receipt('enabled', 'applied', appliedReason)
227
- : receipt('enabled', 'failed', result.stderr || 'nmcli returned a non-zero exit code');
228
- }
@@ -1,52 +0,0 @@
1
- // The nmcli invocation seam. The shipping adapter spawns the real `nmcli`; tests
2
- // inject A2.3's stateful in-memory runner (structurally the same `run(argv)`). The
3
- // port never spawns directly — it only ever talks to an injected `NmcliRunner`, so
4
- // the exact code path exercised under test is the one that runs on-device.
5
-
6
- export interface NmcliResult {
7
- readonly stdout: string;
8
- readonly stderr: string;
9
- readonly exitCode: number;
10
- }
11
-
12
- /**
13
- * A runner over nmcli argv. `run` may be synchronous (the stateful test double) or
14
- * asynchronous (the real `Bun.spawn` adapter) — the port awaits either way.
15
- */
16
- export interface NmcliRunner {
17
- run(argv: readonly string[]): NmcliResult | Promise<NmcliResult>;
18
- }
19
-
20
- /** Await a runner's result whether it returned synchronously or as a promise. */
21
- export async function runNmcli(runner: NmcliRunner, argv: readonly string[]): Promise<NmcliResult> {
22
- return runner.run(argv);
23
- }
24
-
25
- /**
26
- * The device-exact runner: spawns the real `nmcli` via Bun. Every argv the port
27
- * builds is passed verbatim, so what the tests assert against the stateful stub is
28
- * byte-for-byte what runs on the device.
29
- */
30
- export class SpawnNmcliRunner implements NmcliRunner {
31
- async run(argv: readonly string[]): Promise<NmcliResult> {
32
- const proc = Bun.spawn(['nmcli', ...argv], { stdout: 'pipe', stderr: 'pipe' });
33
- const [stdout, stderr, exitCode] = await Promise.all([
34
- new Response(proc.stdout).text(),
35
- new Response(proc.stderr).text(),
36
- proc.exited,
37
- ]);
38
- return { stdout, stderr, exitCode };
39
- }
40
- }
41
-
42
- /** Parse nmcli terse (`-t`) `key:value` lines into a map, splitting on the first `:`. */
43
- export function parseTerse(stdout: string): Map<string, string> {
44
- const settings = new Map<string, string>();
45
- for (const line of stdout.split('\n')) {
46
- const separator = line.indexOf(':');
47
- if (separator >= 0) {
48
- settings.set(line.slice(0, separator), line.slice(separator + 1));
49
- }
50
- }
51
- return settings;
52
- }