@ceralive/modem-control 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +196 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +19 -0
  146. package/dist/index.js +26 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +47 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
@@ -0,0 +1,68 @@
1
+ import type { Brand, EpochMillis } from '../domain/index.js';
2
+ import type { Receipt } from './receipts.js';
3
+ /** A NetworkManager connection-profile UUID. */
4
+ export type ConnectionId = Brand<string, 'ConnectionId'>;
5
+ /** A kernel network-device interface name (e.g. `wwan0`). */
6
+ export type DeviceIfname = Brand<string, 'DeviceIfname'>;
7
+ /** Construct a `ConnectionId` from a non-empty NM connection UUID. */
8
+ export declare function connectionId(value: string): ConnectionId;
9
+ /** Construct a `DeviceIfname` from a non-empty interface name. */
10
+ export declare function deviceIfname(value: string): DeviceIfname;
11
+ /**
12
+ * A GSM connection profile — NM's `gsm.*` setting group. `password` is SENSITIVE
13
+ * and MUST be redacted in every log / output (see `../redact`). The full nine-field
14
+ * nmcli write parity lands with the concrete adapter (A4.1); this is the port
15
+ * contract that adapter fulfils.
16
+ */
17
+ export interface GsmProfileInput {
18
+ readonly connectionName: string;
19
+ /** Concrete APN, or the empty string when `autoConfig` drives it (A4.1). */
20
+ readonly apn: string;
21
+ readonly username?: string;
22
+ /** SENSITIVE — redact everywhere. */
23
+ readonly password?: string;
24
+ /** `true` ⇒ `gsm.home-only` (roaming disabled). */
25
+ readonly homeOnly: boolean;
26
+ /** `true` ⇒ `gsm.auto-config yes` (Auto-APN); mutually exclusive with creds. */
27
+ readonly autoConfig: boolean;
28
+ /** Manual operator selection (`gsm.network-id`); empty unless roaming-pinned. */
29
+ readonly networkId?: string;
30
+ }
31
+ /** A persisted GSM profile as read back from NM, keyed by its connection id. */
32
+ export interface GsmProfile extends GsmProfileInput {
33
+ readonly connectionId: ConnectionId;
34
+ }
35
+ /** A partial update to an existing GSM profile. */
36
+ export type GsmProfilePatch = Partial<GsmProfileInput>;
37
+ /**
38
+ * A quiesce lease — a held guarantee that the connection on THIS exact device stays
39
+ * deactivated while a disruptive MM operation runs, then is reactivated on release.
40
+ * Bound to BOTH `connectionId` and `deviceIfname` so it can never quiesce the wrong
41
+ * device (the two-device isolation invariant, A4.1).
42
+ */
43
+ export interface QuiesceLease {
44
+ readonly connectionId: ConnectionId;
45
+ readonly deviceIfname: DeviceIfname;
46
+ readonly acquiredAt: EpochMillis;
47
+ }
48
+ /**
49
+ * The NetworkManager port. Activation and deactivation take BOTH the connection id
50
+ * AND the device interface name — never an id alone. This encodes, at the type
51
+ * level, that we address a connection on an EXACT device (`nmcli connection up
52
+ * <uuid> ifname <dev>` / verify-then-`device disconnect <ifname>`); the id-only
53
+ * `nmcli connection down` is structurally impossible to express here (A4.1).
54
+ */
55
+ export interface NetworkManagerPort {
56
+ createGsmProfile(profile: GsmProfileInput): Promise<GsmProfile>;
57
+ readGsmProfile(id: ConnectionId): Promise<GsmProfile | undefined>;
58
+ updateGsmProfile(id: ConnectionId, patch: GsmProfilePatch): Promise<GsmProfile>;
59
+ deleteGsmProfile(id: ConnectionId): Promise<void>;
60
+ /** Activate `id` on the EXACT device `ifname`. */
61
+ activate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt>;
62
+ /** Deactivate `id` on the EXACT device `ifname`. */
63
+ deactivate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt>;
64
+ /** Take a quiesce lease over `id` on the EXACT device `ifname`. */
65
+ acquireQuiesceLease(id: ConnectionId, ifname: DeviceIfname): Promise<QuiesceLease>;
66
+ /** Release a quiesce lease, reactivating the connection it held down. */
67
+ releaseQuiesceLease(lease: QuiesceLease): Promise<void>;
68
+ }
@@ -0,0 +1,13 @@
1
+ // The NetworkManager port — NM is the SOLE owner of bearers, APN, auth, roaming,
2
+ // autoconnect, and activation (see README ownership table). This port is the ONLY
3
+ // way the controller touches any of those resources; the ModemManager port has no
4
+ // bearer / connection verbs at all.
5
+ import { nonEmptyString } from '../domain/index.js';
6
+ /** Construct a `ConnectionId` from a non-empty NM connection UUID. */
7
+ export function connectionId(value) {
8
+ return nonEmptyString(value, 'connectionId');
9
+ }
10
+ /** Construct a `DeviceIfname` from a non-empty interface name. */
11
+ export function deviceIfname(value) {
12
+ return nonEmptyString(value, 'deviceIfname');
13
+ }
@@ -1,18 +1,8 @@
1
- // The observation contract the read side shared by every ModemManager backend.
2
- //
3
- // `ModemObservationPort` is the NARROW port: it observes modems and reports them as
4
- // a stream of discriminated list results. It carries NO mutation methods at all.
5
- // The full `ModemManagerPort` (mutations) EXTENDS this, so a shadow / read-only
6
- // consumer can depend on observation alone and never gain a mutating verb.
7
-
8
- import type { CellularSnapshot } from '../domain';
9
-
1
+ import type { CellularSnapshot } from '../domain/index.js';
10
2
  /** Cancels a subscription created by `observe`. Calling it twice is a no-op. */
11
3
  export type Unsubscribe = () => void;
12
-
13
4
  /** Why an observation could not produce an authoritative list this cycle. */
14
5
  export type ObservationFailureReason = 'not-started' | 'source-unavailable' | 'bus-error';
15
-
16
6
  /**
17
7
  * The result of listing modems — DISCRIMINATED on `ok`, and it NEVER throws away
18
8
  * rows. Even the failure arm carries `rows`: the last-known modems are RETAINED so
@@ -20,18 +10,17 @@ export type ObservationFailureReason = 'not-started' | 'source-unavailable' | 'b
20
10
  * expressed by an `ok: true` snapshot that OMITS a modem (A3.1 epoch authority) —
21
11
  * the false-removal class is dead by construction.
22
12
  */
23
- export type ObservationList<T = CellularSnapshot> =
24
- | { readonly ok: true; readonly rows: readonly T[] }
25
- | {
26
- readonly ok: false;
27
- readonly reason: ObservationFailureReason;
28
- /** Retained rows from the last authoritative snapshot — never dropped. */
29
- readonly rows: readonly T[];
30
- };
31
-
13
+ export type ObservationList<T = CellularSnapshot> = {
14
+ readonly ok: true;
15
+ readonly rows: readonly T[];
16
+ } | {
17
+ readonly ok: false;
18
+ readonly reason: ObservationFailureReason;
19
+ /** Retained rows from the last authoritative snapshot — never dropped. */
20
+ readonly rows: readonly T[];
21
+ };
32
22
  /** A subscriber to the ongoing observation stream. */
33
23
  export type ObservationListener<T = CellularSnapshot> = (list: ObservationList<T>) => void;
34
-
35
24
  /**
36
25
  * The read-only observation port. `start()` connects, subscribes, and resolves the
37
26
  * first AUTHORITATIVE list; `observe()` streams every subsequent list (each snapshot
@@ -39,10 +28,10 @@ export type ObservationListener<T = CellularSnapshot> = (list: ObservationList<T
39
28
  * here can change modem state — that narrowness is the whole point of the port.
40
29
  */
41
30
  export interface ModemObservationPort {
42
- /** Connect, subscribe, and resolve the first authoritative observation list. */
43
- start(): Promise<ObservationList>;
44
- /** Subscribe to the ongoing stream of observation lists. Returns an unsubscribe. */
45
- observe(listener: ObservationListener): Unsubscribe;
46
- /** Tear down the subscription and release the source. Idempotent. */
47
- stop(): Promise<void>;
31
+ /** Connect, subscribe, and resolve the first authoritative observation list. */
32
+ start(): Promise<ObservationList>;
33
+ /** Subscribe to the ongoing stream of observation lists. Returns an unsubscribe. */
34
+ observe(listener: ObservationListener): Unsubscribe;
35
+ /** Tear down the subscription and release the source. Idempotent. */
36
+ stop(): Promise<void>;
48
37
  }
@@ -0,0 +1,7 @@
1
+ // The observation contract — the read side shared by every ModemManager backend.
2
+ //
3
+ // `ModemObservationPort` is the NARROW port: it observes modems and reports them as
4
+ // a stream of discriminated list results. It carries NO mutation methods at all.
5
+ // The full `ModemManagerPort` (mutations) EXTENDS this, so a shadow / read-only
6
+ // consumer can depend on observation alone and never gain a mutating verb.
7
+ export {};
@@ -0,0 +1,44 @@
1
+ import type { DesiredRadio } from '../domain/index.js';
2
+ import type { ConnectionId, DeviceIfname, GsmProfileInput, GsmProfilePatch } from './network-manager.js';
3
+ /** Radio + SIM ops — owned SOLELY by the ModemManager port. */
4
+ export type MmOp = {
5
+ readonly kind: 'setRadioModes';
6
+ readonly preference: DesiredRadio;
7
+ } | {
8
+ readonly kind: 'setPrimarySimSlot';
9
+ readonly slotIndex: number;
10
+ };
11
+ /** Connection / APN / activation ops — owned SOLELY by the NetworkManager port. */
12
+ export type NmOp = {
13
+ readonly kind: 'createGsmProfile';
14
+ readonly profile: GsmProfileInput;
15
+ } | {
16
+ readonly kind: 'updateGsmProfile';
17
+ readonly connectionId: ConnectionId;
18
+ readonly patch: GsmProfilePatch;
19
+ } | {
20
+ readonly kind: 'activate';
21
+ readonly connectionId: ConnectionId;
22
+ readonly deviceIfname: DeviceIfname;
23
+ } | {
24
+ readonly kind: 'deactivate';
25
+ readonly connectionId: ConnectionId;
26
+ readonly deviceIfname: DeviceIfname;
27
+ };
28
+ /**
29
+ * A planned op tagged with its owning port. The two arms are disjoint on BOTH the
30
+ * `port` discriminant AND the op `kind` space, so the planner cannot emit — and a
31
+ * reviewer cannot write — a radio op under the NM port, or a connection op under
32
+ * the MM port.
33
+ */
34
+ export type PortTaggedOp = {
35
+ readonly port: 'mm';
36
+ readonly op: MmOp;
37
+ } | {
38
+ readonly port: 'nm';
39
+ readonly op: NmOp;
40
+ };
41
+ /** Tag an MM op for the ModemManager port. Only an `MmOp` is accepted. */
42
+ export declare function mmOp(op: MmOp): PortTaggedOp;
43
+ /** Tag an NM op for the NetworkManager port. Only an `NmOp` is accepted. */
44
+ export declare function nmOp(op: NmOp): PortTaggedOp;
@@ -0,0 +1,16 @@
1
+ // Port-tagged reconcile ops — a planned mutation TAGGED with the port that owns it.
2
+ //
3
+ // The op kinds are DISJOINT by construction: a radio / SIM op (`MmOp`) shares no
4
+ // `kind` with a connection / APN op (`NmOp`). Tagging is therefore type-checked —
5
+ // `{ port: 'nm', op: { kind: 'setRadioModes', … } }` does NOT compile, because a
6
+ // radio op is not assignable to the NM arm. That mis-tag is a COMPILE-TIME error
7
+ // (see `ops.type-test.ts` for the `@ts-expect-error` proofs). This is the ownership
8
+ // matrix (README) enforced by the type system, not merely by convention.
9
+ /** Tag an MM op for the ModemManager port. Only an `MmOp` is accepted. */
10
+ export function mmOp(op) {
11
+ return { port: 'mm', op };
12
+ }
13
+ /** Tag an NM op for the NetworkManager port. Only an `NmOp` is accepted. */
14
+ export function nmOp(op) {
15
+ return { port: 'nm', op };
16
+ }
@@ -1,19 +1,5 @@
1
- // Reconcile receipts — the HONEST outcome of trying to apply one policy dimension.
2
- //
3
- // Every attempt to converge a dimension yields exactly one receipt with a status
4
- // AND a reason. Nothing is ever silently dropped: "prefer 5G" on a 4G-only modem is
5
- // reported as `unsupported` with a reason, never quietly downgraded to "5G off".
6
-
7
1
  /** The policy dimension a receipt is about. */
8
- export type PolicyDimension =
9
- | 'enabled'
10
- | 'connection'
11
- | 'roaming'
12
- | 'radio'
13
- | 'simSlot'
14
- | 'recovery'
15
- | 'usage';
16
-
2
+ export type PolicyDimension = 'enabled' | 'connection' | 'roaming' | 'radio' | 'band' | 'simSlot' | 'recovery' | 'usage';
17
3
  /**
18
4
  * Receipt status taxonomy:
19
5
  * - applied — the desired state is in effect (converged, or already was).
@@ -22,22 +8,14 @@ export type PolicyDimension =
22
8
  * - failed — an op was attempted and errored.
23
9
  */
24
10
  export type ReceiptStatus = 'applied' | 'pending' | 'unsupported' | 'failed';
25
-
26
11
  /**
27
12
  * One reconcile receipt. `reason` is ALWAYS populated — a receipt with no reason
28
13
  * would be a silent outcome, which the contract forbids.
29
14
  */
30
15
  export interface Receipt {
31
- readonly dimension: PolicyDimension;
32
- readonly status: ReceiptStatus;
33
- readonly reason: string;
16
+ readonly dimension: PolicyDimension;
17
+ readonly status: ReceiptStatus;
18
+ readonly reason: string;
34
19
  }
35
-
36
20
  /** Build a receipt. Kept as a helper so `reason` can never be forgotten. */
37
- export function receipt(
38
- dimension: PolicyDimension,
39
- status: ReceiptStatus,
40
- reason: string,
41
- ): Receipt {
42
- return { dimension, status, reason };
43
- }
21
+ export declare function receipt(dimension: PolicyDimension, status: ReceiptStatus, reason: string): Receipt;
@@ -0,0 +1,9 @@
1
+ // Reconcile receipts — the HONEST outcome of trying to apply one policy dimension.
2
+ //
3
+ // Every attempt to converge a dimension yields exactly one receipt with a status
4
+ // AND a reason. Nothing is ever silently dropped: "prefer 5G" on a 4G-only modem is
5
+ // reported as `unsupported` with a reason, never quietly downgraded to "5G off".
6
+ /** Build a receipt. Kept as a helper so `reason` can never be forgotten. */
7
+ export function receipt(dimension, status, reason) {
8
+ return { dimension, status, reason };
9
+ }
@@ -0,0 +1,33 @@
1
+ import type { DesiredCellularPolicy, NmActivation, RadioAccessTechnology } from '../domain/index.js';
2
+ import type { ConnectionId, DeviceIfname } from './network-manager.js';
3
+ import { type PortTaggedOp } from './ops.js';
4
+ import { type Receipt } from './receipts.js';
5
+ /** What the modem / stack can actually do — the capability set the planner honors. */
6
+ export interface ModemCapabilities {
7
+ readonly supportedRats: ReadonlySet<RadioAccessTechnology>;
8
+ readonly simSlotCount: number;
9
+ readonly supportsAutoApn: boolean;
10
+ }
11
+ /**
12
+ * The currently-applied cellular configuration, as the planner sees it — the "is"
13
+ * state, distinct from the observational snapshot: it tracks what has actually been
14
+ * written (profile, radio preference, primary slot) plus the live NM activation.
15
+ */
16
+ export interface AppliedCellularState {
17
+ readonly nmActivation: NmActivation;
18
+ readonly hasProfile: boolean;
19
+ readonly connectionId?: ConnectionId;
20
+ readonly deviceIfname?: DeviceIfname;
21
+ readonly appliedApn?: 'auto' | string;
22
+ readonly appliedRoaming?: boolean;
23
+ readonly appliedRadioPreference?: readonly RadioAccessTechnology[];
24
+ readonly activePrimarySlot?: number;
25
+ readonly activationFailureReason?: string;
26
+ }
27
+ /** A reconcile plan: the ops to run and one receipt per policy dimension. */
28
+ export interface Plan {
29
+ readonly ops: readonly PortTaggedOp[];
30
+ readonly receipts: readonly Receipt[];
31
+ }
32
+ /** Reconcile a desired policy against the applied state into ops + receipts. */
33
+ export declare function planReconcile(current: AppliedCellularState, desired: DesiredCellularPolicy, capabilities: ModemCapabilities): Plan;
@@ -0,0 +1,200 @@
1
+ // The desired-state planner — pure. Given the currently-applied state, a desired
2
+ // policy, and the modem's capabilities, it produces the port-tagged ops needed to
3
+ // converge PLUS an honest receipt per policy dimension. It performs no I/O and no
4
+ // side effects: the same inputs always yield the same ops and receipts, which is
5
+ // what makes re-applying a policy idempotent (see reconcile.test.ts).
6
+ import { mmOp, nmOp } from './ops.js';
7
+ import { receipt } from './receipts.js';
8
+ /** Reconcile a desired policy against the applied state into ops + receipts. */
9
+ export function planReconcile(current, desired, capabilities) {
10
+ const profile = planNmProfile(current, desired, capabilities);
11
+ const results = [
12
+ planRadio(current, desired, capabilities),
13
+ planSimSlot(current, desired, capabilities),
14
+ planEnabled(current, desired),
15
+ { receipt: planRecovery(desired) },
16
+ { receipt: planUsage(desired) },
17
+ ];
18
+ const ops = [];
19
+ const receipts = [profile.connection, profile.roaming];
20
+ if (profile.op !== undefined) {
21
+ ops.push(profile.op);
22
+ }
23
+ for (const result of results) {
24
+ receipts.push(result.receipt);
25
+ if (result.op !== undefined) {
26
+ ops.push(result.op);
27
+ }
28
+ }
29
+ return { ops, receipts };
30
+ }
31
+ function profileFromPolicy(desired) {
32
+ const auto = desired.connection.apn === 'auto';
33
+ const base = {
34
+ connectionName: 'ceralive-cellular',
35
+ apn: auto ? '' : desired.connection.apn,
36
+ homeOnly: !desired.roaming,
37
+ autoConfig: auto,
38
+ };
39
+ // SENSITIVE creds only when explicitly provided AND not in auto-config mode.
40
+ if (!auto && desired.connection.auth !== undefined) {
41
+ const { username, password } = desired.connection.auth;
42
+ return {
43
+ ...base,
44
+ ...(username !== undefined ? { username } : {}),
45
+ ...(password !== undefined ? { password } : {}),
46
+ };
47
+ }
48
+ return base;
49
+ }
50
+ // NM owns APN / auth / roaming / autoconnect: one profile write converges all of
51
+ // them, so connection + roaming share a single op and each get their own receipt.
52
+ function planNmProfile(current, desired, capabilities) {
53
+ const auto = desired.connection.apn === 'auto';
54
+ if (auto && !capabilities.supportsAutoApn) {
55
+ return {
56
+ connection: receipt('connection', 'unsupported', 'Auto-APN is not available on this NetworkManager / modem'),
57
+ roaming: receipt('roaming', 'unsupported', 'roaming cannot be applied without a connection profile'),
58
+ };
59
+ }
60
+ const needsWrite = !current.hasProfile ||
61
+ current.appliedApn !== desired.connection.apn ||
62
+ current.appliedRoaming !== desired.roaming;
63
+ if (!needsWrite) {
64
+ return {
65
+ connection: receipt('connection', 'applied', `connection APN '${desired.connection.apn}' already configured`),
66
+ roaming: receipt('roaming', 'applied', `roaming already ${desired.roaming ? 'enabled' : 'disabled'}`),
67
+ };
68
+ }
69
+ const op = current.hasProfile && current.connectionId !== undefined
70
+ ? nmOp({
71
+ kind: 'updateGsmProfile',
72
+ connectionId: current.connectionId,
73
+ patch: profileFromPolicy(desired),
74
+ })
75
+ : nmOp({ kind: 'createGsmProfile', profile: profileFromPolicy(desired) });
76
+ const verb = current.hasProfile ? 'updating' : 'creating';
77
+ return {
78
+ connection: receipt('connection', 'pending', `${verb} connection profile for APN '${desired.connection.apn}'`),
79
+ roaming: receipt('roaming', 'pending', `roaming ${desired.roaming ? 'enabled' : 'disabled'} via connection profile`),
80
+ op,
81
+ };
82
+ }
83
+ function radioMatches(applied, desired) {
84
+ if (applied === undefined || applied.length !== desired.length) {
85
+ return false;
86
+ }
87
+ return applied.every((rat, index) => rat === desired[index]);
88
+ }
89
+ function planRadio(current, desired, capabilities) {
90
+ const preference = desired.radio.preferenceOrdered;
91
+ const top = preference[0];
92
+ if (top === undefined) {
93
+ return {
94
+ receipt: receipt('radio', 'failed', 'radio preference must list at least one access technology'),
95
+ };
96
+ }
97
+ if (!capabilities.supportedRats.has(top)) {
98
+ const supported = [...capabilities.supportedRats].join(', ') || 'none';
99
+ return {
100
+ receipt: receipt('radio', 'unsupported', `preferred radio access technology '${top}' is not supported by this modem (supports ${supported})`),
101
+ };
102
+ }
103
+ if (radioMatches(current.appliedRadioPreference, preference)) {
104
+ return {
105
+ receipt: receipt('radio', 'applied', `radio preference already ${preference.join(' > ')}`),
106
+ };
107
+ }
108
+ return {
109
+ receipt: receipt('radio', 'pending', `setting radio preference to ${preference.join(' > ')}`),
110
+ op: mmOp({ kind: 'setRadioModes', preference: desired.radio }),
111
+ };
112
+ }
113
+ function planSimSlot(current, desired, capabilities) {
114
+ if (desired.simSlot === undefined) {
115
+ return { receipt: receipt('simSlot', 'applied', 'no primary SIM slot preference set') };
116
+ }
117
+ if (capabilities.simSlotCount <= 1) {
118
+ return {
119
+ receipt: receipt('simSlot', 'unsupported', `primary SIM slot selection requires a multi-slot modem (this modem has ${capabilities.simSlotCount})`),
120
+ };
121
+ }
122
+ if (desired.simSlot < 1 || desired.simSlot > capabilities.simSlotCount) {
123
+ return {
124
+ receipt: receipt('simSlot', 'failed', `SIM slot ${desired.simSlot} is out of range (1..${capabilities.simSlotCount})`),
125
+ };
126
+ }
127
+ if (current.activePrimarySlot === desired.simSlot) {
128
+ return {
129
+ receipt: receipt('simSlot', 'applied', `SIM slot ${desired.simSlot} already primary`),
130
+ };
131
+ }
132
+ return {
133
+ receipt: receipt('simSlot', 'pending', `switching primary SIM slot to ${desired.simSlot}`),
134
+ op: mmOp({ kind: 'setPrimarySimSlot', slotIndex: desired.simSlot }),
135
+ };
136
+ }
137
+ function planEnabled(current, desired) {
138
+ const nm = current.nmActivation;
139
+ const addressable = current.connectionId !== undefined && current.deviceIfname !== undefined;
140
+ if (desired.enabled) {
141
+ switch (nm) {
142
+ case 'activated':
143
+ return { receipt: receipt('enabled', 'applied', 'connection is active') };
144
+ case 'activating':
145
+ return { receipt: receipt('enabled', 'pending', 'connection activation in progress') };
146
+ case 'failed':
147
+ return {
148
+ receipt: receipt('enabled', 'failed', `connection activation failed${current.activationFailureReason ? `: ${current.activationFailureReason}` : ''}`),
149
+ };
150
+ case 'unmanaged':
151
+ return {
152
+ receipt: receipt('enabled', 'unsupported', 'device is not managed by NetworkManager'),
153
+ };
154
+ default:
155
+ if (!addressable ||
156
+ current.connectionId === undefined ||
157
+ current.deviceIfname === undefined) {
158
+ return {
159
+ receipt: receipt('enabled', 'pending', 'awaiting connection profile and data interface before activation'),
160
+ };
161
+ }
162
+ return {
163
+ receipt: receipt('enabled', 'pending', 'activating connection'),
164
+ op: nmOp({
165
+ kind: 'activate',
166
+ connectionId: current.connectionId,
167
+ deviceIfname: current.deviceIfname,
168
+ }),
169
+ };
170
+ }
171
+ }
172
+ if ((nm === 'activated' || nm === 'activating') &&
173
+ current.connectionId !== undefined &&
174
+ current.deviceIfname !== undefined) {
175
+ return {
176
+ receipt: receipt('enabled', 'pending', 'deactivating connection'),
177
+ op: nmOp({
178
+ kind: 'deactivate',
179
+ connectionId: current.connectionId,
180
+ deviceIfname: current.deviceIfname,
181
+ }),
182
+ };
183
+ }
184
+ return { receipt: receipt('enabled', 'applied', 'connection is inactive') };
185
+ }
186
+ // Recovery + usage are LOCAL-CONTROLLER owned (README ownership table): they emit no
187
+ // MM / NM op, only a receipt recording that the local policy was accepted.
188
+ function planRecovery(desired) {
189
+ return receipt('recovery', 'applied', desired.recovery.enabled ? 'recovery policy recorded (enabled)' : 'recovery disabled (default)');
190
+ }
191
+ function planUsage(desired) {
192
+ const parts = [];
193
+ if (desired.usage.cycleDay !== undefined) {
194
+ parts.push(`cycle day ${desired.usage.cycleDay}`);
195
+ }
196
+ if (desired.usage.thresholdBytes !== undefined) {
197
+ parts.push(`threshold ${desired.usage.thresholdBytes} bytes`);
198
+ }
199
+ return receipt('usage', 'applied', parts.length > 0 ? `usage policy recorded (${parts.join(', ')})` : 'no usage policy set');
200
+ }
@@ -0,0 +1,29 @@
1
+ export declare const DEFAULT_MODEM_CONTROL_LOCK_PATH = "/run/ceralive/modem-control.lock";
2
+ export type ExclusiveResource = 'file-store' | 'router-session' | 'usb-hub';
3
+ export type ResourceOwnershipRequest = {
4
+ readonly resource: ExclusiveResource;
5
+ };
6
+ export type ResourceOwnershipHolder = {
7
+ readonly pid: number;
8
+ readonly startedAtEpochMs: number;
9
+ };
10
+ export type ResourceOwnershipLoss = {
11
+ readonly reason: 'holder-exited';
12
+ };
13
+ export interface ResourceOwnershipLease {
14
+ readonly holder: ResourceOwnershipHolder;
15
+ readonly lost: Promise<ResourceOwnershipLoss>;
16
+ release(): Promise<void>;
17
+ }
18
+ export type ResourceOwnershipResult = {
19
+ readonly status: 'acquired';
20
+ readonly lease: ResourceOwnershipLease;
21
+ } | {
22
+ readonly status: 'refused';
23
+ readonly reason: 'already-owned';
24
+ readonly holder?: ResourceOwnershipHolder;
25
+ };
26
+ /** Acquire-or-refuse ownership; implementations must never queue a contender. */
27
+ export interface ResourceOwnershipPort {
28
+ acquire(request: ResourceOwnershipRequest): Promise<ResourceOwnershipResult>;
29
+ }
@@ -0,0 +1 @@
1
+ export const DEFAULT_MODEM_CONTROL_LOCK_PATH = '/run/ceralive/modem-control.lock';
@@ -0,0 +1,19 @@
1
+ import type { EpochMillis } from '../domain/index.js';
2
+ import type { DeviceIfname } from './network-manager.js';
3
+ /** Whether a router-class device is present on an interface. */
4
+ export type RouterPresence = 'present' | 'absent';
5
+ /**
6
+ * Advisory health of a router-class uplink. Every field is informational — a
7
+ * degraded router is reported, never removed from the routing set on health alone.
8
+ */
9
+ export interface RouterHealth {
10
+ readonly presence: RouterPresence;
11
+ readonly gatewayReachable: boolean;
12
+ readonly egressHealthy: boolean;
13
+ readonly observedAt: EpochMillis;
14
+ }
15
+ /** The router port — presence and advisory health only. No mutation verbs. */
16
+ export interface RouterPort {
17
+ probePresence(ifname: DeviceIfname): Promise<RouterPresence>;
18
+ checkHealth(ifname: DeviceIfname): Promise<RouterHealth>;
19
+ }
@@ -0,0 +1,7 @@
1
+ // The router port — for devices MM cannot control (HiLink, router-ethernet class).
2
+ //
3
+ // This port is DELIBERATELY tiny: presence and advisory health ONLY. Health never
4
+ // drives activation or recovery — a degraded router stays in the routing set (it is
5
+ // never evicted on health alone). A router device is an Ethernet uplink we OBSERVE,
6
+ // not a modem we configure; there is no bearer / APN / radio verb here.
7
+ export {};
@@ -0,0 +1,64 @@
1
+ import type { Unsubscribe } from './observation.js';
2
+ /** A message's lifecycle state, verbatim from ModemManager's own vocabulary. */
3
+ export type SmsState = 'unknown' | 'stored' | 'receiving' | 'received' | 'sending' | 'sent';
4
+ /** One stored message, normalized. `text` is `''` for a data-only (WAP/PDU) message. */
5
+ export interface SmsMessage {
6
+ /** The trailing `/SMS/<n>` object index, stringified. */
7
+ readonly id: string;
8
+ readonly from?: string;
9
+ /** The service-centre timestamp, verbatim as the device reported it. */
10
+ readonly timestamp?: string;
11
+ readonly text: string;
12
+ readonly state: SmsState;
13
+ }
14
+ /**
15
+ * Why an inbox read produced no list. These are the four operator-actionable
16
+ * facts, and they are deliberately distinct from one another — a modem with no
17
+ * Messaging interface, a radio that has not come up, a selector nothing answers
18
+ * to, and drift in whatever the source printed are four different next steps.
19
+ */
20
+ export type SmsReadRefusal = 'unsupported' | 'not_enabled' | 'unknown_modem' | 'read_failed';
21
+ /**
22
+ * The result of an inbox read. A REFUSAL IS NEVER AN EMPTY LIST: `{ok: true,
23
+ * messages: []}` means this modem has an inbox and it is empty.
24
+ */
25
+ export type SmsInboxResult = {
26
+ readonly ok: true;
27
+ readonly messages: readonly SmsMessage[];
28
+ } | {
29
+ readonly ok: false;
30
+ readonly reason: SmsReadRefusal;
31
+ };
32
+ /**
33
+ * One inbox change. `resynced` carries a full authoritative list and is what a
34
+ * source restart produces — a consumer replaces its rows with it rather than
35
+ * merging, so a restart can never duplicate a row it already holds.
36
+ */
37
+ export type SmsInboxEvent = {
38
+ readonly kind: 'added';
39
+ readonly message: SmsMessage;
40
+ } | {
41
+ readonly kind: 'deleted';
42
+ readonly id: string;
43
+ } | {
44
+ readonly kind: 'resynced';
45
+ readonly messages: readonly SmsMessage[];
46
+ };
47
+ export type SmsInboxListener = (event: SmsInboxEvent) => void;
48
+ /**
49
+ * The read-only SMS port. `list()` resolves the current inbox once; `observe()`
50
+ * streams every subsequent change; `stop()` releases the source.
51
+ *
52
+ * It is deliberately NOT an extension of `ModemManagerPort`: a consumer that
53
+ * only reads an inbox must not acquire radio, SIM, or inhibit verbs along the
54
+ * way — the same narrowing argument `ModemObservationPort` makes for the
55
+ * shadow reader.
56
+ */
57
+ export interface SmsObservationPort {
58
+ /** Read the modem's stored inbox, newest-first and capped. */
59
+ list(): Promise<SmsInboxResult>;
60
+ /** Subscribe to Added/Deleted/resync events. Returns an unsubscribe. */
61
+ observe(listener: SmsInboxListener): Unsubscribe;
62
+ /** Release the source. Idempotent. */
63
+ stop(): Promise<void>;
64
+ }