@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,196 @@
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 → 3b catalog `applyCommand`
9
+ // (only for a SKU whose AT command writes NV without re-enumerating) → 4 expected
10
+ // port-drop → 5 uninhibit → 6 await SAME physical UID → 7 POSTCONDITION →
11
+ // 8 resolve new ifname → 9 reactivate (uuid, newIfname) → 10 release interlock
12
+ // (finally, always).
13
+ //
14
+ // THE POSTCONDITION IS THE ONLY PROOF OF SUCCESS. An AT `OK` proves nothing — only a
15
+ // re-enumerated device whose descriptors AND observed mode equal the catalog target
16
+ // counts. On a postcondition MISMATCH the whole transaction fails `degraded`, does NOT
17
+ // reactivate, and still releases the interlock via `finally`. A hung command trips the
18
+ // AT watchdog, which force-uninhibits so the system reprobes rather than wedging.
19
+ import { deviceIfname } from '../ports/index.js';
20
+ import { CERTIFIED_CATALOG } from '../usb-mode/index.js';
21
+ import { AtCommandLease, computeAtAllowlist, } from './at-lease.js';
22
+ import { descriptorsMatch, detectUsbMode } from './device-classifier.js';
23
+ import { ALLOW_ALL_TRANSITION_INTERLOCK, checkTransitionPreconditions, } from './transition-preconditions.js';
24
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
25
+ const DEFAULT_WATCHDOG_MS = 30_000;
26
+ const DEFAULT_REENUM_TIMEOUT_MS = 60_000;
27
+ const DEFAULT_POLL_INTERVAL_MS = 250;
28
+ function defaultResolveIfname(device) {
29
+ return device.ifname !== undefined && device.ifname !== ''
30
+ ? deviceIfname(device.ifname)
31
+ : undefined;
32
+ }
33
+ /** The USB-mode transition transaction. One instance is reusable across requests. */
34
+ export class UsbModeTransition {
35
+ #actor;
36
+ #nm;
37
+ #modemManager;
38
+ #atSender;
39
+ #enumerate;
40
+ #interlock;
41
+ #catalog;
42
+ #audit;
43
+ #resolveIfname;
44
+ #watchdogMs;
45
+ #reenumMs;
46
+ #pollMs;
47
+ constructor(deps) {
48
+ this.#actor = deps.actor;
49
+ this.#nm = deps.nm;
50
+ this.#modemManager = deps.modemManager;
51
+ this.#atSender = deps.atSender;
52
+ this.#enumerate = deps.enumerate;
53
+ this.#interlock = deps.interlock ?? ALLOW_ALL_TRANSITION_INTERLOCK;
54
+ this.#catalog = deps.catalog ?? CERTIFIED_CATALOG;
55
+ this.#audit = deps.audit;
56
+ this.#resolveIfname = deps.resolveIfname ?? defaultResolveIfname;
57
+ this.#watchdogMs = deps.watchdogMs ?? DEFAULT_WATCHDOG_MS;
58
+ this.#reenumMs = deps.reenumerationTimeoutMs ?? DEFAULT_REENUM_TIMEOUT_MS;
59
+ this.#pollMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
60
+ }
61
+ /** Run one transition. Preconditions are checked at entry, then again in-actor. */
62
+ async execute(request) {
63
+ const steps = [];
64
+ // ENTRY check — a doomed request NEVER enters the actor (TIER A: zero calls).
65
+ const entry = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
66
+ if (!entry.ok) {
67
+ return { status: 'refused', stage: 'entry', reason: entry.reason, steps };
68
+ }
69
+ return this.#actor.run(request.stableKey, () => this.#inActor(request, steps));
70
+ }
71
+ async #inActor(request, steps) {
72
+ steps.push('actor-enter');
73
+ // IN-ACTOR re-check — catches a race that closed a gate while queued (TIER B).
74
+ const recheck = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
75
+ if (!recheck.ok) {
76
+ return { status: 'refused', stage: 'in-actor', reason: recheck.reason, steps };
77
+ }
78
+ const hold = await this.#interlock.hold({ stableKey: request.stableKey });
79
+ try {
80
+ return await this.#runTransaction(request, recheck.entry.permittedTransitions, recheck.transition, steps);
81
+ }
82
+ finally {
83
+ steps.push('release-interlock');
84
+ await hold.release().catch(() => undefined);
85
+ }
86
+ }
87
+ async #runTransaction(request, allCommands, transition, steps) {
88
+ let inhibit;
89
+ let reactivated = false;
90
+ const forceUninhibit = async () => {
91
+ if (inhibit === undefined) {
92
+ return;
93
+ }
94
+ const held = inhibit;
95
+ inhibit = undefined;
96
+ steps.push('force-uninhibit');
97
+ await this.#modemManager.uninhibit(held).catch(() => undefined);
98
+ };
99
+ const lease = new AtCommandLease({
100
+ sender: this.#atSender,
101
+ allowlist: computeAtAllowlist(allCommands.flatMap((t) => t.applyCommand === undefined ? [t.atCommand] : [t.atCommand, t.applyCommand])),
102
+ timeoutMs: this.#watchdogMs,
103
+ onWatchdog: forceUninhibit,
104
+ ...(this.#audit !== undefined ? { audit: this.#audit } : {}),
105
+ });
106
+ steps.push('nm-quiesce');
107
+ const quiesce = await this.#nm.acquireQuiesceLease(request.connectionId, request.deviceIfname);
108
+ try {
109
+ steps.push('inhibit');
110
+ inhibit = await this.#modemManager.inhibit(request.inhibitUid);
111
+ // AT `OK` is IGNORED for success — only the postcondition below decides.
112
+ steps.push('at-command');
113
+ await lease.run(transition.atCommand, { inhibitUid: request.inhibitUid });
114
+ if (transition.applyCommand !== undefined) {
115
+ steps.push('apply-command');
116
+ await lease.run(transition.applyCommand, { inhibitUid: request.inhibitUid });
117
+ }
118
+ steps.push('await-port-drop');
119
+ await this.#awaitPortDrop(request.cachedPhysicalUid);
120
+ steps.push('uninhibit');
121
+ if (inhibit !== undefined) {
122
+ const held = inhibit;
123
+ inhibit = undefined;
124
+ await this.#modemManager.uninhibit(held);
125
+ }
126
+ steps.push('await-reenumeration');
127
+ const device = await this.#awaitReenumeration(request.cachedPhysicalUid);
128
+ steps.push('postcondition');
129
+ const observedMode = detectUsbMode(device);
130
+ const descriptorsOk = descriptorsMatch(device, transition.expectedDescriptors);
131
+ if (observedMode !== request.toMode || !descriptorsOk) {
132
+ return {
133
+ status: 'failed',
134
+ degraded: true,
135
+ reason: `postcondition mismatch: observed ${observedMode ?? 'unknown'} vs target ${request.toMode}; descriptors ${descriptorsOk ? 'ok' : 'mismatch'}`,
136
+ steps,
137
+ };
138
+ }
139
+ steps.push('resolve-ifname');
140
+ const newIfname = this.#resolveIfname(device);
141
+ if (newIfname === undefined) {
142
+ return { status: 'failed', degraded: true, reason: 'could not resolve new ifname', steps };
143
+ }
144
+ steps.push('reactivate');
145
+ await this.#nm.activate(request.connectionId, newIfname);
146
+ reactivated = true;
147
+ return { status: 'succeeded', newIfname, steps };
148
+ }
149
+ catch (error) {
150
+ await forceUninhibit();
151
+ await this.#reprobe();
152
+ return {
153
+ status: 'failed',
154
+ degraded: true,
155
+ reason: error instanceof Error ? error.message : String(error),
156
+ steps,
157
+ };
158
+ }
159
+ finally {
160
+ await forceUninhibit();
161
+ if (!reactivated) {
162
+ // Failure path: restore the old connection. On success the new-ifname
163
+ // activation supersedes the quiesce lease (old ifname is gone).
164
+ steps.push('release-quiesce');
165
+ await this.#nm.releaseQuiesceLease(quiesce).catch(() => undefined);
166
+ }
167
+ }
168
+ }
169
+ async #awaitPortDrop(uid) {
170
+ const deadline = Date.now() + this.#reenumMs;
171
+ while (Date.now() < deadline) {
172
+ const devices = await this.#enumerate();
173
+ if (!devices.some((d) => d.physicalUid === uid)) {
174
+ return;
175
+ }
176
+ await sleep(this.#pollMs);
177
+ }
178
+ throw new Error(`control port did not drop within ${this.#reenumMs}ms (uid ${uid})`);
179
+ }
180
+ async #awaitReenumeration(uid) {
181
+ const deadline = Date.now() + this.#reenumMs;
182
+ while (Date.now() < deadline) {
183
+ const devices = await this.#enumerate();
184
+ const device = devices.find((d) => d.physicalUid === uid);
185
+ if (device !== undefined) {
186
+ return device;
187
+ }
188
+ await sleep(this.#pollMs);
189
+ }
190
+ throw new Error(`device did not re-enumerate within ${this.#reenumMs}ms (uid ${uid})`);
191
+ }
192
+ /** Best-effort state re-read after a crash — the transaction still fails degraded. */
193
+ async #reprobe() {
194
+ await this.#enumerate().then(() => undefined, () => undefined);
195
+ }
196
+ }
@@ -0,0 +1,42 @@
1
+ /** A band as every operator-facing surface spells it. Opaque; compare by equality. */
2
+ export type BandName = string;
3
+ /** `MM_MODEM_BAND_UNKNOWN`. Never a member of a supported/current set we act on. */
4
+ export declare const BAND_UNKNOWN = "unknown";
5
+ /**
6
+ * `MM_MODEM_BAND_ANY` (256) — "let the modem choose", i.e. the reset value.
7
+ * Setting exactly this is how a band lock is released; there is no separate
8
+ * ModemManager verb for it.
9
+ */
10
+ export declare const BAND_ANY = "any";
11
+ /** Decode one `MMModemBand` value. Total: an unknown value round-trips. */
12
+ export declare function bandName(value: number): BandName;
13
+ /** Encode one band name. `undefined` for a name this build cannot place. */
14
+ export declare function bandValue(name: BandName): number | undefined;
15
+ /** True when this build recognises the name (a `band-<n>` passthrough does not). */
16
+ export declare function isNamedBand(name: BandName): boolean;
17
+ /**
18
+ * Decode a `SupportedBands` / `CurrentBands` property value.
19
+ *
20
+ * Non-numeric members are DROPPED rather than coerced — a malformed member says
21
+ * nothing about the radio, and coercing it would invent a band. `unknown` is
22
+ * dropped for the same reason: MM emits it for "the modem did not say", which is
23
+ * not a band an operator can select.
24
+ */
25
+ export declare function decodeBandList(value: unknown): readonly BandName[];
26
+ /**
27
+ * Encode a band selection for `SetCurrentBands`.
28
+ *
29
+ * FAILS CLOSED AS A WHOLE. One unplaceable name rejects the entire request
30
+ * rather than silently narrowing the selection: a partial band set is a
31
+ * DIFFERENT lock from the one the operator asked for, and applying it would
32
+ * strand the radio on bands they never chose.
33
+ */
34
+ export declare function encodeBandList(names: readonly BandName[]): {
35
+ readonly ok: true;
36
+ readonly values: number[];
37
+ } | {
38
+ readonly ok: false;
39
+ readonly unknown: BandName;
40
+ };
41
+ /** True when the selection is exactly the reset value (`any`, alone). */
42
+ export declare function isResetSelection(names: readonly BandName[]): boolean;
@@ -0,0 +1,150 @@
1
+ // MMModemBand ↔ band NAME, in both directions.
2
+ //
3
+ // The D-Bus surface speaks numbers: `Modem.SupportedBands` and
4
+ // `Modem.CurrentBands` are `au`, and `Modem.SetCurrentBands` takes `au`. Every
5
+ // operator-facing surface — mmcli's `--set-current-bands`, this package's API,
6
+ // CeraUI's selector — speaks the NAME (`eutran-3`, `utran-1`, `egsm`, `any`).
7
+ // One mapping, here, so the two can never disagree.
8
+ //
9
+ // WHERE THE NUMBERS COME FROM. ModemManager's `MMModemBand` (`mm-enums.h`) is
10
+ // two different things stitched together, and this module reproduces exactly
11
+ // that shape rather than flattening it into one big table:
12
+ //
13
+ // * the GSM/UTRAN block (1..20) is IRREGULAR — `UTRAN_2` is 12 while `UTRAN_6`
14
+ // is 8 — because MM appended bands in the order they were needed, so it is
15
+ // an explicit table and can only ever be an explicit table;
16
+ // * every later block is ARITHMETIC, by MM's own construction: `EUTRAN_n` is
17
+ // `30 + n`, `CDMA_BCn` is `128 + n`, `NGRAN_n` is `300 + n`.
18
+ //
19
+ // Deriving the arithmetic blocks rather than transcribing ~350 constants is the
20
+ // point: a transcription is where a wrong band number would hide, and a wrong
21
+ // band number sets a radio to a band the SIM's network does not operate on.
22
+ //
23
+ // A value this module does not recognise is NEVER dropped and NEVER guessed at.
24
+ // It round-trips as `band-<value>`, so an unfamiliar band a future ModemManager
25
+ // reports is still shown to the operator, still comparable, and still settable —
26
+ // the same "unknown is an answer about the read" discipline `detect.ts` follows.
27
+ /** `MM_MODEM_BAND_UNKNOWN`. Never a member of a supported/current set we act on. */
28
+ export const BAND_UNKNOWN = 'unknown';
29
+ /**
30
+ * `MM_MODEM_BAND_ANY` (256) — "let the modem choose", i.e. the reset value.
31
+ * Setting exactly this is how a band lock is released; there is no separate
32
+ * ModemManager verb for it.
33
+ */
34
+ export const BAND_ANY = 'any';
35
+ const BAND_ANY_VALUE = 256;
36
+ /**
37
+ * The irregular head of the enum (`MM_MODEM_BAND_EGSM` = 1 … `G810` = 20),
38
+ * transcribed because it cannot be derived.
39
+ */
40
+ const IRREGULAR = {
41
+ 0: BAND_UNKNOWN,
42
+ 1: 'egsm',
43
+ 2: 'dcs',
44
+ 3: 'pcs',
45
+ 4: 'g850',
46
+ 5: 'utran-1',
47
+ 6: 'utran-3',
48
+ 7: 'utran-4',
49
+ 8: 'utran-6',
50
+ 9: 'utran-5',
51
+ 10: 'utran-8',
52
+ 11: 'utran-9',
53
+ 12: 'utran-2',
54
+ 13: 'utran-7',
55
+ 14: 'g450',
56
+ 15: 'g480',
57
+ 16: 'g750',
58
+ 17: 'g380',
59
+ 18: 'g410',
60
+ 19: 'g710',
61
+ 20: 'g810',
62
+ [BAND_ANY_VALUE]: BAND_ANY,
63
+ };
64
+ /** Arithmetic blocks: `[prefix, base, firstIndex, lastIndex]`. */
65
+ const ARITHMETIC = [
66
+ // MM_MODEM_BAND_EUTRAN_1 = 31 … EUTRAN_71 = 101.
67
+ ['eutran-', 30, 1, 71],
68
+ // MM_MODEM_BAND_CDMA_BC0 = 128 … CDMA_BC19 = 147.
69
+ ['cdma-bc', 128, 0, 19],
70
+ // MM_MODEM_BAND_NGRAN_1 = 301 … the 5G NR block.
71
+ ['ngran-', 300, 1, 261],
72
+ ];
73
+ const NUMBER_TO_NAME = new Map();
74
+ const NAME_TO_NUMBER = new Map();
75
+ for (const [value, name] of Object.entries(IRREGULAR)) {
76
+ NUMBER_TO_NAME.set(Number(value), name);
77
+ NAME_TO_NUMBER.set(name, Number(value));
78
+ }
79
+ for (const [prefix, base, first, last] of ARITHMETIC) {
80
+ for (let index = first; index <= last; index += 1) {
81
+ const name = `${prefix}${index}`;
82
+ NUMBER_TO_NAME.set(base + index, name);
83
+ NAME_TO_NUMBER.set(name, base + index);
84
+ }
85
+ }
86
+ /** The passthrough spelling for a band value this build does not name. */
87
+ const PASSTHROUGH_RE = /^band-(\d+)$/;
88
+ /** Decode one `MMModemBand` value. Total: an unknown value round-trips. */
89
+ export function bandName(value) {
90
+ return NUMBER_TO_NAME.get(value) ?? `band-${value}`;
91
+ }
92
+ /** Encode one band name. `undefined` for a name this build cannot place. */
93
+ export function bandValue(name) {
94
+ const known = NAME_TO_NUMBER.get(name);
95
+ if (known !== undefined)
96
+ return known;
97
+ const passthrough = PASSTHROUGH_RE.exec(name);
98
+ if (passthrough?.[1] === undefined)
99
+ return undefined;
100
+ const parsed = Number(passthrough[1]);
101
+ return Number.isSafeInteger(parsed) ? parsed : undefined;
102
+ }
103
+ /** True when this build recognises the name (a `band-<n>` passthrough does not). */
104
+ export function isNamedBand(name) {
105
+ return NAME_TO_NUMBER.has(name);
106
+ }
107
+ /**
108
+ * Decode a `SupportedBands` / `CurrentBands` property value.
109
+ *
110
+ * Non-numeric members are DROPPED rather than coerced — a malformed member says
111
+ * nothing about the radio, and coercing it would invent a band. `unknown` is
112
+ * dropped for the same reason: MM emits it for "the modem did not say", which is
113
+ * not a band an operator can select.
114
+ */
115
+ export function decodeBandList(value) {
116
+ if (!Array.isArray(value))
117
+ return [];
118
+ const names = [];
119
+ for (const member of value) {
120
+ if (typeof member !== 'number' || !Number.isSafeInteger(member))
121
+ continue;
122
+ const name = bandName(member);
123
+ if (name === BAND_UNKNOWN)
124
+ continue;
125
+ names.push(name);
126
+ }
127
+ return names;
128
+ }
129
+ /**
130
+ * Encode a band selection for `SetCurrentBands`.
131
+ *
132
+ * FAILS CLOSED AS A WHOLE. One unplaceable name rejects the entire request
133
+ * rather than silently narrowing the selection: a partial band set is a
134
+ * DIFFERENT lock from the one the operator asked for, and applying it would
135
+ * strand the radio on bands they never chose.
136
+ */
137
+ export function encodeBandList(names) {
138
+ const values = [];
139
+ for (const name of names) {
140
+ const value = bandValue(name);
141
+ if (value === undefined)
142
+ return { ok: false, unknown: name };
143
+ values.push(value);
144
+ }
145
+ return { ok: true, values };
146
+ }
147
+ /** True when the selection is exactly the reset value (`any`, alone). */
148
+ export function isResetSelection(names) {
149
+ return names.length === 1 && names[0] === BAND_ANY;
150
+ }
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The device a certification is about. Firmware is a PREFIX for the same reason
4
+ * the USB-mode catalog matches one: an entry certifies a firmware FAMILY, and
5
+ * where the family ends is a per-SKU judgement a reviewer makes, not something a
6
+ * device can compute.
7
+ */
8
+ export declare const bandSkuSchema: z.ZodObject<{
9
+ vidPid: z.ZodString;
10
+ model: z.ZodString;
11
+ firmwarePrefix: z.ZodString;
12
+ }, z.core.$strict>;
13
+ export type BandSku = z.infer<typeof bandSkuSchema>;
14
+ export declare const bandProofSchema: z.ZodObject<{
15
+ supportedRead: z.ZodLiteral<true>;
16
+ set: z.ZodLiteral<true>;
17
+ readback: z.ZodLiteral<true>;
18
+ reset: z.ZodLiteral<true>;
19
+ }, z.core.$strict>;
20
+ /**
21
+ * All four proofs are `z.literal(true)`, so a HALF-certified entry cannot be
22
+ * expressed at all. A reviewer with three of four proofs has an uncertified SKU
23
+ * and the file must say so by omitting it — a `false` field would read as a
24
+ * catalog row, and a catalog row is what surfaces the control.
25
+ */
26
+ export declare const bandCertificationEntrySchema: z.ZodObject<{
27
+ vidPid: z.ZodString;
28
+ model: z.ZodString;
29
+ firmwarePrefix: z.ZodString;
30
+ evidence: z.ZodString;
31
+ proofs: z.ZodObject<{
32
+ supportedRead: z.ZodLiteral<true>;
33
+ set: z.ZodLiteral<true>;
34
+ readback: z.ZodLiteral<true>;
35
+ reset: z.ZodLiteral<true>;
36
+ }, z.core.$strict>;
37
+ provenBands: z.ZodDefault<z.ZodArray<z.ZodString>>;
38
+ }, z.core.$strict>;
39
+ export type BandCertificationEntry = z.infer<typeof bandCertificationEntrySchema>;
40
+ export declare const bandCertificationCatalogSchema: z.ZodObject<{
41
+ schemaVersion: z.ZodLiteral<1>;
42
+ entries: z.ZodArray<z.ZodObject<{
43
+ vidPid: z.ZodString;
44
+ model: z.ZodString;
45
+ firmwarePrefix: z.ZodString;
46
+ evidence: z.ZodString;
47
+ proofs: z.ZodObject<{
48
+ supportedRead: z.ZodLiteral<true>;
49
+ set: z.ZodLiteral<true>;
50
+ readback: z.ZodLiteral<true>;
51
+ reset: z.ZodLiteral<true>;
52
+ }, z.core.$strict>;
53
+ provenBands: z.ZodDefault<z.ZodArray<z.ZodString>>;
54
+ }, z.core.$strict>>;
55
+ }, z.core.$strict>;
56
+ export type BandCertificationCatalog = z.infer<typeof bandCertificationCatalogSchema>;
57
+ /** Parse an untrusted value as a band catalog. Throws with a precise path. */
58
+ export declare function loadBandCertificationCatalog(value: unknown): BandCertificationCatalog;
59
+ /** The catalog shipped in this package, validated at module load. */
60
+ export declare const BAND_CERTIFICATION_CATALOG: BandCertificationCatalog;
61
+ /**
62
+ * The entry certifying this exact device, or `undefined`.
63
+ *
64
+ * All three discriminators must match, and the firmware match is a PREFIX of the
65
+ * device's FULL revision — never a truncation of the device's revision to the
66
+ * entry's length, which would certify a family the reviewer never looked at.
67
+ */
68
+ export declare function findBandCertification(catalog: BandCertificationCatalog, sku: BandSku): BandCertificationEntry | undefined;
69
+ /**
70
+ * Whether a band-lock control may be OFFERED for this device.
71
+ *
72
+ * Fail-closed in every direction: an unknown SKU, a SKU with no entry, and a SKU
73
+ * whose entry predates a proof step all answer `false`.
74
+ */
75
+ export declare function isBandControlCertified(catalog: BandCertificationCatalog, sku: BandSku | undefined): boolean;
76
+ /**
77
+ * Narrow an advertised band set to what the certification proves is settable.
78
+ *
79
+ * An entry with no `provenBands` proves the whole advertised set (the drill swept
80
+ * it); an entry that names bands offers ONLY those, intersected with what the
81
+ * modem advertises right now — a certified band the device no longer advertises
82
+ * is not offerable, and the device's own answer outranks the catalog.
83
+ */
84
+ export declare function offerableBands(entry: BandCertificationEntry | undefined, supported: readonly string[]): readonly string[];
@@ -0,0 +1,127 @@
1
+ // The band-lock certification catalog — what has actually been PROVEN, per SKU.
2
+ //
3
+ // A band lock is the one capability module that can take a working uplink off the
4
+ // air and leave it there: a band the SIM's network does not operate on registers
5
+ // nowhere, and a modem that does not honour a reset leaves the operator with no
6
+ // way back short of a replug they may not be able to reach. So band-lock is
7
+ // deliberately STRICTER than the framework floor in `support-claim.ts`. The
8
+ // framework offers a control at `capable` (the modem advertises the capability)
9
+ // because hiding an uncertified-but-working control puts hardware behind a
10
+ // paperwork gate. For this module the paperwork IS the safety argument, so the
11
+ // control stays HIDDEN until an entry here proves the whole round trip.
12
+ //
13
+ // FOUR STEPS, ALL FOUR REQUIRED, and they are separate booleans rather than one
14
+ // `certified: true` because each can fail on its own and each failure means a
15
+ // different thing:
16
+ //
17
+ // supportedRead — `SupportedBands` was read and named real bands. Without it
18
+ // there is nothing to offer.
19
+ // set — `SetCurrentBands` was accepted for a band in that set.
20
+ // readback — `CurrentBands` afterwards reported exactly what was asked
21
+ // for. An accepted-but-ignored write is the failure mode that
22
+ // looks like success from the call site alone.
23
+ // reset — setting `any` restored the baseline. This is the escape
24
+ // hatch; a SKU that cannot be shown to reset must never be
25
+ // locked in the first place.
26
+ //
27
+ // THE SHIPPED CATALOG IS EMPTY, and that is the honest state. Nothing in the
28
+ // fleet has been through the drill — see the phase-C band-lock evidence for why
29
+ // (the bench Quectel's SIM never registers, so "re-registration proven" cannot
30
+ // be claimed today). An entry is added by a human-reviewed commit carrying the
31
+ // bench transcript, exactly like `usb-mode/certified-catalog.json`.
32
+ import { z } from 'zod';
33
+ import rawCatalog from './certified-bands.json' with { type: 'json' };
34
+ /**
35
+ * The device a certification is about. Firmware is a PREFIX for the same reason
36
+ * the USB-mode catalog matches one: an entry certifies a firmware FAMILY, and
37
+ * where the family ends is a per-SKU judgement a reviewer makes, not something a
38
+ * device can compute.
39
+ */
40
+ export const bandSkuSchema = z
41
+ .object({
42
+ vidPid: z.string().regex(/^[0-9a-f]{4}:[0-9a-f]{4}$/),
43
+ model: z.string().min(1),
44
+ firmwarePrefix: z.string().min(1),
45
+ })
46
+ .strict();
47
+ export const bandProofSchema = z
48
+ .object({
49
+ supportedRead: z.literal(true),
50
+ set: z.literal(true),
51
+ readback: z.literal(true),
52
+ reset: z.literal(true),
53
+ })
54
+ .strict();
55
+ /**
56
+ * All four proofs are `z.literal(true)`, so a HALF-certified entry cannot be
57
+ * expressed at all. A reviewer with three of four proofs has an uncertified SKU
58
+ * and the file must say so by omitting it — a `false` field would read as a
59
+ * catalog row, and a catalog row is what surfaces the control.
60
+ */
61
+ export const bandCertificationEntrySchema = z
62
+ .object({
63
+ vidPid: z.string().regex(/^[0-9a-f]{4}:[0-9a-f]{4}$/),
64
+ model: z.string().min(1),
65
+ firmwarePrefix: z.string().min(1),
66
+ /** Free-form: where the transcript proving the four steps lives. */
67
+ evidence: z.string().min(1),
68
+ proofs: bandProofSchema,
69
+ /**
70
+ * Bands the reviewer proved were individually settable AND resettable.
71
+ * An EMPTY list means "the whole advertised set", which is what a drill
72
+ * that swept the set records; a non-empty list NARROWS what is offered.
73
+ */
74
+ provenBands: z.array(z.string()).default([]),
75
+ })
76
+ .strict();
77
+ export const bandCertificationCatalogSchema = z
78
+ .object({
79
+ schemaVersion: z.literal(1),
80
+ entries: z.array(bandCertificationEntrySchema),
81
+ })
82
+ .strict();
83
+ /** Parse an untrusted value as a band catalog. Throws with a precise path. */
84
+ export function loadBandCertificationCatalog(value) {
85
+ return bandCertificationCatalogSchema.parse(value);
86
+ }
87
+ /** The catalog shipped in this package, validated at module load. */
88
+ export const BAND_CERTIFICATION_CATALOG = loadBandCertificationCatalog(rawCatalog);
89
+ /**
90
+ * The entry certifying this exact device, or `undefined`.
91
+ *
92
+ * All three discriminators must match, and the firmware match is a PREFIX of the
93
+ * device's FULL revision — never a truncation of the device's revision to the
94
+ * entry's length, which would certify a family the reviewer never looked at.
95
+ */
96
+ export function findBandCertification(catalog, sku) {
97
+ return catalog.entries.find((entry) => entry.vidPid === sku.vidPid &&
98
+ entry.model === sku.model &&
99
+ sku.firmwarePrefix.startsWith(entry.firmwarePrefix));
100
+ }
101
+ /**
102
+ * Whether a band-lock control may be OFFERED for this device.
103
+ *
104
+ * Fail-closed in every direction: an unknown SKU, a SKU with no entry, and a SKU
105
+ * whose entry predates a proof step all answer `false`.
106
+ */
107
+ export function isBandControlCertified(catalog, sku) {
108
+ if (sku === undefined)
109
+ return false;
110
+ return findBandCertification(catalog, sku) !== undefined;
111
+ }
112
+ /**
113
+ * Narrow an advertised band set to what the certification proves is settable.
114
+ *
115
+ * An entry with no `provenBands` proves the whole advertised set (the drill swept
116
+ * it); an entry that names bands offers ONLY those, intersected with what the
117
+ * modem advertises right now — a certified band the device no longer advertises
118
+ * is not offerable, and the device's own answer outranks the catalog.
119
+ */
120
+ export function offerableBands(entry, supported) {
121
+ if (entry === undefined)
122
+ return [];
123
+ if (entry.provenBands.length === 0)
124
+ return supported;
125
+ const proven = new Set(entry.provenBands);
126
+ return supported.filter((band) => proven.has(band));
127
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "entries": []
4
+ }
@@ -0,0 +1,2 @@
1
+ export * from './band-names.js';
2
+ export * from './certification.js';
@@ -0,0 +1,8 @@
1
+ // The band-lock capability module: the MMModemBand vocabulary and the
2
+ // certification catalog that gates whether a control may be offered at all.
3
+ //
4
+ // The D-Bus verbs themselves live with the other ModemManager mutations
5
+ // (`backend/mm-mutations.ts`), declared on `ModemManagerPort` — a band change is
6
+ // a radio mutation like a mode change, not a subsystem of its own.
7
+ export * from './band-names.js';
8
+ export * from './certification.js';
@@ -0,0 +1,52 @@
1
+ import type { MmPropertyProbe } from '../backend/features.js';
2
+ import type { RadioAccessTechnology } from '../domain/index.js';
3
+ import type { CapabilityEvidence, CapabilityModule } from './support-claim.js';
4
+ /** MM's Messaging interface — SMS list/read lives here. */
5
+ export declare const MESSAGING_IFACE = "org.freedesktop.ModemManager1.Modem.Messaging";
6
+ /** MM's Location interface — GNSS sources are advertised here. */
7
+ export declare const LOCATION_IFACE = "org.freedesktop.ModemManager1.Modem.Location";
8
+ /** MM's 3GPP USSD interface — the same name the USSD adapter dials. */
9
+ export declare const USSD_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd";
10
+ /**
11
+ * What A3.2 observed, plus the two signals the static object tree cannot carry.
12
+ *
13
+ * `interfaces` is the set of interface NAMES exported on the modem object, which
14
+ * is how SMS/USSD/Location are advertised — they are separate interfaces, not
15
+ * properties, so `MmPropertyProbe` alone cannot see them.
16
+ */
17
+ export interface ModuleCapabilityProbe extends MmPropertyProbe {
18
+ readonly interfaces?: ReadonlySet<string>;
19
+ /**
20
+ * `Location.Capabilities` decoded to source names (`gps-raw`, `gps-nmea`, …).
21
+ * An EMPTY set is a real answer — the interface exists and offers no GNSS.
22
+ */
23
+ readonly locationSources?: ReadonlySet<string>;
24
+ /**
25
+ * `SupportedModes` decoded to the RAT families it actually names.
26
+ *
27
+ * OPTIONAL, and its absence is the pre-existing behaviour verbatim: a caller
28
+ * that decoded only property NAMES answers exactly as before. Supplying it
29
+ * strictly NARROWS `five-g-pref`, because the property's mere presence is
30
+ * equally true of a 4G-only modem — see `detectCapabilityModules`.
31
+ */
32
+ readonly supportedRats?: ReadonlySet<RadioAccessTechnology>;
33
+ }
34
+ /**
35
+ * Detect every module's capability for one modem. Pure, total, never throws.
36
+ *
37
+ * `fcc-auto-unlock` is deliberately always `unknown`: FCC unlock is carried out
38
+ * by a ModemManager PLUGIN keyed on the device, and nothing on the modem's own
39
+ * D-Bus surface advertises whether one applies. Reporting `absent` would hide the
40
+ * module on hardware that supports it, and `present` would promise a plugin that
41
+ * may not be installed — so the honest answer is that this probe cannot tell, and
42
+ * evidence for it has to come from the catalog instead.
43
+ *
44
+ * `five-g-pref` is the one module whose property NAME is not the question. Every
45
+ * ModemManager modem exports `SupportedModes`, including a 4G-only one, so the
46
+ * name alone would resolve `present` on hardware with no 5G at all and offer a 5G
47
+ * posture nothing could honour. When the caller decoded the property's VALUE the
48
+ * verdict narrows to whether the catalog actually names 5GNR; when it did not,
49
+ * the property-name answer stands, so this is a strict narrowing and never a new
50
+ * way to claim a capability.
51
+ */
52
+ export declare function detectCapabilityModules(probe: ModuleCapabilityProbe): Record<CapabilityModule, CapabilityEvidence>;