@ceralive/modem-control 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/README.md +341 -0
  2. package/dist/backend/at-lease.d.ts +59 -0
  3. package/dist/backend/at-lease.js +118 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +101 -0
  61. package/dist/backend/transition-preconditions.js +132 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +216 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +20 -0
  146. package/dist/index.js +27 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +61 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operation-ids.d.ts +2 -0
  196. package/dist/operation-ids.js +26 -0
  197. package/dist/operations/contracts.d.ts +72 -0
  198. package/dist/operations/contracts.js +1 -0
  199. package/dist/operations/index.d.ts +1 -0
  200. package/dist/operations/index.js +1 -0
  201. package/dist/operations/operation-engine.d.ts +16 -0
  202. package/dist/operations/operation-engine.js +194 -0
  203. package/dist/ports/index.d.ts +12 -0
  204. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  205. package/dist/ports/location.d.ts +87 -0
  206. package/dist/ports/location.js +36 -0
  207. package/dist/ports/modem-manager.d.ts +89 -0
  208. package/dist/ports/modem-manager.js +9 -0
  209. package/dist/ports/mutation-admission.d.ts +27 -0
  210. package/dist/ports/mutation-admission.js +9 -0
  211. package/dist/ports/network-manager.d.ts +68 -0
  212. package/dist/ports/network-manager.js +13 -0
  213. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  214. package/dist/ports/observation.js +7 -0
  215. package/dist/ports/ops.d.ts +44 -0
  216. package/dist/ports/ops.js +16 -0
  217. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  218. package/dist/ports/receipts.js +9 -0
  219. package/dist/ports/reconcile.d.ts +33 -0
  220. package/dist/ports/reconcile.js +200 -0
  221. package/dist/ports/resource-ownership.d.ts +29 -0
  222. package/dist/ports/resource-ownership.js +1 -0
  223. package/dist/ports/router.d.ts +19 -0
  224. package/dist/ports/router.js +7 -0
  225. package/dist/ports/sms.d.ts +64 -0
  226. package/dist/ports/sms.js +24 -0
  227. package/dist/ports/uhubctl.d.ts +6 -0
  228. package/dist/ports/uhubctl.js +1 -0
  229. package/dist/providers/contracts.d.ts +124 -0
  230. package/dist/providers/contracts.js +10 -0
  231. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  232. package/dist/providers/huawei-hilink/index.js +2 -0
  233. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  234. package/dist/providers/huawei-hilink/operations.js +56 -0
  235. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  236. package/dist/providers/huawei-hilink/provider.js +76 -0
  237. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  238. package/dist/providers/huawei-hilink/runtime.js +171 -0
  239. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  240. package/dist/providers/huawei-hilink/session.js +120 -0
  241. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  242. package/dist/providers/huawei-hilink/transport.js +1 -0
  243. package/dist/providers/index.d.ts +8 -0
  244. package/dist/providers/index.js +8 -0
  245. package/dist/providers/matcher.d.ts +3 -0
  246. package/dist/providers/matcher.js +205 -0
  247. package/dist/providers/modem-manager/errors.d.ts +7 -0
  248. package/dist/providers/modem-manager/errors.js +37 -0
  249. package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
  250. package/dist/providers/modem-manager/generic-operations.js +209 -0
  251. package/dist/providers/modem-manager/index.d.ts +4 -0
  252. package/dist/providers/modem-manager/index.js +4 -0
  253. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  254. package/dist/providers/modem-manager/module-operations.js +118 -0
  255. package/dist/providers/modem-manager/provider.d.ts +41 -0
  256. package/dist/providers/modem-manager/provider.js +155 -0
  257. package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
  258. package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
  259. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  260. package/dist/providers/modem-manager/snapshot.js +204 -0
  261. package/dist/providers/modem-manager/types.d.ts +137 -0
  262. package/dist/providers/modem-manager/types.js +1 -0
  263. package/dist/providers/network-manager/adapter.d.ts +71 -0
  264. package/dist/providers/network-manager/adapter.js +348 -0
  265. package/dist/providers/network-manager/index.d.ts +2 -0
  266. package/dist/providers/network-manager/index.js +2 -0
  267. package/dist/providers/network-manager/types.d.ts +171 -0
  268. package/dist/providers/network-manager/types.js +77 -0
  269. package/dist/providers/registry.d.ts +13 -0
  270. package/dist/providers/registry.js +33 -0
  271. package/dist/providers/ufi-himi/index.d.ts +6 -0
  272. package/dist/providers/ufi-himi/index.js +6 -0
  273. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  274. package/dist/providers/ufi-himi/operations.js +66 -0
  275. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  276. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  277. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  278. package/dist/providers/ufi-himi/provider.js +204 -0
  279. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  280. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  281. package/dist/providers/ufi-himi/session.d.ts +46 -0
  282. package/dist/providers/ufi-himi/session.js +92 -0
  283. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  284. package/dist/providers/ufi-himi/transport.js +25 -0
  285. package/dist/providers/zte-goform/index.d.ts +2 -0
  286. package/dist/providers/zte-goform/index.js +2 -0
  287. package/dist/providers/zte-goform/provider.d.ts +56 -0
  288. package/dist/providers/zte-goform/provider.js +101 -0
  289. package/dist/providers/zte-goform/session.d.ts +23 -0
  290. package/dist/providers/zte-goform/session.js +197 -0
  291. package/dist/providers/zte-goform/transport.d.ts +16 -0
  292. package/dist/providers/zte-goform/transport.js +1 -0
  293. package/dist/radio/band-truth.d.ts +50 -0
  294. package/dist/radio/band-truth.js +92 -0
  295. package/dist/radio/index.d.ts +3 -0
  296. package/dist/radio/index.js +10 -0
  297. package/dist/radio/mode-combinations.d.ts +88 -0
  298. package/dist/radio/mode-combinations.js +198 -0
  299. package/dist/radio/mode-truth.d.ts +67 -0
  300. package/dist/radio/mode-truth.js +112 -0
  301. package/dist/redact.d.ts +15 -0
  302. package/dist/redact.js +189 -0
  303. package/dist/safety/composition-root.d.ts +28 -0
  304. package/dist/safety/composition-root.js +65 -0
  305. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  306. package/dist/safety/flock-resource-ownership.js +138 -0
  307. package/dist/safety/index.d.ts +2 -0
  308. package/dist/safety/index.js +2 -0
  309. package/dist/sms/dbus-messaging.d.ts +17 -0
  310. package/dist/sms/dbus-messaging.js +185 -0
  311. package/dist/sms/inbox-store.d.ts +10 -0
  312. package/dist/sms/inbox-store.js +82 -0
  313. package/dist/sms/index.d.ts +4 -0
  314. package/dist/sms/index.js +10 -0
  315. package/dist/sms/mmcli-parse.d.ts +54 -0
  316. package/dist/sms/mmcli-parse.js +224 -0
  317. package/dist/sms/normalize.d.ts +42 -0
  318. package/dist/sms/normalize.js +95 -0
  319. package/dist/testing/domain-fakes.d.ts +58 -0
  320. package/dist/testing/domain-fakes.js +98 -0
  321. package/dist/testing/index.d.ts +2 -0
  322. package/dist/testing/index.js +17 -0
  323. package/dist/testing/provider-fakes.d.ts +45 -0
  324. package/dist/testing/provider-fakes.js +64 -0
  325. package/dist/transport/calls.d.ts +9 -0
  326. package/dist/transport/calls.js +88 -0
  327. package/dist/transport/codec.d.ts +3 -0
  328. package/dist/transport/codec.js +207 -0
  329. package/dist/transport/dbus-native.d.ts +57 -0
  330. package/dist/transport/dbus-native.js +17 -0
  331. package/dist/transport/errors.d.ts +21 -0
  332. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  333. package/dist/transport/index.d.ts +4 -0
  334. package/dist/transport/index.js +9 -0
  335. package/dist/transport/signals.d.ts +15 -0
  336. package/dist/transport/signals.js +123 -0
  337. package/dist/transport/signature.d.ts +7 -0
  338. package/dist/transport/signature.js +94 -0
  339. package/dist/transport/transport.d.ts +2 -0
  340. package/dist/transport/transport.js +202 -0
  341. package/dist/transport/types.d.ts +61 -0
  342. package/dist/transport/types.js +19 -0
  343. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  344. package/dist/usb-mode/catalog-schema.js +97 -0
  345. package/dist/usb-mode/catalog.d.ts +21 -0
  346. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  347. package/dist/usb-mode/certified-catalog.json +67 -0
  348. package/dist/usb-mode/index.d.ts +6 -0
  349. package/dist/usb-mode/index.js +16 -0
  350. package/dist/usb-mode/ingestion.d.ts +111 -0
  351. package/dist/usb-mode/ingestion.js +187 -0
  352. package/dist/usb-mode/promotion-review.d.ts +21 -0
  353. package/dist/usb-mode/promotion-review.js +87 -0
  354. package/dist/usb-mode/runtime-capability.d.ts +59 -0
  355. package/dist/usb-mode/runtime-capability.js +157 -0
  356. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  357. package/dist/usb-mode/usb-devices-parse.js +157 -0
  358. package/dist/ussd/calls.d.ts +32 -0
  359. package/dist/ussd/calls.js +96 -0
  360. package/dist/ussd/index.d.ts +5 -0
  361. package/dist/ussd/index.js +12 -0
  362. package/dist/ussd/mm-ussd.d.ts +37 -0
  363. package/dist/ussd/mm-ussd.js +205 -0
  364. package/dist/ussd/refusal.d.ts +53 -0
  365. package/dist/ussd/refusal.js +154 -0
  366. package/dist/ussd/registration.d.ts +20 -0
  367. package/dist/ussd/registration.js +101 -0
  368. package/dist/ussd/session.d.ts +106 -0
  369. package/dist/ussd/session.js +163 -0
  370. package/package.json +38 -4
  371. package/src/backend/at-lease.test.ts +0 -106
  372. package/src/backend/at-lease.ts +0 -158
  373. package/src/backend/cell-info.test.ts +0 -154
  374. package/src/backend/cell-info.ts +0 -160
  375. package/src/backend/device-classifier.test.ts +0 -168
  376. package/src/backend/device-classifier.ts +0 -248
  377. package/src/backend/enrichment.ts +0 -96
  378. package/src/backend/features.test.ts +0 -162
  379. package/src/backend/features.ts +0 -179
  380. package/src/backend/identity-ladder.test.ts +0 -117
  381. package/src/backend/identity-ladder.ts +0 -221
  382. package/src/backend/identity-registry.test.ts +0 -89
  383. package/src/backend/identity-registry.ts +0 -151
  384. package/src/backend/index.ts +0 -236
  385. package/src/backend/lifecycle-interlock.ts +0 -38
  386. package/src/backend/managed-objects.ts +0 -108
  387. package/src/backend/mapping.ts +0 -160
  388. package/src/backend/mm-backend.ts +0 -191
  389. package/src/backend/mm-mutations.ts +0 -228
  390. package/src/backend/modem-actor.test.ts +0 -95
  391. package/src/backend/modem-actor.ts +0 -112
  392. package/src/backend/nm-auto-apn.ts +0 -161
  393. package/src/backend/nm-gsm-fields.ts +0 -122
  394. package/src/backend/nmcli-nm-port.ts +0 -228
  395. package/src/backend/nmcli-runner.ts +0 -52
  396. package/src/backend/observer.ts +0 -297
  397. package/src/backend/power-contract.test.ts +0 -40
  398. package/src/backend/power-contract.ts +0 -83
  399. package/src/backend/recovery-attribution.test.ts +0 -102
  400. package/src/backend/recovery-attribution.ts +0 -86
  401. package/src/backend/recovery-budget.test.ts +0 -64
  402. package/src/backend/recovery-budget.ts +0 -84
  403. package/src/backend/recovery-ladder.test.ts +0 -257
  404. package/src/backend/recovery-ladder.ts +0 -249
  405. package/src/backend/router-ethernet.test.ts +0 -71
  406. package/src/backend/router-ethernet.ts +0 -90
  407. package/src/backend/row-store.ts +0 -105
  408. package/src/backend/signal-setup.ts +0 -112
  409. package/src/backend/sim-unlock.ts +0 -193
  410. package/src/backend/transition-preconditions.ts +0 -149
  411. package/src/backend/uhubctl-power-hook.test.ts +0 -274
  412. package/src/backend/uhubctl-power-hook.ts +0 -377
  413. package/src/backend/usage/accounting.test.ts +0 -147
  414. package/src/backend/usage/accounting.ts +0 -123
  415. package/src/backend/usage/billing-cycle.test.ts +0 -62
  416. package/src/backend/usage/billing-cycle.ts +0 -45
  417. package/src/backend/usage/index.ts +0 -60
  418. package/src/backend/usage/policy-store.test.ts +0 -164
  419. package/src/backend/usage/policy-store.ts +0 -216
  420. package/src/backend/usage/policy-write.test.ts +0 -198
  421. package/src/backend/usage/policy-write.ts +0 -207
  422. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  423. package/src/backend/usage/sampler.test.ts +0 -327
  424. package/src/backend/usage/sampler.ts +0 -282
  425. package/src/backend/usage/store.test.ts +0 -148
  426. package/src/backend/usage/store.ts +0 -177
  427. package/src/backend/usb-enumerator.test.ts +0 -87
  428. package/src/backend/usb-enumerator.ts +0 -181
  429. package/src/backend/usb-mode-transition.test.ts +0 -323
  430. package/src/backend/usb-mode-transition.ts +0 -253
  431. package/src/domain/brand.ts +0 -29
  432. package/src/domain/errors.ts +0 -77
  433. package/src/domain/guards.test.ts +0 -218
  434. package/src/domain/guards.ts +0 -144
  435. package/src/domain/identity.test.ts +0 -83
  436. package/src/domain/identity.ts +0 -165
  437. package/src/domain/index.ts +0 -12
  438. package/src/domain/snapshot.test.ts +0 -266
  439. package/src/domain/snapshot.ts +0 -120
  440. package/src/index.test.ts +0 -6
  441. package/src/index.ts +0 -15
  442. package/src/ports/README.md +0 -61
  443. package/src/ports/forbidden-surface.test.ts +0 -241
  444. package/src/ports/modem-manager.ts +0 -72
  445. package/src/ports/network-manager.ts +0 -87
  446. package/src/ports/ops.ts +0 -60
  447. package/src/ports/ops.type-test.ts +0 -39
  448. package/src/ports/receipts.test.ts +0 -153
  449. package/src/ports/reconcile.test.ts +0 -152
  450. package/src/ports/reconcile.ts +0 -338
  451. package/src/ports/router.ts +0 -29
  452. package/src/redact.test.ts +0 -82
  453. package/src/redact.ts +0 -73
  454. package/src/transport/README.md +0 -65
  455. package/src/transport/calls.ts +0 -113
  456. package/src/transport/characterization.test.ts +0 -260
  457. package/src/transport/codec.test.ts +0 -118
  458. package/src/transport/codec.ts +0 -240
  459. package/src/transport/conformance-python.test.ts +0 -152
  460. package/src/transport/conformance-same-lib.test.ts +0 -115
  461. package/src/transport/dbus-native-lib.d.ts +0 -19
  462. package/src/transport/dbus-native.ts +0 -85
  463. package/src/transport/index.ts +0 -30
  464. package/src/transport/no-library-leak.test.ts +0 -61
  465. package/src/transport/reliability.test.ts +0 -173
  466. package/src/transport/signals.ts +0 -150
  467. package/src/transport/signature.ts +0 -110
  468. package/src/transport/test-support/fake-service.ts +0 -168
  469. package/src/transport/test-support/independent-producer.py +0 -110
  470. package/src/transport/test-support/private-bus.ts +0 -66
  471. package/src/transport/transport.ts +0 -250
  472. package/src/transport/types.ts +0 -118
  473. package/src/usb-mode/catalog-schema.test.ts +0 -181
  474. package/src/usb-mode/catalog-schema.ts +0 -113
  475. package/src/usb-mode/certified-catalog.json +0 -67
  476. package/src/usb-mode/index.ts +0 -58
  477. package/src/usb-mode/ingestion.test.ts +0 -268
  478. package/src/usb-mode/ingestion.ts +0 -297
  479. package/src/usb-mode/promotion-review.ts +0 -117
  480. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -0,0 +1,124 @@
1
+ // Cell-info normalization — a decoded ModemManager cell dict → a stable reading.
2
+ //
3
+ // `Modem.GetCellInfo` returns `aa{sv}`: one `a{sv}` dict per visible cell. The keys
4
+ // vary by RAT and MM version, so this module pins EXACTLY the mappings the rest of
5
+ // the stack depends on and ignores everything else:
6
+ //
7
+ // - `physical-ci` → `pci` (the real MM key; never guessed from anything else)
8
+ // - `rsrp`, `rsrq` pass through as numbers
9
+ // - `sinr` the REAL NR SINR key. A dict carrying `snr` (the
10
+ // WRONG name) is IGNORED — `sinr` stays undefined.
11
+ // - `cell-id` the cell identifier (serving-cell tiebreak)
12
+ // - `band` surfaced ONLY when the source supplies it directly;
13
+ // never inferred from earfcn / frequency / anything.
14
+ // - `serving` / `cell-type` whether this is the serving cell.
15
+ //
16
+ // Every reading also carries `source` + `observedAt` provenance, so a consumer can
17
+ // tell a fresh reading from a cached one and know where it came from. Pure — no I/O.
18
+ import { numberProp, stringProp } from './managed-objects.js';
19
+ /** Read a `serving` flag: an explicit `serving` bool, else a `cell-type` naming it. */
20
+ function readServing(cell) {
21
+ const flag = cell.find(([key]) => key === 'serving')?.[1]?.value;
22
+ if (typeof flag === 'boolean') {
23
+ return flag;
24
+ }
25
+ const cellType = stringProp(cell, 'cell-type');
26
+ return cellType?.toLowerCase().includes('serving') ?? false;
27
+ }
28
+ /** Normalize ONE cell's `a{sv}` dict into a `CellReading`, pinning the known keys. */
29
+ export function normalizeCellReading(cell, provenance) {
30
+ const cellId = stringProp(cell, 'cell-id');
31
+ const pci = numberProp(cell, 'physical-ci');
32
+ const rsrp = numberProp(cell, 'rsrp');
33
+ const rsrq = numberProp(cell, 'rsrq');
34
+ // `sinr` ONLY — a dict carrying `snr` must never populate this field.
35
+ const sinr = numberProp(cell, 'sinr');
36
+ // `band` ONLY when directly supplied; never inferred.
37
+ const band = stringProp(cell, 'band');
38
+ return {
39
+ serving: readServing(cell),
40
+ ...(cellId !== undefined ? { cellId } : {}),
41
+ ...(pci !== undefined ? { pci } : {}),
42
+ ...(rsrp !== undefined ? { rsrp } : {}),
43
+ ...(rsrq !== undefined ? { rsrq } : {}),
44
+ ...(sinr !== undefined ? { sinr } : {}),
45
+ ...(band !== undefined ? { band } : {}),
46
+ source: provenance.source,
47
+ observedAt: provenance.observedAt,
48
+ };
49
+ }
50
+ /** Normalize a whole `GetCellInfo` reply (`aa{sv}` → cell dicts) into readings. */
51
+ export function normalizeCellInfo(cells, provenance) {
52
+ return cells.map((cell) => normalizeCellReading(cell, provenance));
53
+ }
54
+ /**
55
+ * Serving-cell TOTAL order — a strict, permutation-invariant ranking of readings.
56
+ * Returns < 0 when `a` outranks `b` (should sort first / is the better serving cell):
57
+ *
58
+ * 1. a `serving`-marked cell outranks a non-serving one;
59
+ * 2. then the HIGHER `rsrp` outranks the lower — a cell with NO `rsrp` sorts LAST;
60
+ * 3. ties break lexicographically by `cell-id` (a cell with none sorts last);
61
+ * 4. final deterministic tiebreaks (`pci`, then a stable serialization) guarantee
62
+ * the order is total even for otherwise-identical distinct cells, so the winner
63
+ * never depends on input order.
64
+ */
65
+ export function compareServing(a, b) {
66
+ if (a.serving !== b.serving) {
67
+ return a.serving ? -1 : 1;
68
+ }
69
+ const rsrpRank = compareOptionalDesc(a.rsrp, b.rsrp);
70
+ if (rsrpRank !== 0) {
71
+ return rsrpRank;
72
+ }
73
+ const cellRank = compareOptionalAsc(a.cellId, b.cellId);
74
+ if (cellRank !== 0) {
75
+ return cellRank;
76
+ }
77
+ const pciRank = compareOptionalAsc(a.pci, b.pci);
78
+ if (pciRank !== 0) {
79
+ return pciRank;
80
+ }
81
+ return stableTag(a) < stableTag(b) ? -1 : stableTag(a) > stableTag(b) ? 1 : 0;
82
+ }
83
+ /** Higher value first; `undefined` last. */
84
+ function compareOptionalDesc(a, b) {
85
+ if (a === b)
86
+ return 0;
87
+ if (a === undefined)
88
+ return 1;
89
+ if (b === undefined)
90
+ return -1;
91
+ return b - a;
92
+ }
93
+ /** Lower value first; `undefined` last. Works for numbers and strings. */
94
+ function compareOptionalAsc(a, b) {
95
+ if (a === b)
96
+ return 0;
97
+ if (a === undefined)
98
+ return 1;
99
+ if (b === undefined)
100
+ return -1;
101
+ return a < b ? -1 : 1;
102
+ }
103
+ /** A stable, order-independent fingerprint used only as the final total-order tiebreak. */
104
+ function stableTag(reading) {
105
+ return JSON.stringify([
106
+ reading.serving,
107
+ reading.cellId ?? null,
108
+ reading.pci ?? null,
109
+ reading.rsrp ?? null,
110
+ reading.rsrq ?? null,
111
+ reading.sinr ?? null,
112
+ reading.band ?? null,
113
+ ]);
114
+ }
115
+ /**
116
+ * Select the serving cell under the total order above. Permutation-invariant: the
117
+ * same set of readings always yields the same winner regardless of their order.
118
+ */
119
+ export function selectServingCell(cells) {
120
+ if (cells.length === 0) {
121
+ return undefined;
122
+ }
123
+ return cells.reduce((best, cell) => (compareServing(cell, best) < 0 ? cell : best));
124
+ }
@@ -0,0 +1,24 @@
1
+ /** The well-known bus name ModemManager owns. */
2
+ export declare const MM_BUS_NAME = "org.freedesktop.ModemManager1";
3
+ /** The root ObjectManager object path. */
4
+ export declare const MM_ROOT_PATH = "/org/freedesktop/ModemManager1";
5
+ /** `org.freedesktop.DBus.ObjectManager` — `GetManagedObjects`, `InterfacesAdded/Removed`. */
6
+ export declare const OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager";
7
+ /** `org.freedesktop.DBus.Properties` — `PropertiesChanged`. */
8
+ export declare const PROPERTIES_IFACE = "org.freedesktop.DBus.Properties";
9
+ /** The root manager interface — `InhibitDevice`, `ScanDevices`, `Version`. */
10
+ export declare const MM_MANAGER_IFACE = "org.freedesktop.ModemManager1";
11
+ /** The core `Modem` interface. */
12
+ export declare const MODEM_IFACE = "org.freedesktop.ModemManager1.Modem";
13
+ /** The separate `Modem.Modem3gpp` interface (never merged into `Modem`). */
14
+ export declare const MODEM3GPP_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp";
15
+ /** `Modem.Modem3gpp.Ussd` — a SEPARATE interface a modem may omit entirely. */
16
+ export declare const MODEM3GPP_USSD_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd";
17
+ /** The `Modem.Location` interface — GNSS capabilities, `Setup`, `GetLocation`. */
18
+ export declare const MODEM_LOCATION_IFACE = "org.freedesktop.ModemManager1.Modem.Location";
19
+ /** A SIM object's `Sim` interface (SIMs are separate `/SIM/<n>` objects). */
20
+ export declare const SIM_IFACE = "org.freedesktop.ModemManager1.Sim";
21
+ /** The bus daemon itself — `NameOwnerChanged`, `GetNameOwner`. */
22
+ export declare const DBUS_IFACE = "org.freedesktop.DBus";
23
+ export declare const DBUS_PATH = "/org/freedesktop/DBus";
24
+ export declare const DBUS_DESTINATION = "org.freedesktop.DBus";
@@ -4,31 +4,26 @@
4
4
  // than imported from the A2.3 test fake) on purpose: `control/test-support/` is not
5
5
  // published in the npm package, so `src` must never depend on it — the observer that
6
6
  // SHIPS owns its own copy of the constants it needs.
7
-
8
7
  /** The well-known bus name ModemManager owns. */
9
8
  export const MM_BUS_NAME = 'org.freedesktop.ModemManager1';
10
-
11
9
  /** The root ObjectManager object path. */
12
10
  export const MM_ROOT_PATH = '/org/freedesktop/ModemManager1';
13
-
14
11
  /** `org.freedesktop.DBus.ObjectManager` — `GetManagedObjects`, `InterfacesAdded/Removed`. */
15
12
  export const OBJECT_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager';
16
-
17
13
  /** `org.freedesktop.DBus.Properties` — `PropertiesChanged`. */
18
14
  export const PROPERTIES_IFACE = 'org.freedesktop.DBus.Properties';
19
-
20
15
  /** The root manager interface — `InhibitDevice`, `ScanDevices`, `Version`. */
21
16
  export const MM_MANAGER_IFACE = 'org.freedesktop.ModemManager1';
22
-
23
17
  /** The core `Modem` interface. */
24
18
  export const MODEM_IFACE = 'org.freedesktop.ModemManager1.Modem';
25
-
26
19
  /** The separate `Modem.Modem3gpp` interface (never merged into `Modem`). */
27
20
  export const MODEM3GPP_IFACE = 'org.freedesktop.ModemManager1.Modem.Modem3gpp';
28
-
21
+ /** `Modem.Modem3gpp.Ussd` — a SEPARATE interface a modem may omit entirely. */
22
+ export const MODEM3GPP_USSD_IFACE = `${MODEM3GPP_IFACE}.Ussd`;
23
+ /** The `Modem.Location` interface — GNSS capabilities, `Setup`, `GetLocation`. */
24
+ export const MODEM_LOCATION_IFACE = 'org.freedesktop.ModemManager1.Modem.Location';
29
25
  /** A SIM object's `Sim` interface (SIMs are separate `/SIM/<n>` objects). */
30
26
  export const SIM_IFACE = 'org.freedesktop.ModemManager1.Sim';
31
-
32
27
  /** The bus daemon itself — `NameOwnerChanged`, `GetNameOwner`. */
33
28
  export const DBUS_IFACE = 'org.freedesktop.DBus';
34
29
  export const DBUS_PATH = '/org/freedesktop/DBus';
@@ -0,0 +1,46 @@
1
+ import type { CanonicalUsbMode, ExpectedDescriptors } from '../usb-mode/index.js';
2
+ import type { UsbDeviceSnapshot } from './usb-device-snapshot.js';
3
+ export type { UsbDeviceSnapshot, UsbInterface } from './usb-device-snapshot.js';
4
+ /** The four device classes. `pending-modeswitch` is distinct from `unmanaged`. */
5
+ export type DeviceClass = 'mm-managed' | 'router-mode' | 'unmanaged' | 'pending-modeswitch';
6
+ export type UsbNetClass = 'mm-managed' | 'router-cellular' | 'wired-ethernet' | 'unknown';
7
+ /** A classification plus a human-readable reason (always populated). */
8
+ export interface DeviceClassification {
9
+ readonly deviceClass: DeviceClass;
10
+ readonly reason: string;
11
+ }
12
+ export interface UsbNetClassification {
13
+ readonly deviceClass: UsbNetClass;
14
+ readonly reason: string;
15
+ }
16
+ export declare const CELLULAR_USB_VENDOR_IDS: ReadonlyMap<string, string>;
17
+ /**
18
+ * Classify one USB device from its udev/sysfs snapshot. Precedence:
19
+ * 1. a recognized MM control port (MBIM / QMI / AT) ⇒ `mm-managed`;
20
+ * 2. a network tether with NO control port (ECM / NCM / RNDIS) ⇒ `router-mode`;
21
+ * 3. mass storage with a `usb_modeswitch` trigger ⇒ `pending-modeswitch`;
22
+ * 4. anything else ⇒ `unmanaged`, with an honest reason — NEVER a guessed class.
23
+ */
24
+ export declare function classifyDevice(snapshot: UsbDeviceSnapshot): DeviceClassification;
25
+ export declare function cellularVendorName(vendorId: string): string | undefined;
26
+ export declare function cellularEvidence(device: UsbDeviceSnapshot): string | undefined;
27
+ export declare function classifyUsbNetDevice(device: UsbDeviceSnapshot): UsbNetClassification;
28
+ export declare function publishesGenericIdentity(device: UsbDeviceSnapshot): boolean;
29
+ export declare function vendorLabel(device: UsbDeviceSnapshot): string;
30
+ export declare function modelLabel(device: UsbDeviceSnapshot): string;
31
+ export declare function unitDiscriminator(device: UsbDeviceSnapshot): string | undefined;
32
+ /**
33
+ * Derive the USB composition MODE a device is currently in (for a transition's
34
+ * postcondition). Distinct from `classifyDevice`: this reads the data-plane
35
+ * composition, MBIM and RNDIS being unambiguous descriptor signatures, an ECM/NCM
36
+ * data interface being `ecm-ncm` only when a control/vendor port accompanies it
37
+ * (else it is a dumb `router-ethernet` tether), and a bare vendor interface being
38
+ * `qmi`. Returns `undefined` when nothing recognizable is present.
39
+ */
40
+ export declare function detectUsbMode(snapshot: UsbDeviceSnapshot): CanonicalUsbMode | undefined;
41
+ /**
42
+ * Whether a device presents (at least) the descriptors a catalog transition expects
43
+ * after re-enumeration — the descriptor half of the postcondition. Every expected
44
+ * interface triple must be present and `bDeviceClass` must match.
45
+ */
46
+ export declare function descriptorsMatch(snapshot: UsbDeviceSnapshot, expected: ExpectedDescriptors): boolean;
@@ -0,0 +1,258 @@
1
+ // Device classification — deciding, from a udev/sysfs snapshot alone, whether a USB
2
+ // device is a ModemManager-manageable modem, a router-ethernet uplink MM cannot
3
+ // control, a modem still in mass-storage installer mode, or something we simply do
4
+ // not recognize.
5
+ //
6
+ // THE HONESTY RULE (draft §gap sweep, matrix §1): the classifier NEVER guesses. An
7
+ // ambiguous descriptor set returns `unmanaged` with a truthful reason, not a
8
+ // confident-sounding wrong class. A control-port presence — not a vendor id — decides
9
+ // `mm-managed`; a bare vendor-specific interface with no recognized driver is NOT a
10
+ // modem. `pending-modeswitch` is a DISTINCT state (a modem installer awaiting
11
+ // `usb_modeswitch`), never conflated with `unmanaged`.
12
+ //
13
+ // SCOPE — USB ONLY: the whole input here is a `UsbDeviceSnapshot`, a udev/sysfs view of a
14
+ // USB device. PCIe modems are out of scope by construction and get NO entry in this model —
15
+ // a PCI `vendor:device` pair is never smuggled in as a pseudo-USB identity. The Fibocom
16
+ // FM350 is the canonical example: it is a PCIe module (PCI `14c3:4d75`, bound by the
17
+ // `mtk_t7xx` driver on the `wwan`/`net` subsystems, with no USB VID:PID), so it is
18
+ // documented-deferred rather than classified here. See `docs/FM350-DECISION.md` for the
19
+ // evidence and the three-gate ledger behind that decision.
20
+ // USB-IF class / subclass / protocol codes used below.
21
+ const CLASS_COMM = 0x02; // CDC communications (control interface)
22
+ const CLASS_CDC_DATA = 0x0a;
23
+ const CLASS_MASS_STORAGE = 0x08;
24
+ const CLASS_WIRELESS = 0xe0; // wireless controller (RNDIS lives here)
25
+ const CLASS_VENDOR = 0xff;
26
+ const SUB_ACM = 0x02; // abstract control model (AT commands)
27
+ const SUB_ECM = 0x06;
28
+ const SUB_NCM = 0x0d;
29
+ const SUB_MBIM = 0x0e;
30
+ const SUB_RNDIS_WIRELESS = 0x01;
31
+ const PROTO_VENDOR = 0xff;
32
+ const PROTO_RNDIS = 0x03;
33
+ const QMI_DRIVERS = new Set(['qmi_wwan']);
34
+ const AT_DRIVERS = new Set(['option', 'qcserial', 'cdc_acm']);
35
+ const ECM_NCM_DRIVERS = new Set(['cdc_ether', 'cdc_ncm']);
36
+ const RNDIS_DRIVERS = new Set(['rndis_host']);
37
+ const STORAGE_DRIVERS = new Set(['usb-storage', 'uas']);
38
+ export const CELLULAR_USB_VENDOR_IDS = new Map([
39
+ ['05c6', 'Qualcomm'],
40
+ ['0af0', 'Option'],
41
+ ['1199', 'Sierra Wireless'],
42
+ ['12d1', 'Huawei'],
43
+ ['1546', 'u-blox'],
44
+ ['19d2', 'ZTE'],
45
+ ['1bbb', 'TCL/Alcatel'],
46
+ ['1c9e', 'Longcheer'],
47
+ ['1e0e', 'SIMCom'],
48
+ ['2c7c', 'Quectel'],
49
+ ['2cb7', 'Fibocom'],
50
+ ['413c', 'Dell'],
51
+ ]);
52
+ function isMbimControl(i) {
53
+ return i.interfaceClass === CLASS_COMM && i.interfaceSubClass === SUB_MBIM;
54
+ }
55
+ function isQmiControl(i) {
56
+ return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && QMI_DRIVERS.has(i.driver);
57
+ }
58
+ function isAtControl(i) {
59
+ // Standard CDC-ACM AT port (protocol 0xff on 0x02/0x02 is RNDIS, not AT).
60
+ if (i.interfaceClass === CLASS_COMM &&
61
+ i.interfaceSubClass === SUB_ACM &&
62
+ i.interfaceProtocol !== PROTO_VENDOR) {
63
+ return true;
64
+ }
65
+ // Vendor-specific serial port bound to a known AT driver.
66
+ return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && AT_DRIVERS.has(i.driver);
67
+ }
68
+ function isRndis(i) {
69
+ if (i.interfaceClass === CLASS_WIRELESS &&
70
+ i.interfaceSubClass === SUB_RNDIS_WIRELESS &&
71
+ i.interfaceProtocol === PROTO_RNDIS) {
72
+ return true;
73
+ }
74
+ if (i.interfaceClass === CLASS_COMM &&
75
+ i.interfaceSubClass === SUB_ACM &&
76
+ i.interfaceProtocol === PROTO_VENDOR) {
77
+ return true;
78
+ }
79
+ return i.driver !== undefined && RNDIS_DRIVERS.has(i.driver);
80
+ }
81
+ function isEcmNcmData(i) {
82
+ if (i.interfaceClass === CLASS_COMM &&
83
+ (i.interfaceSubClass === SUB_ECM || i.interfaceSubClass === SUB_NCM)) {
84
+ return true;
85
+ }
86
+ return i.driver !== undefined && ECM_NCM_DRIVERS.has(i.driver);
87
+ }
88
+ function isMassStorage(i) {
89
+ if (i.interfaceClass === CLASS_MASS_STORAGE) {
90
+ return true;
91
+ }
92
+ return i.driver !== undefined && STORAGE_DRIVERS.has(i.driver);
93
+ }
94
+ function isVendorSpecific(i) {
95
+ return i.interfaceClass === CLASS_VENDOR;
96
+ }
97
+ function controlKind(i) {
98
+ if (isMbimControl(i)) {
99
+ return 'MBIM';
100
+ }
101
+ return isQmiControl(i) ? 'QMI' : 'AT';
102
+ }
103
+ /** Whether a mass-storage device is a modem awaiting `usb_modeswitch` (not a plain disk). */
104
+ function isModeswitchCandidate(snapshot) {
105
+ const flag = snapshot.udevProperties?.ID_USB_MODESWITCH;
106
+ if (flag !== undefined && flag !== '' && flag !== '0') {
107
+ return true;
108
+ }
109
+ const model = (snapshot.model ?? '').toLowerCase();
110
+ return ['cd-rom', 'installer', 'zerocd', 'autoinstall'].some((marker) => model.includes(marker));
111
+ }
112
+ function unmanagedReason(snapshot, hasStorage) {
113
+ if (hasStorage) {
114
+ return 'mass-storage device with no usb_modeswitch trigger — not a recognized modem installer';
115
+ }
116
+ if (snapshot.interfaces.some(isVendorSpecific)) {
117
+ return 'vendor-specific interface(s) with no recognized modem driver — cannot confidently classify';
118
+ }
119
+ return 'no recognized modem control port, Ethernet tether, or installer interface';
120
+ }
121
+ /**
122
+ * Classify one USB device from its udev/sysfs snapshot. Precedence:
123
+ * 1. a recognized MM control port (MBIM / QMI / AT) ⇒ `mm-managed`;
124
+ * 2. a network tether with NO control port (ECM / NCM / RNDIS) ⇒ `router-mode`;
125
+ * 3. mass storage with a `usb_modeswitch` trigger ⇒ `pending-modeswitch`;
126
+ * 4. anything else ⇒ `unmanaged`, with an honest reason — NEVER a guessed class.
127
+ */
128
+ export function classifyDevice(snapshot) {
129
+ const ifaces = snapshot.interfaces;
130
+ const control = ifaces.find((i) => isMbimControl(i) || isQmiControl(i) || isAtControl(i));
131
+ if (control !== undefined) {
132
+ return {
133
+ deviceClass: 'mm-managed',
134
+ reason: `recognized ${controlKind(control)} control interface — ModemManager-manageable`,
135
+ };
136
+ }
137
+ const tether = ifaces.find((i) => isRndis(i) || isEcmNcmData(i));
138
+ if (tether !== undefined) {
139
+ const kind = isRndis(tether) ? 'RNDIS' : 'ECM/NCM';
140
+ return {
141
+ deviceClass: 'router-mode',
142
+ reason: `${kind} Ethernet tether with no modem control port — router-ethernet class, not MM-manageable`,
143
+ };
144
+ }
145
+ const hasStorage = ifaces.some(isMassStorage) || snapshot.bDeviceClass === CLASS_MASS_STORAGE;
146
+ if (hasStorage && isModeswitchCandidate(snapshot)) {
147
+ return {
148
+ deviceClass: 'pending-modeswitch',
149
+ reason: 'mass-storage installer mode with a usb_modeswitch trigger — awaiting mode switch',
150
+ };
151
+ }
152
+ return { deviceClass: 'unmanaged', reason: unmanagedReason(snapshot, hasStorage) };
153
+ }
154
+ export function cellularVendorName(vendorId) {
155
+ return CELLULAR_USB_VENDOR_IDS.get(vendorId.toLowerCase());
156
+ }
157
+ export function cellularEvidence(device) {
158
+ const vendor = cellularVendorName(device.vendorId);
159
+ if (vendor !== undefined)
160
+ return `USB vendor ${device.vendorId} is ${vendor}, a cellular-module vendor`;
161
+ const modeswitch = device.udevProperties?.ID_USB_MODESWITCH;
162
+ if (modeswitch !== undefined && modeswitch !== '' && modeswitch !== '0')
163
+ return 'device carries a usb_modeswitch trigger — a mode-switching dongle';
164
+ return device.interfaces.some(isMassStorage)
165
+ ? 'device also presents a mass-storage installer interface — the ZeroCD personality of a router-mode dongle'
166
+ : undefined;
167
+ }
168
+ export function classifyUsbNetDevice(device) {
169
+ const control = device.interfaces.find((i) => isMbimControl(i) || isQmiControl(i) || isAtControl(i));
170
+ if (control !== undefined)
171
+ return {
172
+ deviceClass: 'mm-managed',
173
+ reason: `recognized ${controlKind(control)} control interface — ModemManager-manageable`,
174
+ };
175
+ const tether = device.interfaces.find((i) => isRndis(i) || isEcmNcmData(i) || i.interfaceClass === CLASS_CDC_DATA);
176
+ if (tether !== undefined) {
177
+ const kind = isRndis(tether) ? 'RNDIS' : 'ECM/NCM';
178
+ const evidence = cellularEvidence(device);
179
+ return evidence === undefined
180
+ ? {
181
+ deviceClass: 'wired-ethernet',
182
+ reason: `${kind} Ethernet tether with no modem control port and no cellular evidence — a USB network adapter`,
183
+ }
184
+ : {
185
+ deviceClass: 'router-cellular',
186
+ reason: `${kind} Ethernet tether with no modem control port; ${evidence}`,
187
+ };
188
+ }
189
+ return {
190
+ deviceClass: 'unknown',
191
+ reason: unmanagedReason(device, device.interfaces.some(isMassStorage) || device.bDeviceClass === CLASS_MASS_STORAGE),
192
+ };
193
+ }
194
+ export function publishesGenericIdentity(device) {
195
+ const manufacturer = device.manufacturer?.trim();
196
+ const product = device.product?.trim();
197
+ return Boolean(manufacturer && product && manufacturer.toLowerCase() === product.toLowerCase());
198
+ }
199
+ export function vendorLabel(device) {
200
+ const published = device.manufacturer?.trim();
201
+ if (published && !publishesGenericIdentity(device))
202
+ return published;
203
+ return cellularVendorName(device.vendorId) ?? device.databaseVendor?.trim() ?? device.vendorId;
204
+ }
205
+ export function modelLabel(device) {
206
+ const published = device.product?.trim();
207
+ if (published && !publishesGenericIdentity(device))
208
+ return published;
209
+ return device.databaseModel?.trim() ?? device.productId;
210
+ }
211
+ export function unitDiscriminator(device) {
212
+ const serial = device.serialNumber?.trim();
213
+ if (!serial)
214
+ return undefined;
215
+ const folded = serial.toLowerCase();
216
+ return folded === device.manufacturer?.trim().toLowerCase() ||
217
+ folded === device.product?.trim().toLowerCase()
218
+ ? undefined
219
+ : serial;
220
+ }
221
+ /**
222
+ * Derive the USB composition MODE a device is currently in (for a transition's
223
+ * postcondition). Distinct from `classifyDevice`: this reads the data-plane
224
+ * composition, MBIM and RNDIS being unambiguous descriptor signatures, an ECM/NCM
225
+ * data interface being `ecm-ncm` only when a control/vendor port accompanies it
226
+ * (else it is a dumb `router-ethernet` tether), and a bare vendor interface being
227
+ * `qmi`. Returns `undefined` when nothing recognizable is present.
228
+ */
229
+ export function detectUsbMode(snapshot) {
230
+ const ifaces = snapshot.interfaces;
231
+ if (ifaces.some(isMbimControl)) {
232
+ return 'mbim';
233
+ }
234
+ if (ifaces.some(isRndis)) {
235
+ return 'rndis';
236
+ }
237
+ if (ifaces.some(isEcmNcmData)) {
238
+ const hasControl = ifaces.some((i) => isAtControl(i) || isVendorSpecific(i));
239
+ return hasControl ? 'ecm-ncm' : 'router-ethernet';
240
+ }
241
+ if (ifaces.some((i) => isQmiControl(i) || isVendorSpecific(i))) {
242
+ return 'qmi';
243
+ }
244
+ return undefined;
245
+ }
246
+ /**
247
+ * Whether a device presents (at least) the descriptors a catalog transition expects
248
+ * after re-enumeration — the descriptor half of the postcondition. Every expected
249
+ * interface triple must be present and `bDeviceClass` must match.
250
+ */
251
+ export function descriptorsMatch(snapshot, expected) {
252
+ if (snapshot.bDeviceClass !== expected.deviceClass) {
253
+ return false;
254
+ }
255
+ return expected.interfaces.every((exp) => snapshot.interfaces.some((i) => i.interfaceClass === exp.interfaceClass &&
256
+ i.interfaceSubClass === exp.interfaceSubClass &&
257
+ i.interfaceProtocol === exp.interfaceProtocol));
258
+ }
@@ -0,0 +1,28 @@
1
+ import type { CellReading } from './cell-info.js';
2
+ import type { DecodedManagedObjects } from './managed-objects.js';
3
+ import type { SignalCadence } from './signal-setup.js';
4
+ /** eSIM provisioning type — `Sim.SimType` (MMSimType), 1.20+. */
5
+ export type SimType = 'physical' | 'esim' | 'unknown';
6
+ /** eSIM profile status — `Sim.EsimStatus` (MMSimEsimStatus), 1.20+. */
7
+ export type EsimStatus = 'no-profiles' | 'with-profiles' | 'unknown';
8
+ /** The eSIM facts of a modem's active SIM. */
9
+ export interface EsimInfo {
10
+ readonly simType: SimType;
11
+ readonly esimStatus: EsimStatus;
12
+ }
13
+ /** The full read-only enrichment surfaced beside a modem's lifecycle snapshot. */
14
+ export interface ModemEnrichment {
15
+ /** `Modem.Revision` — firmware/hardware revision string, when present. */
16
+ readonly revision?: string;
17
+ readonly esim: EsimInfo;
18
+ readonly signalCadence: SignalCadence;
19
+ readonly cellInfo: readonly CellReading[];
20
+ /** The selected serving cell (total-order winner), when any cell is present. */
21
+ readonly servingCell?: CellReading;
22
+ }
23
+ /** Read `Modem.Revision`, when present. */
24
+ export declare function readRevision(tree: DecodedManagedObjects, modemPath: string): string | undefined;
25
+ /** Read the eSIM `SimType` / `EsimStatus` off the modem's active SIM object. */
26
+ export declare function readEsimInfo(tree: DecodedManagedObjects, modemPath: string): EsimInfo;
27
+ /** Assemble the full enrichment for one modem from its tree, cadence, and cell info. */
28
+ export declare function buildEnrichment(tree: DecodedManagedObjects, modemPath: string, signalCadence: SignalCadence, cellInfo: readonly CellReading[]): ModemEnrichment;
@@ -0,0 +1,59 @@
1
+ // Read-only status enrichment — firmware revision, eSIM facts, signal cadence,
2
+ // and normalized cell info, assembled for one modem's status surface.
3
+ //
4
+ // These are additive, read-only details layered on top of the lifecycle snapshot
5
+ // (A3.1) — they never gate anything and never fail a backend start. `Modem.Revision`
6
+ // and the eSIM `SimType` / `EsimStatus` (MM 1.20+) are read straight from the decoded
7
+ // `GetManagedObjects` tree; `signalCadence` comes from the Signal.Setup manager; cell
8
+ // info is normalized from a `GetCellInfo` reply by `./cell-info`.
9
+ import { selectServingCell } from './cell-info.js';
10
+ import { MODEM_IFACE, SIM_IFACE } from './constants.js';
11
+ import { findInterface, followObjectPath, numberProp, stringProp } from './managed-objects.js';
12
+ /** MMSimType (`Sim.SimType`) → the domain `SimType`. */
13
+ function mapSimType(value) {
14
+ switch (value) {
15
+ case 1:
16
+ return 'physical';
17
+ case 2:
18
+ return 'esim';
19
+ default:
20
+ return 'unknown';
21
+ }
22
+ }
23
+ /** MMSimEsimStatus (`Sim.EsimStatus`) → the domain `EsimStatus`. */
24
+ function mapEsimStatus(value) {
25
+ switch (value) {
26
+ case 1:
27
+ return 'no-profiles';
28
+ case 2:
29
+ return 'with-profiles';
30
+ default:
31
+ return 'unknown';
32
+ }
33
+ }
34
+ /** Read `Modem.Revision`, when present. */
35
+ export function readRevision(tree, modemPath) {
36
+ const revision = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Revision');
37
+ return revision !== undefined && revision.length > 0 ? revision : undefined;
38
+ }
39
+ /** Read the eSIM `SimType` / `EsimStatus` off the modem's active SIM object. */
40
+ export function readEsimInfo(tree, modemPath) {
41
+ const modem = findInterface(tree, modemPath, MODEM_IFACE);
42
+ const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
43
+ return {
44
+ simType: mapSimType(numberProp(sim, 'SimType')),
45
+ esimStatus: mapEsimStatus(numberProp(sim, 'EsimStatus')),
46
+ };
47
+ }
48
+ /** Assemble the full enrichment for one modem from its tree, cadence, and cell info. */
49
+ export function buildEnrichment(tree, modemPath, signalCadence, cellInfo) {
50
+ const revision = readRevision(tree, modemPath);
51
+ const serving = selectServingCell(cellInfo);
52
+ return {
53
+ ...(revision !== undefined ? { revision } : {}),
54
+ esim: readEsimInfo(tree, modemPath),
55
+ signalCadence,
56
+ cellInfo,
57
+ ...(serving !== undefined ? { servingCell: serving } : {}),
58
+ };
59
+ }
@@ -0,0 +1,61 @@
1
+ import { type DecodedManagedObjects } from './managed-objects.js';
2
+ /** The capabilities the D-Bus backend gates its behaviour on. */
3
+ export interface MmFeatures {
4
+ /** `Modem.Physdev` present (physical topology path, 1.22+). Property-probed. */
5
+ readonly physdev: boolean;
6
+ /**
7
+ * Cell-info tier:
8
+ * - `none` — below 1.20, or the modem advertises no cell-info capability.
9
+ * - `basic` — 1.20/1.21 `GetCellInfo`.
10
+ * - `rich` — 1.22+ (serving-type / bandwidth fields).
11
+ */
12
+ readonly cellInfo: 'none' | 'basic' | 'rich';
13
+ /** eSIM read model (`Sim.SimType` / `Sim.EsimStatus`), 1.20+. */
14
+ readonly esimStatus: boolean;
15
+ /** Per-modem operation serialization is available (the shared disruptive actor). */
16
+ readonly opSerialization: boolean;
17
+ }
18
+ /**
19
+ * What A3.2 observed about ONE modem's property surface.
20
+ *
21
+ * `properties` is the union of property NAMES seen across the modem's interfaces
22
+ * (built by `probeModemProperties` from a decoded `GetManagedObjects` tree). The
23
+ * two optional fields let a caller feed richer cell-info evidence than the static
24
+ * object tree carries — MM does not advertise cell-info richness as a Modem
25
+ * property (it comes back from the `GetCellInfo` method), so a backend that has
26
+ * probed cell info can pass what it saw; absent that, detection falls back to the
27
+ * version + `Physdev` co-introduction signal.
28
+ */
29
+ export interface MmPropertyProbe {
30
+ /** Property names present on the observed modem (Modem + Modem3gpp + active SIM). */
31
+ readonly properties: ReadonlySet<string>;
32
+ /** Explicit "modem advertises cell info": `false` forces `cellInfo: 'none'`. */
33
+ readonly cellInfoAvailable?: boolean;
34
+ /** Cell-info field names seen in a probe; `serving-type`/`bandwidth` ⇒ `rich`. */
35
+ readonly cellInfoFields?: ReadonlySet<string>;
36
+ }
37
+ /** A parsed `major.minor` MM version. `null` when the string is not parseable. */
38
+ export interface MmVersion {
39
+ readonly major: number;
40
+ readonly minor: number;
41
+ }
42
+ /**
43
+ * Parse an MM `Version` string (`1.20.0`, `1.24`, `1.26.0-rc1`, …) to `major.minor`.
44
+ * Returns `null` on anything unparseable — callers must degrade, never throw.
45
+ */
46
+ export declare function parseMmVersion(version: string): MmVersion | null;
47
+ /**
48
+ * Detect what an MM daemon can do from its version string AND the properties
49
+ * observed on a modem. Pure — no I/O, never throws (an unparseable version yields
50
+ * the safe floor: property-probed `physdev`, everything else off).
51
+ */
52
+ export declare function detectMmFeatures(version: string, probe: MmPropertyProbe): MmFeatures;
53
+ /**
54
+ * Build an `MmPropertyProbe` for one modem from a decoded `GetManagedObjects` tree:
55
+ * the union of property names on its `Modem`, `Modem.Modem3gpp`, and active `Sim`
56
+ * interfaces. Cell-info fields are not carried by the static tree, so the returned
57
+ * probe leaves them unset (detection falls back to version + `Physdev`).
58
+ */
59
+ export declare function probeModemProperties(tree: DecodedManagedObjects, modemPath: string): MmPropertyProbe;
60
+ /** Convenience: detect features straight off a decoded tree for one modem. */
61
+ export declare function detectModemFeatures(version: string, tree: DecodedManagedObjects, modemPath: string): MmFeatures;