@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
package/README.md ADDED
@@ -0,0 +1,341 @@
1
+ # `@ceralive/modem-control`
2
+
3
+ Cellular modem control for CeraLive: the frozen v1.1 domain contracts, the provider
4
+ registry and evidence-scored matcher, the ModemManager D-Bus backend, the
5
+ NetworkManager adapter, the desired-state reconciler, the USB composition-mode model,
6
+ the data-usage sampler, and the gated capability modules.
7
+
8
+ **This package is a LIBRARY.** It ships no `bin`, no systemd unit, no shebang, and
9
+ nothing that opens a listening socket — it is imported by a controller, it is not one.
10
+ That claim is checked against the real `bun pm pack` output, not against the source
11
+ tree; see [Shape gate](#shape-gate).
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install @ceralive/modem-control # or: bun add @ceralive/modem-control
17
+ ```
18
+
19
+ ESM only (`"type": "module"`). Node 26 and Bun 1.4 are the runtimes the published
20
+ tarball is exercised against on every CI run.
21
+
22
+ ## Public entry points
23
+
24
+ Seven specifiers, and nothing else. Every other module is internal and reachable only
25
+ through the root entry, so an internal reorganisation is not a breaking change.
26
+
27
+ | Specifier | What it carries |
28
+ |-----------|-----------------|
29
+ | `@ceralive/modem-control` | Everything below plus the ports, backend, reconciler, redaction, SMS, USSD, location and FCC modules |
30
+ | `@ceralive/modem-control/domain` | Frozen v1.1 contracts: `PhysicalModemId`, `DeviceGeneration`, `ObservationEnvelope`, `OperationDescriptor` / `OperationResult` |
31
+ | `@ceralive/modem-control/providers` | `ProviderDefinition`, the registry, and the evidence-scored matcher |
32
+ | `@ceralive/modem-control/capabilities` | The five-state support-claim taxonomy and per-modem capability detection |
33
+ | `@ceralive/modem-control/hardware` | The per-SKU hardware model: USB composition modes + certified catalog, and the band vocabulary + band-lock certification |
34
+ | `@ceralive/modem-control/transport` | The D-Bus transport seam (no underlying-library type is re-exported) |
35
+ | `@ceralive/modem-control/testing` | Public **contract fakes** for consumers' own tests |
36
+
37
+ The `./hardware` surface also owns the transport-free response parsers migrated
38
+ from CeraUI: SIM-presence evidence plus normalized Huawei HiLink, ZTE goform,
39
+ and Qualcomm UFI/HIMI signal, detail, and capability reads. They accept response
40
+ bodies only; HTTP sessions, interface binding, retries, caches, and writes remain
41
+ consumer-owned.
42
+
43
+ The root entry also carries the **observation layer** built on those parsers. It turns a raw
44
+ per-vendor payload into one `ObservationEnvelope<NormalizedModemObservation>` in which every
45
+ metric records which source produced it and when, and in which a missing value carries a
46
+ reason that says whether the source *cannot* report it (`unsupported`) or merely *did not*
47
+ on this read. `fresh`, `stale`, `unavailable` and `unknown` are four distinct shapes rather
48
+ than a value plus a flag: stale keeps its value, unavailable carries none and never ages into
49
+ stale, and unknown says which field is missing and why. Nothing the provider sent is
50
+ discarded — every provider-native field is retained verbatim in a typed diagnostics block,
51
+ with `unmapped` derived rather than declared. Desired, applied and observed state stay in
52
+ three separate slots.
53
+
54
+ The migration surface is broader than response parsing but remains pure: the root and
55
+ existing `./domain`, `./capabilities`, and `./hardware` entries expose portable physical
56
+ identity/link-id derivation, modem presentation rules, ModemManager enum decoding,
57
+ USB-network classification and labels, capability-module selection, and shadow-backend
58
+ divergence folding. Every helper consumes caller-supplied values or snapshots; none discovers
59
+ devices, opens a transport, persists state, or performs a modem write.
60
+
61
+ USB snapshots retain the udev `P:` record as an absolute `sysfsPath`, allowing consumers to
62
+ correlate ModemManager `Device`/`Physdev` paths to the most-specific USB parent without relying
63
+ on a network-interface name.
64
+
65
+ ### Typed ModemManager provider
66
+
67
+ `createModemManagerProvider({ transport })` returns the concrete `ModemManagerProvider` and its
68
+ provider-registry `definition`. Matching is based on the live ObjectManager tree, not a certified
69
+ model allowlist, so unknown future modems retain generic mode, signal, SIM, and power controls when
70
+ their runtime interfaces/properties advertise them. Its normalized `observe` result uses the root
71
+ observation envelope; its lifecycle `start` / `observe` / `stop` methods reuse the epoch-scoped
72
+ signal observer.
73
+
74
+ ### Huawei HiLink provider
75
+
76
+ `createHuaweiHiLinkDefinition()` exposes exact E3372H firmware profiles for password types 3 and
77
+ 4. Firmware plus `SesTokInfo` evidence chooses one profile, and `state-login` must confirm that
78
+ profile's password type before the provider makes its only login attempt. Every request is bound to
79
+ the injected network interface with redirects disabled. Mode and mobile-data writes acquire
80
+ `router-session`, probe their own capability, and require a new authenticated readback before they
81
+ can report `applied`; no Wi-Fi write exists. Credentials and session material remain private to the
82
+ provider runtime. See [`../docs/HUAWEI-HILINK-PROVIDER.md`](../docs/HUAWEI-HILINK-PROVIDER.md).
83
+
84
+ ### ZTE goform provider
85
+
86
+ `createZteGoformDefinition()` exposes three incompatible authentication profiles without
87
+ fallback between them: MF79U legacy base64 under `LOGIN`, MF79U `LD`-salted SHA-256 under
88
+ the same bare `LOGIN`, and MF266 salted SHA-256 under `LOGIN_MULTI_USER`. One batched
89
+ pre-auth evidence GET selects the exact shape and refuses a reported lockout before any
90
+ credential POST. MF266 derives `AD` from the probed version data and `RD`. Session material stays
91
+ in memory. Unknown ZTE firmware is fingerprinted into a read-only telemetry profile, and
92
+ Wi-Fi writes are absent from every operation surface. See
93
+ [`../docs/MF79U-DIAGNOSIS.md`](../docs/MF79U-DIAGNOSIS.md).
94
+
95
+ ### UFI / HIMI provider — read-only by construction
96
+
97
+ `createUfiHimiDefinition()` normalizes the Qualcomm UFI/HIMI telemetry over the vendor's
98
+ single `POST /himiapi/json` endpoint. Because that API puts its verb in the request
99
+ body's `cmdid` rather than in the HTTP method, read-only is enforced as a **frozen
100
+ command vocabulary** — seven `get*` reads plus `login` — so a write command cannot be
101
+ expressed at all. `operations()` returns `ProviderReadOperations` entries verbatim and
102
+ exposes zero write descriptors.
103
+
104
+ The prohibited Qualcomm operations — NV, EFS, identity and calibration writes, firmware
105
+ flashing, EDL automation, blind driver/interface retries, DIAG writes, the DIAG info
106
+ probe, and shell transport fallback — are inert table entries with **no implementation
107
+ anywhere**. `planUfiOperation()` answers each with a typed reason and takes no transport
108
+ parameter, so the refusal provably precedes any device contact; the same ids driven
109
+ through `OperationEngine` are refused before execution too.
110
+
111
+ `05c6:9024` is evidence of an RNDIS+ADB composition, not a permission. `05c6:9091` is a
112
+ firmware-chosen product id and is **not** proof of DIAG — only an interface descriptor is,
113
+ and production access stays `prohibited` regardless. The supervised, read-only, bench-only
114
+ probe is documented in [`../docs/UFI-DIAG-PROBE.md`](../docs/UFI-DIAG-PROBE.md), together
115
+ with the read-only descriptor capture (`scripts/ufi-himi-capture.sh`) and its bundle
116
+ schema, interface-role classifier and redaction sweep (`scripts/ufi-himi-evidence.ts`).
117
+ Neither ships in the package: `files: ["dist"]`, and bench tooling is not a public surface.
118
+
119
+ ### NetworkManager adapter — saved vs applied
120
+
121
+ `new NetworkManagerAdapter({ port })` is the bearer/APN authority, and the only one. It holds
122
+ three separate slots per NM connection: the **desired** profile (what an operator asked for,
123
+ recorded from the request), the **applied** bearer (what NM actually put into force, recorded
124
+ from NM's readback, together with the interface it landed on), and the **observed** device
125
+ state. `observe()` folds one complete NM readout and reports a typed applied-state LOSS —
126
+ `interface-absent`, `interface-detached`, `connection-replaced`, or `activation-failed` — while
127
+ leaving the desired profile untouched, so a modem that re-enumerates costs you the bearer and
128
+ never the configuration. A device still settling is reported `pending` rather than lost, and a
129
+ readout from a superseded generation is refused rather than folded late.
130
+
131
+ It composes the existing `NmcliNmPort` rather than replacing it, performs no radio, band, SIM
132
+ or power operation, keys every slot by NM's connection UUID rather than by a physical modem
133
+ identity, mirrors no credential into a state slot, and has no profile-delete path.
134
+
135
+ The operation surface composes the existing radio/band backend, GPS location adapter and bounded
136
+ fix-state machine, read-only SMS port, USSD session adapter, and FCC coverage catalog. Generic band
137
+ reads are always runtime-driven; disruptive band writes additionally require a certification
138
+ catalog entry for the device's SKU. It contains no bearer/APN authority and no command-line
139
+ fallback. The provider never invokes `mmcli`, `qmicli`, or `mbimcli`—those remain operator
140
+ diagnostics only.
141
+
142
+ ### Radio capability truth — the modem's own catalog, unedited
143
+
144
+ The root export also carries the mode/band **capability truth** layer. `SupportedModes`
145
+ and `CurrentModes` are decoded without loss: a combination whose preferred mask is 0
146
+ reads `preferred: 'none'` — a value, not a missing field — and reaches
147
+ `descriptor.constraints.values` exactly as the modem stated it. A mode bit this build
148
+ does not name round-trips as `mode-bit-<n>`, its combination is classified
149
+ `unknown-combination`, and it stays **offered**; a catalog member that is not a `(uu)`
150
+ pair is retained in `undecodable` rather than dropped, so decoded plus undecodable is
151
+ always the member count the modem sent. A selection the modem never advertised is
152
+ refused, never rounded to the nearest one.
153
+
154
+ `modes` and `bands` are typed write operations with **required readback**: the daemon
155
+ accepting `SetCurrentModes` / `SetCurrentBands` only proves the call was accepted, so
156
+ the applied value is re-read and compared before either reports success. `bands`
157
+ additionally carries `mutationImpact: 'disruptive'`, a `band-certification-present`
158
+ live precondition, and an availability that is `refused` with
159
+ `band-certification-required` unless the device's SKU resolves to an entry in the
160
+ band-lock certification catalog — which ships empty, so that is today's answer for every
161
+ device. Supply `bandSku` to `createModemManagerProvider` to resolve one;
162
+ ModemManager exposes no USB `vid:pid`, so the package cannot build a `BandSku` alone.
163
+
164
+ Both operations expose `describe(context)` alongside their static `descriptor`, because
165
+ a static descriptor cannot carry a device's own catalog or its certification state.
166
+
167
+ ### USB composition — runtime-derived targets, two proof tiers
168
+
169
+ `operations().usbComposition` asks a known vendor for its current and enumerated USB
170
+ composition modes and offers only targets from that reply after the reply also proves a
171
+ represented return path. Its suppression vocabulary is `unknown-vendor`,
172
+ `no-return-path`, `blocked-by-state`, and `provisioning-disabled`; suppressed states expose
173
+ no targets. Unknown/disabled/blocked decisions happen before transport contact, and a
174
+ capability read sends only the named READ/TEST forms, never a SET.
175
+
176
+ A reviewed catalog transition still provides the strongest success proof: canonical mode
177
+ and USB descriptors must both match. Without one, the weaker fallback proof is the
178
+ re-enumerated device's own post-switch READ reporting the target. AT `OK` is never success.
179
+ The write remains disruptive and requires admission, journal, rollback, and readback hooks.
180
+ Band writes do not share this policy and remain behind their four-proof certification gate.
181
+
182
+ ### SIM presence is evidence, never inference
183
+
184
+ `readSimPresence` returns the presence together with the `SimPresenceEvidence` that
185
+ decided it, and `absent` is reachable through exactly one evidence kind — ModemManager's
186
+ own `StateFailedReason: sim-missing`. A blank `Sim` object path proves nothing (MM
187
+ reports `/` while a modem initializes and while a slot switch is in flight) and reads
188
+ `unknown`. `ModemManagerSimState.present` is positive evidence only: `false` is not a
189
+ claim of absence. The Huawei, ZTE and UFI sources still claim no presence at all and now
190
+ NAME the vendor code they left undecoded, which stays verbatim in the diagnostics block.
191
+
192
+ `Modem.CurrentModes` and `Modem.SignalQuality` are retained as the D-Bus structs they
193
+ are, so the preferred mode and the measurement-recency flag survive normalization;
194
+ `NormalizedSignal.qualityRecent` claims that flag, and the router sources answer
195
+ `unsupported` for it.
196
+
197
+ ### Mutation safety ports
198
+
199
+ The root export includes `MutationAdmissionPort`, `ResourceOwnershipPort`,
200
+ `ModemManagerInhibitPort`, and `UhubctlPort`. Admission remains consumer-owned: a required
201
+ mutation without an injected admission port is refused as `admission-port-missing`; this
202
+ package does not know or infer why the consumer refused it.
203
+
204
+ File stores, router sessions, and USB-hub access use acquire-or-refuse exclusive ownership.
205
+ `createFlockResourceOwnershipPort({ lockPath })` is the Linux adapter: non-blocking `flock`,
206
+ holder PID/start-time metadata, and clean release when the holder process dies. The lock path
207
+ is mandatory input; `DEFAULT_MODEM_CONTROL_LOCK_PATH` is only a conventional value callers
208
+ may select. There is no pass-through ownership implementation.
209
+
210
+ The adapter holds the lock with an external `/bin/cat` whose pipe round-trip acknowledges
211
+ successful acquisition. It never launches `process.execPath -e`: a compiled Bun executable's
212
+ `process.execPath` points back to the application, so re-executing it would parse `-e` as an
213
+ application option and misreport the resulting exit as contention.
214
+
215
+ One `createModemControlCompositionRoot()` may be live per process. A second construction
216
+ throws, and `actorFor(physicalModemId)` shares one actor for that modem across all callers in
217
+ the root. `UhubctlPort` has no control-package implementation; an embedding process must
218
+ inject one and own its executable policy.
219
+
220
+ ### Descriptor-gated operation engine
221
+
222
+ `createOperationEngine()` executes `OperationDescriptor` contracts through that composition
223
+ root. Every mutation enters the root's shared physical-modem actor before its live preconditions
224
+ and admission are checked. Writes are therefore single-flight per physical modem, and a mutation
225
+ that waited in the queue cannot reuse facts checked before it waited. Reads do not occupy the
226
+ write queue; the engine retries only a failed read whose descriptor explicitly says
227
+ `idempotent-read`, once.
228
+
229
+ A stale-generation completion or a timed-out/dropped write reply is classified by the frozen
230
+ domain helper as `unknown-outcome`. The engine then closes a per-`PhysicalModemId` mutation gate:
231
+ subsequent mutations are refused as `reconciliation-required` without calling the provider.
232
+ `engine.reconcile()` uses the same actor and reopens the gate only when reconciliation finishes in
233
+ the requested current generation. Required readback, rollback, and journal hooks are checked before
234
+ execution and fired according to the descriptor; an unknown outcome is never treated as a definite
235
+ failure that is safe to roll back.
236
+
237
+ ### Transaction journal — the path comes from you
238
+
239
+ `createFileJournalStore({ path })` and `createJournalEngine({ store })` are the durable
240
+ half of that reconciliation gate. The engine satisfies the operation engine's
241
+ `OperationJournalHook`, so it can be handed straight to an `OperationExecution` as its
242
+ `journal`, and `engine.recover()` reads the file back after a restart and reports which
243
+ operations were still `pending` and which ended `unknown-outcome` — the set a controller
244
+ must reconcile before it mutates those modems again.
245
+
246
+ **The path is required and this package has no default for it.** Where a journal lives is
247
+ a property of the system embedding this library, not of the library, so there is no
248
+ fallback location to accidentally write to. An empty path is refused with
249
+ `JournalPathError`.
250
+
251
+ The store is append-only and never truncates itself. A record it cannot decode is returned
252
+ as typed damage alongside every record that *did* decode — including the ones after it — so
253
+ a single corrupt line can never take the rest of the journal with it. Call
254
+ `assertJournalIntact(recovery)` to escalate that damage to a `JournalRecoveryError` once
255
+ you have seen what survived. Neither an operation's input nor its returned value is ever
256
+ written to disk.
257
+
258
+ `readLegacyCeraUiJournal({ dir })` reads an older per-modem snapshot journal into the same
259
+ recovery model, so a consumer migrating onto this package can enumerate outstanding work
260
+ from files written before it existed. It only ever reads.
261
+
262
+ ### `./testing` is the contract-fakes surface
263
+
264
+ A consumer writing tests against this package needs valid instances of the domain and
265
+ provider contracts. Hand-rolling them is how a consumer's fixtures come to disagree
266
+ with the package — a hand-written `OperationResult` literal quietly stops matching what
267
+ `classifyOperationCompletion` actually returns. Every fake in `./testing` is built
268
+ through this package's own constructors and classifiers, so it cannot express a shape
269
+ the domain refuses.
270
+
271
+ ```ts
272
+ import { createProviderMatcher, createProviderRegistry } from '@ceralive/modem-control/providers';
273
+ import { fakeProviderDefinition, fakeProviderMatchRequest } from '@ceralive/modem-control/testing';
274
+
275
+ const registry = createProviderRegistry();
276
+ registry.register(fakeProviderDefinition({ observation: { registered: true } }));
277
+
278
+ const result = await createProviderMatcher(registry).match(fakeProviderMatchRequest());
279
+ ```
280
+
281
+ `./testing` is pure data and functions — no bus, no daemon, no process, no filesystem.
282
+ It is **not** the repository's `test-support/` directory, which holds the heavy
283
+ internals this package's own tests use (an MM-faithful fake D-Bus service on a private
284
+ session bus, a stateful `nmcli` harness, and the provider-matching conformance corpus).
285
+ Those are unpublished and are not a reusable surface.
286
+
287
+ ### Provider-matching conformance matrix
288
+
289
+ `src/providers/conformance-matrix.test.ts` runs 20 cases — nine fleet profiles plus
290
+ eleven ambiguity / malformed / auth-expired / lockout / unknown-firmware /
291
+ wrong-interface / wrong-transport cases — with the ModemManager, Huawei HiLink, ZTE
292
+ goform and UFI/HIMI providers **all registered at once**, asserting the exact provider,
293
+ profile, writability and evidence score per case. A companion suite asserts the exact
294
+ per-firmware HTTP transcript (method, path, form/JSON/XML body, header order, cookie,
295
+ and request count), and a third is a **software upper-bound fixture at 16 concurrently
296
+ attached modems** — a fixture result, not a hardware claim; the bench-verified fleet
297
+ size remains 8. See [`../docs/PROVIDER-MATCHING.md`](../docs/PROVIDER-MATCHING.md).
298
+
299
+ ## Build
300
+
301
+ ```sh
302
+ bun run build # tsc -> dist/ (ESM + .d.ts), then fully specify every relative specifier
303
+ bun run verify:tarball # pack and assert the published artifact's shape
304
+ bun run verify:consumers # install the tarball into standalone Node 26 + Bun projects and import every subpath
305
+ ```
306
+
307
+ `dist/` mirrors `src/` one-to-one rather than being bundled. Bundling with code
308
+ splitting produced an entry whose `export { … }` list named symbols the file never
309
+ imported — accepted by one loader, a `SyntaxError` in another. Bundling *without*
310
+ splitting instead gives each subpath its own copy of the shared modules, which silently
311
+ breaks `instanceof` across two subpaths of the same package. A 1:1 emit has exactly one
312
+ instance of every module.
313
+
314
+ Because `tsc` never rewrites a specifier and this package's sources are written for
315
+ bundler resolution, `scripts/build.ts` rewrites each emitted `./x` into `./x.js` or
316
+ `./x/index.js` — resolved against the emit itself — and fails the build if one
317
+ extensionless specifier survives.
318
+
319
+ `prepack` runs the build, so `npm pack` / `bun pm pack` can never publish a stale `dist/`.
320
+
321
+ ## Shape gate
322
+
323
+ `scripts/tarball-shape.ts` runs six rules over the extracted tarball, driven from
324
+ `bun test` (`scripts/tarball-shape.test.ts`) and from the CLI
325
+ (`scripts/assert-tarball-shape.ts`):
326
+
327
+ 1. no raw source ships — the published surface is built output;
328
+ 2. `dist/` actually contains JavaScript and declarations;
329
+ 3. every declared public entry is in the exports map **and** its files are packed;
330
+ 4. no subpath beyond the declared set — internal barrels stay internal;
331
+ 5. no export target, `main` or `types` points outside `./dist/`;
332
+ 6. the library-only proof: no `bin`, no systemd unit, no shebang, no listening-socket
333
+ construct.
334
+
335
+ The declared entries live in `scripts/entries.ts` and the test additionally spells the
336
+ seven specifiers out as a literal, so a subpath cannot be dropped without a reviewable
337
+ change to the public contract.
338
+
339
+ ## License
340
+
341
+ AGPL-3.0
@@ -0,0 +1,59 @@
1
+ import { type EpochMillis } from '../domain/index.js';
2
+ /** Named read-only fence: the exact vendor READ/TEST forms reviewed for runtime discovery. */
3
+ export declare const AT_RUNTIME_QUERY_ALLOWLIST: ReadonlySet<string>;
4
+ /** Identify plus reviewed runtime queries. Exact catalog/runtime SET commands union in per use. */
5
+ export declare const AT_BASELINE_ALLOWLIST: ReadonlySet<string>;
6
+ /** Union the baseline allowlist with a catalog entry's declared transition commands. */
7
+ export declare function computeAtAllowlist(commands: Iterable<string>): ReadonlySet<string>;
8
+ /** An AT command's response. `ok` (an `OK` terminator) is NEVER transition-success alone. */
9
+ export interface AtResponse {
10
+ readonly ok: boolean;
11
+ readonly raw: string;
12
+ }
13
+ /** The raw AT transport — a serial write, injected so tests need no hardware. */
14
+ export interface AtCommandSender {
15
+ send(command: string): Promise<AtResponse>;
16
+ }
17
+ /** One audited AT attempt. Recorded only after passing through `redact`. */
18
+ export interface AtAuditEntry {
19
+ readonly command: string;
20
+ readonly outcome: 'sent' | 'rejected' | 'timeout' | 'error';
21
+ readonly at: EpochMillis;
22
+ readonly ok?: boolean;
23
+ readonly reason?: string;
24
+ readonly context?: Record<string, unknown>;
25
+ }
26
+ /** Where audit entries go — receives a REDACTED copy of each `AtAuditEntry`. */
27
+ export interface AtAuditSink {
28
+ record(entry: unknown): void;
29
+ }
30
+ /** Thrown when a command outside the allowlist is attempted — the sender is never called. */
31
+ export declare class AtCommandNotAllowedError extends Error {
32
+ constructor(command: string);
33
+ }
34
+ /** Thrown when a command exceeds the watchdog timeout. */
35
+ export declare class AtCommandTimeoutError extends Error {
36
+ constructor(command: string, timeoutMs: number);
37
+ }
38
+ /** Construction dependencies for an `AtCommandLease`. */
39
+ export interface AtCommandLeaseDeps {
40
+ readonly sender: AtCommandSender;
41
+ readonly allowlist: ReadonlySet<string>;
42
+ readonly audit?: AtAuditSink;
43
+ readonly now?: () => EpochMillis;
44
+ readonly timeoutMs?: number;
45
+ /** Fired when a command exceeds the timeout — the transition wires force-uninhibit here. */
46
+ readonly onWatchdog?: (command: string) => void | Promise<void>;
47
+ }
48
+ /**
49
+ * A held AT-command lease. `run` enforces the allowlist, bounds the send with a
50
+ * watchdog, and audits every attempt (redacted). The allowlist is fixed at
51
+ * construction from `computeAtAllowlist(entry)`, so a lease can only ever emit the
52
+ * commands one certified SKU permits.
53
+ */
54
+ export declare class AtCommandLease {
55
+ #private;
56
+ constructor(deps: AtCommandLeaseDeps);
57
+ /** Send one AT command through the lease. `context` is redacted into the audit entry. */
58
+ run(command: string, context?: Record<string, unknown>): Promise<AtResponse>;
59
+ }
@@ -0,0 +1,118 @@
1
+ // The AT-command lease baseline — the ONLY channel raw AT commands may travel.
2
+ //
3
+ // Three non-negotiable safety properties (draft §rounds 5/6, §84 raw-AT lease):
4
+ // 1. ALLOWLIST — only `ATI` (identify) plus the exact commands a certified catalog
5
+ // entry declares may ever be sent. Anything else is rejected BEFORE the sender
6
+ // is touched. There is no escape hatch.
7
+ // 2. WATCHDOG — a command that does not return within the timeout fires the
8
+ // `onWatchdog` hook (the transition wires this to force-uninhibit) and rejects,
9
+ // so a hung AT write can never wedge the transaction forever.
10
+ // 3. AUDIT + REDACTION — every attempt is recorded through A2.2's `redact` (never a
11
+ // reimplementation), so an identifier that lands in an audit entry's context is
12
+ // stripped before it is stored.
13
+ import { epochMillis } from '../domain/index.js';
14
+ import { redact } from '../redact.js';
15
+ import { RUNTIME_COMPOSITION_QUERY_REGISTRY } from '../usb-mode/runtime-capability.js';
16
+ /** Named read-only fence: the exact vendor READ/TEST forms reviewed for runtime discovery. */
17
+ export const AT_RUNTIME_QUERY_ALLOWLIST = new Set(Object.values(RUNTIME_COMPOSITION_QUERY_REGISTRY).flatMap(({ current, enumerate }) => [
18
+ current,
19
+ enumerate,
20
+ ]));
21
+ /** Identify plus reviewed runtime queries. Exact catalog/runtime SET commands union in per use. */
22
+ export const AT_BASELINE_ALLOWLIST = new Set([
23
+ 'ATI',
24
+ ...AT_RUNTIME_QUERY_ALLOWLIST,
25
+ ]);
26
+ /** Union the baseline allowlist with a catalog entry's declared transition commands. */
27
+ export function computeAtAllowlist(commands) {
28
+ return new Set([...AT_BASELINE_ALLOWLIST, ...commands]);
29
+ }
30
+ /** Thrown when a command outside the allowlist is attempted — the sender is never called. */
31
+ export class AtCommandNotAllowedError extends Error {
32
+ constructor(command) {
33
+ super(`AT command not in allowlist: ${command}`);
34
+ this.name = 'AtCommandNotAllowedError';
35
+ Object.setPrototypeOf(this, AtCommandNotAllowedError.prototype);
36
+ }
37
+ }
38
+ /** Thrown when a command exceeds the watchdog timeout. */
39
+ export class AtCommandTimeoutError extends Error {
40
+ constructor(command, timeoutMs) {
41
+ super(`AT command timed out after ${timeoutMs}ms: ${command}`);
42
+ this.name = 'AtCommandTimeoutError';
43
+ Object.setPrototypeOf(this, AtCommandTimeoutError.prototype);
44
+ }
45
+ }
46
+ const DEFAULT_AT_TIMEOUT_MS = 10_000;
47
+ /**
48
+ * A held AT-command lease. `run` enforces the allowlist, bounds the send with a
49
+ * watchdog, and audits every attempt (redacted). The allowlist is fixed at
50
+ * construction from `computeAtAllowlist(entry)`, so a lease can only ever emit the
51
+ * commands one certified SKU permits.
52
+ */
53
+ export class AtCommandLease {
54
+ #sender;
55
+ #allowlist;
56
+ #audit;
57
+ #now;
58
+ #timeoutMs;
59
+ #onWatchdog;
60
+ constructor(deps) {
61
+ this.#sender = deps.sender;
62
+ this.#allowlist = deps.allowlist;
63
+ this.#audit = deps.audit;
64
+ this.#now = deps.now ?? (() => epochMillis(Date.now()));
65
+ this.#timeoutMs = deps.timeoutMs ?? DEFAULT_AT_TIMEOUT_MS;
66
+ this.#onWatchdog = deps.onWatchdog;
67
+ }
68
+ /** Send one AT command through the lease. `context` is redacted into the audit entry. */
69
+ async run(command, context) {
70
+ const ctx = context !== undefined ? { context } : {};
71
+ if (!this.#allowlist.has(command)) {
72
+ this.#record({
73
+ command,
74
+ outcome: 'rejected',
75
+ at: this.#now(),
76
+ reason: 'not in allowlist',
77
+ ...ctx,
78
+ });
79
+ throw new AtCommandNotAllowedError(command);
80
+ }
81
+ try {
82
+ const response = await this.#sendWithWatchdog(command);
83
+ this.#record({ command, outcome: 'sent', at: this.#now(), ok: response.ok, ...ctx });
84
+ return response;
85
+ }
86
+ catch (error) {
87
+ const timedOut = error instanceof AtCommandTimeoutError;
88
+ if (timedOut) {
89
+ await this.#onWatchdog?.(command);
90
+ }
91
+ this.#record({
92
+ command,
93
+ outcome: timedOut ? 'timeout' : 'error',
94
+ at: this.#now(),
95
+ reason: error instanceof Error ? error.message : String(error),
96
+ ...ctx,
97
+ });
98
+ throw error;
99
+ }
100
+ }
101
+ async #sendWithWatchdog(command) {
102
+ let timer;
103
+ const watchdog = new Promise((_, reject) => {
104
+ timer = setTimeout(() => reject(new AtCommandTimeoutError(command, this.#timeoutMs)), this.#timeoutMs);
105
+ });
106
+ try {
107
+ return await Promise.race([this.#sender.send(command), watchdog]);
108
+ }
109
+ finally {
110
+ if (timer !== undefined) {
111
+ clearTimeout(timer);
112
+ }
113
+ }
114
+ }
115
+ #record(entry) {
116
+ this.#audit?.record(redact(entry));
117
+ }
118
+ }
@@ -0,0 +1,46 @@
1
+ import type { EpochMillis } from '../domain/index.js';
2
+ import type { DecodedProps } from './managed-objects.js';
3
+ /** Where a batch of cell readings came from, and when it was observed. */
4
+ export interface CellInfoProvenance {
5
+ /** A human/source tag, e.g. the modem path or `'Modem.GetCellInfo'`. */
6
+ readonly source: string;
7
+ readonly observedAt: EpochMillis;
8
+ }
9
+ /** One normalized cell reading — only the pinned fields, plus provenance. */
10
+ export interface CellReading {
11
+ /** `true` when this dict is marked as the serving cell. */
12
+ readonly serving: boolean;
13
+ /** The cell identifier, when supplied (serving-cell tiebreak key). */
14
+ readonly cellId?: string;
15
+ /** Physical cell id — from the `physical-ci` key ONLY. */
16
+ readonly pci?: number;
17
+ readonly rsrp?: number;
18
+ readonly rsrq?: number;
19
+ /** NR SINR — from the `sinr` key ONLY (a `snr`-keyed dict is ignored). */
20
+ readonly sinr?: number;
21
+ /** Radio band — present ONLY when the source supplied it directly. */
22
+ readonly band?: string;
23
+ readonly source: string;
24
+ readonly observedAt: EpochMillis;
25
+ }
26
+ /** Normalize ONE cell's `a{sv}` dict into a `CellReading`, pinning the known keys. */
27
+ export declare function normalizeCellReading(cell: DecodedProps, provenance: CellInfoProvenance): CellReading;
28
+ /** Normalize a whole `GetCellInfo` reply (`aa{sv}` → cell dicts) into readings. */
29
+ export declare function normalizeCellInfo(cells: readonly DecodedProps[], provenance: CellInfoProvenance): readonly CellReading[];
30
+ /**
31
+ * Serving-cell TOTAL order — a strict, permutation-invariant ranking of readings.
32
+ * Returns < 0 when `a` outranks `b` (should sort first / is the better serving cell):
33
+ *
34
+ * 1. a `serving`-marked cell outranks a non-serving one;
35
+ * 2. then the HIGHER `rsrp` outranks the lower — a cell with NO `rsrp` sorts LAST;
36
+ * 3. ties break lexicographically by `cell-id` (a cell with none sorts last);
37
+ * 4. final deterministic tiebreaks (`pci`, then a stable serialization) guarantee
38
+ * the order is total even for otherwise-identical distinct cells, so the winner
39
+ * never depends on input order.
40
+ */
41
+ export declare function compareServing(a: CellReading, b: CellReading): number;
42
+ /**
43
+ * Select the serving cell under the total order above. Permutation-invariant: the
44
+ * same set of readings always yields the same winner regardless of their order.
45
+ */
46
+ export declare function selectServingCell(cells: readonly CellReading[]): CellReading | undefined;