@ceralive/modem-control 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/README.md +341 -0
  2. package/dist/backend/at-lease.d.ts +59 -0
  3. package/dist/backend/at-lease.js +118 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +101 -0
  61. package/dist/backend/transition-preconditions.js +132 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +216 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +20 -0
  146. package/dist/index.js +27 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +61 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operation-ids.d.ts +2 -0
  196. package/dist/operation-ids.js +26 -0
  197. package/dist/operations/contracts.d.ts +72 -0
  198. package/dist/operations/contracts.js +1 -0
  199. package/dist/operations/index.d.ts +1 -0
  200. package/dist/operations/index.js +1 -0
  201. package/dist/operations/operation-engine.d.ts +16 -0
  202. package/dist/operations/operation-engine.js +194 -0
  203. package/dist/ports/index.d.ts +12 -0
  204. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  205. package/dist/ports/location.d.ts +87 -0
  206. package/dist/ports/location.js +36 -0
  207. package/dist/ports/modem-manager.d.ts +89 -0
  208. package/dist/ports/modem-manager.js +9 -0
  209. package/dist/ports/mutation-admission.d.ts +27 -0
  210. package/dist/ports/mutation-admission.js +9 -0
  211. package/dist/ports/network-manager.d.ts +68 -0
  212. package/dist/ports/network-manager.js +13 -0
  213. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  214. package/dist/ports/observation.js +7 -0
  215. package/dist/ports/ops.d.ts +44 -0
  216. package/dist/ports/ops.js +16 -0
  217. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  218. package/dist/ports/receipts.js +9 -0
  219. package/dist/ports/reconcile.d.ts +33 -0
  220. package/dist/ports/reconcile.js +200 -0
  221. package/dist/ports/resource-ownership.d.ts +29 -0
  222. package/dist/ports/resource-ownership.js +1 -0
  223. package/dist/ports/router.d.ts +19 -0
  224. package/dist/ports/router.js +7 -0
  225. package/dist/ports/sms.d.ts +64 -0
  226. package/dist/ports/sms.js +24 -0
  227. package/dist/ports/uhubctl.d.ts +6 -0
  228. package/dist/ports/uhubctl.js +1 -0
  229. package/dist/providers/contracts.d.ts +124 -0
  230. package/dist/providers/contracts.js +10 -0
  231. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  232. package/dist/providers/huawei-hilink/index.js +2 -0
  233. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  234. package/dist/providers/huawei-hilink/operations.js +56 -0
  235. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  236. package/dist/providers/huawei-hilink/provider.js +76 -0
  237. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  238. package/dist/providers/huawei-hilink/runtime.js +171 -0
  239. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  240. package/dist/providers/huawei-hilink/session.js +120 -0
  241. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  242. package/dist/providers/huawei-hilink/transport.js +1 -0
  243. package/dist/providers/index.d.ts +8 -0
  244. package/dist/providers/index.js +8 -0
  245. package/dist/providers/matcher.d.ts +3 -0
  246. package/dist/providers/matcher.js +205 -0
  247. package/dist/providers/modem-manager/errors.d.ts +7 -0
  248. package/dist/providers/modem-manager/errors.js +37 -0
  249. package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
  250. package/dist/providers/modem-manager/generic-operations.js +209 -0
  251. package/dist/providers/modem-manager/index.d.ts +4 -0
  252. package/dist/providers/modem-manager/index.js +4 -0
  253. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  254. package/dist/providers/modem-manager/module-operations.js +118 -0
  255. package/dist/providers/modem-manager/provider.d.ts +41 -0
  256. package/dist/providers/modem-manager/provider.js +155 -0
  257. package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
  258. package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
  259. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  260. package/dist/providers/modem-manager/snapshot.js +204 -0
  261. package/dist/providers/modem-manager/types.d.ts +137 -0
  262. package/dist/providers/modem-manager/types.js +1 -0
  263. package/dist/providers/network-manager/adapter.d.ts +71 -0
  264. package/dist/providers/network-manager/adapter.js +348 -0
  265. package/dist/providers/network-manager/index.d.ts +2 -0
  266. package/dist/providers/network-manager/index.js +2 -0
  267. package/dist/providers/network-manager/types.d.ts +171 -0
  268. package/dist/providers/network-manager/types.js +77 -0
  269. package/dist/providers/registry.d.ts +13 -0
  270. package/dist/providers/registry.js +33 -0
  271. package/dist/providers/ufi-himi/index.d.ts +6 -0
  272. package/dist/providers/ufi-himi/index.js +6 -0
  273. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  274. package/dist/providers/ufi-himi/operations.js +66 -0
  275. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  276. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  277. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  278. package/dist/providers/ufi-himi/provider.js +204 -0
  279. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  280. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  281. package/dist/providers/ufi-himi/session.d.ts +46 -0
  282. package/dist/providers/ufi-himi/session.js +92 -0
  283. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  284. package/dist/providers/ufi-himi/transport.js +25 -0
  285. package/dist/providers/zte-goform/index.d.ts +2 -0
  286. package/dist/providers/zte-goform/index.js +2 -0
  287. package/dist/providers/zte-goform/provider.d.ts +56 -0
  288. package/dist/providers/zte-goform/provider.js +101 -0
  289. package/dist/providers/zte-goform/session.d.ts +23 -0
  290. package/dist/providers/zte-goform/session.js +197 -0
  291. package/dist/providers/zte-goform/transport.d.ts +16 -0
  292. package/dist/providers/zte-goform/transport.js +1 -0
  293. package/dist/radio/band-truth.d.ts +50 -0
  294. package/dist/radio/band-truth.js +92 -0
  295. package/dist/radio/index.d.ts +3 -0
  296. package/dist/radio/index.js +10 -0
  297. package/dist/radio/mode-combinations.d.ts +88 -0
  298. package/dist/radio/mode-combinations.js +198 -0
  299. package/dist/radio/mode-truth.d.ts +67 -0
  300. package/dist/radio/mode-truth.js +112 -0
  301. package/dist/redact.d.ts +15 -0
  302. package/dist/redact.js +189 -0
  303. package/dist/safety/composition-root.d.ts +28 -0
  304. package/dist/safety/composition-root.js +65 -0
  305. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  306. package/dist/safety/flock-resource-ownership.js +138 -0
  307. package/dist/safety/index.d.ts +2 -0
  308. package/dist/safety/index.js +2 -0
  309. package/dist/sms/dbus-messaging.d.ts +17 -0
  310. package/dist/sms/dbus-messaging.js +185 -0
  311. package/dist/sms/inbox-store.d.ts +10 -0
  312. package/dist/sms/inbox-store.js +82 -0
  313. package/dist/sms/index.d.ts +4 -0
  314. package/dist/sms/index.js +10 -0
  315. package/dist/sms/mmcli-parse.d.ts +54 -0
  316. package/dist/sms/mmcli-parse.js +224 -0
  317. package/dist/sms/normalize.d.ts +42 -0
  318. package/dist/sms/normalize.js +95 -0
  319. package/dist/testing/domain-fakes.d.ts +58 -0
  320. package/dist/testing/domain-fakes.js +98 -0
  321. package/dist/testing/index.d.ts +2 -0
  322. package/dist/testing/index.js +17 -0
  323. package/dist/testing/provider-fakes.d.ts +45 -0
  324. package/dist/testing/provider-fakes.js +64 -0
  325. package/dist/transport/calls.d.ts +9 -0
  326. package/dist/transport/calls.js +88 -0
  327. package/dist/transport/codec.d.ts +3 -0
  328. package/dist/transport/codec.js +207 -0
  329. package/dist/transport/dbus-native.d.ts +57 -0
  330. package/dist/transport/dbus-native.js +17 -0
  331. package/dist/transport/errors.d.ts +21 -0
  332. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  333. package/dist/transport/index.d.ts +4 -0
  334. package/dist/transport/index.js +9 -0
  335. package/dist/transport/signals.d.ts +15 -0
  336. package/dist/transport/signals.js +123 -0
  337. package/dist/transport/signature.d.ts +7 -0
  338. package/dist/transport/signature.js +94 -0
  339. package/dist/transport/transport.d.ts +2 -0
  340. package/dist/transport/transport.js +202 -0
  341. package/dist/transport/types.d.ts +61 -0
  342. package/dist/transport/types.js +19 -0
  343. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  344. package/dist/usb-mode/catalog-schema.js +97 -0
  345. package/dist/usb-mode/catalog.d.ts +21 -0
  346. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  347. package/dist/usb-mode/certified-catalog.json +67 -0
  348. package/dist/usb-mode/index.d.ts +6 -0
  349. package/dist/usb-mode/index.js +16 -0
  350. package/dist/usb-mode/ingestion.d.ts +111 -0
  351. package/dist/usb-mode/ingestion.js +187 -0
  352. package/dist/usb-mode/promotion-review.d.ts +21 -0
  353. package/dist/usb-mode/promotion-review.js +87 -0
  354. package/dist/usb-mode/runtime-capability.d.ts +59 -0
  355. package/dist/usb-mode/runtime-capability.js +157 -0
  356. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  357. package/dist/usb-mode/usb-devices-parse.js +157 -0
  358. package/dist/ussd/calls.d.ts +32 -0
  359. package/dist/ussd/calls.js +96 -0
  360. package/dist/ussd/index.d.ts +5 -0
  361. package/dist/ussd/index.js +12 -0
  362. package/dist/ussd/mm-ussd.d.ts +37 -0
  363. package/dist/ussd/mm-ussd.js +205 -0
  364. package/dist/ussd/refusal.d.ts +53 -0
  365. package/dist/ussd/refusal.js +154 -0
  366. package/dist/ussd/registration.d.ts +20 -0
  367. package/dist/ussd/registration.js +101 -0
  368. package/dist/ussd/session.d.ts +106 -0
  369. package/dist/ussd/session.js +163 -0
  370. package/package.json +38 -4
  371. package/src/backend/at-lease.test.ts +0 -106
  372. package/src/backend/at-lease.ts +0 -158
  373. package/src/backend/cell-info.test.ts +0 -154
  374. package/src/backend/cell-info.ts +0 -160
  375. package/src/backend/device-classifier.test.ts +0 -168
  376. package/src/backend/device-classifier.ts +0 -248
  377. package/src/backend/enrichment.ts +0 -96
  378. package/src/backend/features.test.ts +0 -162
  379. package/src/backend/features.ts +0 -179
  380. package/src/backend/identity-ladder.test.ts +0 -117
  381. package/src/backend/identity-ladder.ts +0 -221
  382. package/src/backend/identity-registry.test.ts +0 -89
  383. package/src/backend/identity-registry.ts +0 -151
  384. package/src/backend/index.ts +0 -236
  385. package/src/backend/lifecycle-interlock.ts +0 -38
  386. package/src/backend/managed-objects.ts +0 -108
  387. package/src/backend/mapping.ts +0 -160
  388. package/src/backend/mm-backend.ts +0 -191
  389. package/src/backend/mm-mutations.ts +0 -228
  390. package/src/backend/modem-actor.test.ts +0 -95
  391. package/src/backend/modem-actor.ts +0 -112
  392. package/src/backend/nm-auto-apn.ts +0 -161
  393. package/src/backend/nm-gsm-fields.ts +0 -122
  394. package/src/backend/nmcli-nm-port.ts +0 -228
  395. package/src/backend/nmcli-runner.ts +0 -52
  396. package/src/backend/observer.ts +0 -297
  397. package/src/backend/power-contract.test.ts +0 -40
  398. package/src/backend/power-contract.ts +0 -83
  399. package/src/backend/recovery-attribution.test.ts +0 -102
  400. package/src/backend/recovery-attribution.ts +0 -86
  401. package/src/backend/recovery-budget.test.ts +0 -64
  402. package/src/backend/recovery-budget.ts +0 -84
  403. package/src/backend/recovery-ladder.test.ts +0 -257
  404. package/src/backend/recovery-ladder.ts +0 -249
  405. package/src/backend/router-ethernet.test.ts +0 -71
  406. package/src/backend/router-ethernet.ts +0 -90
  407. package/src/backend/row-store.ts +0 -105
  408. package/src/backend/signal-setup.ts +0 -112
  409. package/src/backend/sim-unlock.ts +0 -193
  410. package/src/backend/transition-preconditions.ts +0 -149
  411. package/src/backend/uhubctl-power-hook.test.ts +0 -274
  412. package/src/backend/uhubctl-power-hook.ts +0 -377
  413. package/src/backend/usage/accounting.test.ts +0 -147
  414. package/src/backend/usage/accounting.ts +0 -123
  415. package/src/backend/usage/billing-cycle.test.ts +0 -62
  416. package/src/backend/usage/billing-cycle.ts +0 -45
  417. package/src/backend/usage/index.ts +0 -60
  418. package/src/backend/usage/policy-store.test.ts +0 -164
  419. package/src/backend/usage/policy-store.ts +0 -216
  420. package/src/backend/usage/policy-write.test.ts +0 -198
  421. package/src/backend/usage/policy-write.ts +0 -207
  422. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  423. package/src/backend/usage/sampler.test.ts +0 -327
  424. package/src/backend/usage/sampler.ts +0 -282
  425. package/src/backend/usage/store.test.ts +0 -148
  426. package/src/backend/usage/store.ts +0 -177
  427. package/src/backend/usb-enumerator.test.ts +0 -87
  428. package/src/backend/usb-enumerator.ts +0 -181
  429. package/src/backend/usb-mode-transition.test.ts +0 -323
  430. package/src/backend/usb-mode-transition.ts +0 -253
  431. package/src/domain/brand.ts +0 -29
  432. package/src/domain/errors.ts +0 -77
  433. package/src/domain/guards.test.ts +0 -218
  434. package/src/domain/guards.ts +0 -144
  435. package/src/domain/identity.test.ts +0 -83
  436. package/src/domain/identity.ts +0 -165
  437. package/src/domain/index.ts +0 -12
  438. package/src/domain/snapshot.test.ts +0 -266
  439. package/src/domain/snapshot.ts +0 -120
  440. package/src/index.test.ts +0 -6
  441. package/src/index.ts +0 -15
  442. package/src/ports/README.md +0 -61
  443. package/src/ports/forbidden-surface.test.ts +0 -241
  444. package/src/ports/modem-manager.ts +0 -72
  445. package/src/ports/network-manager.ts +0 -87
  446. package/src/ports/ops.ts +0 -60
  447. package/src/ports/ops.type-test.ts +0 -39
  448. package/src/ports/receipts.test.ts +0 -153
  449. package/src/ports/reconcile.test.ts +0 -152
  450. package/src/ports/reconcile.ts +0 -338
  451. package/src/ports/router.ts +0 -29
  452. package/src/redact.test.ts +0 -82
  453. package/src/redact.ts +0 -73
  454. package/src/transport/README.md +0 -65
  455. package/src/transport/calls.ts +0 -113
  456. package/src/transport/characterization.test.ts +0 -260
  457. package/src/transport/codec.test.ts +0 -118
  458. package/src/transport/codec.ts +0 -240
  459. package/src/transport/conformance-python.test.ts +0 -152
  460. package/src/transport/conformance-same-lib.test.ts +0 -115
  461. package/src/transport/dbus-native-lib.d.ts +0 -19
  462. package/src/transport/dbus-native.ts +0 -85
  463. package/src/transport/index.ts +0 -30
  464. package/src/transport/no-library-leak.test.ts +0 -61
  465. package/src/transport/reliability.test.ts +0 -173
  466. package/src/transport/signals.ts +0 -150
  467. package/src/transport/signature.ts +0 -110
  468. package/src/transport/test-support/fake-service.ts +0 -168
  469. package/src/transport/test-support/independent-producer.py +0 -110
  470. package/src/transport/test-support/private-bus.ts +0 -66
  471. package/src/transport/transport.ts +0 -250
  472. package/src/transport/types.ts +0 -118
  473. package/src/usb-mode/catalog-schema.test.ts +0 -181
  474. package/src/usb-mode/catalog-schema.ts +0 -113
  475. package/src/usb-mode/certified-catalog.json +0 -67
  476. package/src/usb-mode/index.ts +0 -58
  477. package/src/usb-mode/ingestion.test.ts +0 -268
  478. package/src/usb-mode/ingestion.ts +0 -297
  479. package/src/usb-mode/promotion-review.ts +0 -117
  480. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -1,160 +0,0 @@
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
-
19
- import type { EpochMillis } from '../domain';
20
- import type { DecodedProps } from './managed-objects';
21
- import { numberProp, stringProp } from './managed-objects';
22
-
23
- /** Where a batch of cell readings came from, and when it was observed. */
24
- export interface CellInfoProvenance {
25
- /** A human/source tag, e.g. the modem path or `'Modem.GetCellInfo'`. */
26
- readonly source: string;
27
- readonly observedAt: EpochMillis;
28
- }
29
-
30
- /** One normalized cell reading — only the pinned fields, plus provenance. */
31
- export interface CellReading {
32
- /** `true` when this dict is marked as the serving cell. */
33
- readonly serving: boolean;
34
- /** The cell identifier, when supplied (serving-cell tiebreak key). */
35
- readonly cellId?: string;
36
- /** Physical cell id — from the `physical-ci` key ONLY. */
37
- readonly pci?: number;
38
- readonly rsrp?: number;
39
- readonly rsrq?: number;
40
- /** NR SINR — from the `sinr` key ONLY (a `snr`-keyed dict is ignored). */
41
- readonly sinr?: number;
42
- /** Radio band — present ONLY when the source supplied it directly. */
43
- readonly band?: string;
44
- readonly source: string;
45
- readonly observedAt: EpochMillis;
46
- }
47
-
48
- /** Read a `serving` flag: an explicit `serving` bool, else a `cell-type` naming it. */
49
- function readServing(cell: DecodedProps): boolean {
50
- const flag = cell.find(([key]) => key === 'serving')?.[1]?.value;
51
- if (typeof flag === 'boolean') {
52
- return flag;
53
- }
54
- const cellType = stringProp(cell, 'cell-type');
55
- return cellType?.toLowerCase().includes('serving') ?? false;
56
- }
57
-
58
- /** Normalize ONE cell's `a{sv}` dict into a `CellReading`, pinning the known keys. */
59
- export function normalizeCellReading(
60
- cell: DecodedProps,
61
- provenance: CellInfoProvenance,
62
- ): CellReading {
63
- const cellId = stringProp(cell, 'cell-id');
64
- const pci = numberProp(cell, 'physical-ci');
65
- const rsrp = numberProp(cell, 'rsrp');
66
- const rsrq = numberProp(cell, 'rsrq');
67
- // `sinr` ONLY — a dict carrying `snr` must never populate this field.
68
- const sinr = numberProp(cell, 'sinr');
69
- // `band` ONLY when directly supplied; never inferred.
70
- const band = stringProp(cell, 'band');
71
- return {
72
- serving: readServing(cell),
73
- ...(cellId !== undefined ? { cellId } : {}),
74
- ...(pci !== undefined ? { pci } : {}),
75
- ...(rsrp !== undefined ? { rsrp } : {}),
76
- ...(rsrq !== undefined ? { rsrq } : {}),
77
- ...(sinr !== undefined ? { sinr } : {}),
78
- ...(band !== undefined ? { band } : {}),
79
- source: provenance.source,
80
- observedAt: provenance.observedAt,
81
- };
82
- }
83
-
84
- /** Normalize a whole `GetCellInfo` reply (`aa{sv}` → cell dicts) into readings. */
85
- export function normalizeCellInfo(
86
- cells: readonly DecodedProps[],
87
- provenance: CellInfoProvenance,
88
- ): readonly CellReading[] {
89
- return cells.map((cell) => normalizeCellReading(cell, provenance));
90
- }
91
-
92
- /**
93
- * Serving-cell TOTAL order — a strict, permutation-invariant ranking of readings.
94
- * Returns < 0 when `a` outranks `b` (should sort first / is the better serving cell):
95
- *
96
- * 1. a `serving`-marked cell outranks a non-serving one;
97
- * 2. then the HIGHER `rsrp` outranks the lower — a cell with NO `rsrp` sorts LAST;
98
- * 3. ties break lexicographically by `cell-id` (a cell with none sorts last);
99
- * 4. final deterministic tiebreaks (`pci`, then a stable serialization) guarantee
100
- * the order is total even for otherwise-identical distinct cells, so the winner
101
- * never depends on input order.
102
- */
103
- export function compareServing(a: CellReading, b: CellReading): number {
104
- if (a.serving !== b.serving) {
105
- return a.serving ? -1 : 1;
106
- }
107
- const rsrpRank = compareOptionalDesc(a.rsrp, b.rsrp);
108
- if (rsrpRank !== 0) {
109
- return rsrpRank;
110
- }
111
- const cellRank = compareOptionalAsc(a.cellId, b.cellId);
112
- if (cellRank !== 0) {
113
- return cellRank;
114
- }
115
- const pciRank = compareOptionalAsc(a.pci, b.pci);
116
- if (pciRank !== 0) {
117
- return pciRank;
118
- }
119
- return stableTag(a) < stableTag(b) ? -1 : stableTag(a) > stableTag(b) ? 1 : 0;
120
- }
121
-
122
- /** Higher value first; `undefined` last. */
123
- function compareOptionalDesc(a: number | undefined, b: number | undefined): number {
124
- if (a === b) return 0;
125
- if (a === undefined) return 1;
126
- if (b === undefined) return -1;
127
- return b - a;
128
- }
129
-
130
- /** Lower value first; `undefined` last. Works for numbers and strings. */
131
- function compareOptionalAsc<T extends number | string>(a: T | undefined, b: T | undefined): number {
132
- if (a === b) return 0;
133
- if (a === undefined) return 1;
134
- if (b === undefined) return -1;
135
- return a < b ? -1 : 1;
136
- }
137
-
138
- /** A stable, order-independent fingerprint used only as the final total-order tiebreak. */
139
- function stableTag(reading: CellReading): string {
140
- return JSON.stringify([
141
- reading.serving,
142
- reading.cellId ?? null,
143
- reading.pci ?? null,
144
- reading.rsrp ?? null,
145
- reading.rsrq ?? null,
146
- reading.sinr ?? null,
147
- reading.band ?? null,
148
- ]);
149
- }
150
-
151
- /**
152
- * Select the serving cell under the total order above. Permutation-invariant: the
153
- * same set of readings always yields the same winner regardless of their order.
154
- */
155
- export function selectServingCell(cells: readonly CellReading[]): CellReading | undefined {
156
- if (cells.length === 0) {
157
- return undefined;
158
- }
159
- return cells.reduce((best, cell) => (compareServing(cell, best) < 0 ? cell : best));
160
- }
@@ -1,168 +0,0 @@
1
- // The classifier's contract, one fixture per class — plus the HONESTY guarantee: an
2
- // ambiguous descriptor set returns `unmanaged` with a truthful reason, never a
3
- // confident guess, and mass storage without a modeswitch trigger is NOT a modem.
4
-
5
- import { describe, expect, test } from 'bun:test';
6
- import { classifyDevice, detectUsbMode, type UsbDeviceSnapshot } from './device-classifier';
7
-
8
- /** A Quectel-style QMI composition: a `qmi_wwan` control port (+ AT serials). */
9
- const QMI: UsbDeviceSnapshot = {
10
- vendorId: '2c7c',
11
- productId: '0125',
12
- model: 'EG25-G',
13
- bDeviceClass: 0,
14
- interfaces: [
15
- { interfaceClass: 0xff, interfaceSubClass: 0xff, interfaceProtocol: 0xff, driver: 'option' },
16
- { interfaceClass: 0xff, interfaceSubClass: 0xff, interfaceProtocol: 0xff, driver: 'qmi_wwan' },
17
- ],
18
- };
19
-
20
- /** An MBIM composition: the standard 0x02/0x0e control + 0x0a data pair. */
21
- const MBIM: UsbDeviceSnapshot = {
22
- vendorId: '1199',
23
- productId: '9071',
24
- model: 'Sierra EM-series',
25
- bDeviceClass: 0,
26
- interfaces: [
27
- { interfaceClass: 0x02, interfaceSubClass: 0x0e, interfaceProtocol: 0x00, driver: 'cdc_mbim' },
28
- { interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x02, driver: 'cdc_mbim' },
29
- ],
30
- };
31
-
32
- /** A Huawei HiLink personality: CDC-ECM Ethernet, no control port — firmware, not switchable. */
33
- const HILINK_ECM: UsbDeviceSnapshot = {
34
- vendorId: '12d1',
35
- productId: '14db',
36
- model: 'HUAWEI HiLink',
37
- bDeviceClass: 0x02,
38
- interfaces: [
39
- { interfaceClass: 0x02, interfaceSubClass: 0x06, interfaceProtocol: 0x00, driver: 'cdc_ether' },
40
- { interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x00, driver: 'cdc_ether' },
41
- ],
42
- };
43
-
44
- /** An RNDIS tether: the wireless-RNDIS descriptor, no control port. */
45
- const RNDIS: UsbDeviceSnapshot = {
46
- vendorId: '19d2',
47
- productId: '0601',
48
- model: 'RNDIS tether',
49
- bDeviceClass: 0x00,
50
- interfaces: [
51
- {
52
- interfaceClass: 0xe0,
53
- interfaceSubClass: 0x01,
54
- interfaceProtocol: 0x03,
55
- driver: 'rndis_host',
56
- },
57
- {
58
- interfaceClass: 0x0a,
59
- interfaceSubClass: 0x00,
60
- interfaceProtocol: 0x00,
61
- driver: 'rndis_host',
62
- },
63
- ],
64
- };
65
-
66
- /** A modem in mass-storage installer mode, flagged for usb_modeswitch. */
67
- const STORAGE_PRE_MODESWITCH: UsbDeviceSnapshot = {
68
- vendorId: '12d1',
69
- productId: '1f01',
70
- model: 'HUAWEI Mobile',
71
- bDeviceClass: 0x00,
72
- interfaces: [
73
- {
74
- interfaceClass: 0x08,
75
- interfaceSubClass: 0x06,
76
- interfaceProtocol: 0x50,
77
- driver: 'usb-storage',
78
- },
79
- ],
80
- udevProperties: { ID_USB_MODESWITCH: '1' },
81
- };
82
-
83
- /** A composite that matches NO known modem pattern — vendor + HID, no driver. */
84
- const AMBIGUOUS: UsbDeviceSnapshot = {
85
- vendorId: '1234',
86
- productId: '5678',
87
- model: 'Unknown Composite',
88
- bDeviceClass: 0x00,
89
- interfaces: [
90
- { interfaceClass: 0x03, interfaceSubClass: 0x00, interfaceProtocol: 0x00 },
91
- { interfaceClass: 0xff, interfaceSubClass: 0x42, interfaceProtocol: 0x01 },
92
- ],
93
- };
94
-
95
- describe('classifyDevice — the six canonical fixtures', () => {
96
- test('QMI composition → mm-managed', () => {
97
- expect(classifyDevice(QMI).deviceClass).toBe('mm-managed');
98
- expect(detectUsbMode(QMI)).toBe('qmi');
99
- });
100
-
101
- test('MBIM composition → mm-managed', () => {
102
- const result = classifyDevice(MBIM);
103
- expect(result.deviceClass).toBe('mm-managed');
104
- expect(result.reason).toContain('MBIM');
105
- expect(detectUsbMode(MBIM)).toBe('mbim');
106
- });
107
-
108
- test('Huawei HiLink-ECM (firmware personality) → router-mode', () => {
109
- const result = classifyDevice(HILINK_ECM);
110
- expect(result.deviceClass).toBe('router-mode');
111
- expect(result.reason).toContain('ECM');
112
- expect(detectUsbMode(HILINK_ECM)).toBe('router-ethernet');
113
- });
114
-
115
- test('RNDIS tether → router-mode', () => {
116
- const result = classifyDevice(RNDIS);
117
- expect(result.deviceClass).toBe('router-mode');
118
- expect(result.reason).toContain('RNDIS');
119
- expect(detectUsbMode(RNDIS)).toBe('rndis');
120
- });
121
-
122
- test('mass-storage before usb_modeswitch → pending-modeswitch (a distinct state)', () => {
123
- const result = classifyDevice(STORAGE_PRE_MODESWITCH);
124
- expect(result.deviceClass).toBe('pending-modeswitch');
125
- expect(result.reason).toContain('usb_modeswitch');
126
- });
127
-
128
- test('ambiguous composite → unmanaged, with an HONEST reason (never guessed)', () => {
129
- const result = classifyDevice(AMBIGUOUS);
130
- expect(result.deviceClass).toBe('unmanaged');
131
- expect(result.reason).toContain('vendor-specific');
132
- // Never a confident modem/router class.
133
- expect(result.deviceClass).not.toBe('mm-managed');
134
- expect(result.deviceClass).not.toBe('router-mode');
135
- });
136
- });
137
-
138
- describe('classifyDevice — honesty guards', () => {
139
- test('mass storage WITHOUT a modeswitch trigger is unmanaged, not pending-modeswitch', () => {
140
- const plainDisk: UsbDeviceSnapshot = {
141
- vendorId: '0781',
142
- productId: '5567',
143
- model: 'SanDisk Cruzer',
144
- bDeviceClass: 0x00,
145
- interfaces: [
146
- {
147
- interfaceClass: 0x08,
148
- interfaceSubClass: 0x06,
149
- interfaceProtocol: 0x50,
150
- driver: 'usb-storage',
151
- },
152
- ],
153
- };
154
- const result = classifyDevice(plainDisk);
155
- expect(result.deviceClass).toBe('unmanaged');
156
- expect(result.reason).toContain('usb_modeswitch');
157
- });
158
-
159
- test('a bare vendor interface with no driver is NOT assumed to be a QMI modem', () => {
160
- const bareVendor: UsbDeviceSnapshot = {
161
- vendorId: '2357',
162
- productId: '0001',
163
- bDeviceClass: 0x00,
164
- interfaces: [{ interfaceClass: 0xff, interfaceSubClass: 0x00, interfaceProtocol: 0x00 }],
165
- };
166
- expect(classifyDevice(bareVendor).deviceClass).toBe('unmanaged');
167
- });
168
- });
@@ -1,248 +0,0 @@
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
-
21
- import type { CanonicalUsbMode, ExpectedDescriptors } from '../usb-mode';
22
-
23
- /** One USB interface's descriptor bytes plus its bound kernel driver, if any. */
24
- export interface UsbInterface {
25
- readonly interfaceClass: number;
26
- readonly interfaceSubClass: number;
27
- readonly interfaceProtocol: number;
28
- /** The bound kernel driver (`qmi_wwan`, `cdc_mbim`, `option`, `cdc_ether`, …). */
29
- readonly driver?: string;
30
- }
31
-
32
- /** A single USB device as observed from udev/sysfs — the classifier's whole input. */
33
- export interface UsbDeviceSnapshot {
34
- readonly vendorId: string;
35
- readonly productId: string;
36
- readonly model?: string;
37
- readonly firmwareRevision?: string;
38
- /** The device-descriptor `bDeviceClass` byte (0 ⇒ class is per-interface). */
39
- readonly bDeviceClass: number;
40
- readonly interfaces: readonly UsbInterface[];
41
- /** Stable physical-topology UID (udev `ID_PATH` / physdev) — survives a mode change. */
42
- readonly physicalUid?: string;
43
- /** The bound network interface name, if the device presents one (`wwan0`, `usb0`). */
44
- readonly ifname?: string;
45
- /** Raw udev properties (`ID_USB_MODESWITCH`, `ID_MM_CANDIDATE`, …). */
46
- readonly udevProperties?: Readonly<Record<string, string>>;
47
- }
48
-
49
- /** The four device classes. `pending-modeswitch` is distinct from `unmanaged`. */
50
- export type DeviceClass = 'mm-managed' | 'router-mode' | 'unmanaged' | 'pending-modeswitch';
51
-
52
- /** A classification plus a human-readable reason (always populated). */
53
- export interface DeviceClassification {
54
- readonly deviceClass: DeviceClass;
55
- readonly reason: string;
56
- }
57
-
58
- // USB-IF class / subclass / protocol codes used below.
59
- const CLASS_COMM = 0x02; // CDC communications (control interface)
60
- const CLASS_MASS_STORAGE = 0x08;
61
- const CLASS_WIRELESS = 0xe0; // wireless controller (RNDIS lives here)
62
- const CLASS_VENDOR = 0xff;
63
- const SUB_ACM = 0x02; // abstract control model (AT commands)
64
- const SUB_ECM = 0x06;
65
- const SUB_NCM = 0x0d;
66
- const SUB_MBIM = 0x0e;
67
- const SUB_RNDIS_WIRELESS = 0x01;
68
- const PROTO_VENDOR = 0xff;
69
- const PROTO_RNDIS = 0x03;
70
-
71
- const QMI_DRIVERS: ReadonlySet<string> = new Set(['qmi_wwan']);
72
- const AT_DRIVERS: ReadonlySet<string> = new Set(['option', 'qcserial', 'cdc_acm']);
73
- const ECM_NCM_DRIVERS: ReadonlySet<string> = new Set(['cdc_ether', 'cdc_ncm']);
74
- const RNDIS_DRIVERS: ReadonlySet<string> = new Set(['rndis_host']);
75
- const STORAGE_DRIVERS: ReadonlySet<string> = new Set(['usb-storage', 'uas']);
76
-
77
- function isMbimControl(i: UsbInterface): boolean {
78
- return i.interfaceClass === CLASS_COMM && i.interfaceSubClass === SUB_MBIM;
79
- }
80
-
81
- function isQmiControl(i: UsbInterface): boolean {
82
- return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && QMI_DRIVERS.has(i.driver);
83
- }
84
-
85
- function isAtControl(i: UsbInterface): boolean {
86
- // Standard CDC-ACM AT port (protocol 0xff on 0x02/0x02 is RNDIS, not AT).
87
- if (
88
- i.interfaceClass === CLASS_COMM &&
89
- i.interfaceSubClass === SUB_ACM &&
90
- i.interfaceProtocol !== PROTO_VENDOR
91
- ) {
92
- return true;
93
- }
94
- // Vendor-specific serial port bound to a known AT driver.
95
- return i.interfaceClass === CLASS_VENDOR && i.driver !== undefined && AT_DRIVERS.has(i.driver);
96
- }
97
-
98
- function isRndis(i: UsbInterface): boolean {
99
- if (
100
- i.interfaceClass === CLASS_WIRELESS &&
101
- i.interfaceSubClass === SUB_RNDIS_WIRELESS &&
102
- i.interfaceProtocol === PROTO_RNDIS
103
- ) {
104
- return true;
105
- }
106
- if (
107
- i.interfaceClass === CLASS_COMM &&
108
- i.interfaceSubClass === SUB_ACM &&
109
- i.interfaceProtocol === PROTO_VENDOR
110
- ) {
111
- return true;
112
- }
113
- return i.driver !== undefined && RNDIS_DRIVERS.has(i.driver);
114
- }
115
-
116
- function isEcmNcmData(i: UsbInterface): boolean {
117
- if (
118
- i.interfaceClass === CLASS_COMM &&
119
- (i.interfaceSubClass === SUB_ECM || i.interfaceSubClass === SUB_NCM)
120
- ) {
121
- return true;
122
- }
123
- return i.driver !== undefined && ECM_NCM_DRIVERS.has(i.driver);
124
- }
125
-
126
- function isMassStorage(i: UsbInterface): boolean {
127
- if (i.interfaceClass === CLASS_MASS_STORAGE) {
128
- return true;
129
- }
130
- return i.driver !== undefined && STORAGE_DRIVERS.has(i.driver);
131
- }
132
-
133
- function isVendorSpecific(i: UsbInterface): boolean {
134
- return i.interfaceClass === CLASS_VENDOR;
135
- }
136
-
137
- function controlKind(i: UsbInterface): string {
138
- if (isMbimControl(i)) {
139
- return 'MBIM';
140
- }
141
- return isQmiControl(i) ? 'QMI' : 'AT';
142
- }
143
-
144
- /** Whether a mass-storage device is a modem awaiting `usb_modeswitch` (not a plain disk). */
145
- function isModeswitchCandidate(snapshot: UsbDeviceSnapshot): boolean {
146
- const flag = snapshot.udevProperties?.ID_USB_MODESWITCH;
147
- if (flag !== undefined && flag !== '' && flag !== '0') {
148
- return true;
149
- }
150
- const model = (snapshot.model ?? '').toLowerCase();
151
- return ['cd-rom', 'installer', 'zerocd', 'autoinstall'].some((marker) => model.includes(marker));
152
- }
153
-
154
- function unmanagedReason(snapshot: UsbDeviceSnapshot, hasStorage: boolean): string {
155
- if (hasStorage) {
156
- return 'mass-storage device with no usb_modeswitch trigger — not a recognized modem installer';
157
- }
158
- if (snapshot.interfaces.some(isVendorSpecific)) {
159
- return 'vendor-specific interface(s) with no recognized modem driver — cannot confidently classify';
160
- }
161
- return 'no recognized modem control port, Ethernet tether, or installer interface';
162
- }
163
-
164
- /**
165
- * Classify one USB device from its udev/sysfs snapshot. Precedence:
166
- * 1. a recognized MM control port (MBIM / QMI / AT) ⇒ `mm-managed`;
167
- * 2. a network tether with NO control port (ECM / NCM / RNDIS) ⇒ `router-mode`;
168
- * 3. mass storage with a `usb_modeswitch` trigger ⇒ `pending-modeswitch`;
169
- * 4. anything else ⇒ `unmanaged`, with an honest reason — NEVER a guessed class.
170
- */
171
- export function classifyDevice(snapshot: UsbDeviceSnapshot): DeviceClassification {
172
- const ifaces = snapshot.interfaces;
173
-
174
- const control = ifaces.find((i) => isMbimControl(i) || isQmiControl(i) || isAtControl(i));
175
- if (control !== undefined) {
176
- return {
177
- deviceClass: 'mm-managed',
178
- reason: `recognized ${controlKind(control)} control interface — ModemManager-manageable`,
179
- };
180
- }
181
-
182
- const tether = ifaces.find((i) => isRndis(i) || isEcmNcmData(i));
183
- if (tether !== undefined) {
184
- const kind = isRndis(tether) ? 'RNDIS' : 'ECM/NCM';
185
- return {
186
- deviceClass: 'router-mode',
187
- reason: `${kind} Ethernet tether with no modem control port — router-ethernet class, not MM-manageable`,
188
- };
189
- }
190
-
191
- const hasStorage = ifaces.some(isMassStorage) || snapshot.bDeviceClass === CLASS_MASS_STORAGE;
192
- if (hasStorage && isModeswitchCandidate(snapshot)) {
193
- return {
194
- deviceClass: 'pending-modeswitch',
195
- reason: 'mass-storage installer mode with a usb_modeswitch trigger — awaiting mode switch',
196
- };
197
- }
198
-
199
- return { deviceClass: 'unmanaged', reason: unmanagedReason(snapshot, hasStorage) };
200
- }
201
-
202
- /**
203
- * Derive the USB composition MODE a device is currently in (for a transition's
204
- * postcondition). Distinct from `classifyDevice`: this reads the data-plane
205
- * composition, MBIM and RNDIS being unambiguous descriptor signatures, an ECM/NCM
206
- * data interface being `ecm-ncm` only when a control/vendor port accompanies it
207
- * (else it is a dumb `router-ethernet` tether), and a bare vendor interface being
208
- * `qmi`. Returns `undefined` when nothing recognizable is present.
209
- */
210
- export function detectUsbMode(snapshot: UsbDeviceSnapshot): CanonicalUsbMode | undefined {
211
- const ifaces = snapshot.interfaces;
212
- if (ifaces.some(isMbimControl)) {
213
- return 'mbim';
214
- }
215
- if (ifaces.some(isRndis)) {
216
- return 'rndis';
217
- }
218
- if (ifaces.some(isEcmNcmData)) {
219
- const hasControl = ifaces.some((i) => isAtControl(i) || isVendorSpecific(i));
220
- return hasControl ? 'ecm-ncm' : 'router-ethernet';
221
- }
222
- if (ifaces.some((i) => isQmiControl(i) || isVendorSpecific(i))) {
223
- return 'qmi';
224
- }
225
- return undefined;
226
- }
227
-
228
- /**
229
- * Whether a device presents (at least) the descriptors a catalog transition expects
230
- * after re-enumeration — the descriptor half of the postcondition. Every expected
231
- * interface triple must be present and `bDeviceClass` must match.
232
- */
233
- export function descriptorsMatch(
234
- snapshot: UsbDeviceSnapshot,
235
- expected: ExpectedDescriptors,
236
- ): boolean {
237
- if (snapshot.bDeviceClass !== expected.deviceClass) {
238
- return false;
239
- }
240
- return expected.interfaces.every((exp) =>
241
- snapshot.interfaces.some(
242
- (i) =>
243
- i.interfaceClass === exp.interfaceClass &&
244
- i.interfaceSubClass === exp.interfaceSubClass &&
245
- i.interfaceProtocol === exp.interfaceProtocol,
246
- ),
247
- );
248
- }
@@ -1,96 +0,0 @@
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
-
10
- import type { CellReading } from './cell-info';
11
- import { selectServingCell } from './cell-info';
12
- import { MODEM_IFACE, SIM_IFACE } from './constants';
13
- import type { DecodedManagedObjects } from './managed-objects';
14
- import { findInterface, followObjectPath, numberProp, stringProp } from './managed-objects';
15
- import type { SignalCadence } from './signal-setup';
16
-
17
- /** eSIM provisioning type — `Sim.SimType` (MMSimType), 1.20+. */
18
- export type SimType = 'physical' | 'esim' | 'unknown';
19
-
20
- /** eSIM profile status — `Sim.EsimStatus` (MMSimEsimStatus), 1.20+. */
21
- export type EsimStatus = 'no-profiles' | 'with-profiles' | 'unknown';
22
-
23
- /** The eSIM facts of a modem's active SIM. */
24
- export interface EsimInfo {
25
- readonly simType: SimType;
26
- readonly esimStatus: EsimStatus;
27
- }
28
-
29
- /** The full read-only enrichment surfaced beside a modem's lifecycle snapshot. */
30
- export interface ModemEnrichment {
31
- /** `Modem.Revision` — firmware/hardware revision string, when present. */
32
- readonly revision?: string;
33
- readonly esim: EsimInfo;
34
- readonly signalCadence: SignalCadence;
35
- readonly cellInfo: readonly CellReading[];
36
- /** The selected serving cell (total-order winner), when any cell is present. */
37
- readonly servingCell?: CellReading;
38
- }
39
-
40
- /** MMSimType (`Sim.SimType`) → the domain `SimType`. */
41
- function mapSimType(value: number | undefined): SimType {
42
- switch (value) {
43
- case 1:
44
- return 'physical';
45
- case 2:
46
- return 'esim';
47
- default:
48
- return 'unknown';
49
- }
50
- }
51
-
52
- /** MMSimEsimStatus (`Sim.EsimStatus`) → the domain `EsimStatus`. */
53
- function mapEsimStatus(value: number | undefined): EsimStatus {
54
- switch (value) {
55
- case 1:
56
- return 'no-profiles';
57
- case 2:
58
- return 'with-profiles';
59
- default:
60
- return 'unknown';
61
- }
62
- }
63
-
64
- /** Read `Modem.Revision`, when present. */
65
- export function readRevision(tree: DecodedManagedObjects, modemPath: string): string | undefined {
66
- const revision = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Revision');
67
- return revision !== undefined && revision.length > 0 ? revision : undefined;
68
- }
69
-
70
- /** Read the eSIM `SimType` / `EsimStatus` off the modem's active SIM object. */
71
- export function readEsimInfo(tree: DecodedManagedObjects, modemPath: string): EsimInfo {
72
- const modem = findInterface(tree, modemPath, MODEM_IFACE);
73
- const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
74
- return {
75
- simType: mapSimType(numberProp(sim, 'SimType')),
76
- esimStatus: mapEsimStatus(numberProp(sim, 'EsimStatus')),
77
- };
78
- }
79
-
80
- /** Assemble the full enrichment for one modem from its tree, cadence, and cell info. */
81
- export function buildEnrichment(
82
- tree: DecodedManagedObjects,
83
- modemPath: string,
84
- signalCadence: SignalCadence,
85
- cellInfo: readonly CellReading[],
86
- ): ModemEnrichment {
87
- const revision = readRevision(tree, modemPath);
88
- const serving = selectServingCell(cellInfo);
89
- return {
90
- ...(revision !== undefined ? { revision } : {}),
91
- esim: readEsimInfo(tree, modemPath),
92
- signalCadence,
93
- cellInfo,
94
- ...(serving !== undefined ? { servingCell: serving } : {}),
95
- };
96
- }