@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,162 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import {
3
- detectMmFeatures,
4
- type MmPropertyProbe,
5
- parseMmVersion,
6
- probeModemProperties,
7
- } from './features';
8
- import type { DecodedManagedObjects } from './managed-objects';
9
-
10
- const BASE_MODEM_PROPS = new Set(['Manufacturer', 'Model', 'Device', 'State', 'SimType']);
11
-
12
- function probe(overrides: Partial<MmPropertyProbe> = {}): MmPropertyProbe {
13
- return { properties: BASE_MODEM_PROPS, ...overrides };
14
- }
15
-
16
- function withPhysdev(): MmPropertyProbe {
17
- return { properties: new Set([...BASE_MODEM_PROPS, 'Physdev']) };
18
- }
19
-
20
- describe('parseMmVersion', () => {
21
- test('parses major.minor.patch', () => {
22
- expect(parseMmVersion('1.24.0')).toEqual({ major: 1, minor: 24 });
23
- });
24
-
25
- test('parses the 1.24.2 FM350-fix release', () => {
26
- expect(parseMmVersion('1.24.2')).toEqual({ major: 1, minor: 24 });
27
- });
28
-
29
- test('parses major.minor without a patch', () => {
30
- expect(parseMmVersion('1.20')).toEqual({ major: 1, minor: 20 });
31
- });
32
-
33
- test('tolerates a pre-release suffix', () => {
34
- expect(parseMmVersion('1.26.0-rc1')).toEqual({ major: 1, minor: 26 });
35
- });
36
-
37
- test('returns null for an unparseable string', () => {
38
- expect(parseMmVersion('not-a-version')).toBeNull();
39
- });
40
- });
41
-
42
- describe('detectMmFeatures — 3 property shapes', () => {
43
- test('1.20 shape: no Physdev, basic cell info, eSIM, serialization', () => {
44
- expect(detectMmFeatures('1.20.0', probe())).toEqual({
45
- physdev: false,
46
- cellInfo: 'basic',
47
- esimStatus: true,
48
- opSerialization: true,
49
- });
50
- });
51
-
52
- test('1.22 shape: Physdev present, rich cell info', () => {
53
- expect(detectMmFeatures('1.22.0', withPhysdev())).toEqual({
54
- physdev: true,
55
- cellInfo: 'rich',
56
- esimStatus: true,
57
- opSerialization: true,
58
- });
59
- });
60
-
61
- test('1.24 shape: Physdev present, rich cell info', () => {
62
- expect(detectMmFeatures('1.24.0', withPhysdev())).toEqual({
63
- physdev: true,
64
- cellInfo: 'rich',
65
- esimStatus: true,
66
- opSerialization: true,
67
- });
68
- });
69
- });
70
-
71
- describe('detectMmFeatures — property presence overrides the version', () => {
72
- test('physdev is property-driven: 1.24 version but no Physdev property ⇒ false', () => {
73
- expect(detectMmFeatures('1.24.0', probe()).physdev).toBe(false);
74
- });
75
-
76
- test('a 1.22+ modem that omits Physdev still reports rich cell info by version', () => {
77
- expect(detectMmFeatures('1.22.0', probe()).cellInfo).toBe('rich');
78
- });
79
-
80
- test('rich cell-info fields promote the tier even without a version signal', () => {
81
- const withFields = probe({ cellInfoFields: new Set(['serving-type', 'bandwidth']) });
82
- expect(detectMmFeatures('1.20.0', withFields).cellInfo).toBe('rich');
83
- });
84
- });
85
-
86
- describe('detectMmFeatures — degrade paths', () => {
87
- test('below 1.20 has no cell info, no eSIM, no serialization', () => {
88
- expect(detectMmFeatures('1.18.0', probe({ properties: new Set(['Device']) }))).toEqual({
89
- physdev: false,
90
- cellInfo: 'none',
91
- esimStatus: false,
92
- opSerialization: false,
93
- });
94
- });
95
-
96
- test('a modem advertising no cell-info capability degrades to none on a supported version', () => {
97
- expect(detectMmFeatures('1.24.0', withPhysdevNoCellInfo()).cellInfo).toBe('none');
98
- });
99
-
100
- test('an eSIM property forces esimStatus true even on an unparseable version', () => {
101
- const esimProbe = probe({ properties: new Set(['EsimStatus']) });
102
- expect(detectMmFeatures('garbage', esimProbe).esimStatus).toBe(true);
103
- });
104
- });
105
-
106
- describe('detectMmFeatures — unknown future version probes, never throws', () => {
107
- test('1.26.0 falls back to property probing, not a whitelist throw', () => {
108
- expect(() => detectMmFeatures('1.26.0', withPhysdev())).not.toThrow();
109
- expect(detectMmFeatures('1.26.0', withPhysdev())).toEqual({
110
- physdev: true,
111
- cellInfo: 'rich',
112
- esimStatus: true,
113
- opSerialization: true,
114
- });
115
- });
116
-
117
- test('a totally unparseable version degrades to the safe floor (property-only physdev)', () => {
118
- const physdevOnly = { properties: new Set(['Device', 'Physdev']) };
119
- expect(detectMmFeatures('???', physdevOnly)).toEqual({
120
- physdev: true,
121
- cellInfo: 'none',
122
- esimStatus: false,
123
- opSerialization: false,
124
- });
125
- });
126
- });
127
-
128
- function withPhysdevNoCellInfo(): MmPropertyProbe {
129
- return { properties: new Set([...BASE_MODEM_PROPS, 'Physdev']), cellInfoAvailable: false };
130
- }
131
-
132
- describe('probeModemProperties', () => {
133
- test('collects property names across Modem, Modem3gpp and the active SIM', () => {
134
- const tree: DecodedManagedObjects = [
135
- [
136
- '/org/freedesktop/ModemManager1/Modem/0',
137
- [
138
- [
139
- 'org.freedesktop.ModemManager1.Modem',
140
- [
141
- ['Device', { signature: 's', value: 'slot-usb2-1' }],
142
- ['Physdev', { signature: 's', value: '/sys/devices/usb2' }],
143
- ['Sim', { signature: 'o', value: '/org/freedesktop/ModemManager1/SIM/0' }],
144
- ],
145
- ],
146
- [
147
- 'org.freedesktop.ModemManager1.Modem.Modem3gpp',
148
- [['Imei', { signature: 's', value: '490154203237518' }]],
149
- ],
150
- ],
151
- ],
152
- [
153
- '/org/freedesktop/ModemManager1/SIM/0',
154
- [['org.freedesktop.ModemManager1.Sim', [['SimType', { signature: 'u', value: 2 }]]]],
155
- ],
156
- ];
157
- const result = probeModemProperties(tree, '/org/freedesktop/ModemManager1/Modem/0');
158
- expect(result.properties.has('Physdev')).toBe(true);
159
- expect(result.properties.has('Imei')).toBe(true);
160
- expect(result.properties.has('SimType')).toBe(true);
161
- });
162
- });
@@ -1,179 +0,0 @@
1
- // ModemManager feature detection — what a given MM daemon can actually do.
2
- //
3
- // A3.2 must run against MM 1.20 (the bookworm floor), 1.22, 1.24, and versions
4
- // nobody has shipped yet. The plan is explicit that this is NOT a version-string
5
- // whitelist: a version we have never seen must degrade gracefully by PROBING the
6
- // observed property set, never throw. So detection combines two signals:
7
- //
8
- // - the daemon `Version` string (a floor: e.g. eSIM read model is 1.20+), and
9
- // - the properties actually present on the observed modem (authoritative for
10
- // `physdev` — the plan requires `physdev` be true ONLY when the `Physdev`
11
- // property is really there, never merely inferred from a version number).
12
- //
13
- // Property facts pinned by the reviewed design (draft §Oracle round-2 #9 / round-4):
14
- // - `Modem.Device` carries the udev slot UID on ALL versions.
15
- // - `Modem.Physdev` is the physical topology path, 1.22+ ONLY.
16
- // - there is NO `PhysdevUid` property anywhere — do not look for one.
17
-
18
- import { MODEM_IFACE, MODEM3GPP_IFACE, SIM_IFACE } from './constants';
19
- import { type DecodedManagedObjects, findInterface, followObjectPath } from './managed-objects';
20
-
21
- /** The capabilities the D-Bus backend gates its behaviour on. */
22
- export interface MmFeatures {
23
- /** `Modem.Physdev` present (physical topology path, 1.22+). Property-probed. */
24
- readonly physdev: boolean;
25
- /**
26
- * Cell-info tier:
27
- * - `none` — below 1.20, or the modem advertises no cell-info capability.
28
- * - `basic` — 1.20/1.21 `GetCellInfo`.
29
- * - `rich` — 1.22+ (serving-type / bandwidth fields).
30
- */
31
- readonly cellInfo: 'none' | 'basic' | 'rich';
32
- /** eSIM read model (`Sim.SimType` / `Sim.EsimStatus`), 1.20+. */
33
- readonly esimStatus: boolean;
34
- /** Per-modem operation serialization is available (the shared disruptive actor). */
35
- readonly opSerialization: boolean;
36
- }
37
-
38
- /**
39
- * What A3.2 observed about ONE modem's property surface.
40
- *
41
- * `properties` is the union of property NAMES seen across the modem's interfaces
42
- * (built by `probeModemProperties` from a decoded `GetManagedObjects` tree). The
43
- * two optional fields let a caller feed richer cell-info evidence than the static
44
- * object tree carries — MM does not advertise cell-info richness as a Modem
45
- * property (it comes back from the `GetCellInfo` method), so a backend that has
46
- * probed cell info can pass what it saw; absent that, detection falls back to the
47
- * version + `Physdev` co-introduction signal.
48
- */
49
- export interface MmPropertyProbe {
50
- /** Property names present on the observed modem (Modem + Modem3gpp + active SIM). */
51
- readonly properties: ReadonlySet<string>;
52
- /** Explicit "modem advertises cell info": `false` forces `cellInfo: 'none'`. */
53
- readonly cellInfoAvailable?: boolean;
54
- /** Cell-info field names seen in a probe; `serving-type`/`bandwidth` ⇒ `rich`. */
55
- readonly cellInfoFields?: ReadonlySet<string>;
56
- }
57
-
58
- /** A parsed `major.minor` MM version. `null` when the string is not parseable. */
59
- export interface MmVersion {
60
- readonly major: number;
61
- readonly minor: number;
62
- }
63
-
64
- const VERSION_PATTERN = /^(\d+)\.(\d+)(?:\.\d+)*/;
65
-
66
- /**
67
- * Parse an MM `Version` string (`1.20.0`, `1.24`, `1.26.0-rc1`, …) to `major.minor`.
68
- * Returns `null` on anything unparseable — callers must degrade, never throw.
69
- */
70
- export function parseMmVersion(version: string): MmVersion | null {
71
- const match = VERSION_PATTERN.exec(version.trim());
72
- if (match === null) {
73
- return null;
74
- }
75
- const major = Number(match[1]);
76
- const minor = Number(match[2]);
77
- if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor)) {
78
- return null;
79
- }
80
- return { major, minor };
81
- }
82
-
83
- /** Whether `version` is at least `major.minor` (false when unparseable). */
84
- function atLeast(version: MmVersion | null, major: number, minor: number): boolean {
85
- if (version === null) {
86
- return false;
87
- }
88
- return version.major > major || (version.major === major && version.minor >= minor);
89
- }
90
-
91
- /** MM baseline this stack supports — eSIM read model, `GetCellInfo`, Signal.Setup. */
92
- const MM_MIN = { major: 1, minor: 20 } as const;
93
- /** Physdev + rich cell-info were co-introduced at 1.22. */
94
- const MM_RICH = { major: 1, minor: 22 } as const;
95
-
96
- function detectCellInfo(
97
- probe: MmPropertyProbe,
98
- supported: boolean,
99
- modern: boolean,
100
- physdev: boolean,
101
- ): MmFeatures['cellInfo'] {
102
- // Version floor first: below 1.20 there is no GetCellInfo at all.
103
- if (!supported) {
104
- return 'none';
105
- }
106
- // A modem that explicitly advertises no cell-info capability degrades to none,
107
- // even on a version that would otherwise support it.
108
- if (probe.cellInfoAvailable === false) {
109
- return 'none';
110
- }
111
- const fields = probe.cellInfoFields;
112
- if (fields !== undefined && (fields.has('serving-type') || fields.has('bandwidth'))) {
113
- return 'rich';
114
- }
115
- // 1.22 co-introduced `Physdev` and the rich cell-info fields, so either signal
116
- // promotes the tier — this is what makes an unseen future version (e.g. 1.26)
117
- // resolve to `rich` by probing rather than by matching a whitelist.
118
- if (modern || physdev) {
119
- return 'rich';
120
- }
121
- return 'basic';
122
- }
123
-
124
- /**
125
- * Detect what an MM daemon can do from its version string AND the properties
126
- * observed on a modem. Pure — no I/O, never throws (an unparseable version yields
127
- * the safe floor: property-probed `physdev`, everything else off).
128
- */
129
- export function detectMmFeatures(version: string, probe: MmPropertyProbe): MmFeatures {
130
- const parsed = parseMmVersion(version);
131
- const supported = atLeast(parsed, MM_MIN.major, MM_MIN.minor);
132
- const modern = atLeast(parsed, MM_RICH.major, MM_RICH.minor);
133
-
134
- // `physdev` is property-authoritative: present iff the `Physdev` property is
135
- // really in the observed set (never inferred from the version alone).
136
- const physdev = probe.properties.has('Physdev');
137
-
138
- const esimStatus =
139
- probe.properties.has('SimType') || probe.properties.has('EsimStatus') || supported;
140
-
141
- const cellInfo = detectCellInfo(probe, supported, modern, physdev);
142
-
143
- return { physdev, cellInfo, esimStatus, opSerialization: supported };
144
- }
145
-
146
- /**
147
- * Build an `MmPropertyProbe` for one modem from a decoded `GetManagedObjects` tree:
148
- * the union of property names on its `Modem`, `Modem.Modem3gpp`, and active `Sim`
149
- * interfaces. Cell-info fields are not carried by the static tree, so the returned
150
- * probe leaves them unset (detection falls back to version + `Physdev`).
151
- */
152
- export function probeModemProperties(
153
- tree: DecodedManagedObjects,
154
- modemPath: string,
155
- ): MmPropertyProbe {
156
- const modem = findInterface(tree, modemPath, MODEM_IFACE);
157
- const modem3gpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
158
- const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
159
-
160
- const properties = new Set<string>();
161
- for (const source of [modem, modem3gpp, sim]) {
162
- if (source === undefined) {
163
- continue;
164
- }
165
- for (const [name] of source) {
166
- properties.add(name);
167
- }
168
- }
169
- return { properties };
170
- }
171
-
172
- /** Convenience: detect features straight off a decoded tree for one modem. */
173
- export function detectModemFeatures(
174
- version: string,
175
- tree: DecodedManagedObjects,
176
- modemPath: string,
177
- ): MmFeatures {
178
- return detectMmFeatures(version, probeModemProperties(tree, modemPath));
179
- }
@@ -1,117 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import {
3
- canBindPolicy,
4
- imeiEquipmentId,
5
- logicalSlotId,
6
- NO_EQUIPMENT_ID,
7
- PolicyBindingRefusedError,
8
- policyBindingKey,
9
- } from '../domain';
10
- import {
11
- looksLikeSlotUid,
12
- type ModemIdentityFacts,
13
- resolveModemIdentities,
14
- resolveModemIdentity,
15
- } from './identity-ladder';
16
-
17
- const IMEI_A = '490154203237518';
18
- const IMEI_B = '356938035643809';
19
- const PATH = '/org/freedesktop/ModemManager1/Modem/0';
20
-
21
- function facts(overrides: Partial<ModemIdentityFacts> = {}): ModemIdentityFacts {
22
- return { runtimePath: PATH, equipmentId: imeiEquipmentId(IMEI_A), ...overrides };
23
- }
24
-
25
- describe('looksLikeSlotUid', () => {
26
- test('our slot-* naming is detected', () => {
27
- expect(looksLikeSlotUid('slot-usb2-1')).toBe(true);
28
- });
29
-
30
- test('MM path-shaped default is not a slot UID', () => {
31
- expect(looksLikeSlotUid('/sys/devices/pci0000:00/usb2')).toBe(false);
32
- });
33
-
34
- test('the bare prefix alone is not a slot UID', () => {
35
- expect(looksLikeSlotUid('slot-')).toBe(false);
36
- });
37
-
38
- test('an absent Device is not a slot UID', () => {
39
- expect(looksLikeSlotUid(undefined)).toBe(false);
40
- });
41
- });
42
-
43
- describe('identity ladder rungs (in priority order)', () => {
44
- test('rung 1: a slot-UID Device wins over Physdev and ports', () => {
45
- const resolved = resolveModemIdentity(
46
- facts({ device: 'slot-usb2-1', physdev: '/sys/devices/usb2', ports: ['ttyUSB0'] }),
47
- );
48
- expect(resolved.slotSource).toBe('device-slot-uid');
49
- expect(resolved.confidence).toBe('high');
50
- expect(resolved.identity.logicalSlotId).toBe(logicalSlotId('slot-usb2-1'));
51
- expect(resolved.stableKey).toBe('slot:slot-usb2-1');
52
- });
53
-
54
- test('rung 2: Physdev is used when Device is a path-shaped default', () => {
55
- const resolved = resolveModemIdentity(
56
- facts({ device: '/sys/devices/usb2', physdev: '/sys/devices/pci/usb2/2-1' }),
57
- );
58
- expect(resolved.slotSource).toBe('physdev');
59
- expect(resolved.confidence).toBe('high');
60
- expect(resolved.stableKey).toBe('physdev:/sys/devices/pci/usb2/2-1');
61
- });
62
-
63
- test('rung 3: a ports-derived sysfs walk when no slot UID or Physdev', () => {
64
- const resolved = resolveModemIdentity(facts({ ports: ['wwan0', 'ttyUSB2', 'ttyUSB0'] }));
65
- expect(resolved.slotSource).toBe('sysfs-walk');
66
- expect(resolved.confidence).toBe('medium');
67
- expect(resolved.stableKey).toBe('sysfs:ttyUSB0+ttyUSB2+wwan0');
68
- });
69
-
70
- test('rung 4: equipment fallback is low confidence with no slot', () => {
71
- const resolved = resolveModemIdentity(facts());
72
- expect(resolved.slotSource).toBe('equipment-fallback');
73
- expect(resolved.confidence).toBe('low');
74
- expect(resolved.identity.logicalSlotId).toBeUndefined();
75
- expect(resolved.stableKey).toBe(`equip:${IMEI_A}`);
76
- });
77
-
78
- test('rung 4 with no equipment id keys off the transient path', () => {
79
- const resolved = resolveModemIdentity(facts({ equipmentId: NO_EQUIPMENT_ID }));
80
- expect(resolved.stableKey).toBe(`path:${PATH}`);
81
- });
82
- });
83
-
84
- describe('durable policy binding through the ladder', () => {
85
- test('a slot-resolved unique modem may bind durable policy', () => {
86
- const resolved = resolveModemIdentity(facts({ device: 'slot-usb2-1' }));
87
- expect(canBindPolicy(resolved.identity)).toBe(true);
88
- expect(policyBindingKey(resolved.identity).logicalSlotId).toBe(logicalSlotId('slot-usb2-1'));
89
- });
90
-
91
- test('duplicate-IMEI fixture: the ladder falls to low-confidence equipment fallback and refuses binding', () => {
92
- const resolved = resolveModemIdentities([
93
- facts({
94
- runtimePath: '/org/freedesktop/ModemManager1/Modem/0',
95
- equipmentId: imeiEquipmentId(IMEI_A),
96
- }),
97
- facts({
98
- runtimePath: '/org/freedesktop/ModemManager1/Modem/1',
99
- equipmentId: imeiEquipmentId(IMEI_A),
100
- }),
101
- ]);
102
- for (const each of resolved) {
103
- expect(each.slotSource).toBe('equipment-fallback');
104
- expect(each.identity.equipmentId.confidence).toBe('low');
105
- expect(canBindPolicy(each.identity)).toBe(false);
106
- expect(() => policyBindingKey(each.identity)).toThrow(PolicyBindingRefusedError);
107
- }
108
- });
109
-
110
- test('unique IMEIs are NOT demoted in a batch resolve', () => {
111
- const resolved = resolveModemIdentities([
112
- facts({ device: 'slot-a', equipmentId: imeiEquipmentId(IMEI_A) }),
113
- facts({ device: 'slot-b', equipmentId: imeiEquipmentId(IMEI_B) }),
114
- ]);
115
- expect(resolved.every((each) => each.identity.equipmentId.confidence === 'high')).toBe(true);
116
- });
117
- });
@@ -1,221 +0,0 @@
1
- // The identity ladder — resolving a STABLE slot handle for a live modem.
2
- //
3
- // A modem's D-Bus object path (`/org/freedesktop/ModemManager1/Modem/3`) is a
4
- // per-boot, per-plug value: it changes on replug and daemon restart, so it can
5
- // never be the durable key. The ladder derives a stable key by trying, in order:
6
- //
7
- // 1. slot-UID in `Modem.Device` — when OUR udev rules label the slot they set
8
- // `Device` to a `slot-*` UID (e.g. `slot-usb2-1`). That is stable and ours,
9
- // distinguishable from MM's own path-shaped default (`/sys/devices/...`).
10
- // 2. `Modem.Physdev` (1.22+) — the physical USB topology path. Stable across
11
- // replug into the same port; used when no slot-UID is present.
12
- // 3. a ports-derived sysfs walk — a deterministic key from the modem's port
13
- // list when neither of the above is available (MM 1.20, no udev rule yet).
14
- // 4. equipment-id fallback — LOW confidence. Per A2.1's ambiguity rules a
15
- // low-confidence identity must never bind durable policy, so this rung sets
16
- // `confidence: 'low'` and `canBindPolicy` refuses on an ambiguous equipment id.
17
- //
18
- // Phase A note: no image-pipeline udev rules ship yet, so on the bench `Device`
19
- // is usually MM's path-shaped default and the ladder lands on Physdev / sysfs.
20
- // The `slot-*` heuristic is documented here so a bench operator can label a slot
21
- // by hand and see rung 1 fire.
22
-
23
- import {
24
- demoteToLowConfidence,
25
- type EquipmentId,
26
- type IdentityConfidence,
27
- imeiEquipmentId,
28
- type LogicalSlotId,
29
- logicalSlotId,
30
- type ModemIdentity,
31
- runtimePath,
32
- } from '../domain';
33
- import { MODEM_IFACE, MODEM3GPP_IFACE } from './constants';
34
- import {
35
- type DecodedManagedObjects,
36
- findInterface,
37
- propValue,
38
- stringProp,
39
- } from './managed-objects';
40
-
41
- /** Which rung of the ladder produced the stable key. */
42
- export type SlotSource = 'device-slot-uid' | 'physdev' | 'sysfs-walk' | 'equipment-fallback';
43
-
44
- /** The prefix OUR udev rules use to label a slot in `Modem.Device`. */
45
- export const SLOT_UID_PREFIX = 'slot-';
46
-
47
- /** The raw facts the ladder resolves from — one modem's observed identity inputs. */
48
- export interface ModemIdentityFacts {
49
- /** The live MM object path (becomes `runtimePath`; never a durable key). */
50
- readonly runtimePath: string;
51
- /** `Modem.Device` — a `slot-*` UID (ours) or MM's path-shaped default. */
52
- readonly device?: string;
53
- /** `Modem.Physdev` — physical topology path (1.22+; absent on 1.20). */
54
- readonly physdev?: string;
55
- /** The modem's port device names (e.g. `ttyUSB0`, `wwan0`) for the sysfs walk. */
56
- readonly ports?: readonly string[];
57
- /** IMEI/serial equipment id with its own confidence (A2.1). */
58
- readonly equipmentId: EquipmentId;
59
- }
60
-
61
- /** The ladder's output: a resolved identity plus how (and how firmly) we got it. */
62
- export interface ResolvedIdentity {
63
- /** The four-part identity; `logicalSlotId` is set for rungs 1-3 only. */
64
- readonly identity: ModemIdentity;
65
- /** Which rung produced the result. */
66
- readonly slotSource: SlotSource;
67
- /** Confidence in the SLOT resolution (rung 4 is always `low`). */
68
- readonly confidence: IdentityConfidence;
69
- /** The durable row key — stable across replug for rungs 1-3 (and unique-IMEI rung 4). */
70
- readonly stableKey: string;
71
- }
72
-
73
- /**
74
- * Whether a `Modem.Device` value is one of OUR slot UIDs (shaped `slot-*`) rather
75
- * than MM's path-shaped default. This is the documented Phase-A heuristic that
76
- * keeps a hand-labelled slot preferred over a `/sys/devices/...` path.
77
- */
78
- export function looksLikeSlotUid(device: string | undefined): boolean {
79
- if (device === undefined) {
80
- return false;
81
- }
82
- return device.startsWith(SLOT_UID_PREFIX) && device.length > SLOT_UID_PREFIX.length;
83
- }
84
-
85
- /** Derive a deterministic key from a modem's port list (sorted, stable across replug). */
86
- function sysfsWalkKey(ports: readonly string[]): string | undefined {
87
- const named = ports.map((p) => p.trim()).filter((p) => p.length > 0);
88
- if (named.length === 0) {
89
- return undefined;
90
- }
91
- return [...named].sort().join('+');
92
- }
93
-
94
- function buildIdentity(facts: ModemIdentityFacts, slot: LogicalSlotId | undefined): ModemIdentity {
95
- return {
96
- equipmentId: facts.equipmentId,
97
- runtimePath: runtimePath(facts.runtimePath),
98
- ...(slot !== undefined ? { logicalSlotId: slot } : {}),
99
- };
100
- }
101
-
102
- /**
103
- * Resolve ONE modem's stable identity by walking the ladder. Pure — no I/O.
104
- *
105
- * Rungs 1-3 yield a `logicalSlotId` and a stable key that survives replug into the
106
- * same slot. Rung 4 (equipment fallback) yields NO slot and `confidence: 'low'`,
107
- * so a durable policy bound via `policyBindingKey` is refused for an ambiguous
108
- * (duplicate/zero) equipment id.
109
- */
110
- export function resolveModemIdentity(facts: ModemIdentityFacts): ResolvedIdentity {
111
- // Rung 1 — our slot-UID in `Device`.
112
- if (looksLikeSlotUid(facts.device)) {
113
- const device = facts.device as string;
114
- const slot = logicalSlotId(device);
115
- return {
116
- identity: buildIdentity(facts, slot),
117
- slotSource: 'device-slot-uid',
118
- confidence: 'high',
119
- stableKey: `slot:${device}`,
120
- };
121
- }
122
-
123
- // Rung 2 — `Physdev` physical topology path (1.22+).
124
- const physdev = facts.physdev?.trim();
125
- if (physdev !== undefined && physdev.length > 0) {
126
- const slot = logicalSlotId(`physdev:${physdev}`);
127
- return {
128
- identity: buildIdentity(facts, slot),
129
- slotSource: 'physdev',
130
- confidence: 'high',
131
- stableKey: `physdev:${physdev}`,
132
- };
133
- }
134
-
135
- // Rung 3 — ports-derived sysfs walk.
136
- const walk = facts.ports !== undefined ? sysfsWalkKey(facts.ports) : undefined;
137
- if (walk !== undefined) {
138
- const slot = logicalSlotId(`sysfs:${walk}`);
139
- return {
140
- identity: buildIdentity(facts, slot),
141
- slotSource: 'sysfs-walk',
142
- confidence: 'medium',
143
- stableKey: `sysfs:${walk}`,
144
- };
145
- }
146
-
147
- // Rung 4 — equipment fallback, LOW confidence, no durable slot.
148
- const equip = facts.equipmentId;
149
- const stableKey =
150
- equip.provenance !== 'none' && equip.value.trim().length > 0
151
- ? `equip:${equip.value}`
152
- : `path:${facts.runtimePath}`;
153
- return {
154
- identity: buildIdentity(facts, undefined),
155
- slotSource: 'equipment-fallback',
156
- confidence: 'low',
157
- stableKey,
158
- };
159
- }
160
-
161
- /**
162
- * Resolve a batch of modems, demoting any equipment id that appears on more than
163
- * one modem to LOW confidence FIRST (cross-modem duplicate detection A2.1 cannot do
164
- * from a single value). A duplicate IMEI thus reaches rung 4 with a low-confidence
165
- * equipment id, and `canBindPolicy` refuses durable binding on it.
166
- */
167
- export function resolveModemIdentities(
168
- factsList: readonly ModemIdentityFacts[],
169
- ): ResolvedIdentity[] {
170
- const counts = new Map<string, number>();
171
- for (const facts of factsList) {
172
- const { equipmentId } = facts;
173
- if (equipmentId.provenance !== 'none' && equipmentId.value.trim().length > 0) {
174
- counts.set(equipmentId.value, (counts.get(equipmentId.value) ?? 0) + 1);
175
- }
176
- }
177
- return factsList.map((facts) => {
178
- const { equipmentId } = facts;
179
- const duplicated =
180
- equipmentId.provenance !== 'none' && (counts.get(equipmentId.value) ?? 0) > 1;
181
- const resolvedFacts: ModemIdentityFacts = duplicated
182
- ? { ...facts, equipmentId: demoteToLowConfidence(equipmentId) }
183
- : facts;
184
- return resolveModemIdentity(resolvedFacts);
185
- });
186
- }
187
-
188
- /** MM `Modem.Ports` is `a(su)`: entries of `[portName, portType]`. */
189
- function portNames(value: ReturnType<typeof propValue>): string[] | undefined {
190
- if (!Array.isArray(value)) {
191
- return undefined;
192
- }
193
- const names = value
194
- .map((entry) => (Array.isArray(entry) ? entry[0] : undefined))
195
- .filter((name): name is string => typeof name === 'string');
196
- return names.length > 0 ? names : undefined;
197
- }
198
-
199
- /**
200
- * Extract one modem's `ModemIdentityFacts` from a decoded `GetManagedObjects` tree —
201
- * the bridge real backends use to feed the ladder from an observed snapshot.
202
- */
203
- export function modemIdentityFactsFromTree(
204
- tree: DecodedManagedObjects,
205
- modemPath: string,
206
- ): ModemIdentityFacts {
207
- const modem = findInterface(tree, modemPath, MODEM_IFACE);
208
- const modem3gpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
209
- const equipment = stringProp(modem, 'EquipmentIdentifier') ?? stringProp(modem3gpp, 'Imei') ?? '';
210
- const device = stringProp(modem, 'Device');
211
- const physdev = stringProp(modem, 'Physdev');
212
- const ports = portNames(propValue(modem, 'Ports'));
213
-
214
- return {
215
- runtimePath: modemPath,
216
- equipmentId: imeiEquipmentId(equipment),
217
- ...(device !== undefined ? { device } : {}),
218
- ...(physdev !== undefined ? { physdev } : {}),
219
- ...(ports !== undefined ? { ports } : {}),
220
- };
221
- }