@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,198 @@
1
+ // `MMModemMode` ↔ mode NAME, and the (allowed, preferred) COMBINATION, verbatim.
2
+ //
3
+ // ModemManager advertises what a radio can do as `Modem.SupportedModes`, an `a(uu)`
4
+ // of (allowed-mask, preferred-mask) pairs, and what it is doing as `Modem.CurrentModes`,
5
+ // one `(uu)`. Everything an operator may legally ask for is in that list, and this
6
+ // module's whole job is to carry it across UNCHANGED.
7
+ //
8
+ // THREE THINGS ARE ROUTINELY LOST BY A DECODER THAT MEANS WELL, and each one is a
9
+ // documented CeraLive case rather than a hypothetical:
10
+ //
11
+ // 1. `preferred: 0` (`MM_MODEM_MODE_NONE`). The bench Fibocom FM350-GL advertises
12
+ // exactly one combination, and its preferred mask is 0 — the modem allows a set
13
+ // of modes and states NO preference within it. That is a real, legal answer, and
14
+ // it is NOT the same as "prefer the highest allowed mode". Substituting a default
15
+ // shows an operator a preference the modem never expressed and cannot be returned
16
+ // to. `preferred` is therefore the name `none`, carried through to the operation
17
+ // descriptor's own allowed-value list.
18
+ // 2. A bit this build does not name. MM's mode enum grows; a mask carrying an
19
+ // unfamiliar bit is still a combination the modem advertised and will accept. It
20
+ // round-trips as `mode-bit-<n>` (the `band-<n>` discipline from `band-names.ts`),
21
+ // the combination is CLASSIFIED `unknown-combination`, and it stays offerable.
22
+ // `unknown` is never coerced to `unsupported` — that is the support-claim
23
+ // taxonomy's first rule, and it applies to a mode catalog exactly as it does to a
24
+ // capability probe.
25
+ // 3. A member that is not a `(uu)` at all. Dropping it silently shortens the catalog,
26
+ // so `decodeSupportedModeCombinations` RETAINS it in a separate `undecodable`
27
+ // list. Decoded + undecodable always equals what the provider sent.
28
+ //
29
+ // This module is pure and total: no transport, no clock, no I/O. The `SetCurrentModes`
30
+ // call itself stays where every other radio mutation lives (`backend/mm-mutations.ts`).
31
+ /**
32
+ * `MM_MODEM_MODE_NONE` (0) — the modem expresses no preference within its allowed set.
33
+ * A legal, load-bearing value; never a stand-in for "not reported".
34
+ */
35
+ export const MODE_NONE = 'none';
36
+ /** `MM_MODEM_MODE_ANY` (0xFFFFFFFF) — every mode the modem has. */
37
+ export const MODE_ANY = 'any';
38
+ const MODE_ANY_VALUE = 0xffffffff;
39
+ /** The named `MMModemMode` bits, in MM's own bit order. */
40
+ const NAMED_BITS = [
41
+ [1 << 0, 'cs'],
42
+ [1 << 1, '2g'],
43
+ [1 << 2, '3g'],
44
+ [1 << 3, '4g'],
45
+ [1 << 4, '5g'],
46
+ ];
47
+ const BIT_TO_NAME = new Map(NAMED_BITS);
48
+ const NAME_TO_BIT = new Map(NAMED_BITS.map(([bit, name]) => [name, bit]));
49
+ /** The passthrough spelling for a mode bit this build does not name. */
50
+ const PASSTHROUGH_RE = /^mode-bit-(\d+)$/;
51
+ const NAMED_MASK = NAMED_BITS.reduce((mask, [bit]) => mask | bit, 0);
52
+ /** Decode one `MMModemMode` bit. Total: an unnamed bit round-trips. */
53
+ export function modeName(bit) {
54
+ return BIT_TO_NAME.get(bit) ?? `mode-bit-${bit}`;
55
+ }
56
+ /** Encode one mode name. `undefined` for a name this build cannot place. */
57
+ export function modeValue(name) {
58
+ if (name === MODE_NONE)
59
+ return 0;
60
+ if (name === MODE_ANY)
61
+ return MODE_ANY_VALUE;
62
+ const known = NAME_TO_BIT.get(name);
63
+ if (known !== undefined)
64
+ return known;
65
+ const passthrough = PASSTHROUGH_RE.exec(name);
66
+ if (passthrough?.[1] === undefined)
67
+ return undefined;
68
+ const parsed = Number(passthrough[1]);
69
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
70
+ }
71
+ /** True when this build recognises the name (a `mode-bit-<n>` passthrough does not). */
72
+ export function isNamedMode(name) {
73
+ return name === MODE_NONE || name === MODE_ANY || NAME_TO_BIT.has(name);
74
+ }
75
+ /**
76
+ * Split a mask into its set bits, named where this build can and passed through where
77
+ * it cannot. A zero mask is the EMPTY set — the caller decides whether that reads as
78
+ * `none` (a preference) or as an anomaly (an allowed set nothing can be chosen from).
79
+ */
80
+ export function modeNames(mask) {
81
+ if (!Number.isSafeInteger(mask) || mask <= 0)
82
+ return [];
83
+ if (mask === MODE_ANY_VALUE)
84
+ return [MODE_ANY];
85
+ const names = [];
86
+ for (let bit = 1; bit <= mask && bit > 0; bit *= 2) {
87
+ if ((mask & bit) !== 0)
88
+ names.push(modeName(bit));
89
+ }
90
+ return names;
91
+ }
92
+ /** Encode a set of mode names into one mask. Fails closed on an unplaceable name. */
93
+ export function encodeModeNames(names) {
94
+ let mask = 0;
95
+ for (const name of names) {
96
+ const value = modeValue(name);
97
+ if (value === undefined)
98
+ return { ok: false, unknown: name };
99
+ mask |= value;
100
+ }
101
+ return { ok: true, mask: mask >>> 0 };
102
+ }
103
+ /** Why a combination could not be fully placed. Never a reason to hide it. */
104
+ export const MODE_COMBINATION_ANOMALIES = [
105
+ /** The allowed mask carries a bit this build does not name. */
106
+ 'unnamed-allowed-bit',
107
+ /** The preferred mask carries a bit this build does not name. */
108
+ 'unnamed-preferred-bit',
109
+ /** The preferred mode is not a member of the allowed set. */
110
+ 'preferred-not-in-allowed',
111
+ /** The preferred mask names more than one mode; MM's contract is at most one. */
112
+ 'preferred-not-singular',
113
+ /** The allowed mask is zero — nothing can be selected from this combination. */
114
+ 'empty-allowed',
115
+ ];
116
+ function isUnnamed(names) {
117
+ return names.some((name) => !isNamedMode(name));
118
+ }
119
+ /** Build a combination from two raw masks, recording every anomaly it carries. */
120
+ export function modeCombination(allowedMask, preferredMask) {
121
+ const allowed = modeNames(allowedMask);
122
+ const preferredNames = modeNames(preferredMask);
123
+ const anomalies = [];
124
+ if (allowed.length === 0)
125
+ anomalies.push('empty-allowed');
126
+ if (isUnnamed(allowed))
127
+ anomalies.push('unnamed-allowed-bit');
128
+ if (isUnnamed(preferredNames))
129
+ anomalies.push('unnamed-preferred-bit');
130
+ if (preferredNames.length > 1)
131
+ anomalies.push('preferred-not-singular');
132
+ // A zero preferred mask is `none` and is NOT an anomaly: it is MM's own way of
133
+ // saying "no preference within the allowed set", which the FM350 actually reports.
134
+ if (preferredNames.length > 0 &&
135
+ preferredMask !== MODE_ANY_VALUE &&
136
+ (preferredMask & allowedMask) !== preferredMask) {
137
+ anomalies.push('preferred-not-in-allowed');
138
+ }
139
+ return {
140
+ allowedMask,
141
+ allowed,
142
+ preferredMask,
143
+ preferred: preferredNames.length === 0 ? MODE_NONE : preferredNames.join('+'),
144
+ classification: anomalies.length === 0 ? 'named' : 'unknown-combination',
145
+ anomalies,
146
+ };
147
+ }
148
+ /** True when the modem stated no preference within this combination's allowed set. */
149
+ export function statesNoPreference(combination) {
150
+ return combination.preferredMask === 0;
151
+ }
152
+ function decodePair(value) {
153
+ if (!Array.isArray(value) || value.length !== 2)
154
+ return undefined;
155
+ const [allowed, preferred] = value;
156
+ if (typeof allowed !== 'number' || !Number.isSafeInteger(allowed) || allowed < 0)
157
+ return undefined;
158
+ if (typeof preferred !== 'number' || !Number.isSafeInteger(preferred) || preferred < 0) {
159
+ return undefined;
160
+ }
161
+ return [allowed, preferred];
162
+ }
163
+ /** Decode one `CurrentModes` `(uu)`. `undefined` only when the value is not a pair. */
164
+ export function decodeModeCombination(value) {
165
+ const pair = decodePair(value);
166
+ return pair === undefined ? undefined : modeCombination(pair[0], pair[1]);
167
+ }
168
+ /**
169
+ * Decode a `SupportedModes` `a(uu)`.
170
+ *
171
+ * Nothing is dropped: a member that is not a pair lands in `undecodable`, and a pair
172
+ * this build cannot fully name lands in `combinations` classified
173
+ * `unknown-combination`. A non-array value is an empty catalog, not an error — a modem
174
+ * that advertises no mode control is a real reading.
175
+ */
176
+ export function decodeSupportedModeCombinations(value) {
177
+ if (!Array.isArray(value))
178
+ return { combinations: [], undecodable: [] };
179
+ const combinations = [];
180
+ const undecodable = [];
181
+ for (const member of value) {
182
+ const pair = decodePair(member);
183
+ if (pair === undefined)
184
+ undecodable.push(member);
185
+ else
186
+ combinations.push(modeCombination(pair[0], pair[1]));
187
+ }
188
+ return { combinations, undecodable };
189
+ }
190
+ /** True when any advertised combination carries an anomaly this build could not place. */
191
+ export function hasUnknownCombination(set) {
192
+ return (set.undecodable.length > 0 ||
193
+ set.combinations.some((each) => each.classification === 'unknown-combination'));
194
+ }
195
+ /** Whether a mask names a mode this build recognises across every set bit. */
196
+ export function isFullyNamedMask(mask) {
197
+ return mask === MODE_ANY_VALUE || mask === 0 || (mask & ~NAMED_MASK) === 0;
198
+ }
@@ -0,0 +1,67 @@
1
+ import { type OperationDescriptor } from '../domain/index.js';
2
+ import { type ModeCombination, type ModeCombinationSet, type ModeName } from './mode-combinations.js';
3
+ /**
4
+ * A mode selection as a caller expresses it: the set to allow, and the one to prefer
5
+ * within it. `preferred: 'none'` is a first-class selection, not a missing field.
6
+ */
7
+ export type ModeSelection = {
8
+ readonly allowed: readonly ModeName[];
9
+ readonly preferred: ModeName;
10
+ };
11
+ /** `CurrentModes` as a reading — "the modem did not report it" is an answer, not a null. */
12
+ export type ModeCombinationReading = {
13
+ readonly state: 'reported';
14
+ readonly combination: ModeCombination;
15
+ } | {
16
+ readonly state: 'not-reported';
17
+ };
18
+ export type RadioModeTruth = {
19
+ readonly current: ModeCombinationReading;
20
+ readonly supported: ModeCombinationSet;
21
+ };
22
+ export declare function readRadioModeTruth(input: {
23
+ readonly currentModes: unknown;
24
+ readonly supportedModes: unknown;
25
+ }): RadioModeTruth;
26
+ /** The selection a combination represents, with `preferred` carried across verbatim. */
27
+ export declare function selectionOf(combination: ModeCombination): ModeSelection;
28
+ /** Two selections are the same when both the allowed set AND the preference agree. */
29
+ export declare function sameSelection(left: ModeSelection, right: ModeSelection): boolean;
30
+ /**
31
+ * The advertised combination a selection names, or `undefined`.
32
+ *
33
+ * `undefined` is the whole answer — never the nearest neighbour. `prefer-4g` silently
34
+ * becoming `prefer-5g` on a marginal cell is the exact substitution
35
+ * `five-g-preference.ts` refuses, and it is refused here for the same reason.
36
+ */
37
+ export declare function matchAdvertisedCombination(supported: ModeCombinationSet, selection: ModeSelection): ModeCombination | undefined;
38
+ /** The masks `SetCurrentModes` needs, or the name that could not be placed. */
39
+ export declare function encodeModeSelection(selection: ModeSelection): {
40
+ readonly ok: true;
41
+ readonly allowedMask: number;
42
+ readonly preferredMask: number;
43
+ } | {
44
+ readonly ok: false;
45
+ readonly unknown: ModeName;
46
+ };
47
+ export declare const MODE_WRITE_OPERATION_ID = "modemmanager.mode-combination";
48
+ /** Why a mode write is not on offer right now. */
49
+ export type ModeWriteRefusal = 'mode-write-unsupported' | 'no-advertised-mode-combinations';
50
+ export type ModeWriteDescriptorInput = {
51
+ readonly provider: string;
52
+ readonly profile: string;
53
+ readonly truth: RadioModeTruth;
54
+ readonly writeSupported: boolean;
55
+ };
56
+ /**
57
+ * The live mode-write descriptor.
58
+ *
59
+ * `constraints` is an `allowed-values` list built from the modem's OWN catalog, so a
60
+ * combination carrying `preferred: 'none'` reaches a consumer through the descriptor
61
+ * exactly as the modem stated it. Readback is REQUIRED: `SetCurrentModes` returning
62
+ * without an error only proves the daemon accepted the call, and an
63
+ * accepted-but-ignored mode write is indistinguishable from success at the call site.
64
+ */
65
+ export declare function buildModeWriteDescriptor(input: ModeWriteDescriptorInput): OperationDescriptor<ModeSelection, RadioModeTruth>;
66
+ /** True when the modem advertises at least one combination stating no preference. */
67
+ export declare function advertisesNoPreference(truth: RadioModeTruth): boolean;
@@ -0,0 +1,112 @@
1
+ // What the modem says it can do with its modes, and the operation descriptor built
2
+ // from exactly that — nothing added, nothing narrowed.
3
+ //
4
+ // `capability/five-g-preference.ts` maps four named POSTURES onto an (allowed,
5
+ // preferred) pair and refuses to name one the modem never advertised. This module is
6
+ // the layer underneath it: the modem's own catalog, unedited, so a posture selector
7
+ // and a raw combination selector are reading the same truth. It deliberately does not
8
+ // know what a posture is.
9
+ //
10
+ // THE OFFERED SET IS THE ADVERTISED SET. A combination classified
11
+ // `unknown-combination` — an unfamiliar mode bit, a preferred mode outside its own
12
+ // allowed set — is STILL offered, because the modem advertised it and will accept it.
13
+ // Hiding it would be coercing `unknown` into `unsupported`, which is the one thing
14
+ // `support-claim.ts` exists to stop.
15
+ import { defineOperationDescriptor } from '../domain/index.js';
16
+ import { decodeModeCombination, decodeSupportedModeCombinations, encodeModeNames, MODE_NONE, statesNoPreference, } from './mode-combinations.js';
17
+ export function readRadioModeTruth(input) {
18
+ const current = decodeModeCombination(input.currentModes);
19
+ return {
20
+ current: current === undefined
21
+ ? { state: 'not-reported' }
22
+ : { state: 'reported', combination: current },
23
+ supported: decodeSupportedModeCombinations(input.supportedModes),
24
+ };
25
+ }
26
+ /** The selection a combination represents, with `preferred` carried across verbatim. */
27
+ export function selectionOf(combination) {
28
+ return { allowed: combination.allowed, preferred: combination.preferred };
29
+ }
30
+ function sameNameSet(left, right) {
31
+ if (left.length !== right.length)
32
+ return false;
33
+ const sorted = [...right].sort();
34
+ return [...left].sort().every((name, index) => name === sorted[index]);
35
+ }
36
+ /** Two selections are the same when both the allowed set AND the preference agree. */
37
+ export function sameSelection(left, right) {
38
+ return left.preferred === right.preferred && sameNameSet(left.allowed, right.allowed);
39
+ }
40
+ /**
41
+ * The advertised combination a selection names, or `undefined`.
42
+ *
43
+ * `undefined` is the whole answer — never the nearest neighbour. `prefer-4g` silently
44
+ * becoming `prefer-5g` on a marginal cell is the exact substitution
45
+ * `five-g-preference.ts` refuses, and it is refused here for the same reason.
46
+ */
47
+ export function matchAdvertisedCombination(supported, selection) {
48
+ return supported.combinations.find((each) => sameSelection(selectionOf(each), selection));
49
+ }
50
+ /** The masks `SetCurrentModes` needs, or the name that could not be placed. */
51
+ export function encodeModeSelection(selection) {
52
+ const allowed = encodeModeNames(selection.allowed);
53
+ if (!allowed.ok)
54
+ return allowed;
55
+ const preferred = encodeModeNames(selection.preferred === MODE_NONE ? [] : [selection.preferred]);
56
+ if (!preferred.ok)
57
+ return preferred;
58
+ return { ok: true, allowedMask: allowed.mask, preferredMask: preferred.mask };
59
+ }
60
+ export const MODE_WRITE_OPERATION_ID = 'modemmanager.mode-combination';
61
+ /**
62
+ * The live mode-write descriptor.
63
+ *
64
+ * `constraints` is an `allowed-values` list built from the modem's OWN catalog, so a
65
+ * combination carrying `preferred: 'none'` reaches a consumer through the descriptor
66
+ * exactly as the modem stated it. Readback is REQUIRED: `SetCurrentModes` returning
67
+ * without an error only proves the daemon accepted the call, and an
68
+ * accepted-but-ignored mode write is indistinguishable from success at the call site.
69
+ */
70
+ export function buildModeWriteDescriptor(input) {
71
+ const values = input.truth.supported.combinations.map(selectionOf);
72
+ const refusal = !input.writeSupported
73
+ ? 'mode-write-unsupported'
74
+ : values.length === 0
75
+ ? 'no-advertised-mode-combinations'
76
+ : undefined;
77
+ return defineOperationDescriptor({
78
+ id: MODE_WRITE_OPERATION_ID,
79
+ support: {
80
+ read: { supported: true },
81
+ write: input.writeSupported
82
+ ? { supported: true }
83
+ : { supported: false, reason: 'mode-write-unsupported' },
84
+ },
85
+ authority: 'provider',
86
+ provider: input.provider,
87
+ constraints: { kind: 'allowed-values', values },
88
+ livePreconditions: [
89
+ 'modem-present',
90
+ 'runtime-interface-present',
91
+ 'mode-combination-advertised',
92
+ ],
93
+ availability: refusal === undefined ? { state: 'available' } : { state: 'refused', reason: refusal },
94
+ mutationImpact: 'disruptive',
95
+ retryClass: 'never',
96
+ readback: {
97
+ required: true,
98
+ reason: 'mode-write-readback',
99
+ matches: (selection, observed) => observed.current.state === 'reported' &&
100
+ sameSelection(selectionOf(observed.current.combination), selection),
101
+ },
102
+ rollback: { required: false },
103
+ journal: { required: true, reason: 'disruptive-radio-write' },
104
+ admission: { required: true, reason: 'provider-mutation' },
105
+ evidence: { profiles: [input.profile], firmware: [] },
106
+ confidence: 'high',
107
+ });
108
+ }
109
+ /** True when the modem advertises at least one combination stating no preference. */
110
+ export function advertisesNoPreference(truth) {
111
+ return truth.supported.combinations.some(statesNoPreference);
112
+ }
@@ -0,0 +1,15 @@
1
+ /** The marker substituted for every redacted value. */
2
+ export declare const REDACTED = "[redacted]";
3
+ /** Whole-key SMS-content test, case- and separator-insensitive. */
4
+ export declare function isSmsSensitiveKey(key: string): boolean;
5
+ /** Whole-key USSD-content test, case- and separator-insensitive. */
6
+ export declare function isUssdSensitiveKey(key: string): boolean;
7
+ /** Whole-key own-number test, case-, separator- and dot-insensitive. */
8
+ export declare function isOwnNumberSensitiveKey(key: string): boolean;
9
+ /**
10
+ * Return a deep copy of `value` with every sensitive field redacted. Plain objects
11
+ * and arrays are walked recursively; all other values (primitives, and opaque
12
+ * objects like `Date` / `Set` / `Map`) are returned unchanged. The input is never
13
+ * mutated.
14
+ */
15
+ export declare function redact(value: unknown): unknown;
package/dist/redact.js ADDED
@@ -0,0 +1,189 @@
1
+ // Redaction — strip sensitive identifiers from any value before it is logged,
2
+ // serialized into a receipt, or written to a bundle.
3
+ //
4
+ // The sensitive CLASSES (draft §Oracle #1, round-5 auth semantics): ICCID, IMSI,
5
+ // EID, SIM PIN, SIM PUK, and APN / connection passwords, PLUS the GNSS coordinate
6
+ // class below. Redaction is KEY-BASED and RECURSIVE: it walks nested objects and
7
+ // arrays and replaces the value under any sensitive key with a fixed marker, no
8
+ // matter how deep — e.g. a password at `policy.connection.auth.password`, or an
9
+ // `iccid` inside an array of SIM slots.
10
+ /** The marker substituted for every redacted value. */
11
+ export const REDACTED = '[redacted]';
12
+ // Leaf key names carrying a sensitive value, matched case-insensitively. The match
13
+ // is EXACT (or exact on the last dotted segment), so NM-style keys like
14
+ // `gsm.password` are caught while non-secret siblings like `gsm.password-flags`
15
+ // (a "0"/"4" flag, not a secret) are not.
16
+ const SENSITIVE_KEYS = new Set([
17
+ 'iccid',
18
+ 'imsi',
19
+ 'eid',
20
+ 'imei',
21
+ 'equipmentidentifier',
22
+ 'equipment-identifier',
23
+ 'equipment_identifier',
24
+ 'pin',
25
+ 'pin2',
26
+ 'newpin',
27
+ 'puk',
28
+ 'puk2',
29
+ 'password',
30
+ 'passwd',
31
+ 'subscriptionid',
32
+ ]);
33
+ // GNSS coordinate keys. A fix says where the operator physically is, so it is
34
+ // sensitive for a reason none of the keys above share, and it gets its own set so
35
+ // the privacy fence stays readable: the GPS module keeps a fix in memory for a
36
+ // live display and NEVER persists, uploads, or logs one.
37
+ //
38
+ // Scoped to a GNSS fix on purpose. `3gpp-lac-ci` (coarse cell location) is NOT
39
+ // here — it is the existing cell-info module's deliberate, separately-gated output,
40
+ // and silently blanking it would break a surface that already ships.
41
+ const LOCATION_KEYS = new Set([
42
+ 'latitude',
43
+ 'longitude',
44
+ 'altitude',
45
+ 'lat',
46
+ 'lon',
47
+ 'lng',
48
+ 'nmea',
49
+ 'nmeasentences',
50
+ 'coordinates',
51
+ ]);
52
+ /**
53
+ * SMS content is its own key class, and it may NOT be folded into
54
+ * `SENSITIVE_KEYS` above. That set matches a leaf name exactly (or the last
55
+ * dotted segment), so adding `text` / `number` / `sender` to it would redact
56
+ * every unrelated `text` and `number` in the package — a receipt's reason text,
57
+ * a slot number, a signal reading. These keys are matched WHOLE after case- AND
58
+ * separator-folding, so only a key that genuinely names message content or an
59
+ * originator is scrubbed.
60
+ *
61
+ * A message body routinely carries a one-time code (the bench SIM's inbox holds
62
+ * a literal "Tu pin es: …") and a sender number identifies the subscriber, so
63
+ * both are treated exactly like a PIN: never rendered anywhere.
64
+ *
65
+ * This mirrors CeraUI's `isSmsSensitiveKey` (`helpers/logger.ts`) key-for-key.
66
+ * It is a Rule-D MIRROR, never a shared import — the two halves are kept honest
67
+ * by their tests, not by a path.
68
+ */
69
+ const SMS_SENSITIVE_KEYS = new Set([
70
+ 'smstext',
71
+ 'smsbody',
72
+ 'smsfrom',
73
+ 'smssender',
74
+ 'smsnumber',
75
+ 'messagetext',
76
+ 'messagebody',
77
+ 'msisdn',
78
+ 'sender',
79
+ 'sms.content.text',
80
+ 'sms.content.number',
81
+ ]);
82
+ /** Whole-key SMS-content test, case- and separator-insensitive. */
83
+ export function isSmsSensitiveKey(key) {
84
+ return SMS_SENSITIVE_KEYS.has(key.toLowerCase().replace(/[_-]/g, ''));
85
+ }
86
+ /**
87
+ * USSD carrier text — its own class, for the same reason SMS is: `reply`,
88
+ * `command`, and `response` are far too common to redact by leaf name, so these
89
+ * are matched WHOLE after case- and separator-folding.
90
+ *
91
+ * BOTH DIRECTIONS are sensitive, not just the reply. A USSD dialogue is how a
92
+ * subscriber tops up a prepaid line, so the COMMAND routinely carries a voucher
93
+ * code (`*123*<16 digits>#`) and the reply carries a balance, a subscriber
94
+ * number, or a one-time code. `NetworkNotification` / `NetworkRequest` are
95
+ * ModemManager's own property names for network-initiated USSD text and are
96
+ * included so a raw property dump cannot leak what the call path masks.
97
+ *
98
+ * This is why the USSD module names its carrier-text fields `ussdCommand`,
99
+ * `ussdResponse`, and `ussdReply` rather than the shorter names that read better:
100
+ * redaction here is key-based, so the FIELD NAME is the guarantee.
101
+ */
102
+ const USSD_SENSITIVE_KEYS = new Set([
103
+ 'ussd',
104
+ 'ussdcommand',
105
+ 'ussdreply',
106
+ 'ussdresponse',
107
+ 'ussdtext',
108
+ 'networknotification',
109
+ 'networkrequest',
110
+ ]);
111
+ /** Whole-key USSD-content test, case- and separator-insensitive. */
112
+ export function isUssdSensitiveKey(key) {
113
+ return USSD_SENSITIVE_KEYS.has(key.toLowerCase().replace(/[_-]/g, ''));
114
+ }
115
+ /**
116
+ * The SIM's OWN number (MSISDN) — its own class, matched WHOLE after case-,
117
+ * separator- AND dot-folding so ModemManager's `Modem.OwnNumbers` and mmcli's
118
+ * `modem.generic.own-numbers` are both caught by one rule.
119
+ *
120
+ * It cannot join `SENSITIVE_KEYS`: that set matches a leaf name, and `number` /
121
+ * `numbers` are far too common — a slot number and a band count would both
122
+ * vanish. `msisdn` stays in the SMS set (its historical home) and is not
123
+ * duplicated here.
124
+ *
125
+ * It is DISPLAYED to the operator behind an explicit reveal. That is a
126
+ * rendering decision about a surface the subscriber already owns; it does not
127
+ * make the value loggable, so it is redacted exactly like a PIN.
128
+ */
129
+ const OWN_NUMBER_SENSITIVE_KEYS = new Set([
130
+ 'ownnumber',
131
+ 'ownnumbers',
132
+ 'phonenumber',
133
+ 'phonenumbers',
134
+ 'simnumber',
135
+ 'subscribernumber',
136
+ 'modemgenericownnumbers',
137
+ 'modemownnumbers',
138
+ ]);
139
+ /** Whole-key own-number test, case-, separator- and dot-insensitive. */
140
+ export function isOwnNumberSensitiveKey(key) {
141
+ return OWN_NUMBER_SENSITIVE_KEYS.has(key.toLowerCase().replace(/[_.-]/g, ''));
142
+ }
143
+ function isSensitiveKey(key) {
144
+ const lower = key.toLowerCase();
145
+ if (SENSITIVE_KEYS.has(lower) || LOCATION_KEYS.has(lower)) {
146
+ return true;
147
+ }
148
+ if (isSmsSensitiveKey(key) || isUssdSensitiveKey(key) || isOwnNumberSensitiveKey(key)) {
149
+ return true;
150
+ }
151
+ const dot = lower.lastIndexOf('.');
152
+ if (dot < 0) {
153
+ return false;
154
+ }
155
+ const leaf = lower.slice(dot + 1);
156
+ return SENSITIVE_KEYS.has(leaf) || LOCATION_KEYS.has(leaf);
157
+ }
158
+ function isPlainObject(value) {
159
+ if (typeof value !== 'object' || value === null) {
160
+ return false;
161
+ }
162
+ const proto = Object.getPrototypeOf(value);
163
+ return proto === Object.prototype || proto === null;
164
+ }
165
+ function redactValue(value, underSensitiveKey) {
166
+ if (underSensitiveKey) {
167
+ return REDACTED;
168
+ }
169
+ if (Array.isArray(value)) {
170
+ return value.map((item) => redactValue(item, false));
171
+ }
172
+ if (isPlainObject(value)) {
173
+ const out = {};
174
+ for (const [key, child] of Object.entries(value)) {
175
+ out[key] = redactValue(child, isSensitiveKey(key));
176
+ }
177
+ return out;
178
+ }
179
+ return value;
180
+ }
181
+ /**
182
+ * Return a deep copy of `value` with every sensitive field redacted. Plain objects
183
+ * and arrays are walked recursively; all other values (primitives, and opaque
184
+ * objects like `Date` / `Set` / `Map`) are returned unchanged. The input is never
185
+ * mutated.
186
+ */
187
+ export function redact(value) {
188
+ return redactValue(value, false);
189
+ }
@@ -0,0 +1,28 @@
1
+ import { ModemActor, type QuiesceHook } from '../backend/modem-actor.js';
2
+ import type { PhysicalModemId } from '../domain/index.js';
3
+ import type { MutationAdmissionPort, ResourceOwnershipPort, ResourceOwnershipRequest, ResourceOwnershipResult, UhubctlPort } from '../ports/index.js';
4
+ export declare class CompositionRootAlreadyExistsError extends Error {
5
+ readonly name = "CompositionRootAlreadyExistsError";
6
+ constructor();
7
+ }
8
+ export type ModemControlCompositionRootOptions = {
9
+ readonly admission: MutationAdmissionPort;
10
+ readonly ownership: ResourceOwnershipPort | undefined;
11
+ readonly quiesce?: QuiesceHook;
12
+ readonly uhubctl?: UhubctlPort;
13
+ };
14
+ export declare class ModemControlCompositionRoot {
15
+ #private;
16
+ readonly admission: MutationAdmissionPort;
17
+ readonly ownership: ResourceOwnershipPort;
18
+ readonly uhubctl: UhubctlPort | undefined;
19
+ constructor(options: ModemControlCompositionRootOptions);
20
+ actorFor(modemId: PhysicalModemId): ModemActor;
21
+ acquireOwnership(request: ResourceOwnershipRequest): Promise<ResourceOwnershipResult>;
22
+ dispose(): Promise<void>;
23
+ }
24
+ export declare class MissingResourceOwnershipPortError extends Error {
25
+ readonly name = "MissingResourceOwnershipPortError";
26
+ constructor();
27
+ }
28
+ export declare function createModemControlCompositionRoot(options: ModemControlCompositionRootOptions): ModemControlCompositionRoot;
@@ -0,0 +1,65 @@
1
+ import { ModemActor } from '../backend/modem-actor.js';
2
+ let compositionRootExists = false;
3
+ export class CompositionRootAlreadyExistsError extends Error {
4
+ name = 'CompositionRootAlreadyExistsError';
5
+ constructor() {
6
+ super('a modem-control composition root already exists in this process');
7
+ }
8
+ }
9
+ export class ModemControlCompositionRoot {
10
+ admission;
11
+ ownership;
12
+ uhubctl;
13
+ #quiesce;
14
+ #actors = new Map();
15
+ #ownershipLeases = [];
16
+ #disposed = false;
17
+ constructor(options) {
18
+ if (compositionRootExists)
19
+ throw new CompositionRootAlreadyExistsError();
20
+ if (options.ownership === undefined)
21
+ throw new MissingResourceOwnershipPortError();
22
+ compositionRootExists = true;
23
+ this.admission = options.admission;
24
+ this.ownership = options.ownership;
25
+ this.#quiesce = options.quiesce;
26
+ this.uhubctl = options.uhubctl;
27
+ }
28
+ actorFor(modemId) {
29
+ const existing = this.#actors.get(modemId);
30
+ if (existing !== undefined)
31
+ return existing;
32
+ const actor = new ModemActor(this.#quiesce);
33
+ this.#actors.set(modemId, actor);
34
+ return actor;
35
+ }
36
+ async acquireOwnership(request) {
37
+ const result = await this.ownership.acquire(request);
38
+ if (result.status === 'acquired')
39
+ this.#ownershipLeases.push(result.lease);
40
+ return result;
41
+ }
42
+ async dispose() {
43
+ if (this.#disposed)
44
+ return;
45
+ this.#disposed = true;
46
+ try {
47
+ for (const lease of this.#ownershipLeases.splice(0).reverse()) {
48
+ await lease.release();
49
+ }
50
+ }
51
+ finally {
52
+ this.#actors.clear();
53
+ compositionRootExists = false;
54
+ }
55
+ }
56
+ }
57
+ export class MissingResourceOwnershipPortError extends Error {
58
+ name = 'MissingResourceOwnershipPortError';
59
+ constructor() {
60
+ super('a ResourceOwnershipPort is required; no pass-through default exists');
61
+ }
62
+ }
63
+ export function createModemControlCompositionRoot(options) {
64
+ return new ModemControlCompositionRoot(options);
65
+ }