@ceralive/modem-control 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +196 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +19 -0
  146. package/dist/index.js +26 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +47 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
@@ -0,0 +1,87 @@
1
+ // Rendering the REVIEW ARTIFACT for a catalog promotion — the PR-comment template.
2
+ //
3
+ // Catalog additions are human-reviewed commits (Phase-A rule). This module renders what
4
+ // a reviewer reads: the proposed entry, the classifier fixture derived from the same
5
+ // bundle, and a checklist whose boxes a machine cannot tick. It renders a REFUSAL with
6
+ // equal prominence — a refused promotion produces a comment that says so, never silence,
7
+ // because a silently-absent comment is indistinguishable from a forgotten run.
8
+ //
9
+ // Nothing here writes a file or opens a PR. The output is text.
10
+ function refusalBlock(what, refusal) {
11
+ return [
12
+ `### ❌ ${what} — REFUSED`,
13
+ '',
14
+ `**Reason:** \`${refusal.reason}\``,
15
+ '',
16
+ `> ${refusal.detail}`,
17
+ '',
18
+ 'This is a typed refusal from the ingestion seam, not a review opinion. Fix the',
19
+ 'capture and re-run the runbook; do not hand-author the artifact around it.',
20
+ ].join('\n');
21
+ }
22
+ function entryBlock(entry) {
23
+ return [
24
+ '### Proposed `certified-catalog.json` entry',
25
+ '',
26
+ '```json',
27
+ JSON.stringify(entry, null, 2),
28
+ '```',
29
+ ].join('\n');
30
+ }
31
+ function fixtureBlock(fixture) {
32
+ const { snapshot, provenance } = fixture;
33
+ const syntheticNote = provenance.synthetic
34
+ ? '> ⚠️ Derived from a **synthetic** bundle — valid as test data, never as certification evidence.'
35
+ : `> Derived from a real capture, bundle sha256 \`${provenance.bundleSha256}\`.`;
36
+ return [
37
+ '### Proposed classifier fixture (`control/src/backend/device-classifier.test.ts`)',
38
+ '',
39
+ syntheticNote,
40
+ '',
41
+ '```ts',
42
+ `const FIXTURE: UsbDeviceSnapshot = ${JSON.stringify(snapshot, null, 2)};`,
43
+ '```',
44
+ ].join('\n');
45
+ }
46
+ function checklistBlock(context, entry) {
47
+ const transitions = entry.permittedTransitions.length;
48
+ return [
49
+ '### Reviewer checklist (every box is a human judgement)',
50
+ '',
51
+ `- [ ] The bundle at \`${context.evidencePath}\` was captured by **${context.runbook}** on real hardware, and its \`CERTIFY OK\` line reads \`synthetic=false\`.`,
52
+ '- [ ] The bundle sha256 in the entry matches the sha256 the capture printed — recomputed, not copied from this comment.',
53
+ `- [ ] \`canonicalMode: "${entry.canonicalMode}"\` is the mode the device was actually observed in, not the mode it was expected to be in.`,
54
+ transitions === 0
55
+ ? '- [ ] `permittedTransitions: []` is correct for this stage — a stage-1 entry never declares a transition.'
56
+ : '- [ ] The declared transition was OBSERVED end to end: the AT command executed, the port dropped if `expectsPortDrop`, and the device re-enumerated presenting `expectedDescriptors`.',
57
+ '- [ ] No claim in `docs/MODEM-SUPPORT-MATRIX.md` is being changed by this commit without its own evidence.',
58
+ ].join('\n');
59
+ }
60
+ /**
61
+ * Render the review comment for a promotion request. Always returns a comment: a
62
+ * refusal renders a refusal block, so a run that produced nothing promotable still
63
+ * leaves a visible, auditable trace.
64
+ */
65
+ export function renderPromotionReview(request) {
66
+ const { context, entry, fixture } = request;
67
+ const parts = [
68
+ `## Catalog promotion review — ${context.runbook}`,
69
+ '',
70
+ `Evidence: \`${context.evidencePath}\``,
71
+ '',
72
+ 'Generated by the `control/src/usb-mode/` ingestion seam. **This comment promotes',
73
+ 'nothing** — the promotion is the human-reviewed commit that follows it.',
74
+ '',
75
+ ];
76
+ parts.push(entry.ok ? entryBlock(entry.value) : refusalBlock('Catalog entry', entry));
77
+ parts.push('');
78
+ parts.push(fixture.ok ? fixtureBlock(fixture.value) : refusalBlock('Classifier fixture', fixture));
79
+ parts.push('');
80
+ if (entry.ok) {
81
+ parts.push(checklistBlock(context, entry.value));
82
+ }
83
+ else {
84
+ parts.push('### No checklist', '', 'The catalog entry was refused, so there is nothing to review. A checklist here', 'would invite a reviewer to approve an artifact that does not exist.');
85
+ }
86
+ return `${parts.join('\n')}\n`;
87
+ }
@@ -0,0 +1,36 @@
1
+ /** One interface line of a `usb-devices` record. */
2
+ export interface ParsedUsbInterface {
3
+ readonly interfaceClass: number;
4
+ readonly interfaceSubClass: number;
5
+ readonly interfaceProtocol: number;
6
+ /** The bound kernel driver, omitted when `usb-devices` reports `(none)`. */
7
+ readonly driver?: string;
8
+ }
9
+ /** One device block of a `usb-devices` capture. */
10
+ export interface ParsedUsbDevice {
11
+ /** Lowercase hex `xxxx:xxxx`, exactly the catalog's `vidPid` discriminator shape. */
12
+ readonly vidPid: string;
13
+ /** The `D:` line's `Cls=` byte — the device-descriptor `bDeviceClass`. */
14
+ readonly bDeviceClass: number;
15
+ readonly manufacturer?: string;
16
+ readonly product?: string;
17
+ readonly interfaces: readonly ParsedUsbInterface[];
18
+ }
19
+ /**
20
+ * Parse `usb-devices` output into one record per device. Pure and total: malformed
21
+ * lines are skipped rather than guessed at, and a block with no `P:` line yields no
22
+ * record (it has no identity, so inventing one would be a lie).
23
+ */
24
+ export declare function parseUsbDevices(text: string): ParsedUsbDevice[];
25
+ /**
26
+ * Find the single device matching `vidPid` in a parsed capture. Returns `undefined`
27
+ * when there is NO match, and — deliberately — also when there is more than one: a
28
+ * duplicate VID:PID (this bench has two identical Huawei HiLink units) makes the
29
+ * selection ambiguous, and an ambiguous selection must refuse rather than pick the
30
+ * first. The caller turns that into a typed refusal.
31
+ */
32
+ export declare function selectUniqueDevice(devices: readonly ParsedUsbDevice[], vidPid: string): {
33
+ readonly device: ParsedUsbDevice;
34
+ } | {
35
+ readonly ambiguousMatches: number;
36
+ };
@@ -0,0 +1,157 @@
1
+ // Parsing `usb-devices` text — the ONLY per-interface descriptor source inside a
2
+ // certification bundle.
3
+ //
4
+ // A base certification bundle (`certify <slot>` with no `--transition`) carries no
5
+ // structured descriptors at all: it holds `lsusb -v` and `usb-devices` as raw text plus
6
+ // the slot's udev property map. Authoring a classifier fixture or a catalog entry from
7
+ // such a bundle therefore requires reading the descriptors back out of that text, and
8
+ // `usb-devices` is the right half to read — it is line-oriented, one fixed-width record
9
+ // per device, and it names each interface's BOUND KERNEL DRIVER, which `lsusb -v` does
10
+ // not. The driver is not optional detail here: `classifyDevice` decides `mm-managed` vs
11
+ // `router-mode` partly on `qmi_wwan` / `cdc_ether` / `option` bindings.
12
+ //
13
+ // The parser is pure and total: unparseable lines are SKIPPED, never guessed at, and a
14
+ // device that yields no interfaces still yields a record (callers decide whether an
15
+ // interface-less device is usable — this file never makes that judgement).
16
+ //
17
+ // Record shape (`usb-devices`, one blank-line-separated block per device):
18
+ // T: Bus=04 Lev=03 Prnt=03 Port=03 Cnt=01 Dev#= 7 Spd=480 MxCh= 0
19
+ // D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
20
+ // P: Vendor=2c7c ProdID=0801 Rev=05.04
21
+ // S: Manufacturer=Quectel
22
+ // S: Product=RM530N-GL
23
+ // I: If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=qmi_wwan
24
+ /** Read `Key=value` from a `usb-devices` line; `undefined` when the key is absent. */
25
+ function field(line, key) {
26
+ // Values are whitespace-delimited and may be preceded by padding spaces (`Dev#= 7`).
27
+ // `Cls=ff(vend.)` carries a trailing gloss, stripped by the hex/number parsers below.
28
+ const match = new RegExp(`${key}=\\s*(\\S+)`).exec(line);
29
+ return match?.[1];
30
+ }
31
+ /** Parse a hex byte field, tolerating `usb-devices`' `ff(vend.)` gloss suffix. */
32
+ function hexByte(line, key) {
33
+ const raw = field(line, key);
34
+ if (raw === undefined) {
35
+ return undefined;
36
+ }
37
+ const digits = /^[0-9a-fA-F]{1,2}/.exec(raw)?.[0];
38
+ if (digits === undefined) {
39
+ return undefined;
40
+ }
41
+ const value = Number.parseInt(digits, 16);
42
+ return Number.isNaN(value) ? undefined : value;
43
+ }
44
+ /** Parse an `S: Manufacturer=…` style line into its `[key, value]` pair. */
45
+ function stringField(line) {
46
+ const eq = line.indexOf('=');
47
+ if (eq < 0) {
48
+ return undefined;
49
+ }
50
+ const key = line.slice(3, eq).trim();
51
+ const value = line.slice(eq + 1).trim();
52
+ return key === '' || value === '' ? undefined : [key, value];
53
+ }
54
+ function emptyAccumulator() {
55
+ return { interfaces: [] };
56
+ }
57
+ function finish(acc, out) {
58
+ // A record with no `P:` line is not a device — never synthesise an identity for it.
59
+ if (acc.vidPid === undefined) {
60
+ return;
61
+ }
62
+ out.push({
63
+ vidPid: acc.vidPid,
64
+ bDeviceClass: acc.bDeviceClass ?? 0,
65
+ interfaces: acc.interfaces,
66
+ ...(acc.manufacturer !== undefined ? { manufacturer: acc.manufacturer } : {}),
67
+ ...(acc.product !== undefined ? { product: acc.product } : {}),
68
+ });
69
+ }
70
+ function applyProductLine(line, acc) {
71
+ const vendor = field(line, 'Vendor');
72
+ const product = field(line, 'ProdID');
73
+ if (vendor !== undefined && product !== undefined) {
74
+ acc.vidPid = `${vendor.toLowerCase()}:${product.toLowerCase()}`;
75
+ }
76
+ }
77
+ function applyStringLine(line, acc) {
78
+ const pair = stringField(line);
79
+ if (pair === undefined) {
80
+ return;
81
+ }
82
+ const [key, value] = pair;
83
+ if (key === 'Manufacturer') {
84
+ acc.manufacturer = value;
85
+ }
86
+ else if (key === 'Product') {
87
+ acc.product = value;
88
+ }
89
+ }
90
+ function applyInterfaceLine(line, acc) {
91
+ const interfaceClass = hexByte(line, 'Cls');
92
+ const interfaceSubClass = hexByte(line, 'Sub');
93
+ const interfaceProtocol = hexByte(line, 'Prot');
94
+ if (interfaceClass === undefined ||
95
+ interfaceSubClass === undefined ||
96
+ interfaceProtocol === undefined) {
97
+ return;
98
+ }
99
+ const driver = field(line, 'Driver');
100
+ acc.interfaces.push({
101
+ interfaceClass,
102
+ interfaceSubClass,
103
+ interfaceProtocol,
104
+ ...(driver !== undefined && driver !== '(none)' ? { driver } : {}),
105
+ });
106
+ }
107
+ /**
108
+ * Parse `usb-devices` output into one record per device. Pure and total: malformed
109
+ * lines are skipped rather than guessed at, and a block with no `P:` line yields no
110
+ * record (it has no identity, so inventing one would be a lie).
111
+ */
112
+ export function parseUsbDevices(text) {
113
+ const out = [];
114
+ let acc = emptyAccumulator();
115
+ for (const line of text.split('\n')) {
116
+ // A `T:` line opens a new device record; `usb-devices` also blank-line-separates
117
+ // them, but the topology line is the reliable delimiter (blank lines are optional
118
+ // in some kernels' output).
119
+ if (line.startsWith('T:')) {
120
+ finish(acc, out);
121
+ acc = emptyAccumulator();
122
+ continue;
123
+ }
124
+ if (line.startsWith('D:')) {
125
+ const deviceClass = hexByte(line, 'Cls');
126
+ if (deviceClass !== undefined) {
127
+ acc.bDeviceClass = deviceClass;
128
+ }
129
+ }
130
+ else if (line.startsWith('P:')) {
131
+ applyProductLine(line, acc);
132
+ }
133
+ else if (line.startsWith('S:')) {
134
+ applyStringLine(line, acc);
135
+ }
136
+ else if (line.startsWith('I:')) {
137
+ applyInterfaceLine(line, acc);
138
+ }
139
+ }
140
+ finish(acc, out);
141
+ return out;
142
+ }
143
+ /**
144
+ * Find the single device matching `vidPid` in a parsed capture. Returns `undefined`
145
+ * when there is NO match, and — deliberately — also when there is more than one: a
146
+ * duplicate VID:PID (this bench has two identical Huawei HiLink units) makes the
147
+ * selection ambiguous, and an ambiguous selection must refuse rather than pick the
148
+ * first. The caller turns that into a typed refusal.
149
+ */
150
+ export function selectUniqueDevice(devices, vidPid) {
151
+ const matches = devices.filter((d) => d.vidPid === vidPid);
152
+ const only = matches[0];
153
+ if (matches.length === 1 && only !== undefined) {
154
+ return { device: only };
155
+ }
156
+ return { ambiguousMatches: matches.length };
157
+ }
@@ -0,0 +1,32 @@
1
+ import type { DbusTransport } from '../transport/index.js';
2
+ import type { UssdRepliedState } from './session.js';
3
+ /** `MMModem3gppUssdSessionState`. */
4
+ export declare const USSD_STATE_UNKNOWN = 0;
5
+ export declare const USSD_STATE_IDLE = 1;
6
+ export declare const USSD_STATE_ACTIVE = 2;
7
+ export declare const USSD_STATE_USER_RESPONSE = 3;
8
+ /**
9
+ * Decode MM's post-call session state. `UNKNOWN` folds onto `released` with the
10
+ * rest: an unreadable state must close the session rather than leave the operator
11
+ * looking at a dialogue nothing can advance.
12
+ */
13
+ export declare function decodeRepliedState(state: number): UssdRepliedState;
14
+ export interface UssdCallTarget {
15
+ readonly transport: DbusTransport;
16
+ readonly destination: string;
17
+ readonly modem: string;
18
+ readonly timeoutMs: number;
19
+ }
20
+ export declare function callInitiate(target: UssdCallTarget, ussdCommand: string): Promise<string>;
21
+ export declare function callRespond(target: UssdCallTarget, ussdResponse: string): Promise<string>;
22
+ export declare function callCancel(target: UssdCallTarget): Promise<void>;
23
+ /**
24
+ * Read the post-call session state.
25
+ *
26
+ * A targeted `Properties.Get` rather than a `GetManagedObjects` sweep: this runs
27
+ * after every network round-trip, and the whole-tree read is the most expensive
28
+ * call in the package. `unknown` on anything unreadable — the caller treats that
29
+ * as "the network released the session", which is the conservative direction (it
30
+ * closes a session rather than leaving one the operator cannot see).
31
+ */
32
+ export declare function readUssdState(target: UssdCallTarget): Promise<number>;
@@ -0,0 +1,96 @@
1
+ // The four D-Bus calls the USSD adapter makes, and nothing else.
2
+ //
3
+ // Split from the adapter so the session machinery above can be read without the
4
+ // marshalling below, and so a call's shape (interface, member, signature) is
5
+ // stated once in one place.
6
+ //
7
+ // CARRIER TEXT DISCIPLINE: `Initiate` and `Respond` both take and return operator
8
+ // text, and neither the command nor the reply may ever reach a log line. Nothing
9
+ // in this file logs, and every error raised here is re-thrown untouched so the
10
+ // classifier — not a string built around the payload — decides what the caller is
11
+ // told.
12
+ import { MODEM3GPP_USSD_IFACE, PROPERTIES_IFACE } from '../backend/constants.js';
13
+ /** `MMModem3gppUssdSessionState`. */
14
+ export const USSD_STATE_UNKNOWN = 0;
15
+ export const USSD_STATE_IDLE = 1;
16
+ export const USSD_STATE_ACTIVE = 2;
17
+ export const USSD_STATE_USER_RESPONSE = 3;
18
+ /**
19
+ * Decode MM's post-call session state. `UNKNOWN` folds onto `released` with the
20
+ * rest: an unreadable state must close the session rather than leave the operator
21
+ * looking at a dialogue nothing can advance.
22
+ */
23
+ export function decodeRepliedState(state) {
24
+ if (state === USSD_STATE_USER_RESPONSE) {
25
+ return 'awaiting-reply';
26
+ }
27
+ return state === USSD_STATE_ACTIVE ? 'active' : 'released';
28
+ }
29
+ function firstString(body) {
30
+ const value = body[0];
31
+ return typeof value === 'string' ? value : '';
32
+ }
33
+ export async function callInitiate(target, ussdCommand) {
34
+ const reply = await target.transport.callMethod({
35
+ destination: target.destination,
36
+ path: target.modem,
37
+ interface: MODEM3GPP_USSD_IFACE,
38
+ member: 'Initiate',
39
+ signature: 's',
40
+ args: [ussdCommand],
41
+ timeoutMs: target.timeoutMs,
42
+ });
43
+ return firstString(reply.body);
44
+ }
45
+ export async function callRespond(target, ussdResponse) {
46
+ const reply = await target.transport.callMethod({
47
+ destination: target.destination,
48
+ path: target.modem,
49
+ interface: MODEM3GPP_USSD_IFACE,
50
+ member: 'Respond',
51
+ signature: 's',
52
+ args: [ussdResponse],
53
+ timeoutMs: target.timeoutMs,
54
+ });
55
+ return firstString(reply.body);
56
+ }
57
+ export async function callCancel(target) {
58
+ await target.transport.callMethod({
59
+ destination: target.destination,
60
+ path: target.modem,
61
+ interface: MODEM3GPP_USSD_IFACE,
62
+ member: 'Cancel',
63
+ timeoutMs: target.timeoutMs,
64
+ });
65
+ }
66
+ /**
67
+ * Read the post-call session state.
68
+ *
69
+ * A targeted `Properties.Get` rather than a `GetManagedObjects` sweep: this runs
70
+ * after every network round-trip, and the whole-tree read is the most expensive
71
+ * call in the package. `unknown` on anything unreadable — the caller treats that
72
+ * as "the network released the session", which is the conservative direction (it
73
+ * closes a session rather than leaving one the operator cannot see).
74
+ */
75
+ export async function readUssdState(target) {
76
+ try {
77
+ const reply = await target.transport.callMethod({
78
+ destination: target.destination,
79
+ path: target.modem,
80
+ interface: PROPERTIES_IFACE,
81
+ member: 'Get',
82
+ signature: 'ss',
83
+ args: [MODEM3GPP_USSD_IFACE, 'State'],
84
+ timeoutMs: target.timeoutMs,
85
+ });
86
+ const wrapped = reply.body[0];
87
+ if (typeof wrapped === 'number') {
88
+ return wrapped;
89
+ }
90
+ const inner = wrapped?.value;
91
+ return typeof inner === 'number' ? inner : USSD_STATE_UNKNOWN;
92
+ }
93
+ catch {
94
+ return USSD_STATE_UNKNOWN;
95
+ }
96
+ }
@@ -0,0 +1,5 @@
1
+ export { callCancel, callInitiate, callRespond, decodeRepliedState, readUssdState, USSD_STATE_ACTIVE, USSD_STATE_IDLE, USSD_STATE_UNKNOWN, USSD_STATE_USER_RESPONSE, type UssdCallTarget, } from './calls.js';
2
+ export { MmUssd, type MmUssdDeps, type UssdScheduler, type UssdTimerHandle, type UssdVerbResult, } from './mm-ussd.js';
3
+ export { classifyUssdFailure, isPacketSwitchedOnly, USSD_REFUSAL_REASONS, type UssdRefusalReason, type UssdRegistrationFacts, } from './refusal.js';
4
+ export { decodeAccessTechnologies, readUssdRegistrationFacts, registrationFactsFromTree, UNKNOWN_REGISTRATION, } from './registration.js';
5
+ export { IDLE_SESSION, isUssdSessionOpen, reduceUssdSession, USSD_SESSION_OUTCOMES, USSD_SESSION_STATES, type UssdRepliedState, type UssdSessionEvent, type UssdSessionOutcome, type UssdSessionSnapshot, type UssdSessionState, type UssdTransition, } from './session.js';
@@ -0,0 +1,12 @@
1
+ // The gated USSD capability module — session state machine, refusal taxonomy, and
2
+ // the ModemManager `Modem3gpp.Ussd` adapter.
3
+ //
4
+ // GATED: nothing here runs unless the operator has enabled the `ussd` capability
5
+ // module and the modem positively advertises the interface (`../capability`).
6
+ // LEASE-ONLY: a USSD session cannot re-register the radio, so it takes the
7
+ // per-modem mutation lease and is NOT journaled.
8
+ export { callCancel, callInitiate, callRespond, decodeRepliedState, readUssdState, USSD_STATE_ACTIVE, USSD_STATE_IDLE, USSD_STATE_UNKNOWN, USSD_STATE_USER_RESPONSE, } from './calls.js';
9
+ export { MmUssd, } from './mm-ussd.js';
10
+ export { classifyUssdFailure, isPacketSwitchedOnly, USSD_REFUSAL_REASONS, } from './refusal.js';
11
+ export { decodeAccessTechnologies, readUssdRegistrationFacts, registrationFactsFromTree, UNKNOWN_REGISTRATION, } from './registration.js';
12
+ export { IDLE_SESSION, isUssdSessionOpen, reduceUssdSession, USSD_SESSION_OUTCOMES, USSD_SESSION_STATES, } from './session.js';
@@ -0,0 +1,37 @@
1
+ import type { ModemActor } from '../backend/modem-actor.js';
2
+ import type { ModemRef } from '../ports/index.js';
3
+ import type { DbusTransport } from '../transport/index.js';
4
+ import { type UssdRefusalReason } from './refusal.js';
5
+ import { type UssdSessionSnapshot } from './session.js';
6
+ export interface UssdTimerHandle {
7
+ cancel(): void;
8
+ }
9
+ export type UssdScheduler = (delayMs: number, run: () => void) => UssdTimerHandle;
10
+ export interface UssdVerbResult {
11
+ readonly ok: boolean;
12
+ readonly snapshot: UssdSessionSnapshot;
13
+ /** Carrier text. Redacted by key everywhere it is serialized. */
14
+ readonly ussdReply?: string;
15
+ readonly refusal?: UssdRefusalReason;
16
+ }
17
+ export interface MmUssdDeps {
18
+ readonly transport: DbusTransport;
19
+ readonly actor: ModemActor;
20
+ readonly destination?: string;
21
+ readonly resolveStableKey: (modem: ModemRef) => string;
22
+ readonly callTimeoutMs?: number;
23
+ readonly sessionIdleTimeoutMs?: number;
24
+ readonly scheduler?: UssdScheduler;
25
+ /** Notified on every stored-state change, so a UI can follow a session. */
26
+ readonly onSessionChange?: (stableKey: string, snapshot: UssdSessionSnapshot) => void;
27
+ }
28
+ export declare class MmUssd {
29
+ #private;
30
+ constructor(deps: MmUssdDeps);
31
+ snapshot(modem: ModemRef): UssdSessionSnapshot;
32
+ initiate(modem: ModemRef, ussdCommand: string): Promise<UssdVerbResult>;
33
+ respond(modem: ModemRef, ussdResponse: string): Promise<UssdVerbResult>;
34
+ cancel(modem: ModemRef): Promise<UssdVerbResult>;
35
+ /** Drop every timer. A live session on the modem is NOT cancelled by this. */
36
+ stop(): void;
37
+ }
@@ -0,0 +1,205 @@
1
+ // The ModemManager USSD adapter — `Modem3gpp.Ussd` driven by the pure session
2
+ // machine in `./session`.
3
+ //
4
+ // Three properties are load-bearing and none of them is obvious from the D-Bus
5
+ // API alone:
6
+ //
7
+ // 1. **Every verb runs through the shared per-modem `ModemActor`.** A USSD
8
+ // session is a single network-side resource per subscriber, so two verbs
9
+ // interleaving on one modem is exactly the double-dialogue the network
10
+ // answers busy. The actor is keyed on the STABLE key, so the serialization
11
+ // survives a replug like every other disruptive op in this package.
12
+ //
13
+ // 2. **An unanswered session is closed at a bound, and the CANCEL is attempted.**
14
+ // A session nobody responds to stays open NETWORK-side; letting it expire in
15
+ // silence would leave the next `Initiate` failing busy for reasons the
16
+ // operator cannot see. The bound closes our machine and best-effort releases
17
+ // the network's — best-effort because a modem that did not answer the last
18
+ // call may not answer this one either, and a timeout must still terminate.
19
+ //
20
+ // 3. **A `closed` session resets the STORED state to idle, while the CALLER is
21
+ // told `closed`.** The machine is deliberately terminal so a finished session
22
+ // cannot be resurrected; the adapter's map is per-modem and long-lived, so it
23
+ // starts each new dialogue from a fresh machine.
24
+ //
25
+ // The carrier's text rides `ussdReply` and NOTHING here logs it. That field name
26
+ // is not cosmetic: `../redact` is key-based, so the name IS what guarantees the
27
+ // value is masked in every receipt, bundle, and log line it can reach.
28
+ import { MM_BUS_NAME } from '../backend/constants.js';
29
+ import { callCancel, callInitiate, callRespond, decodeRepliedState, readUssdState, } from './calls.js';
30
+ import { classifyUssdFailure } from './refusal.js';
31
+ import { readUssdRegistrationFacts } from './registration.js';
32
+ import { IDLE_SESSION, reduceUssdSession, } from './session.js';
33
+ /** A network round-trip. USSD legitimately takes tens of seconds. */
34
+ const DEFAULT_CALL_TIMEOUT_MS = 45_000;
35
+ /** How long a session may sit awaiting an operator response before it is closed. */
36
+ const DEFAULT_SESSION_IDLE_TIMEOUT_MS = 120_000;
37
+ const defaultScheduler = (delayMs, run) => {
38
+ const timer = setTimeout(run, delayMs);
39
+ timer.unref?.();
40
+ return {
41
+ cancel: () => {
42
+ clearTimeout(timer);
43
+ },
44
+ };
45
+ };
46
+ export class MmUssd {
47
+ #deps;
48
+ #destination;
49
+ #callTimeoutMs;
50
+ #idleTimeoutMs;
51
+ #scheduler;
52
+ #sessions = new Map();
53
+ #timers = new Map();
54
+ constructor(deps) {
55
+ this.#deps = deps;
56
+ this.#destination = deps.destination ?? MM_BUS_NAME;
57
+ this.#callTimeoutMs = deps.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
58
+ this.#idleTimeoutMs = deps.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
59
+ this.#scheduler = deps.scheduler ?? defaultScheduler;
60
+ }
61
+ snapshot(modem) {
62
+ return this.#sessions.get(this.#deps.resolveStableKey(modem)) ?? IDLE_SESSION;
63
+ }
64
+ initiate(modem, ussdCommand) {
65
+ return this.#runVerb(modem, { kind: 'initiate' }, (target) => callInitiate(target, ussdCommand));
66
+ }
67
+ respond(modem, ussdResponse) {
68
+ return this.#runVerb(modem, { kind: 'respond' }, (target) => callRespond(target, ussdResponse));
69
+ }
70
+ cancel(modem) {
71
+ const key = this.#deps.resolveStableKey(modem);
72
+ return this.#deps.actor.run(key, async () => {
73
+ const gate = this.#gate(key, { kind: 'cancel' });
74
+ if (!gate.ok) {
75
+ return gate.result;
76
+ }
77
+ const target = this.#target(modem);
78
+ try {
79
+ await callCancel(target);
80
+ return this.#settle(key, { kind: 'cancelled' });
81
+ }
82
+ catch (error) {
83
+ return this.#fail(key, modem, error);
84
+ }
85
+ });
86
+ }
87
+ /** Drop every timer. A live session on the modem is NOT cancelled by this. */
88
+ stop() {
89
+ for (const timer of this.#timers.values()) {
90
+ timer.cancel();
91
+ }
92
+ this.#timers.clear();
93
+ }
94
+ async #runVerb(modem, event, dispatch) {
95
+ const key = this.#deps.resolveStableKey(modem);
96
+ return this.#deps.actor.run(key, async () => {
97
+ const gate = this.#gate(key, event);
98
+ if (!gate.ok) {
99
+ return gate.result;
100
+ }
101
+ const target = this.#target(modem);
102
+ try {
103
+ const ussdReply = await dispatch(target);
104
+ const sessionState = decodeRepliedState(await readUssdState(target));
105
+ const settled = this.#settle(key, { kind: 'replied', sessionState });
106
+ // A session the network kept open — whether or not it asked a
107
+ // question — is what the idle bound exists to release.
108
+ if (sessionState !== 'released') {
109
+ this.#armIdleTimeout(key, modem);
110
+ }
111
+ return { ...settled, ussdReply };
112
+ }
113
+ catch (error) {
114
+ return this.#fail(key, modem, error);
115
+ }
116
+ });
117
+ }
118
+ /**
119
+ * Apply the operator's verb to the machine. A refusal is returned WITHOUT
120
+ * touching the stored state or dialling the bus — a doomed verb must not
121
+ * disturb a live session.
122
+ */
123
+ #gate(key, event) {
124
+ const current = this.#sessions.get(key) ?? IDLE_SESSION;
125
+ const transition = reduceUssdSession(current, event);
126
+ if (!transition.ok) {
127
+ return {
128
+ ok: false,
129
+ result: { ok: false, snapshot: current, refusal: transition.refusal },
130
+ };
131
+ }
132
+ this.#store(key, transition.snapshot);
133
+ return { ok: true };
134
+ }
135
+ #settle(key, event) {
136
+ const current = this.#sessions.get(key) ?? IDLE_SESSION;
137
+ const transition = reduceUssdSession(current, event);
138
+ if (!transition.ok) {
139
+ return { ok: false, snapshot: current, refusal: transition.refusal };
140
+ }
141
+ this.#store(key, transition.snapshot);
142
+ const closedRefusal = transition.snapshot.refusal;
143
+ return {
144
+ ok: closedRefusal === undefined,
145
+ snapshot: transition.snapshot,
146
+ ...(closedRefusal === undefined ? {} : { refusal: closedRefusal }),
147
+ };
148
+ }
149
+ async #fail(key, modem, error) {
150
+ const registration = await readUssdRegistrationFacts(this.#deps.transport, this.#destination, modem);
151
+ return this.#settle(key, { kind: 'failed', reason: classifyUssdFailure(error, registration) });
152
+ }
153
+ /**
154
+ * A session reaching `closed` is REPORTED as closed and STORED as idle, so the
155
+ * next `initiate` starts from a fresh machine rather than the terminal one.
156
+ */
157
+ #store(key, snapshot) {
158
+ this.#clearTimer(key);
159
+ if (snapshot.state === 'closed') {
160
+ this.#sessions.delete(key);
161
+ }
162
+ else {
163
+ this.#sessions.set(key, snapshot);
164
+ }
165
+ this.#deps.onSessionChange?.(key, snapshot);
166
+ }
167
+ #armIdleTimeout(key, modem) {
168
+ this.#clearTimer(key);
169
+ this.#timers.set(key, this.#scheduler(this.#idleTimeoutMs, () => {
170
+ void this.#expire(key, modem);
171
+ }));
172
+ }
173
+ #clearTimer(key) {
174
+ this.#timers.get(key)?.cancel();
175
+ this.#timers.delete(key);
176
+ }
177
+ /**
178
+ * The bound elapsed. The machine closes `timed-out` FIRST — that outcome is
179
+ * the operator's answer whether or not the release lands — and the modem-side
180
+ * cancel is attempted afterwards, best-effort.
181
+ */
182
+ async #expire(key, modem) {
183
+ await this.#deps.actor.run(key, async () => {
184
+ if (!this.#sessions.has(key)) {
185
+ return;
186
+ }
187
+ this.#settle(key, { kind: 'timeout' });
188
+ try {
189
+ await callCancel(this.#target(modem));
190
+ }
191
+ catch {
192
+ // The modem that did not answer the dialogue may not answer this
193
+ // either; the session is already closed on our side.
194
+ }
195
+ });
196
+ }
197
+ #target(modem) {
198
+ return {
199
+ transport: this.#deps.transport,
200
+ destination: this.#destination,
201
+ modem,
202
+ timeoutMs: this.#callTimeoutMs,
203
+ };
204
+ }
205
+ }