@ceralive/modem-control 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/README.md +341 -0
  2. package/dist/backend/at-lease.d.ts +59 -0
  3. package/dist/backend/at-lease.js +118 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +101 -0
  61. package/dist/backend/transition-preconditions.js +132 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +216 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +20 -0
  146. package/dist/index.js +27 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +61 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operation-ids.d.ts +2 -0
  196. package/dist/operation-ids.js +26 -0
  197. package/dist/operations/contracts.d.ts +72 -0
  198. package/dist/operations/contracts.js +1 -0
  199. package/dist/operations/index.d.ts +1 -0
  200. package/dist/operations/index.js +1 -0
  201. package/dist/operations/operation-engine.d.ts +16 -0
  202. package/dist/operations/operation-engine.js +194 -0
  203. package/dist/ports/index.d.ts +12 -0
  204. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  205. package/dist/ports/location.d.ts +87 -0
  206. package/dist/ports/location.js +36 -0
  207. package/dist/ports/modem-manager.d.ts +89 -0
  208. package/dist/ports/modem-manager.js +9 -0
  209. package/dist/ports/mutation-admission.d.ts +27 -0
  210. package/dist/ports/mutation-admission.js +9 -0
  211. package/dist/ports/network-manager.d.ts +68 -0
  212. package/dist/ports/network-manager.js +13 -0
  213. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  214. package/dist/ports/observation.js +7 -0
  215. package/dist/ports/ops.d.ts +44 -0
  216. package/dist/ports/ops.js +16 -0
  217. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  218. package/dist/ports/receipts.js +9 -0
  219. package/dist/ports/reconcile.d.ts +33 -0
  220. package/dist/ports/reconcile.js +200 -0
  221. package/dist/ports/resource-ownership.d.ts +29 -0
  222. package/dist/ports/resource-ownership.js +1 -0
  223. package/dist/ports/router.d.ts +19 -0
  224. package/dist/ports/router.js +7 -0
  225. package/dist/ports/sms.d.ts +64 -0
  226. package/dist/ports/sms.js +24 -0
  227. package/dist/ports/uhubctl.d.ts +6 -0
  228. package/dist/ports/uhubctl.js +1 -0
  229. package/dist/providers/contracts.d.ts +124 -0
  230. package/dist/providers/contracts.js +10 -0
  231. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  232. package/dist/providers/huawei-hilink/index.js +2 -0
  233. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  234. package/dist/providers/huawei-hilink/operations.js +56 -0
  235. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  236. package/dist/providers/huawei-hilink/provider.js +76 -0
  237. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  238. package/dist/providers/huawei-hilink/runtime.js +171 -0
  239. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  240. package/dist/providers/huawei-hilink/session.js +120 -0
  241. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  242. package/dist/providers/huawei-hilink/transport.js +1 -0
  243. package/dist/providers/index.d.ts +8 -0
  244. package/dist/providers/index.js +8 -0
  245. package/dist/providers/matcher.d.ts +3 -0
  246. package/dist/providers/matcher.js +205 -0
  247. package/dist/providers/modem-manager/errors.d.ts +7 -0
  248. package/dist/providers/modem-manager/errors.js +37 -0
  249. package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
  250. package/dist/providers/modem-manager/generic-operations.js +209 -0
  251. package/dist/providers/modem-manager/index.d.ts +4 -0
  252. package/dist/providers/modem-manager/index.js +4 -0
  253. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  254. package/dist/providers/modem-manager/module-operations.js +118 -0
  255. package/dist/providers/modem-manager/provider.d.ts +41 -0
  256. package/dist/providers/modem-manager/provider.js +155 -0
  257. package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
  258. package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
  259. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  260. package/dist/providers/modem-manager/snapshot.js +204 -0
  261. package/dist/providers/modem-manager/types.d.ts +137 -0
  262. package/dist/providers/modem-manager/types.js +1 -0
  263. package/dist/providers/network-manager/adapter.d.ts +71 -0
  264. package/dist/providers/network-manager/adapter.js +348 -0
  265. package/dist/providers/network-manager/index.d.ts +2 -0
  266. package/dist/providers/network-manager/index.js +2 -0
  267. package/dist/providers/network-manager/types.d.ts +171 -0
  268. package/dist/providers/network-manager/types.js +77 -0
  269. package/dist/providers/registry.d.ts +13 -0
  270. package/dist/providers/registry.js +33 -0
  271. package/dist/providers/ufi-himi/index.d.ts +6 -0
  272. package/dist/providers/ufi-himi/index.js +6 -0
  273. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  274. package/dist/providers/ufi-himi/operations.js +66 -0
  275. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  276. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  277. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  278. package/dist/providers/ufi-himi/provider.js +204 -0
  279. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  280. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  281. package/dist/providers/ufi-himi/session.d.ts +46 -0
  282. package/dist/providers/ufi-himi/session.js +92 -0
  283. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  284. package/dist/providers/ufi-himi/transport.js +25 -0
  285. package/dist/providers/zte-goform/index.d.ts +2 -0
  286. package/dist/providers/zte-goform/index.js +2 -0
  287. package/dist/providers/zte-goform/provider.d.ts +56 -0
  288. package/dist/providers/zte-goform/provider.js +101 -0
  289. package/dist/providers/zte-goform/session.d.ts +23 -0
  290. package/dist/providers/zte-goform/session.js +197 -0
  291. package/dist/providers/zte-goform/transport.d.ts +16 -0
  292. package/dist/providers/zte-goform/transport.js +1 -0
  293. package/dist/radio/band-truth.d.ts +50 -0
  294. package/dist/radio/band-truth.js +92 -0
  295. package/dist/radio/index.d.ts +3 -0
  296. package/dist/radio/index.js +10 -0
  297. package/dist/radio/mode-combinations.d.ts +88 -0
  298. package/dist/radio/mode-combinations.js +198 -0
  299. package/dist/radio/mode-truth.d.ts +67 -0
  300. package/dist/radio/mode-truth.js +112 -0
  301. package/dist/redact.d.ts +15 -0
  302. package/dist/redact.js +189 -0
  303. package/dist/safety/composition-root.d.ts +28 -0
  304. package/dist/safety/composition-root.js +65 -0
  305. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  306. package/dist/safety/flock-resource-ownership.js +138 -0
  307. package/dist/safety/index.d.ts +2 -0
  308. package/dist/safety/index.js +2 -0
  309. package/dist/sms/dbus-messaging.d.ts +17 -0
  310. package/dist/sms/dbus-messaging.js +185 -0
  311. package/dist/sms/inbox-store.d.ts +10 -0
  312. package/dist/sms/inbox-store.js +82 -0
  313. package/dist/sms/index.d.ts +4 -0
  314. package/dist/sms/index.js +10 -0
  315. package/dist/sms/mmcli-parse.d.ts +54 -0
  316. package/dist/sms/mmcli-parse.js +224 -0
  317. package/dist/sms/normalize.d.ts +42 -0
  318. package/dist/sms/normalize.js +95 -0
  319. package/dist/testing/domain-fakes.d.ts +58 -0
  320. package/dist/testing/domain-fakes.js +98 -0
  321. package/dist/testing/index.d.ts +2 -0
  322. package/dist/testing/index.js +17 -0
  323. package/dist/testing/provider-fakes.d.ts +45 -0
  324. package/dist/testing/provider-fakes.js +64 -0
  325. package/dist/transport/calls.d.ts +9 -0
  326. package/dist/transport/calls.js +88 -0
  327. package/dist/transport/codec.d.ts +3 -0
  328. package/dist/transport/codec.js +207 -0
  329. package/dist/transport/dbus-native.d.ts +57 -0
  330. package/dist/transport/dbus-native.js +17 -0
  331. package/dist/transport/errors.d.ts +21 -0
  332. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  333. package/dist/transport/index.d.ts +4 -0
  334. package/dist/transport/index.js +9 -0
  335. package/dist/transport/signals.d.ts +15 -0
  336. package/dist/transport/signals.js +123 -0
  337. package/dist/transport/signature.d.ts +7 -0
  338. package/dist/transport/signature.js +94 -0
  339. package/dist/transport/transport.d.ts +2 -0
  340. package/dist/transport/transport.js +202 -0
  341. package/dist/transport/types.d.ts +61 -0
  342. package/dist/transport/types.js +19 -0
  343. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  344. package/dist/usb-mode/catalog-schema.js +97 -0
  345. package/dist/usb-mode/catalog.d.ts +21 -0
  346. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  347. package/dist/usb-mode/certified-catalog.json +67 -0
  348. package/dist/usb-mode/index.d.ts +6 -0
  349. package/dist/usb-mode/index.js +16 -0
  350. package/dist/usb-mode/ingestion.d.ts +111 -0
  351. package/dist/usb-mode/ingestion.js +187 -0
  352. package/dist/usb-mode/promotion-review.d.ts +21 -0
  353. package/dist/usb-mode/promotion-review.js +87 -0
  354. package/dist/usb-mode/runtime-capability.d.ts +59 -0
  355. package/dist/usb-mode/runtime-capability.js +157 -0
  356. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  357. package/dist/usb-mode/usb-devices-parse.js +157 -0
  358. package/dist/ussd/calls.d.ts +32 -0
  359. package/dist/ussd/calls.js +96 -0
  360. package/dist/ussd/index.d.ts +5 -0
  361. package/dist/ussd/index.js +12 -0
  362. package/dist/ussd/mm-ussd.d.ts +37 -0
  363. package/dist/ussd/mm-ussd.js +205 -0
  364. package/dist/ussd/refusal.d.ts +53 -0
  365. package/dist/ussd/refusal.js +154 -0
  366. package/dist/ussd/registration.d.ts +20 -0
  367. package/dist/ussd/registration.js +101 -0
  368. package/dist/ussd/session.d.ts +106 -0
  369. package/dist/ussd/session.js +163 -0
  370. package/package.json +38 -4
  371. package/src/backend/at-lease.test.ts +0 -106
  372. package/src/backend/at-lease.ts +0 -158
  373. package/src/backend/cell-info.test.ts +0 -154
  374. package/src/backend/cell-info.ts +0 -160
  375. package/src/backend/device-classifier.test.ts +0 -168
  376. package/src/backend/device-classifier.ts +0 -248
  377. package/src/backend/enrichment.ts +0 -96
  378. package/src/backend/features.test.ts +0 -162
  379. package/src/backend/features.ts +0 -179
  380. package/src/backend/identity-ladder.test.ts +0 -117
  381. package/src/backend/identity-ladder.ts +0 -221
  382. package/src/backend/identity-registry.test.ts +0 -89
  383. package/src/backend/identity-registry.ts +0 -151
  384. package/src/backend/index.ts +0 -236
  385. package/src/backend/lifecycle-interlock.ts +0 -38
  386. package/src/backend/managed-objects.ts +0 -108
  387. package/src/backend/mapping.ts +0 -160
  388. package/src/backend/mm-backend.ts +0 -191
  389. package/src/backend/mm-mutations.ts +0 -228
  390. package/src/backend/modem-actor.test.ts +0 -95
  391. package/src/backend/modem-actor.ts +0 -112
  392. package/src/backend/nm-auto-apn.ts +0 -161
  393. package/src/backend/nm-gsm-fields.ts +0 -122
  394. package/src/backend/nmcli-nm-port.ts +0 -228
  395. package/src/backend/nmcli-runner.ts +0 -52
  396. package/src/backend/observer.ts +0 -297
  397. package/src/backend/power-contract.test.ts +0 -40
  398. package/src/backend/power-contract.ts +0 -83
  399. package/src/backend/recovery-attribution.test.ts +0 -102
  400. package/src/backend/recovery-attribution.ts +0 -86
  401. package/src/backend/recovery-budget.test.ts +0 -64
  402. package/src/backend/recovery-budget.ts +0 -84
  403. package/src/backend/recovery-ladder.test.ts +0 -257
  404. package/src/backend/recovery-ladder.ts +0 -249
  405. package/src/backend/router-ethernet.test.ts +0 -71
  406. package/src/backend/router-ethernet.ts +0 -90
  407. package/src/backend/row-store.ts +0 -105
  408. package/src/backend/signal-setup.ts +0 -112
  409. package/src/backend/sim-unlock.ts +0 -193
  410. package/src/backend/transition-preconditions.ts +0 -149
  411. package/src/backend/uhubctl-power-hook.test.ts +0 -274
  412. package/src/backend/uhubctl-power-hook.ts +0 -377
  413. package/src/backend/usage/accounting.test.ts +0 -147
  414. package/src/backend/usage/accounting.ts +0 -123
  415. package/src/backend/usage/billing-cycle.test.ts +0 -62
  416. package/src/backend/usage/billing-cycle.ts +0 -45
  417. package/src/backend/usage/index.ts +0 -60
  418. package/src/backend/usage/policy-store.test.ts +0 -164
  419. package/src/backend/usage/policy-store.ts +0 -216
  420. package/src/backend/usage/policy-write.test.ts +0 -198
  421. package/src/backend/usage/policy-write.ts +0 -207
  422. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  423. package/src/backend/usage/sampler.test.ts +0 -327
  424. package/src/backend/usage/sampler.ts +0 -282
  425. package/src/backend/usage/store.test.ts +0 -148
  426. package/src/backend/usage/store.ts +0 -177
  427. package/src/backend/usb-enumerator.test.ts +0 -87
  428. package/src/backend/usb-enumerator.ts +0 -181
  429. package/src/backend/usb-mode-transition.test.ts +0 -323
  430. package/src/backend/usb-mode-transition.ts +0 -253
  431. package/src/domain/brand.ts +0 -29
  432. package/src/domain/errors.ts +0 -77
  433. package/src/domain/guards.test.ts +0 -218
  434. package/src/domain/guards.ts +0 -144
  435. package/src/domain/identity.test.ts +0 -83
  436. package/src/domain/identity.ts +0 -165
  437. package/src/domain/index.ts +0 -12
  438. package/src/domain/snapshot.test.ts +0 -266
  439. package/src/domain/snapshot.ts +0 -120
  440. package/src/index.test.ts +0 -6
  441. package/src/index.ts +0 -15
  442. package/src/ports/README.md +0 -61
  443. package/src/ports/forbidden-surface.test.ts +0 -241
  444. package/src/ports/modem-manager.ts +0 -72
  445. package/src/ports/network-manager.ts +0 -87
  446. package/src/ports/ops.ts +0 -60
  447. package/src/ports/ops.type-test.ts +0 -39
  448. package/src/ports/receipts.test.ts +0 -153
  449. package/src/ports/reconcile.test.ts +0 -152
  450. package/src/ports/reconcile.ts +0 -338
  451. package/src/ports/router.ts +0 -29
  452. package/src/redact.test.ts +0 -82
  453. package/src/redact.ts +0 -73
  454. package/src/transport/README.md +0 -65
  455. package/src/transport/calls.ts +0 -113
  456. package/src/transport/characterization.test.ts +0 -260
  457. package/src/transport/codec.test.ts +0 -118
  458. package/src/transport/codec.ts +0 -240
  459. package/src/transport/conformance-python.test.ts +0 -152
  460. package/src/transport/conformance-same-lib.test.ts +0 -115
  461. package/src/transport/dbus-native-lib.d.ts +0 -19
  462. package/src/transport/dbus-native.ts +0 -85
  463. package/src/transport/index.ts +0 -30
  464. package/src/transport/no-library-leak.test.ts +0 -61
  465. package/src/transport/reliability.test.ts +0 -173
  466. package/src/transport/signals.ts +0 -150
  467. package/src/transport/signature.ts +0 -110
  468. package/src/transport/test-support/fake-service.ts +0 -168
  469. package/src/transport/test-support/independent-producer.py +0 -110
  470. package/src/transport/test-support/private-bus.ts +0 -66
  471. package/src/transport/transport.ts +0 -250
  472. package/src/transport/types.ts +0 -118
  473. package/src/usb-mode/catalog-schema.test.ts +0 -181
  474. package/src/usb-mode/catalog-schema.ts +0 -113
  475. package/src/usb-mode/certified-catalog.json +0 -67
  476. package/src/usb-mode/index.ts +0 -58
  477. package/src/usb-mode/ingestion.test.ts +0 -268
  478. package/src/usb-mode/ingestion.ts +0 -297
  479. package/src/usb-mode/promotion-review.ts +0 -117
  480. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -0,0 +1,216 @@
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. Tier 1 is
15
+ // the strongest proof and remains unchanged: a reviewed catalog transition must match
16
+ // both descriptors and canonical mode. Tier 2 exists only when no reviewed transition
17
+ // matches: the re-enumerated device must report the raw target through its own vendor READ.
18
+ // Tier 2 is explicitly weaker because it proves reported mode, not descriptor composition.
19
+ // On a postcondition MISMATCH the whole transaction fails `degraded`, does NOT
20
+ // reactivate, and still releases the interlock via `finally`. A hung command trips the
21
+ // AT watchdog, which force-uninhibits so the system reprobes rather than wedging.
22
+ import { deviceIfname } from '../ports/index.js';
23
+ import { CERTIFIED_CATALOG, readRuntimeCompositionCurrent, } from '../usb-mode/index.js';
24
+ import { AtCommandLease, computeAtAllowlist, } from './at-lease.js';
25
+ import { descriptorsMatch, detectUsbMode } from './device-classifier.js';
26
+ import { ALLOW_ALL_TRANSITION_INTERLOCK, checkTransitionPreconditions, } from './transition-preconditions.js';
27
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
28
+ const DEFAULT_WATCHDOG_MS = 30_000;
29
+ const DEFAULT_REENUM_TIMEOUT_MS = 60_000;
30
+ const DEFAULT_POLL_INTERVAL_MS = 250;
31
+ function defaultResolveIfname(device) {
32
+ return device.ifname !== undefined && device.ifname !== ''
33
+ ? deviceIfname(device.ifname)
34
+ : undefined;
35
+ }
36
+ /** The USB-mode transition transaction. One instance is reusable across requests. */
37
+ export class UsbModeTransition {
38
+ #actor;
39
+ #nm;
40
+ #modemManager;
41
+ #atSender;
42
+ #enumerate;
43
+ #interlock;
44
+ #catalog;
45
+ #audit;
46
+ #resolveIfname;
47
+ #watchdogMs;
48
+ #reenumMs;
49
+ #pollMs;
50
+ constructor(deps) {
51
+ this.#actor = deps.actor;
52
+ this.#nm = deps.nm;
53
+ this.#modemManager = deps.modemManager;
54
+ this.#atSender = deps.atSender;
55
+ this.#enumerate = deps.enumerate;
56
+ this.#interlock = deps.interlock ?? ALLOW_ALL_TRANSITION_INTERLOCK;
57
+ this.#catalog = deps.catalog ?? CERTIFIED_CATALOG;
58
+ this.#audit = deps.audit;
59
+ this.#resolveIfname = deps.resolveIfname ?? defaultResolveIfname;
60
+ this.#watchdogMs = deps.watchdogMs ?? DEFAULT_WATCHDOG_MS;
61
+ this.#reenumMs = deps.reenumerationTimeoutMs ?? DEFAULT_REENUM_TIMEOUT_MS;
62
+ this.#pollMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
63
+ }
64
+ /** Run one transition. Preconditions are checked at entry, then again in-actor. */
65
+ async execute(request) {
66
+ const steps = [];
67
+ // ENTRY check — a doomed request NEVER enters the actor (TIER A: zero calls).
68
+ const entry = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
69
+ if (!entry.ok) {
70
+ return { status: 'refused', stage: 'entry', reason: entry.reason, steps };
71
+ }
72
+ return this.#actor.run(request.stableKey, () => this.#inActor(request, steps));
73
+ }
74
+ async #inActor(request, steps) {
75
+ steps.push('actor-enter');
76
+ // IN-ACTOR re-check — catches a race that closed a gate while queued (TIER B).
77
+ const recheck = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
78
+ if (!recheck.ok) {
79
+ return { status: 'refused', stage: 'in-actor', reason: recheck.reason, steps };
80
+ }
81
+ const hold = await this.#interlock.hold({ stableKey: request.stableKey });
82
+ try {
83
+ return await this.#runTransaction(request, recheck.allowlistedCommands, recheck.plan, steps);
84
+ }
85
+ finally {
86
+ steps.push('release-interlock');
87
+ await hold.release().catch(() => undefined);
88
+ }
89
+ }
90
+ async #runTransaction(request, allowlistedCommands, plan, steps) {
91
+ let inhibit;
92
+ let reactivated = false;
93
+ const forceUninhibit = async () => {
94
+ if (inhibit === undefined) {
95
+ return;
96
+ }
97
+ const held = inhibit;
98
+ inhibit = undefined;
99
+ steps.push('force-uninhibit');
100
+ await this.#modemManager.uninhibit(held).catch(() => undefined);
101
+ };
102
+ const lease = new AtCommandLease({
103
+ sender: this.#atSender,
104
+ allowlist: computeAtAllowlist(allowlistedCommands),
105
+ timeoutMs: this.#watchdogMs,
106
+ onWatchdog: forceUninhibit,
107
+ ...(this.#audit !== undefined ? { audit: this.#audit } : {}),
108
+ });
109
+ steps.push('nm-quiesce');
110
+ const quiesce = await this.#nm.acquireQuiesceLease(request.connectionId, request.deviceIfname);
111
+ try {
112
+ steps.push('inhibit');
113
+ inhibit = await this.#modemManager.inhibit(request.inhibitUid);
114
+ // AT `OK` is IGNORED for success — only the postcondition below decides.
115
+ steps.push('at-command');
116
+ await lease.run(plan.atCommand, { inhibitUid: request.inhibitUid });
117
+ if (plan.applyCommand !== undefined) {
118
+ steps.push('apply-command');
119
+ await lease.run(plan.applyCommand, { inhibitUid: request.inhibitUid });
120
+ }
121
+ steps.push('await-port-drop');
122
+ await this.#awaitPortDrop(request.cachedPhysicalUid);
123
+ steps.push('uninhibit');
124
+ if (inhibit !== undefined) {
125
+ const held = inhibit;
126
+ inhibit = undefined;
127
+ await this.#modemManager.uninhibit(held);
128
+ }
129
+ steps.push('await-reenumeration');
130
+ const device = await this.#awaitReenumeration(request.cachedPhysicalUid);
131
+ steps.push('postcondition');
132
+ if (plan.proof.tier === 'catalog-descriptors') {
133
+ const observedMode = detectUsbMode(device);
134
+ const descriptorsOk = descriptorsMatch(device, plan.proof.transition.expectedDescriptors);
135
+ if (observedMode !== plan.proof.transition.to || !descriptorsOk) {
136
+ return {
137
+ status: 'failed',
138
+ degraded: true,
139
+ reason: `postcondition mismatch: observed ${observedMode ?? 'unknown'} vs target ${plan.proof.transition.to}; descriptors ${descriptorsOk ? 'ok' : 'mismatch'}`,
140
+ steps,
141
+ };
142
+ }
143
+ }
144
+ else {
145
+ steps.push('postcondition-runtime-read');
146
+ const response = await lease.run(plan.proof.currentQuery, {
147
+ inhibitUid: request.inhibitUid,
148
+ });
149
+ const observed = readRuntimeCompositionCurrent(plan.proof.vendor, response.raw);
150
+ if (!Object.is(observed, plan.proof.target)) {
151
+ return {
152
+ status: 'failed',
153
+ degraded: true,
154
+ reason: `runtime readback mismatch: observed ${observed ?? 'unknown'} vs target ${plan.proof.target}`,
155
+ steps,
156
+ };
157
+ }
158
+ }
159
+ steps.push('resolve-ifname');
160
+ const newIfname = this.#resolveIfname(device);
161
+ if (newIfname === undefined) {
162
+ return { status: 'failed', degraded: true, reason: 'could not resolve new ifname', steps };
163
+ }
164
+ steps.push('reactivate');
165
+ await this.#nm.activate(request.connectionId, newIfname);
166
+ reactivated = true;
167
+ return { status: 'succeeded', newIfname, steps };
168
+ }
169
+ catch (error) {
170
+ await forceUninhibit();
171
+ await this.#reprobe();
172
+ return {
173
+ status: 'failed',
174
+ degraded: true,
175
+ reason: error instanceof Error ? error.message : String(error),
176
+ steps,
177
+ };
178
+ }
179
+ finally {
180
+ await forceUninhibit();
181
+ if (!reactivated) {
182
+ // Failure path: restore the old connection. On success the new-ifname
183
+ // activation supersedes the quiesce lease (old ifname is gone).
184
+ steps.push('release-quiesce');
185
+ await this.#nm.releaseQuiesceLease(quiesce).catch(() => undefined);
186
+ }
187
+ }
188
+ }
189
+ async #awaitPortDrop(uid) {
190
+ const deadline = Date.now() + this.#reenumMs;
191
+ while (Date.now() < deadline) {
192
+ const devices = await this.#enumerate();
193
+ if (!devices.some((d) => d.physicalUid === uid)) {
194
+ return;
195
+ }
196
+ await sleep(this.#pollMs);
197
+ }
198
+ throw new Error(`control port did not drop within ${this.#reenumMs}ms (uid ${uid})`);
199
+ }
200
+ async #awaitReenumeration(uid) {
201
+ const deadline = Date.now() + this.#reenumMs;
202
+ while (Date.now() < deadline) {
203
+ const devices = await this.#enumerate();
204
+ const device = devices.find((d) => d.physicalUid === uid);
205
+ if (device !== undefined) {
206
+ return device;
207
+ }
208
+ await sleep(this.#pollMs);
209
+ }
210
+ throw new Error(`device did not re-enumerate within ${this.#reenumMs}ms (uid ${uid})`);
211
+ }
212
+ /** Best-effort state re-read after a crash — the transaction still fails degraded. */
213
+ async #reprobe() {
214
+ await this.#enumerate().then(() => undefined, () => undefined);
215
+ }
216
+ }
@@ -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';