@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,71 @@
1
+ import type { DeviceGeneration } from '../../domain/index.js';
2
+ import type { AppliedConfiguration, DesiredProfile, ModemStateView, NormalizationContext, ObservedState, StateDivergence } from '../../observations/index.js';
3
+ import type { ConnectionId, DeviceIfname, GsmProfile, NetworkManagerPort } from '../../ports/index.js';
4
+ import type { NmApplyResult, NmBearerState, NmDesiredRequest, NmObservationInput, NmObservationResult, NmSaveResult } from './types.js';
5
+ export interface NetworkManagerAdapterOptions {
6
+ readonly port: NetworkManagerPort;
7
+ /** Clock for the desired/applied slots only; observations carry their own. */
8
+ readonly now?: () => number;
9
+ }
10
+ export interface NmApplyOptions {
11
+ readonly operationId: string;
12
+ readonly generation: DeviceGeneration;
13
+ }
14
+ export declare class NetworkManagerAdapter {
15
+ #private;
16
+ constructor(options: NetworkManagerAdapterOptions);
17
+ /** Every connection this adapter holds a slot for, in insertion order. */
18
+ trackedConnections(): readonly ConnectionId[];
19
+ /**
20
+ * Save an operator's profile and record it as DESIRED.
21
+ *
22
+ * The desired slot is built from what the caller ASKED for, not from what NM read
23
+ * back. That is the whole point of the slot: if NM normalized a field on the way
24
+ * in, the next `describeStateDivergence` is supposed to report it, and seeding
25
+ * desired from the readback would make that divergence structurally invisible.
26
+ */
27
+ saveDesiredProfile(request: NmDesiredRequest): Promise<NmSaveResult>;
28
+ /**
29
+ * Record that the operator wants NO bearer on this connection's device.
30
+ *
31
+ * It performs no I/O on purpose — desired is a record of intent, and the
32
+ * deactivation it implies happens when `applyDesired` is called, so a request and
33
+ * its execution stay two separately observable facts.
34
+ */
35
+ releaseDesired(id: ConnectionId, requestedBy: string): NmSaveResult;
36
+ desiredFor(id: ConnectionId): DesiredProfile<NmBearerState> | null;
37
+ /** The saved profile as NM itself holds it — a straight read, no slot involved. */
38
+ readSavedProfile(id: ConnectionId): Promise<GsmProfile | undefined>;
39
+ /**
40
+ * Put the desired state into force and record what was applied.
41
+ *
42
+ * The applied slot is built from the profile NM READ BACK, not from the request:
43
+ * "what was actually put into force" is a claim about NM's content, and asserting
44
+ * it from the input would make a silently-rejected field look applied.
45
+ */
46
+ applyDesired(id: ConnectionId, options: NmApplyOptions): Promise<NmApplyResult>;
47
+ appliedFor(id: ConnectionId): AppliedConfiguration<NmBearerState> | null;
48
+ /**
49
+ * The interface the applied bearer landed on, or `null` when no bearer is in
50
+ * force. An applied `unbound` state resolves to `null` rather than to its device:
51
+ * there is a device, but no bearer on it, and returning the name would read as one.
52
+ */
53
+ resolveAppliedInterface(id: ConnectionId): DeviceIfname | null;
54
+ /**
55
+ * Fold one complete NM readout.
56
+ *
57
+ * Never touches the desired slot. Clears the applied slot only when the readout
58
+ * positively contradicts it, and always says which of the four contradictions it
59
+ * was.
60
+ */
61
+ observe(input: NmObservationInput): NmObservationResult;
62
+ observedFor(id: ConnectionId): ObservedState<NmBearerState> | null;
63
+ /**
64
+ * The three-slot view. `context` supplies the provenance for the "we have not
65
+ * observed this yet" case — this layer has no clock or epoch counter of its own
66
+ * for observations, so it cannot manufacture one.
67
+ */
68
+ stateView(id: ConnectionId, context: NormalizationContext): ModemStateView<NmBearerState, NmBearerState, NmBearerState> | null;
69
+ /** `desiredVsApplied` ("did our write happen") and `appliedVsObserved` ("did it stick"). */
70
+ divergence(id: ConnectionId, context: NormalizationContext): StateDivergence | null;
71
+ }
@@ -0,0 +1,348 @@
1
+ // The thin NetworkManager adapter: desired profiles, applied bearers, and the
2
+ // interface each one landed on — the saved-vs-applied boundary and nothing else.
3
+ //
4
+ // It is the ONLY bearer/APN authority surface in the package. `ModemManagerPort` has
5
+ // no connect verb by construction (`ports/forbidden-surface.test.ts`), and the
6
+ // ModemManager provider has no bearer operation, so nothing else can put a bearer into
7
+ // force. Correspondingly, this adapter performs no radio, band, SIM or power operation:
8
+ // those belong to the ModemManager provider, and expressing them here would create a
9
+ // second writer for a resource the ownership matrix gives to exactly one.
10
+ //
11
+ // Two rules shape every method below.
12
+ //
13
+ // 1. OBSERVED STATE NEVER WRITES THE DESIRED SLOT. `observe()` may clear the applied
14
+ // slot and always rewrites the observed one; it does not touch `desired` on any
15
+ // path. Reality overtaking a write does not un-ask the question the operator asked
16
+ // — and if it did, a re-enumeration would silently erase the configuration the
17
+ // controller is supposed to restore.
18
+ // 2. WRITES GO THROUGH THE PORT'S TYPED OPERATIONS, AND ONLY THOSE. The NM half of the
19
+ // port-tagged op set (`ports/ops.ts` `NmOp`) is create / update / activate /
20
+ // deactivate. There is deliberately no delete path here: profile removal is not in
21
+ // that set, so this adapter cannot express it.
22
+ import { epochMillis } from '../../domain/index.js';
23
+ import { appliedConfiguration, describeStateDivergence, desiredProfile, freshObservation, observedState, unavailableObservation, } from '../../observations/index.js';
24
+ import { receipt } from '../../ports/index.js';
25
+ import { boundBearer, nmBearerStateEquals, unboundBearer } from './types.js';
26
+ const SOURCE = 'networkmanager';
27
+ /** The device states in which NM is still settling an activation, not losing it. */
28
+ const TRANSITIONAL_STATES = new Set([
29
+ 'prepare',
30
+ 'config',
31
+ 'need-auth',
32
+ 'ip-config',
33
+ 'ip-check',
34
+ 'secondaries',
35
+ 'deactivating',
36
+ ]);
37
+ export class NetworkManagerAdapter {
38
+ #port;
39
+ #now;
40
+ #slots = new Map();
41
+ #observedGeneration = null;
42
+ constructor(options) {
43
+ this.#port = options.port;
44
+ this.#now = options.now ?? Date.now;
45
+ }
46
+ /** Every connection this adapter holds a slot for, in insertion order. */
47
+ trackedConnections() {
48
+ return [...this.#slots.keys()];
49
+ }
50
+ // ── desired ────────────────────────────────────────────────────────────────
51
+ /**
52
+ * Save an operator's profile and record it as DESIRED.
53
+ *
54
+ * The desired slot is built from what the caller ASKED for, not from what NM read
55
+ * back. That is the whole point of the slot: if NM normalized a field on the way
56
+ * in, the next `describeStateDivergence` is supposed to report it, and seeding
57
+ * desired from the readback would make that divergence structurally invisible.
58
+ */
59
+ async saveDesiredProfile(request) {
60
+ let saved;
61
+ try {
62
+ saved =
63
+ request.connectionId === undefined
64
+ ? await this.#port.createGsmProfile(request.profile)
65
+ : await this.#port.updateGsmProfile(request.connectionId, request.profile);
66
+ }
67
+ catch (error) {
68
+ return {
69
+ ok: false,
70
+ reason: 'write-failed',
71
+ receipt: receipt('connection', 'failed', describeError(error)),
72
+ };
73
+ }
74
+ const id = saved.connectionId;
75
+ const desired = desiredProfile(boundBearer({
76
+ connectionId: id,
77
+ deviceIfname: request.deviceIfname,
78
+ apn: request.profile.apn,
79
+ autoConfig: request.profile.autoConfig,
80
+ homeOnly: request.profile.homeOnly,
81
+ }), epochMillis(this.#now()), request.requestedBy);
82
+ this.#slotsFor(id).desired = desired;
83
+ return { ok: true, connectionId: id, desired };
84
+ }
85
+ /**
86
+ * Record that the operator wants NO bearer on this connection's device.
87
+ *
88
+ * It performs no I/O on purpose — desired is a record of intent, and the
89
+ * deactivation it implies happens when `applyDesired` is called, so a request and
90
+ * its execution stay two separately observable facts.
91
+ */
92
+ releaseDesired(id, requestedBy) {
93
+ const slots = this.#slots.get(id);
94
+ const ifname = slots === undefined ? undefined : targetIfname(slots);
95
+ if (ifname === undefined) {
96
+ return {
97
+ ok: false,
98
+ reason: 'no-desired-profile',
99
+ receipt: receipt('connection', 'failed', `no tracked device for connection ${id}`),
100
+ };
101
+ }
102
+ const desired = desiredProfile(unboundBearer(ifname), epochMillis(this.#now()), requestedBy);
103
+ this.#slotsFor(id).desired = desired;
104
+ return { ok: true, connectionId: id, desired };
105
+ }
106
+ desiredFor(id) {
107
+ return this.#slots.get(id)?.desired ?? null;
108
+ }
109
+ /** The saved profile as NM itself holds it — a straight read, no slot involved. */
110
+ async readSavedProfile(id) {
111
+ return await this.#port.readGsmProfile(id);
112
+ }
113
+ // ── applied ────────────────────────────────────────────────────────────────
114
+ /**
115
+ * Put the desired state into force and record what was applied.
116
+ *
117
+ * The applied slot is built from the profile NM READ BACK, not from the request:
118
+ * "what was actually put into force" is a claim about NM's content, and asserting
119
+ * it from the input would make a silently-rejected field look applied.
120
+ */
121
+ async applyDesired(id, options) {
122
+ const desired = this.desiredFor(id);
123
+ if (desired === null) {
124
+ return refuse('no-desired-profile', `connection ${id} has no desired profile`);
125
+ }
126
+ return desired.profile.kind === 'unbound'
127
+ ? await this.#deactivate(id, desired.profile.deviceIfname, options)
128
+ : await this.#activate(id, desired.profile, options);
129
+ }
130
+ appliedFor(id) {
131
+ return this.#slots.get(id)?.applied ?? null;
132
+ }
133
+ /**
134
+ * The interface the applied bearer landed on, or `null` when no bearer is in
135
+ * force. An applied `unbound` state resolves to `null` rather than to its device:
136
+ * there is a device, but no bearer on it, and returning the name would read as one.
137
+ */
138
+ resolveAppliedInterface(id) {
139
+ const applied = this.appliedFor(id);
140
+ if (applied === null || applied.configuration.kind !== 'bound') {
141
+ return null;
142
+ }
143
+ return applied.configuration.binding.deviceIfname;
144
+ }
145
+ // ── observed ───────────────────────────────────────────────────────────────
146
+ /**
147
+ * Fold one complete NM readout.
148
+ *
149
+ * Never touches the desired slot. Clears the applied slot only when the readout
150
+ * positively contradicts it, and always says which of the four contradictions it
151
+ * was.
152
+ */
153
+ observe(input) {
154
+ const generation = input.context.generation;
155
+ if (this.#observedGeneration !== null && generation < this.#observedGeneration) {
156
+ return {
157
+ kind: 'refused',
158
+ reason: 'superseded-generation',
159
+ currentGeneration: this.#observedGeneration,
160
+ };
161
+ }
162
+ this.#observedGeneration = generation;
163
+ const devices = new Map(input.devices.map((device) => [device.ifname, device]));
164
+ const outcomes = [];
165
+ const losses = [];
166
+ for (const [id, slots] of this.#slots) {
167
+ const ifname = targetIfname(slots);
168
+ if (ifname === undefined) {
169
+ continue;
170
+ }
171
+ const device = devices.get(ifname);
172
+ slots.observed = observedState(device === undefined
173
+ ? unavailableObservation(SOURCE, input.context, 'device-absent')
174
+ : freshObservation(SOURCE, input.context, observedBearer(device)));
175
+ const outcome = this.#classify(id, slots, ifname, device, input.context.observedAt);
176
+ outcomes.push({ connectionId: id, outcome });
177
+ if (outcome.status === 'lost') {
178
+ losses.push(outcome.loss);
179
+ }
180
+ }
181
+ return {
182
+ kind: 'accepted',
183
+ generation,
184
+ observedAt: input.context.observedAt,
185
+ outcomes,
186
+ losses,
187
+ };
188
+ }
189
+ observedFor(id) {
190
+ return this.#slots.get(id)?.observed ?? null;
191
+ }
192
+ /**
193
+ * The three-slot view. `context` supplies the provenance for the "we have not
194
+ * observed this yet" case — this layer has no clock or epoch counter of its own
195
+ * for observations, so it cannot manufacture one.
196
+ */
197
+ stateView(id, context) {
198
+ const slots = this.#slots.get(id);
199
+ if (slots === undefined) {
200
+ return null;
201
+ }
202
+ return {
203
+ desired: slots.desired,
204
+ applied: slots.applied,
205
+ observed: slots.observed ??
206
+ observedState(unavailableObservation(SOURCE, context, 'provider-unavailable')),
207
+ };
208
+ }
209
+ /** `desiredVsApplied` ("did our write happen") and `appliedVsObserved` ("did it stick"). */
210
+ divergence(id, context) {
211
+ const view = this.stateView(id, context);
212
+ return view === null ? null : describeStateDivergence(view, nmBearerStateEquals);
213
+ }
214
+ // ── internals ──────────────────────────────────────────────────────────────
215
+ async #activate(id, desired, options) {
216
+ if (desired.kind !== 'bound') {
217
+ return refuse('no-desired-profile', `connection ${id} has no bearer to activate`);
218
+ }
219
+ const ifname = desired.binding.deviceIfname;
220
+ const saved = await this.#port.readGsmProfile(id);
221
+ if (saved === undefined) {
222
+ return refuse('profile-absent', `connection ${id} is no longer saved in NetworkManager`);
223
+ }
224
+ let activation;
225
+ try {
226
+ activation = await this.#port.activate(id, ifname);
227
+ }
228
+ catch (error) {
229
+ return refuse('activation-failed', describeError(error));
230
+ }
231
+ if (activation.status !== 'applied') {
232
+ return { ok: false, reason: 'activation-failed', receipt: activation };
233
+ }
234
+ const applied = appliedConfiguration({
235
+ configuration: boundBearer({
236
+ connectionId: id,
237
+ deviceIfname: ifname,
238
+ apn: saved.apn,
239
+ autoConfig: saved.autoConfig,
240
+ homeOnly: saved.homeOnly,
241
+ }),
242
+ appliedAt: epochMillis(this.#now()),
243
+ generation: options.generation,
244
+ operationId: options.operationId,
245
+ });
246
+ this.#slotsFor(id).applied = applied;
247
+ return { ok: true, applied, receipt: activation };
248
+ }
249
+ async #deactivate(id, ifname, options) {
250
+ let result;
251
+ try {
252
+ result = await this.#port.deactivate(id, ifname);
253
+ }
254
+ catch (error) {
255
+ return refuse('deactivation-failed', describeError(error));
256
+ }
257
+ if (result.status !== 'applied') {
258
+ return { ok: false, reason: 'deactivation-failed', receipt: result };
259
+ }
260
+ const applied = appliedConfiguration({
261
+ configuration: unboundBearer(ifname),
262
+ appliedAt: epochMillis(this.#now()),
263
+ generation: options.generation,
264
+ operationId: options.operationId,
265
+ });
266
+ this.#slotsFor(id).applied = applied;
267
+ return { ok: true, applied, receipt: result };
268
+ }
269
+ #classify(id, slots, ifname, device, observedAt) {
270
+ const applied = slots.applied;
271
+ if (applied === null) {
272
+ return { status: 'unapplied' };
273
+ }
274
+ const lose = (reason) => {
275
+ slots.applied = null;
276
+ return {
277
+ status: 'lost',
278
+ loss: {
279
+ connectionId: id,
280
+ deviceIfname: ifname,
281
+ reason,
282
+ lostAt: observedAt,
283
+ generation: applied.generation,
284
+ previous: applied,
285
+ },
286
+ };
287
+ };
288
+ if (device === undefined) {
289
+ return lose('interface-absent');
290
+ }
291
+ if (device.state === 'failed') {
292
+ return lose('activation-failed');
293
+ }
294
+ if (applied.configuration.kind === 'unbound') {
295
+ // We deliberately took the bearer down; anything active here is somebody else.
296
+ return device.activeConnection === undefined
297
+ ? { status: 'retained', applied }
298
+ : lose('connection-replaced');
299
+ }
300
+ if (device.activeConnection === undefined) {
301
+ return lose('interface-detached');
302
+ }
303
+ if (device.activeConnection.connectionId !== id) {
304
+ return lose('connection-replaced');
305
+ }
306
+ if (device.state === 'activated') {
307
+ return { status: 'retained', applied };
308
+ }
309
+ return TRANSITIONAL_STATES.has(device.state)
310
+ ? { status: 'pending', applied, deviceState: device.state }
311
+ : lose('interface-detached');
312
+ }
313
+ #slotsFor(id) {
314
+ const existing = this.#slots.get(id);
315
+ if (existing !== undefined) {
316
+ return existing;
317
+ }
318
+ const created = { desired: null, applied: null, observed: null };
319
+ this.#slots.set(id, created);
320
+ return created;
321
+ }
322
+ }
323
+ /** The device the slots are about: where the bearer IS, else where it was asked for. */
324
+ function targetIfname(slots) {
325
+ const state = slots.applied?.configuration ?? slots.desired?.profile;
326
+ if (state === undefined) {
327
+ return undefined;
328
+ }
329
+ return state.kind === 'bound' ? state.binding.deviceIfname : state.deviceIfname;
330
+ }
331
+ function observedBearer(device) {
332
+ const active = device.activeConnection;
333
+ return active === undefined
334
+ ? unboundBearer(device.ifname)
335
+ : boundBearer({
336
+ connectionId: active.connectionId,
337
+ deviceIfname: device.ifname,
338
+ apn: active.apn,
339
+ autoConfig: active.autoConfig,
340
+ homeOnly: active.homeOnly,
341
+ });
342
+ }
343
+ function refuse(reason, message) {
344
+ return { ok: false, reason, receipt: receipt('connection', 'failed', message) };
345
+ }
346
+ function describeError(error) {
347
+ return error instanceof Error ? error.message : 'NetworkManager rejected the request';
348
+ }
@@ -0,0 +1,2 @@
1
+ export * from './adapter.js';
2
+ export * from './types.js';
@@ -0,0 +1,2 @@
1
+ export * from './adapter.js';
2
+ export * from './types.js';
@@ -0,0 +1,163 @@
1
+ import type { DeviceGeneration, EpochMillis } from '../../domain/index.js';
2
+ import type { AppliedConfiguration, DesiredProfile, NormalizationContext } from '../../observations/index.js';
3
+ import type { ConnectionId, DeviceIfname, GsmProfileInput, Receipt } from '../../ports/index.js';
4
+ /**
5
+ * NM's own device states (`NMDeviceState`), by name.
6
+ *
7
+ * The transitional members are kept rather than collapsed into "not activated",
8
+ * because a device in `prepare` carrying our connection is COMING UP, and reporting
9
+ * that as a lost bearer would turn every ordinary activation into a false alarm.
10
+ */
11
+ export declare const NM_DEVICE_STATES: readonly ["unknown", "unmanaged", "unavailable", "disconnected", "prepare", "config", "need-auth", "ip-config", "ip-check", "secondaries", "activated", "deactivating", "failed"];
12
+ export type NmDeviceState = (typeof NM_DEVICE_STATES)[number];
13
+ /**
14
+ * The bearer identity all three slots compare in.
15
+ *
16
+ * `username` and `password` are deliberately NOT members. A state slot is read,
17
+ * compared, and surfaced in divergence output; `gsm.password` is the one field in the
18
+ * profile the redaction module masks everywhere else, so putting it in a comparable
19
+ * value would re-open that hole through a side door. The credential lives in NM, is
20
+ * written through the port, and is never mirrored here.
21
+ */
22
+ export interface NmBearerBinding {
23
+ readonly connectionId: ConnectionId;
24
+ readonly deviceIfname: DeviceIfname;
25
+ readonly apn: string;
26
+ readonly autoConfig: boolean;
27
+ readonly homeOnly: boolean;
28
+ }
29
+ /**
30
+ * A bearer, or the positive absence of one.
31
+ *
32
+ * `unbound` exists so "NM answered, and it says nothing is in force on this device"
33
+ * is a VALUE rather than an unavailable observation. The distinction is load-bearing:
34
+ * an unavailable observation compares `indeterminate` against everything, which is
35
+ * right for "the device is gone" and wrong for "the device is here and idle" — the
36
+ * second is a definite divergence from a desired bearer, and an operator needs to see
37
+ * it as one.
38
+ */
39
+ export type NmBearerState = {
40
+ readonly kind: 'bound';
41
+ readonly binding: NmBearerBinding;
42
+ } | {
43
+ readonly kind: 'unbound';
44
+ readonly deviceIfname: DeviceIfname;
45
+ };
46
+ export declare function boundBearer(binding: NmBearerBinding): NmBearerState;
47
+ export declare function unboundBearer(deviceIfname: DeviceIfname): NmBearerState;
48
+ export declare function nmBearerBindingEquals(left: NmBearerBinding, right: NmBearerBinding): boolean;
49
+ /** The equality `describeStateDivergence` is driven with for NM state. */
50
+ export declare function nmBearerStateEquals(left: NmBearerState, right: NmBearerState): boolean;
51
+ /** The connection NM reports active on a device, with the settings it put in force. */
52
+ export interface NmObservedActiveConnection {
53
+ readonly connectionId: ConnectionId;
54
+ readonly apn: string;
55
+ readonly autoConfig: boolean;
56
+ readonly homeOnly: boolean;
57
+ }
58
+ /** One device exactly as NM reports it in a readout. */
59
+ export interface NmObservedDevice {
60
+ readonly ifname: DeviceIfname;
61
+ readonly state: NmDeviceState;
62
+ readonly activeConnection?: NmObservedActiveConnection;
63
+ }
64
+ /**
65
+ * ONE COMPLETE NM readout — an enumeration, never a delta.
66
+ *
67
+ * That is what makes re-enumeration detectable at all: a device missing from `devices`
68
+ * means the device is GONE, and a delta stream has no way to say that without a
69
+ * removal event nobody guarantees will arrive.
70
+ *
71
+ * `context` supplies the clock, the generation and the source epoch, exactly as the
72
+ * observation layer requires — this adapter has no clock of its own for observations
73
+ * and therefore cannot stamp a readout with a time it did not come from.
74
+ */
75
+ export interface NmObservationInput {
76
+ readonly context: NormalizationContext;
77
+ readonly devices: readonly NmObservedDevice[];
78
+ }
79
+ /** What an operator asked for: a saved profile, targeted at an exact device. */
80
+ export interface NmDesiredRequest {
81
+ readonly profile: GsmProfileInput;
82
+ readonly deviceIfname: DeviceIfname;
83
+ /** Opaque origin label (an RPC caller, a policy engine). Never a credential. */
84
+ readonly requestedBy: string;
85
+ /** Update this existing profile instead of creating a new one. */
86
+ readonly connectionId?: ConnectionId;
87
+ }
88
+ export declare const NM_ADAPTER_REFUSAL_REASONS: readonly ["no-desired-profile", "profile-absent", "activation-failed", "deactivation-failed", "write-failed"];
89
+ export type NmAdapterRefusalReason = (typeof NM_ADAPTER_REFUSAL_REASONS)[number];
90
+ export type NmSaveResult = {
91
+ readonly ok: true;
92
+ readonly connectionId: ConnectionId;
93
+ readonly desired: DesiredProfile<NmBearerState>;
94
+ } | {
95
+ readonly ok: false;
96
+ readonly reason: NmAdapterRefusalReason;
97
+ readonly receipt: Receipt;
98
+ };
99
+ /** Applying a desired profile: the write either took, or it did not and says why. */
100
+ export type NmApplyResult = {
101
+ readonly ok: true;
102
+ readonly applied: AppliedConfiguration<NmBearerState>;
103
+ readonly receipt: Receipt;
104
+ } | {
105
+ readonly ok: false;
106
+ readonly reason: NmAdapterRefusalReason;
107
+ readonly receipt: Receipt;
108
+ };
109
+ export declare const NM_APPLIED_LOSS_REASONS: readonly ["interface-absent", "interface-detached", "connection-replaced", "activation-failed"];
110
+ export type NmAppliedLossReason = (typeof NM_APPLIED_LOSS_REASONS)[number];
111
+ /**
112
+ * The applied bearer stopped being in force.
113
+ *
114
+ * `previous` is retained rather than discarded: a caller deciding whether to re-apply
115
+ * or to roll back needs to know what WAS in force, and the applied slot has just been
116
+ * cleared precisely because it no longer describes reality.
117
+ */
118
+ export interface NmAppliedLoss {
119
+ readonly connectionId: ConnectionId;
120
+ readonly deviceIfname: DeviceIfname;
121
+ readonly reason: NmAppliedLossReason;
122
+ readonly lostAt: EpochMillis;
123
+ readonly generation: DeviceGeneration;
124
+ readonly previous: AppliedConfiguration<NmBearerState>;
125
+ }
126
+ export type NmAppliedOutcome =
127
+ /** A desired profile is tracked, and nothing has been put into force for it yet. */
128
+ {
129
+ readonly status: 'unapplied';
130
+ } | {
131
+ readonly status: 'retained';
132
+ readonly applied: AppliedConfiguration<NmBearerState>;
133
+ } | {
134
+ readonly status: 'pending';
135
+ readonly applied: AppliedConfiguration<NmBearerState>;
136
+ readonly deviceState: NmDeviceState;
137
+ } | {
138
+ readonly status: 'lost';
139
+ readonly loss: NmAppliedLoss;
140
+ };
141
+ export interface NmConnectionOutcome {
142
+ readonly connectionId: ConnectionId;
143
+ readonly outcome: NmAppliedOutcome;
144
+ }
145
+ /**
146
+ * The result of folding one readout.
147
+ *
148
+ * A readout from a superseded generation is REFUSED rather than applied late: the
149
+ * generation fence exists so a reply about a previous enumeration cannot clear applied
150
+ * state that belongs to the current one.
151
+ */
152
+ export type NmObservationResult = {
153
+ readonly kind: 'refused';
154
+ readonly reason: 'superseded-generation';
155
+ readonly currentGeneration: DeviceGeneration;
156
+ } | {
157
+ readonly kind: 'accepted';
158
+ readonly generation: DeviceGeneration;
159
+ readonly observedAt: EpochMillis;
160
+ readonly outcomes: readonly NmConnectionOutcome[];
161
+ /** Just the losses, so the case that matters is not behind a filter. */
162
+ readonly losses: readonly NmAppliedLoss[];
163
+ };
@@ -0,0 +1,77 @@
1
+ // The saved-vs-applied vocabulary, in NetworkManager's own terms.
2
+ //
3
+ // NM is the sole writer of bearers, APN, auth, roaming, autoconnect and activation
4
+ // (`ports/README.md` ownership matrix), and it is also the stack that draws the
5
+ // distinction this whole module is built on: a connection PROFILE is what was saved,
6
+ // an ACTIVE connection is what was actually put into force on a device, and the device
7
+ // itself reports what it is currently doing. `observations/state-separation.ts` models
8
+ // those three slots generically; this file gives them an NM-shaped payload.
9
+ //
10
+ // Radio, band, SIM and power operations are deliberately absent. Those belong to the
11
+ // ModemManager provider, and a second surface expressing them here would make two
12
+ // writers for one resource — the exact thing the ownership matrix exists to prevent.
13
+ // Nothing here carries a `PhysicalModemId` either: every slot is keyed by NM's own
14
+ // connection UUID, so this adapter can never become a second authority on which
15
+ // physical modem is which.
16
+ /**
17
+ * NM's own device states (`NMDeviceState`), by name.
18
+ *
19
+ * The transitional members are kept rather than collapsed into "not activated",
20
+ * because a device in `prepare` carrying our connection is COMING UP, and reporting
21
+ * that as a lost bearer would turn every ordinary activation into a false alarm.
22
+ */
23
+ export const NM_DEVICE_STATES = [
24
+ 'unknown',
25
+ 'unmanaged',
26
+ 'unavailable',
27
+ 'disconnected',
28
+ 'prepare',
29
+ 'config',
30
+ 'need-auth',
31
+ 'ip-config',
32
+ 'ip-check',
33
+ 'secondaries',
34
+ 'activated',
35
+ 'deactivating',
36
+ 'failed',
37
+ ];
38
+ export function boundBearer(binding) {
39
+ return { kind: 'bound', binding };
40
+ }
41
+ export function unboundBearer(deviceIfname) {
42
+ return { kind: 'unbound', deviceIfname };
43
+ }
44
+ export function nmBearerBindingEquals(left, right) {
45
+ return (left.connectionId === right.connectionId &&
46
+ left.deviceIfname === right.deviceIfname &&
47
+ left.apn === right.apn &&
48
+ left.autoConfig === right.autoConfig &&
49
+ left.homeOnly === right.homeOnly);
50
+ }
51
+ /** The equality `describeStateDivergence` is driven with for NM state. */
52
+ export function nmBearerStateEquals(left, right) {
53
+ if (left.kind === 'bound' && right.kind === 'bound') {
54
+ return nmBearerBindingEquals(left.binding, right.binding);
55
+ }
56
+ if (left.kind === 'unbound' && right.kind === 'unbound') {
57
+ return left.deviceIfname === right.deviceIfname;
58
+ }
59
+ return false;
60
+ }
61
+ export const NM_ADAPTER_REFUSAL_REASONS = [
62
+ 'no-desired-profile',
63
+ 'profile-absent',
64
+ 'activation-failed',
65
+ 'deactivation-failed',
66
+ 'write-failed',
67
+ ];
68
+ export const NM_APPLIED_LOSS_REASONS = [
69
+ /** The readout has no such device at all — a re-enumeration, or an unplug. */
70
+ 'interface-absent',
71
+ /** The device is present and carries no active connection. */
72
+ 'interface-detached',
73
+ /** A DIFFERENT connection is active on our device. */
74
+ 'connection-replaced',
75
+ /** NM reports the device itself failed. */
76
+ 'activation-failed',
77
+ ];
@@ -0,0 +1,13 @@
1
+ import { DomainError } from '../domain/index.js';
2
+ import type { ProviderDefinition, ProviderOperationsSurface } from './contracts.js';
3
+ export declare class ProviderRegistryError extends DomainError {
4
+ readonly reason: 'duplicate-provider' | 'empty-profile-version';
5
+ readonly name = "ProviderRegistryError";
6
+ constructor(reason: 'duplicate-provider' | 'empty-profile-version');
7
+ }
8
+ export interface ProviderRegistry {
9
+ readonly revision: number;
10
+ register<TObservation, TOperations extends ProviderOperationsSurface>(definition: ProviderDefinition<TObservation, TOperations>): void;
11
+ list(): readonly ProviderDefinition[];
12
+ }
13
+ export declare function createProviderRegistry(): ProviderRegistry;