@ceralive/modem-control 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +196 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +19 -0
  146. package/dist/index.js +26 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +47 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
@@ -1,152 +0,0 @@
1
- // Idempotent re-apply — a minimal in-memory stack executes the planner's
2
- // port-tagged ops; re-applying the same policy performs ZERO new side effects and
3
- // returns the same receipts. NOT the A2.3 fake D-Bus harness — just enough to prove
4
- // the pure planner converges to a fixpoint.
5
-
6
- import { expect, test } from 'bun:test';
7
- import {
8
- type DesiredCellularPolicy,
9
- defaultCellularPolicy,
10
- imeiEquipmentId,
11
- type PolicyBindingKey,
12
- policyBindingKey,
13
- runtimePath,
14
- } from '../domain';
15
- import { connectionId, deviceIfname } from './network-manager';
16
- import type { PortTaggedOp } from './ops';
17
- import type { Receipt } from './receipts';
18
- import { type AppliedCellularState, type ModemCapabilities, planReconcile } from './reconcile';
19
-
20
- const BOUND_TO: PolicyBindingKey = policyBindingKey({
21
- equipmentId: imeiEquipmentId('490154203237518'),
22
- runtimePath: runtimePath('/org/freedesktop/ModemManager1/Modem/0'),
23
- });
24
-
25
- const FIVE_G_DUAL_SLOT: ModemCapabilities = {
26
- supportedRats: new Set(['5gnr', 'lte', 'umts', 'gsm']),
27
- simSlotCount: 2,
28
- supportsAutoApn: true,
29
- };
30
-
31
- const FOUR_G_ONLY: ModemCapabilities = {
32
- supportedRats: new Set(['lte', 'umts', 'gsm']),
33
- simSlotCount: 2,
34
- supportsAutoApn: true,
35
- };
36
-
37
- class InMemoryStack {
38
- private state: AppliedCellularState;
39
- readonly executed: PortTaggedOp[] = [];
40
-
41
- constructor(
42
- initial: AppliedCellularState,
43
- private readonly capabilities: ModemCapabilities,
44
- ) {
45
- this.state = initial;
46
- }
47
-
48
- private execute(op: PortTaggedOp): void {
49
- this.executed.push(op);
50
- if (op.port === 'mm') {
51
- this.state =
52
- op.op.kind === 'setRadioModes'
53
- ? { ...this.state, appliedRadioPreference: op.op.preference.preferenceOrdered }
54
- : { ...this.state, activePrimarySlot: op.op.slotIndex };
55
- return;
56
- }
57
- switch (op.op.kind) {
58
- case 'createGsmProfile':
59
- this.state = {
60
- ...this.state,
61
- hasProfile: true,
62
- connectionId: connectionId('conn-generated'),
63
- appliedApn: op.op.profile.autoConfig ? 'auto' : op.op.profile.apn,
64
- appliedRoaming: !op.op.profile.homeOnly,
65
- };
66
- break;
67
- case 'updateGsmProfile': {
68
- const { patch } = op.op;
69
- const nextApn = patch.autoConfig ? 'auto' : patch.apn;
70
- this.state = {
71
- ...this.state,
72
- ...(nextApn !== undefined ? { appliedApn: nextApn } : {}),
73
- ...(patch.homeOnly !== undefined ? { appliedRoaming: !patch.homeOnly } : {}),
74
- };
75
- break;
76
- }
77
- case 'activate':
78
- this.state = { ...this.state, nmActivation: 'activated' };
79
- break;
80
- case 'deactivate':
81
- this.state = { ...this.state, nmActivation: 'disconnected' };
82
- break;
83
- }
84
- }
85
-
86
- apply(desired: DesiredCellularPolicy): readonly Receipt[] {
87
- for (let iteration = 0; iteration < 20; iteration += 1) {
88
- const plan = planReconcile(this.state, desired, this.capabilities);
89
- if (plan.ops.length === 0) {
90
- return plan.receipts;
91
- }
92
- for (const op of plan.ops) {
93
- this.execute(op);
94
- }
95
- }
96
- throw new Error('reconcile did not converge within the iteration budget');
97
- }
98
- }
99
-
100
- function baseState(): AppliedCellularState {
101
- return { nmActivation: 'disconnected', hasProfile: false, deviceIfname: deviceIfname('wwan0') };
102
- }
103
-
104
- test('a fresh policy converges, then re-apply does nothing and returns the same receipts', () => {
105
- const stack = new InMemoryStack(baseState(), FIVE_G_DUAL_SLOT);
106
- const policy = defaultCellularPolicy(BOUND_TO);
107
-
108
- const first = stack.apply(policy);
109
- const sideEffectsAfterFirst = stack.executed.length;
110
- expect(sideEffectsAfterFirst).toBeGreaterThan(0);
111
-
112
- const second = stack.apply(policy);
113
- expect(stack.executed.length).toBe(sideEffectsAfterFirst);
114
- expect(second).toEqual(first);
115
-
116
- for (const entry of second) {
117
- expect(entry.status).toBe('applied');
118
- }
119
- });
120
-
121
- test('each op is executed exactly once across convergence (no double side-effects)', () => {
122
- const stack = new InMemoryStack(baseState(), FIVE_G_DUAL_SLOT);
123
- const policy = defaultCellularPolicy(BOUND_TO);
124
- stack.apply(policy);
125
-
126
- const kinds = stack.executed.map((op) => op.op.kind).sort();
127
- expect(kinds).toEqual(['activate', 'createGsmProfile', 'setRadioModes']);
128
- });
129
-
130
- test('an unsupported dimension is never applied as an op, on the first apply or any re-apply', () => {
131
- const stack = new InMemoryStack(baseState(), FOUR_G_ONLY);
132
- const policy = defaultCellularPolicy(BOUND_TO);
133
-
134
- const first = stack.apply(policy);
135
- stack.apply(policy);
136
-
137
- expect(stack.executed.some((op) => op.port === 'mm' && op.op.kind === 'setRadioModes')).toBe(
138
- false,
139
- );
140
- expect(first.find((entry) => entry.dimension === 'radio')?.status).toBe('unsupported');
141
- });
142
-
143
- test('deactivation is idempotent — disabling an already-inactive connection is a no-op', () => {
144
- const stack = new InMemoryStack(baseState(), FIVE_G_DUAL_SLOT);
145
- const policy: DesiredCellularPolicy = { ...defaultCellularPolicy(BOUND_TO), enabled: false };
146
-
147
- stack.apply(policy);
148
- const sideEffects = stack.executed.length;
149
- stack.apply(policy);
150
- expect(stack.executed.length).toBe(sideEffects);
151
- expect(stack.executed.some((op) => op.port === 'nm' && op.op.kind === 'activate')).toBe(false);
152
- });
@@ -1,338 +0,0 @@
1
- // The desired-state planner — pure. Given the currently-applied state, a desired
2
- // policy, and the modem's capabilities, it produces the port-tagged ops needed to
3
- // converge PLUS an honest receipt per policy dimension. It performs no I/O and no
4
- // side effects: the same inputs always yield the same ops and receipts, which is
5
- // what makes re-applying a policy idempotent (see reconcile.test.ts).
6
-
7
- import type { DesiredCellularPolicy, NmActivation, RadioAccessTechnology } from '../domain';
8
- import type { ConnectionId, DeviceIfname, GsmProfileInput } from './network-manager';
9
- import { mmOp, nmOp, type PortTaggedOp } from './ops';
10
- import { type Receipt, receipt } from './receipts';
11
-
12
- /** What the modem / stack can actually do — the capability set the planner honors. */
13
- export interface ModemCapabilities {
14
- readonly supportedRats: ReadonlySet<RadioAccessTechnology>;
15
- readonly simSlotCount: number;
16
- readonly supportsAutoApn: boolean;
17
- }
18
-
19
- /**
20
- * The currently-applied cellular configuration, as the planner sees it — the "is"
21
- * state, distinct from the observational snapshot: it tracks what has actually been
22
- * written (profile, radio preference, primary slot) plus the live NM activation.
23
- */
24
- export interface AppliedCellularState {
25
- readonly nmActivation: NmActivation;
26
- readonly hasProfile: boolean;
27
- readonly connectionId?: ConnectionId;
28
- readonly deviceIfname?: DeviceIfname;
29
- readonly appliedApn?: 'auto' | string;
30
- readonly appliedRoaming?: boolean;
31
- readonly appliedRadioPreference?: readonly RadioAccessTechnology[];
32
- readonly activePrimarySlot?: number;
33
- readonly activationFailureReason?: string;
34
- }
35
-
36
- /** A reconcile plan: the ops to run and one receipt per policy dimension. */
37
- export interface Plan {
38
- readonly ops: readonly PortTaggedOp[];
39
- readonly receipts: readonly Receipt[];
40
- }
41
-
42
- interface DimensionResult {
43
- readonly receipt: Receipt;
44
- readonly op?: PortTaggedOp;
45
- }
46
-
47
- interface ProfilePlan {
48
- readonly connection: Receipt;
49
- readonly roaming: Receipt;
50
- readonly op?: PortTaggedOp;
51
- }
52
-
53
- /** Reconcile a desired policy against the applied state into ops + receipts. */
54
- export function planReconcile(
55
- current: AppliedCellularState,
56
- desired: DesiredCellularPolicy,
57
- capabilities: ModemCapabilities,
58
- ): Plan {
59
- const profile = planNmProfile(current, desired, capabilities);
60
- const results: readonly DimensionResult[] = [
61
- planRadio(current, desired, capabilities),
62
- planSimSlot(current, desired, capabilities),
63
- planEnabled(current, desired),
64
- { receipt: planRecovery(desired) },
65
- { receipt: planUsage(desired) },
66
- ];
67
-
68
- const ops: PortTaggedOp[] = [];
69
- const receipts: Receipt[] = [profile.connection, profile.roaming];
70
- if (profile.op !== undefined) {
71
- ops.push(profile.op);
72
- }
73
- for (const result of results) {
74
- receipts.push(result.receipt);
75
- if (result.op !== undefined) {
76
- ops.push(result.op);
77
- }
78
- }
79
- return { ops, receipts };
80
- }
81
-
82
- function profileFromPolicy(desired: DesiredCellularPolicy): GsmProfileInput {
83
- const auto = desired.connection.apn === 'auto';
84
- const base: GsmProfileInput = {
85
- connectionName: 'ceralive-cellular',
86
- apn: auto ? '' : desired.connection.apn,
87
- homeOnly: !desired.roaming,
88
- autoConfig: auto,
89
- };
90
- // SENSITIVE creds only when explicitly provided AND not in auto-config mode.
91
- if (!auto && desired.connection.auth !== undefined) {
92
- const { username, password } = desired.connection.auth;
93
- return {
94
- ...base,
95
- ...(username !== undefined ? { username } : {}),
96
- ...(password !== undefined ? { password } : {}),
97
- };
98
- }
99
- return base;
100
- }
101
-
102
- // NM owns APN / auth / roaming / autoconnect: one profile write converges all of
103
- // them, so connection + roaming share a single op and each get their own receipt.
104
- function planNmProfile(
105
- current: AppliedCellularState,
106
- desired: DesiredCellularPolicy,
107
- capabilities: ModemCapabilities,
108
- ): ProfilePlan {
109
- const auto = desired.connection.apn === 'auto';
110
- if (auto && !capabilities.supportsAutoApn) {
111
- return {
112
- connection: receipt(
113
- 'connection',
114
- 'unsupported',
115
- 'Auto-APN is not available on this NetworkManager / modem',
116
- ),
117
- roaming: receipt(
118
- 'roaming',
119
- 'unsupported',
120
- 'roaming cannot be applied without a connection profile',
121
- ),
122
- };
123
- }
124
- const needsWrite =
125
- !current.hasProfile ||
126
- current.appliedApn !== desired.connection.apn ||
127
- current.appliedRoaming !== desired.roaming;
128
- if (!needsWrite) {
129
- return {
130
- connection: receipt(
131
- 'connection',
132
- 'applied',
133
- `connection APN '${desired.connection.apn}' already configured`,
134
- ),
135
- roaming: receipt(
136
- 'roaming',
137
- 'applied',
138
- `roaming already ${desired.roaming ? 'enabled' : 'disabled'}`,
139
- ),
140
- };
141
- }
142
- const op =
143
- current.hasProfile && current.connectionId !== undefined
144
- ? nmOp({
145
- kind: 'updateGsmProfile',
146
- connectionId: current.connectionId,
147
- patch: profileFromPolicy(desired),
148
- })
149
- : nmOp({ kind: 'createGsmProfile', profile: profileFromPolicy(desired) });
150
- const verb = current.hasProfile ? 'updating' : 'creating';
151
- return {
152
- connection: receipt(
153
- 'connection',
154
- 'pending',
155
- `${verb} connection profile for APN '${desired.connection.apn}'`,
156
- ),
157
- roaming: receipt(
158
- 'roaming',
159
- 'pending',
160
- `roaming ${desired.roaming ? 'enabled' : 'disabled'} via connection profile`,
161
- ),
162
- op,
163
- };
164
- }
165
-
166
- function radioMatches(
167
- applied: readonly RadioAccessTechnology[] | undefined,
168
- desired: readonly RadioAccessTechnology[],
169
- ): boolean {
170
- if (applied === undefined || applied.length !== desired.length) {
171
- return false;
172
- }
173
- return applied.every((rat, index) => rat === desired[index]);
174
- }
175
-
176
- function planRadio(
177
- current: AppliedCellularState,
178
- desired: DesiredCellularPolicy,
179
- capabilities: ModemCapabilities,
180
- ): DimensionResult {
181
- const preference = desired.radio.preferenceOrdered;
182
- const top = preference[0];
183
- if (top === undefined) {
184
- return {
185
- receipt: receipt(
186
- 'radio',
187
- 'failed',
188
- 'radio preference must list at least one access technology',
189
- ),
190
- };
191
- }
192
- if (!capabilities.supportedRats.has(top)) {
193
- const supported = [...capabilities.supportedRats].join(', ') || 'none';
194
- return {
195
- receipt: receipt(
196
- 'radio',
197
- 'unsupported',
198
- `preferred radio access technology '${top}' is not supported by this modem (supports ${supported})`,
199
- ),
200
- };
201
- }
202
- if (radioMatches(current.appliedRadioPreference, preference)) {
203
- return {
204
- receipt: receipt('radio', 'applied', `radio preference already ${preference.join(' > ')}`),
205
- };
206
- }
207
- return {
208
- receipt: receipt('radio', 'pending', `setting radio preference to ${preference.join(' > ')}`),
209
- op: mmOp({ kind: 'setRadioModes', preference: desired.radio }),
210
- };
211
- }
212
-
213
- function planSimSlot(
214
- current: AppliedCellularState,
215
- desired: DesiredCellularPolicy,
216
- capabilities: ModemCapabilities,
217
- ): DimensionResult {
218
- if (desired.simSlot === undefined) {
219
- return { receipt: receipt('simSlot', 'applied', 'no primary SIM slot preference set') };
220
- }
221
- if (capabilities.simSlotCount <= 1) {
222
- return {
223
- receipt: receipt(
224
- 'simSlot',
225
- 'unsupported',
226
- `primary SIM slot selection requires a multi-slot modem (this modem has ${capabilities.simSlotCount})`,
227
- ),
228
- };
229
- }
230
- if (desired.simSlot < 1 || desired.simSlot > capabilities.simSlotCount) {
231
- return {
232
- receipt: receipt(
233
- 'simSlot',
234
- 'failed',
235
- `SIM slot ${desired.simSlot} is out of range (1..${capabilities.simSlotCount})`,
236
- ),
237
- };
238
- }
239
- if (current.activePrimarySlot === desired.simSlot) {
240
- return {
241
- receipt: receipt('simSlot', 'applied', `SIM slot ${desired.simSlot} already primary`),
242
- };
243
- }
244
- return {
245
- receipt: receipt('simSlot', 'pending', `switching primary SIM slot to ${desired.simSlot}`),
246
- op: mmOp({ kind: 'setPrimarySimSlot', slotIndex: desired.simSlot }),
247
- };
248
- }
249
-
250
- function planEnabled(
251
- current: AppliedCellularState,
252
- desired: DesiredCellularPolicy,
253
- ): DimensionResult {
254
- const nm = current.nmActivation;
255
- const addressable = current.connectionId !== undefined && current.deviceIfname !== undefined;
256
- if (desired.enabled) {
257
- switch (nm) {
258
- case 'activated':
259
- return { receipt: receipt('enabled', 'applied', 'connection is active') };
260
- case 'activating':
261
- return { receipt: receipt('enabled', 'pending', 'connection activation in progress') };
262
- case 'failed':
263
- return {
264
- receipt: receipt(
265
- 'enabled',
266
- 'failed',
267
- `connection activation failed${current.activationFailureReason ? `: ${current.activationFailureReason}` : ''}`,
268
- ),
269
- };
270
- case 'unmanaged':
271
- return {
272
- receipt: receipt('enabled', 'unsupported', 'device is not managed by NetworkManager'),
273
- };
274
- default:
275
- if (
276
- !addressable ||
277
- current.connectionId === undefined ||
278
- current.deviceIfname === undefined
279
- ) {
280
- return {
281
- receipt: receipt(
282
- 'enabled',
283
- 'pending',
284
- 'awaiting connection profile and data interface before activation',
285
- ),
286
- };
287
- }
288
- return {
289
- receipt: receipt('enabled', 'pending', 'activating connection'),
290
- op: nmOp({
291
- kind: 'activate',
292
- connectionId: current.connectionId,
293
- deviceIfname: current.deviceIfname,
294
- }),
295
- };
296
- }
297
- }
298
- if (
299
- (nm === 'activated' || nm === 'activating') &&
300
- current.connectionId !== undefined &&
301
- current.deviceIfname !== undefined
302
- ) {
303
- return {
304
- receipt: receipt('enabled', 'pending', 'deactivating connection'),
305
- op: nmOp({
306
- kind: 'deactivate',
307
- connectionId: current.connectionId,
308
- deviceIfname: current.deviceIfname,
309
- }),
310
- };
311
- }
312
- return { receipt: receipt('enabled', 'applied', 'connection is inactive') };
313
- }
314
-
315
- // Recovery + usage are LOCAL-CONTROLLER owned (README ownership table): they emit no
316
- // MM / NM op, only a receipt recording that the local policy was accepted.
317
- function planRecovery(desired: DesiredCellularPolicy): Receipt {
318
- return receipt(
319
- 'recovery',
320
- 'applied',
321
- desired.recovery.enabled ? 'recovery policy recorded (enabled)' : 'recovery disabled (default)',
322
- );
323
- }
324
-
325
- function planUsage(desired: DesiredCellularPolicy): Receipt {
326
- const parts: string[] = [];
327
- if (desired.usage.cycleDay !== undefined) {
328
- parts.push(`cycle day ${desired.usage.cycleDay}`);
329
- }
330
- if (desired.usage.thresholdBytes !== undefined) {
331
- parts.push(`threshold ${desired.usage.thresholdBytes} bytes`);
332
- }
333
- return receipt(
334
- 'usage',
335
- 'applied',
336
- parts.length > 0 ? `usage policy recorded (${parts.join(', ')})` : 'no usage policy set',
337
- );
338
- }
@@ -1,29 +0,0 @@
1
- // The router port — for devices MM cannot control (HiLink, router-ethernet class).
2
- //
3
- // This port is DELIBERATELY tiny: presence and advisory health ONLY. Health never
4
- // drives activation or recovery — a degraded router stays in the routing set (it is
5
- // never evicted on health alone). A router device is an Ethernet uplink we OBSERVE,
6
- // not a modem we configure; there is no bearer / APN / radio verb here.
7
-
8
- import type { EpochMillis } from '../domain';
9
- import type { DeviceIfname } from './network-manager';
10
-
11
- /** Whether a router-class device is present on an interface. */
12
- export type RouterPresence = 'present' | 'absent';
13
-
14
- /**
15
- * Advisory health of a router-class uplink. Every field is informational — a
16
- * degraded router is reported, never removed from the routing set on health alone.
17
- */
18
- export interface RouterHealth {
19
- readonly presence: RouterPresence;
20
- readonly gatewayReachable: boolean;
21
- readonly egressHealthy: boolean;
22
- readonly observedAt: EpochMillis;
23
- }
24
-
25
- /** The router port — presence and advisory health only. No mutation verbs. */
26
- export interface RouterPort {
27
- probePresence(ifname: DeviceIfname): Promise<RouterPresence>;
28
- checkHealth(ifname: DeviceIfname): Promise<RouterHealth>;
29
- }
@@ -1,82 +0,0 @@
1
- // Redaction classes — ICCID, IMSI, EID, PIN, PUK, and APN/connection passwords are
2
- // stripped, including deeply nested occurrences and inside arrays; non-secret
3
- // siblings survive and the input is never mutated.
4
-
5
- import { expect, test } from 'bun:test';
6
- import { REDACTED, redact } from './redact';
7
-
8
- test('redacts every sensitive class at the top level, keeping non-secret siblings', () => {
9
- const input = {
10
- iccid: '8988303000000000000',
11
- imsi: '310150123456789',
12
- eid: '89049032000000000000000000000000',
13
- pin: '1234',
14
- puk: '12345678',
15
- password: 's3cret',
16
- apn: 'internet',
17
- username: 'operator-user',
18
- };
19
- const out = redact(input) as Record<string, unknown>;
20
-
21
- expect(out.iccid).toBe(REDACTED);
22
- expect(out.imsi).toBe(REDACTED);
23
- expect(out.eid).toBe(REDACTED);
24
- expect(out.pin).toBe(REDACTED);
25
- expect(out.puk).toBe(REDACTED);
26
- expect(out.password).toBe(REDACTED);
27
- expect(out.apn).toBe('internet');
28
- expect(out.username).toBe('operator-user');
29
- });
30
-
31
- test('redacts an APN password nested three levels deep', () => {
32
- const policy = { connection: { auth: { username: 'u', password: 'hunter2' } } };
33
- const out = redact(policy) as { connection: { auth: { username: string; password: string } } };
34
- expect(out.connection.auth.password).toBe(REDACTED);
35
- expect(out.connection.auth.username).toBe('u');
36
- });
37
-
38
- test('redacts ICCID inside an array of SIM slots', () => {
39
- const input = {
40
- simSlots: [
41
- { index: 1, iccid: '8988000000000000001' },
42
- { index: 2, iccid: '8988000000000000002' },
43
- ],
44
- };
45
- const out = redact(input) as { simSlots: Array<{ index: number; iccid: string }> };
46
- expect(out.simSlots[0]?.iccid).toBe(REDACTED);
47
- expect(out.simSlots[1]?.iccid).toBe(REDACTED);
48
- expect(out.simSlots[0]?.index).toBe(1);
49
- expect(out.simSlots[1]?.index).toBe(2);
50
- });
51
-
52
- test('redacts NM-style gsm.password but keeps the gsm.password-flags flag', () => {
53
- const input = { 'gsm.password': 'secret', 'gsm.password-flags': '0', 'gsm.apn': 'internet' };
54
- const out = redact(input) as Record<string, unknown>;
55
- expect(out['gsm.password']).toBe(REDACTED);
56
- expect(out['gsm.password-flags']).toBe('0');
57
- expect(out['gsm.apn']).toBe('internet');
58
- });
59
-
60
- test('redacts subscriptionId, newPin, and puk2 variants', () => {
61
- const input = { subscriptionId: '8988303000000000000', newPin: '4321', puk2: '87654321' };
62
- const out = redact(input) as Record<string, unknown>;
63
- expect(out.subscriptionId).toBe(REDACTED);
64
- expect(out.newPin).toBe(REDACTED);
65
- expect(out.puk2).toBe(REDACTED);
66
- });
67
-
68
- test('does not mutate the input', () => {
69
- const input = { pin: '1234', nested: { iccid: '5678' } };
70
- const before = JSON.stringify(input);
71
- redact(input);
72
- expect(JSON.stringify(input)).toBe(before);
73
- });
74
-
75
- test('passes primitives and empty containers through unchanged', () => {
76
- expect(redact('plain')).toBe('plain');
77
- expect(redact(42)).toBe(42);
78
- expect(redact(null)).toBe(null);
79
- expect(redact(undefined)).toBe(undefined);
80
- expect(redact({})).toEqual({});
81
- expect(redact([])).toEqual([]);
82
- });
package/src/redact.ts DELETED
@@ -1,73 +0,0 @@
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. Redaction is KEY-BASED and
6
- // RECURSIVE: it walks nested objects and arrays and replaces the value under any
7
- // sensitive key with a fixed marker, no matter how deep — e.g. a password at
8
- // `policy.connection.auth.password`, or an `iccid` inside an array of SIM slots.
9
-
10
- /** The marker substituted for every redacted value. */
11
- export const REDACTED = '[redacted]';
12
-
13
- // Leaf key names carrying a sensitive value, matched case-insensitively. The match
14
- // is EXACT (or exact on the last dotted segment), so NM-style keys like
15
- // `gsm.password` are caught while non-secret siblings like `gsm.password-flags`
16
- // (a "0"/"4" flag, not a secret) are not.
17
- const SENSITIVE_KEYS: ReadonlySet<string> = new Set<string>([
18
- 'iccid',
19
- 'imsi',
20
- 'eid',
21
- 'pin',
22
- 'pin2',
23
- 'newpin',
24
- 'puk',
25
- 'puk2',
26
- 'password',
27
- 'passwd',
28
- 'subscriptionid',
29
- ]);
30
-
31
- function isSensitiveKey(key: string): boolean {
32
- const lower = key.toLowerCase();
33
- if (SENSITIVE_KEYS.has(lower)) {
34
- return true;
35
- }
36
- const dot = lower.lastIndexOf('.');
37
- return dot >= 0 && SENSITIVE_KEYS.has(lower.slice(dot + 1));
38
- }
39
-
40
- function isPlainObject(value: unknown): value is Record<string, unknown> {
41
- if (typeof value !== 'object' || value === null) {
42
- return false;
43
- }
44
- const proto = Object.getPrototypeOf(value) as unknown;
45
- return proto === Object.prototype || proto === null;
46
- }
47
-
48
- function redactValue(value: unknown, underSensitiveKey: boolean): unknown {
49
- if (underSensitiveKey) {
50
- return REDACTED;
51
- }
52
- if (Array.isArray(value)) {
53
- return value.map((item) => redactValue(item, false));
54
- }
55
- if (isPlainObject(value)) {
56
- const out: Record<string, unknown> = {};
57
- for (const [key, child] of Object.entries(value)) {
58
- out[key] = redactValue(child, isSensitiveKey(key));
59
- }
60
- return out;
61
- }
62
- return value;
63
- }
64
-
65
- /**
66
- * Return a deep copy of `value` with every sensitive field redacted. Plain objects
67
- * and arrays are walked recursively; all other values (primitives, and opaque
68
- * objects like `Date` / `Set` / `Map`) are returned unchanged. The input is never
69
- * mutated.
70
- */
71
- export function redact(value: unknown): unknown {
72
- return redactValue(value, false);
73
- }