@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
@@ -1,323 +0,0 @@
1
- // The transition transaction — the safety-critical core. This proves:
2
- // - the happy path walks all TEN steps in order and yields a NEW ifname on the SAME
3
- // stableKey;
4
- // - the THREE-TIER negative matrix: TIER A entry refusals fire ZERO actor/lease/AT
5
- // calls; TIER B an in-actor race is caught after exactly one actor entry with zero
6
- // lease/AT; TIER C a postcondition mismatch fails degraded, never reactivates, and
7
- // still releases the interlock via finally;
8
- // - a crash mid-transaction trips the watchdog, force-uninhibits, and returns
9
- // degraded rather than hanging forever.
10
-
11
- import { describe, expect, test } from 'bun:test';
12
- import { epochMillis } from '../domain';
13
- import { connectionId, deviceIfname, type NetworkManagerPort, receipt } from '../ports';
14
- import type { UsbDeviceSnapshot } from './device-classifier';
15
- import { ModemActor } from './modem-actor';
16
- import type { TransitionInterlock, UsbModeTransitionRequest } from './transition-preconditions';
17
- import { UsbModeTransition, type UsbModeTransitionDeps } from './usb-mode-transition';
18
-
19
- const CACHED_UID = 'pci-0000:00-usb-0:1';
20
- const SKU = {
21
- vidPid: '2c7c:0125',
22
- model: 'CERALIVE-SYNTHETIC-TEST-SKU',
23
- firmwarePrefix: 'SYNTHETICFW01',
24
- };
25
-
26
- const TEN_STEPS = [
27
- 'nm-quiesce',
28
- 'inhibit',
29
- 'at-command',
30
- 'await-port-drop',
31
- 'uninhibit',
32
- 'await-reenumeration',
33
- 'postcondition',
34
- 'resolve-ifname',
35
- 'reactivate',
36
- 'release-interlock',
37
- ];
38
-
39
- const OLD_QMI: UsbDeviceSnapshot = {
40
- vendorId: '2c7c',
41
- productId: '0125',
42
- bDeviceClass: 0,
43
- physicalUid: CACHED_UID,
44
- ifname: 'wwan0',
45
- interfaces: [
46
- { interfaceClass: 0xff, interfaceSubClass: 0xff, interfaceProtocol: 0xff, driver: 'qmi_wwan' },
47
- ],
48
- };
49
-
50
- /** Re-enumerated as MBIM (target), same physical UID, NEW ifname — matches the catalog. */
51
- const NEW_MBIM: UsbDeviceSnapshot = {
52
- vendorId: '2c7c',
53
- productId: '0126',
54
- bDeviceClass: 0,
55
- physicalUid: CACHED_UID,
56
- ifname: 'wwan1',
57
- interfaces: [
58
- { interfaceClass: 0x02, interfaceSubClass: 0x0e, interfaceProtocol: 0x00, driver: 'cdc_mbim' },
59
- { interfaceClass: 0x0a, interfaceSubClass: 0x00, interfaceProtocol: 0x02, driver: 'cdc_mbim' },
60
- ],
61
- };
62
-
63
- /** Re-enumerated STILL as QMI — the postcondition-mismatch device (same UID). */
64
- const STILL_QMI: UsbDeviceSnapshot = { ...OLD_QMI, ifname: 'wwan0' };
65
-
66
- interface SpyLog {
67
- readonly calls: string[];
68
- }
69
-
70
- function makeNm(log: SpyLog): NetworkManagerPort {
71
- const rcpt = () => receipt('connection', 'applied', 'ok');
72
- return {
73
- createGsmProfile: () => Promise.reject(new Error('unused')),
74
- readGsmProfile: () => Promise.resolve(undefined),
75
- updateGsmProfile: () => Promise.reject(new Error('unused')),
76
- deleteGsmProfile: () => Promise.resolve(),
77
- activate: (_id, ifname) => {
78
- log.calls.push(`nm.activate:${ifname}`);
79
- return Promise.resolve(rcpt());
80
- },
81
- deactivate: () => Promise.resolve(rcpt()),
82
- acquireQuiesceLease: (id, ifname) => {
83
- log.calls.push('nm.acquireQuiesceLease');
84
- return Promise.resolve({
85
- connectionId: id,
86
- deviceIfname: ifname,
87
- acquiredAt: epochMillis(0),
88
- });
89
- },
90
- releaseQuiesceLease: () => {
91
- log.calls.push('nm.releaseQuiesceLease');
92
- return Promise.resolve();
93
- },
94
- };
95
- }
96
-
97
- function makeMm(log: SpyLog): Pick<UsbModeTransitionDeps['modemManager'], 'inhibit' | 'uninhibit'> {
98
- return {
99
- inhibit: (uid) => {
100
- log.calls.push(`mm.inhibit:${uid}`);
101
- return Promise.resolve({ uid, acquiredAt: epochMillis(0) });
102
- },
103
- uninhibit: () => {
104
- log.calls.push('mm.uninhibit');
105
- return Promise.resolve();
106
- },
107
- };
108
- }
109
-
110
- function okSender(log: SpyLog): UsbModeTransitionDeps['atSender'] {
111
- return {
112
- send: (command) => {
113
- log.calls.push(`at.send:${command}`);
114
- return Promise.resolve({ ok: true, raw: 'OK' });
115
- },
116
- };
117
- }
118
-
119
- function scriptedEnumerate(
120
- frames: UsbDeviceSnapshot[][],
121
- ): () => Promise<readonly UsbDeviceSnapshot[]> {
122
- let i = 0;
123
- return () => {
124
- const frame = frames[Math.min(i, frames.length - 1)] ?? [];
125
- i += 1;
126
- return Promise.resolve(frame);
127
- };
128
- }
129
-
130
- function makeRequest(overrides: Partial<UsbModeTransitionRequest> = {}): UsbModeTransitionRequest {
131
- return {
132
- stableKey: 'slot:test',
133
- sku: SKU,
134
- fromMode: 'qmi',
135
- toMode: 'mbim',
136
- connectionId: connectionId('uuid-1'),
137
- deviceIfname: deviceIfname('wwan0'),
138
- cachedPhysicalUid: CACHED_UID,
139
- inhibitUid: CACHED_UID,
140
- confirm: true,
141
- maintenance: true,
142
- now: epochMillis(0),
143
- probeReadiness: () => Promise.resolve({ identityConfidence: 'high' }),
144
- ...overrides,
145
- };
146
- }
147
-
148
- function makeTransition(
149
- log: SpyLog,
150
- overrides: Partial<UsbModeTransitionDeps> = {},
151
- ): UsbModeTransition {
152
- return new UsbModeTransition({
153
- actor: new ModemActor(),
154
- nm: makeNm(log),
155
- modemManager: makeMm(log),
156
- atSender: okSender(log),
157
- enumerate: scriptedEnumerate([[OLD_QMI], [], [NEW_MBIM]]),
158
- reenumerationTimeoutMs: 500,
159
- pollIntervalMs: 1,
160
- watchdogMs: 200,
161
- ...overrides,
162
- });
163
- }
164
-
165
- describe('UsbModeTransition — the happy path walks all ten steps', () => {
166
- test('a certified qmi→mbim transition succeeds with a NEW ifname on the same stableKey', async () => {
167
- const log: SpyLog = { calls: [] };
168
- const transition = makeTransition(log);
169
- const request = makeRequest();
170
- const outcome = await transition.execute(request);
171
-
172
- expect(outcome.status).toBe('succeeded');
173
- if (outcome.status !== 'succeeded') {
174
- return;
175
- }
176
- // The row survives with the SAME stableKey but a NEW ifname.
177
- expect(request.stableKey).toBe('slot:test');
178
- expect(String(outcome.newIfname)).toBe('wwan1');
179
- expect(String(request.deviceIfname)).toBe('wwan0');
180
- // All ten goal steps, in order.
181
- expect(outcome.steps.filter((s) => TEN_STEPS.includes(s))).toEqual(TEN_STEPS);
182
- // The catalog command was sent exactly once; reactivation targeted the new ifname.
183
- expect(log.calls.filter((c) => c.startsWith('at.send:'))).toEqual([
184
- 'at.send:AT+QCFG="usbnet",2',
185
- ]);
186
- expect(log.calls).toContain('nm.activate:wwan1');
187
- });
188
- });
189
-
190
- /** No nm/mm/at side-effecting call ran, and the actor was never entered. */
191
- function expectZeroSideEffects(log: SpyLog, steps: readonly string[]): void {
192
- expect(log.calls).toEqual([]);
193
- expect(steps).not.toContain('actor-enter');
194
- }
195
-
196
- describe('UsbModeTransition — TIER A: entry refusals fire ZERO actor/lease/AT calls', () => {
197
- const cases: Array<[string, Partial<UsbModeTransitionRequest>]> = [
198
- ['unconfirmed (confirm:false)', { confirm: false }],
199
- ['uncertified SKU (no catalog entry)', { sku: { ...SKU, firmwarePrefix: 'UNKNOWNFW' } }],
200
- [
201
- 'non-permitted transition (mbim→ecm-ncm not certified)',
202
- { fromMode: 'mbim', toMode: 'ecm-ncm' },
203
- ],
204
- [
205
- 'ambiguous / low-confidence identity',
206
- { probeReadiness: () => Promise.resolve({ identityConfidence: 'low' }) },
207
- ],
208
- ['missing maintenance flag', { maintenance: false }],
209
- ];
210
- for (const [name, overrides] of cases) {
211
- test(name, async () => {
212
- const log: SpyLog = { calls: [] };
213
- const transition = makeTransition(log);
214
- const outcome = await transition.execute(makeRequest(overrides));
215
- expect(outcome.status).toBe('refused');
216
- if (outcome.status === 'refused') {
217
- expect(outcome.stage).toBe('entry');
218
- }
219
- expectZeroSideEffects(log, outcome.steps);
220
- });
221
- }
222
-
223
- test('interlock already held → refused at entry, hold never acquired', async () => {
224
- const log: SpyLog = { calls: [] };
225
- const heldInterlock: TransitionInterlock = {
226
- canDisrupt: () => Promise.resolve({ allow: false, reason: 'a stream is admitted' }),
227
- hold: () => Promise.reject(new Error('hold must not be acquired')),
228
- };
229
- const transition = makeTransition(log, { interlock: heldInterlock });
230
- const outcome = await transition.execute(makeRequest());
231
- expect(outcome.status).toBe('refused');
232
- expectZeroSideEffects(log, outcome.steps);
233
- });
234
- });
235
-
236
- describe('UsbModeTransition — TIER B: an in-actor race is caught with zero lease/AT', () => {
237
- test('valid at entry, invalid in-actor → one actor entry, zero lease/AT calls', async () => {
238
- const log: SpyLog = { calls: [] };
239
- let probes = 0;
240
- const transition = makeTransition(log, {});
241
- const request = makeRequest({
242
- // High at entry, low by the time the actor runs (a duplicate IMEI appeared).
243
- probeReadiness: () => {
244
- probes += 1;
245
- return Promise.resolve({ identityConfidence: probes === 1 ? 'high' : 'low' });
246
- },
247
- });
248
- const outcome = await transition.execute(request);
249
-
250
- expect(outcome.status).toBe('refused');
251
- if (outcome.status === 'refused') {
252
- expect(outcome.stage).toBe('in-actor');
253
- }
254
- // Exactly one actor entry.
255
- expect(outcome.steps.filter((s) => s === 'actor-enter')).toEqual(['actor-enter']);
256
- // Zero lease/AT (and zero nm/mm) calls fired.
257
- expect(log.calls).toEqual([]);
258
- });
259
- });
260
-
261
- describe('UsbModeTransition — TIER C: a postcondition mismatch fails degraded', () => {
262
- test('AT returned OK but the device stayed qmi → FAILED+degraded, NO reactivation, interlock released', async () => {
263
- const log: SpyLog = { calls: [] };
264
- let holdReleases = 0;
265
- const interlock: TransitionInterlock = {
266
- canDisrupt: () => Promise.resolve({ allow: true }),
267
- hold: () =>
268
- Promise.resolve({
269
- release: () => {
270
- holdReleases += 1;
271
- return Promise.resolve();
272
- },
273
- }),
274
- };
275
- const transition = makeTransition(log, {
276
- interlock,
277
- // Re-enumerates STILL as qmi — the switch did not take, even though AT said OK.
278
- enumerate: scriptedEnumerate([[OLD_QMI], [], [STILL_QMI]]),
279
- });
280
- const outcome = await transition.execute(makeRequest());
281
-
282
- expect(outcome.status).toBe('failed');
283
- if (outcome.status === 'failed') {
284
- expect(outcome.degraded).toBe(true);
285
- expect(outcome.reason).toContain('postcondition mismatch');
286
- }
287
- // The AT command WAS sent (OK ignored) but reactivation NEVER happened.
288
- expect(log.calls.filter((c) => c.startsWith('at.send:'))).toHaveLength(1);
289
- expect(log.calls.some((c) => c.startsWith('nm.activate:'))).toBe(false);
290
- expect(outcome.steps).not.toContain('reactivate');
291
- // The interlock hook was released via finally, on the failure path.
292
- expect(outcome.steps).toContain('release-interlock');
293
- expect(holdReleases).toBe(1);
294
- });
295
- });
296
-
297
- describe('UsbModeTransition — crash mid-transaction trips the watchdog', () => {
298
- test('a hung AT command force-uninhibits, reprobes, and returns degraded (never hangs)', async () => {
299
- const log: SpyLog = { calls: [] };
300
- const hangingSender: UsbModeTransitionDeps['atSender'] = {
301
- send: (command) => {
302
- log.calls.push(`at.send:${command}`);
303
- return new Promise(() => undefined);
304
- },
305
- };
306
- const transition = makeTransition(log, {
307
- atSender: hangingSender,
308
- enumerate: scriptedEnumerate([[OLD_QMI]]),
309
- watchdogMs: 30,
310
- });
311
- const outcome = await transition.execute(makeRequest());
312
-
313
- expect(outcome.status).toBe('failed');
314
- if (outcome.status === 'failed') {
315
- expect(outcome.degraded).toBe(true);
316
- expect(outcome.reason).toContain('timed out');
317
- }
318
- // The watchdog force-uninhibited the modem and released the interlock.
319
- expect(outcome.steps).toContain('force-uninhibit');
320
- expect(outcome.steps).toContain('release-interlock');
321
- expect(log.calls).toContain('mm.uninhibit');
322
- });
323
- });
@@ -1,253 +0,0 @@
1
- // The certified USB-mode transition transaction.
2
- //
3
- // A mode switch is destructive: the modem physically re-enumerates, its interface
4
- // name changes, and MM briefly loses sight of it. This transaction runs the switch
5
- // through A3.3's shared per-modem `ModemActor` (keyed on stableKey, so it serialises
6
- // behind every other disruptive op) in a FIXED order:
7
- //
8
- // 1 NM-quiesce → 2 inhibit-by-cached-UID → 3 AT command → 4 expected port-drop →
9
- // 5 uninhibit → 6 await SAME physical UID → 7 POSTCONDITION → 8 resolve new ifname →
10
- // 9 reactivate (uuid, newIfname) → 10 release interlock (finally, always).
11
- //
12
- // THE POSTCONDITION IS THE ONLY PROOF OF SUCCESS. An AT `OK` proves nothing — only a
13
- // re-enumerated device whose descriptors AND observed mode equal the catalog target
14
- // counts. On a postcondition MISMATCH the whole transaction fails `degraded`, does NOT
15
- // reactivate, and still releases the interlock via `finally`. A hung command trips the
16
- // AT watchdog, which force-uninhibits so the system reprobes rather than wedging.
17
-
18
- import type { DeviceIfname, InhibitLease, ModemManagerPort, NetworkManagerPort } from '../ports';
19
- import { deviceIfname } from '../ports';
20
- import { CERTIFIED_CATALOG, type CertifiedCatalog, type PermittedTransition } from '../usb-mode';
21
- import {
22
- type AtAuditSink,
23
- AtCommandLease,
24
- type AtCommandSender,
25
- computeAtAllowlist,
26
- } from './at-lease';
27
- import { descriptorsMatch, detectUsbMode, type UsbDeviceSnapshot } from './device-classifier';
28
- import type { ModemActor } from './modem-actor';
29
- import {
30
- ALLOW_ALL_TRANSITION_INTERLOCK,
31
- checkTransitionPreconditions,
32
- type TransitionInterlock,
33
- type UsbModeTransitionOutcome,
34
- type UsbModeTransitionRequest,
35
- } from './transition-preconditions';
36
-
37
- const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
38
-
39
- const DEFAULT_WATCHDOG_MS = 30_000;
40
- const DEFAULT_REENUM_TIMEOUT_MS = 60_000;
41
- const DEFAULT_POLL_INTERVAL_MS = 250;
42
-
43
- /** Construction dependencies for the transition. Everything I/O is injectable. */
44
- export interface UsbModeTransitionDeps {
45
- readonly actor: ModemActor;
46
- readonly nm: NetworkManagerPort;
47
- readonly modemManager: Pick<ModemManagerPort, 'inhibit' | 'uninhibit'>;
48
- readonly atSender: AtCommandSender;
49
- /** Refresh-triggered USB enumeration (e.g. `createUsbEnumerator().enumerate`). */
50
- readonly enumerate: () => Promise<readonly UsbDeviceSnapshot[]>;
51
- readonly interlock?: TransitionInterlock;
52
- readonly catalog?: CertifiedCatalog;
53
- readonly audit?: AtAuditSink;
54
- readonly resolveIfname?: (device: UsbDeviceSnapshot) => DeviceIfname | undefined;
55
- readonly watchdogMs?: number;
56
- readonly reenumerationTimeoutMs?: number;
57
- readonly pollIntervalMs?: number;
58
- }
59
-
60
- function defaultResolveIfname(device: UsbDeviceSnapshot): DeviceIfname | undefined {
61
- return device.ifname !== undefined && device.ifname !== ''
62
- ? deviceIfname(device.ifname)
63
- : undefined;
64
- }
65
-
66
- /** The USB-mode transition transaction. One instance is reusable across requests. */
67
- export class UsbModeTransition {
68
- readonly #actor: ModemActor;
69
- readonly #nm: NetworkManagerPort;
70
- readonly #modemManager: Pick<ModemManagerPort, 'inhibit' | 'uninhibit'>;
71
- readonly #atSender: AtCommandSender;
72
- readonly #enumerate: () => Promise<readonly UsbDeviceSnapshot[]>;
73
- readonly #interlock: TransitionInterlock;
74
- readonly #catalog: CertifiedCatalog;
75
- readonly #audit: AtAuditSink | undefined;
76
- readonly #resolveIfname: (device: UsbDeviceSnapshot) => DeviceIfname | undefined;
77
- readonly #watchdogMs: number;
78
- readonly #reenumMs: number;
79
- readonly #pollMs: number;
80
-
81
- constructor(deps: UsbModeTransitionDeps) {
82
- this.#actor = deps.actor;
83
- this.#nm = deps.nm;
84
- this.#modemManager = deps.modemManager;
85
- this.#atSender = deps.atSender;
86
- this.#enumerate = deps.enumerate;
87
- this.#interlock = deps.interlock ?? ALLOW_ALL_TRANSITION_INTERLOCK;
88
- this.#catalog = deps.catalog ?? CERTIFIED_CATALOG;
89
- this.#audit = deps.audit;
90
- this.#resolveIfname = deps.resolveIfname ?? defaultResolveIfname;
91
- this.#watchdogMs = deps.watchdogMs ?? DEFAULT_WATCHDOG_MS;
92
- this.#reenumMs = deps.reenumerationTimeoutMs ?? DEFAULT_REENUM_TIMEOUT_MS;
93
- this.#pollMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
94
- }
95
-
96
- /** Run one transition. Preconditions are checked at entry, then again in-actor. */
97
- async execute(request: UsbModeTransitionRequest): Promise<UsbModeTransitionOutcome> {
98
- const steps: string[] = [];
99
- // ENTRY check — a doomed request NEVER enters the actor (TIER A: zero calls).
100
- const entry = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
101
- if (!entry.ok) {
102
- return { status: 'refused', stage: 'entry', reason: entry.reason, steps };
103
- }
104
- return this.#actor.run(request.stableKey, () => this.#inActor(request, steps));
105
- }
106
-
107
- async #inActor(
108
- request: UsbModeTransitionRequest,
109
- steps: string[],
110
- ): Promise<UsbModeTransitionOutcome> {
111
- steps.push('actor-enter');
112
- // IN-ACTOR re-check — catches a race that closed a gate while queued (TIER B).
113
- const recheck = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
114
- if (!recheck.ok) {
115
- return { status: 'refused', stage: 'in-actor', reason: recheck.reason, steps };
116
- }
117
- const hold = await this.#interlock.hold({ stableKey: request.stableKey });
118
- try {
119
- return await this.#runTransaction(
120
- request,
121
- recheck.entry.permittedTransitions,
122
- recheck.transition,
123
- steps,
124
- );
125
- } finally {
126
- steps.push('release-interlock');
127
- await hold.release().catch(() => undefined);
128
- }
129
- }
130
-
131
- async #runTransaction(
132
- request: UsbModeTransitionRequest,
133
- allCommands: readonly PermittedTransition[],
134
- transition: PermittedTransition,
135
- steps: string[],
136
- ): Promise<UsbModeTransitionOutcome> {
137
- let inhibit: InhibitLease | undefined;
138
- let reactivated = false;
139
- const forceUninhibit = async (): Promise<void> => {
140
- if (inhibit === undefined) {
141
- return;
142
- }
143
- const held = inhibit;
144
- inhibit = undefined;
145
- steps.push('force-uninhibit');
146
- await this.#modemManager.uninhibit(held).catch(() => undefined);
147
- };
148
- const lease = new AtCommandLease({
149
- sender: this.#atSender,
150
- allowlist: computeAtAllowlist(allCommands.map((t) => t.atCommand)),
151
- timeoutMs: this.#watchdogMs,
152
- onWatchdog: forceUninhibit,
153
- ...(this.#audit !== undefined ? { audit: this.#audit } : {}),
154
- });
155
-
156
- steps.push('nm-quiesce');
157
- const quiesce = await this.#nm.acquireQuiesceLease(request.connectionId, request.deviceIfname);
158
- try {
159
- steps.push('inhibit');
160
- inhibit = await this.#modemManager.inhibit(request.inhibitUid);
161
-
162
- // AT `OK` is IGNORED for success — only the postcondition below decides.
163
- steps.push('at-command');
164
- await lease.run(transition.atCommand, { inhibitUid: request.inhibitUid });
165
-
166
- steps.push('await-port-drop');
167
- await this.#awaitPortDrop(request.cachedPhysicalUid);
168
-
169
- steps.push('uninhibit');
170
- if (inhibit !== undefined) {
171
- const held = inhibit;
172
- inhibit = undefined;
173
- await this.#modemManager.uninhibit(held);
174
- }
175
-
176
- steps.push('await-reenumeration');
177
- const device = await this.#awaitReenumeration(request.cachedPhysicalUid);
178
-
179
- steps.push('postcondition');
180
- const observedMode = detectUsbMode(device);
181
- const descriptorsOk = descriptorsMatch(device, transition.expectedDescriptors);
182
- if (observedMode !== request.toMode || !descriptorsOk) {
183
- return {
184
- status: 'failed',
185
- degraded: true,
186
- reason: `postcondition mismatch: observed ${observedMode ?? 'unknown'} vs target ${request.toMode}; descriptors ${descriptorsOk ? 'ok' : 'mismatch'}`,
187
- steps,
188
- };
189
- }
190
-
191
- steps.push('resolve-ifname');
192
- const newIfname = this.#resolveIfname(device);
193
- if (newIfname === undefined) {
194
- return { status: 'failed', degraded: true, reason: 'could not resolve new ifname', steps };
195
- }
196
-
197
- steps.push('reactivate');
198
- await this.#nm.activate(request.connectionId, newIfname);
199
- reactivated = true;
200
- return { status: 'succeeded', newIfname, steps };
201
- } catch (error) {
202
- await forceUninhibit();
203
- await this.#reprobe();
204
- return {
205
- status: 'failed',
206
- degraded: true,
207
- reason: error instanceof Error ? error.message : String(error),
208
- steps,
209
- };
210
- } finally {
211
- await forceUninhibit();
212
- if (!reactivated) {
213
- // Failure path: restore the old connection. On success the new-ifname
214
- // activation supersedes the quiesce lease (old ifname is gone).
215
- steps.push('release-quiesce');
216
- await this.#nm.releaseQuiesceLease(quiesce).catch(() => undefined);
217
- }
218
- }
219
- }
220
-
221
- async #awaitPortDrop(uid: string): Promise<void> {
222
- const deadline = Date.now() + this.#reenumMs;
223
- while (Date.now() < deadline) {
224
- const devices = await this.#enumerate();
225
- if (!devices.some((d) => d.physicalUid === uid)) {
226
- return;
227
- }
228
- await sleep(this.#pollMs);
229
- }
230
- throw new Error(`control port did not drop within ${this.#reenumMs}ms (uid ${uid})`);
231
- }
232
-
233
- async #awaitReenumeration(uid: string): Promise<UsbDeviceSnapshot> {
234
- const deadline = Date.now() + this.#reenumMs;
235
- while (Date.now() < deadline) {
236
- const devices = await this.#enumerate();
237
- const device = devices.find((d) => d.physicalUid === uid);
238
- if (device !== undefined) {
239
- return device;
240
- }
241
- await sleep(this.#pollMs);
242
- }
243
- throw new Error(`device did not re-enumerate within ${this.#reenumMs}ms (uid ${uid})`);
244
- }
245
-
246
- /** Best-effort state re-read after a crash — the transaction still fails degraded. */
247
- async #reprobe(): Promise<void> {
248
- await this.#enumerate().then(
249
- () => undefined,
250
- () => undefined,
251
- );
252
- }
253
- }
@@ -1,29 +0,0 @@
1
- // Nominal (branded) primitive types for the domain layer.
2
- //
3
- // Branding stops the four identity strings — and the two counter numbers — from
4
- // being interchangeable at the type level: a `LogicalSlotId` can never be passed
5
- // where a `SubscriptionId` is expected, even though both are strings at runtime.
6
- // Downstream waves (A2.2 ports, A3.x D-Bus backend) depend on this distinction.
7
-
8
- import { DomainError } from './errors';
9
-
10
- declare const brand: unique symbol;
11
-
12
- /** A primitive `T` tagged with a compile-time-only brand `B`. Erased at runtime. */
13
- export type Brand<T, B extends string> = T & { readonly [brand]: B };
14
-
15
- /** Assert a value is a non-empty string, throwing a typed error otherwise. */
16
- export function nonEmptyString(value: string, label: string): string {
17
- if (value.length === 0) {
18
- throw new DomainError(`${label} must be a non-empty string`);
19
- }
20
- return value;
21
- }
22
-
23
- /** Assert a value is a non-negative safe integer, throwing a typed error otherwise. */
24
- export function nonNegativeInteger(value: number, label: string): number {
25
- if (!Number.isSafeInteger(value) || value < 0) {
26
- throw new DomainError(`${label} must be a non-negative safe integer, got ${value}`);
27
- }
28
- return value;
29
- }
@@ -1,77 +0,0 @@
1
- // Typed errors for the domain layer.
2
- //
3
- // The domain never fails silently: an impossible state combination, a refused
4
- // policy binding, or a non-monotonic revision each raise a distinct, catchable
5
- // error class carrying a machine-readable reason. QA and callers discriminate on
6
- // the class and the `code`, never on a message string.
7
-
8
- /** Base class for every domain-layer error. Callers can catch this to trap all. */
9
- export class DomainError extends Error {
10
- override readonly name: string = 'DomainError';
11
-
12
- constructor(message: string) {
13
- super(message);
14
- // Restore the prototype chain across the ES5 target transpile so
15
- // `instanceof` works on subclasses (standard TS extends-Error guard).
16
- Object.setPrototypeOf(this, new.target.prototype);
17
- }
18
- }
19
-
20
- /**
21
- * Every distinct impossible cross-dimension combination the guards reject.
22
- * One code per rule so a failing construction names exactly what it violated.
23
- */
24
- export type ImpossibleStateCode =
25
- | 'registered-while-absent'
26
- | 'registered-radio-off'
27
- | 'registered-empty-rat-set'
28
- | 'active-state-while-absent'
29
- | 'radio-off-while-active'
30
- | 'nm-activated-while-absent'
31
- | 'nm-activated-without-interface'
32
- | 'nm-activated-without-mm-connected'
33
- | 'multiple-active-sim-slots'
34
- | 'locked-sim-in-empty-slot'
35
- | 'mm-locked-without-sim-lock'
36
- | 'data-interface-name-without-presence'
37
- | 'recovery-attempts-negative'
38
- | 'recovery-cooldown-stage-mismatch'
39
- | 'recovery-idle-with-attempts';
40
-
41
- /** A snapshot was constructed (or transitioned into) a physically impossible state. */
42
- export class ImpossibleStateError extends DomainError {
43
- override readonly name = 'ImpossibleStateError';
44
- readonly code: ImpossibleStateCode;
45
-
46
- constructor(code: ImpossibleStateCode, detail: string) {
47
- super(`impossible cellular state [${code}]: ${detail}`);
48
- this.code = code;
49
- }
50
- }
51
-
52
- /**
53
- * A durable policy binding was attempted against an identity that is not allowed
54
- * to carry one — today only low-confidence (ambiguous) equipment identities.
55
- */
56
- export class PolicyBindingRefusedError extends DomainError {
57
- override readonly name = 'PolicyBindingRefusedError';
58
- readonly reason: 'ambiguous-identity';
59
-
60
- constructor(detail: string) {
61
- super(`durable policy binding refused: ${detail}`);
62
- this.reason = 'ambiguous-identity';
63
- }
64
- }
65
-
66
- /** A snapshot transition tried to keep or lower the monotonic revision. */
67
- export class RevisionMonotonicityError extends DomainError {
68
- override readonly name = 'RevisionMonotonicityError';
69
- readonly previous: number;
70
- readonly next: number;
71
-
72
- constructor(previous: number, next: number) {
73
- super(`revision must strictly increase: ${previous} -> ${next}`);
74
- this.previous = previous;
75
- this.next = next;
76
- }
77
- }