@ceralive/modem-control 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +196 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +19 -0
  146. package/dist/index.js +26 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +47 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
@@ -0,0 +1,146 @@
1
+ // Durable persistence for the operator's FCC-auto-unlock POLICY.
2
+ //
3
+ // WHY THE FILE IS THE RECORD AND THE SYMLINK IS NOT. ModemManager's opt-in
4
+ // mechanism is a symlink in `/etc/ModemManager/fcc-unlock.d/<vid>:<pid>`. `/etc` is
5
+ // on the rootfs, and the rootfs is exactly what a RAUC A/B slot swap REPLACES
6
+ // (`image-building-pipeline/docs/partition-contract.md`), so that symlink survives a
7
+ // reboot and does NOT survive an OTA. `/data` is the only update-surviving store, so
8
+ // the durable record lives there and the symlink is a derived artifact re-created
9
+ // from it on every boot by `ceralive-fcc-reconcile`.
10
+ //
11
+ // The two hard guarantees are the SAME ones `backend/usage/policy-store.ts` makes,
12
+ // and deliberately implemented the same way so the pair can be read side by side:
13
+ // - MODE 0600 via temp → chmod → atomic rename, regardless of umask.
14
+ // - FAIL-SAFE ON CORRUPTION: an unparseable/incompatible file logs METADATA ONLY
15
+ // (byte length + a classification reason, never the content) and is treated as
16
+ // EMPTY. Note the divergence from the usage store, and it is load-bearing: the
17
+ // usage store REWRITES a fresh file, while this one leaves the damaged bytes on
18
+ // disk for an operator to look at and simply refuses to act on them. Enabling a
19
+ // regulatory-unlock procedure is not something to infer from a file we could not
20
+ // read, and silently replacing the evidence would make the next person's
21
+ // diagnosis impossible.
22
+ //
23
+ // A policy row carries ONLY a `<vid>:<pid>` MODEL identifier and a boolean. There is
24
+ // no per-unit identity here by construction — no serial, no ICCID, no ID_PATH —
25
+ // because ModemManager's mechanism is model-wide and a per-unit key would be a
26
+ // promise the dispatcher cannot keep.
27
+ import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
28
+ import { dirname } from 'node:path';
29
+ import { isFccUnlockKey } from './coverage.js';
30
+ /** The current on-disk schema version. Bump when the persisted shape changes. */
31
+ export const FCC_UNLOCK_SCHEMA_VERSION = 1;
32
+ /** The pinned location of the policy of record. `/data` survives a slot swap. */
33
+ export const FCC_UNLOCK_POLICY_PATH = '/data/ceralive/fcc-unlock-policy.json';
34
+ function defaultLogger(event) {
35
+ console.warn(`[fcc-unlock-policy] ${event.kind}: bytes=${event.bytes} reason=${event.reason}`);
36
+ }
37
+ function emptyState(nowMs) {
38
+ return { schemaVersion: FCC_UNLOCK_SCHEMA_VERSION, savedAtMs: nowMs, unlock: {} };
39
+ }
40
+ /** A schema violation naming only the offending FIELD (never file content). */
41
+ class FccPolicySchemaError extends Error {
42
+ constructor(field) {
43
+ super(`schema-mismatch: ${field}`);
44
+ }
45
+ }
46
+ /**
47
+ * Parse + validate the document. Throws `FccPolicySchemaError` (metadata-only).
48
+ *
49
+ * WHOLE-DOCUMENT rejection, never per-key skipping: a half-applied policy is a
50
+ * policy nobody wrote, and the shell reconciler makes the same all-or-nothing
51
+ * judgement, so the two halves agree on what a damaged file means.
52
+ */
53
+ function validate(raw) {
54
+ if (typeof raw !== 'object' || raw === null) {
55
+ throw new FccPolicySchemaError('document');
56
+ }
57
+ const doc = raw;
58
+ if (doc.schemaVersion !== FCC_UNLOCK_SCHEMA_VERSION) {
59
+ throw new FccPolicySchemaError('schemaVersion');
60
+ }
61
+ if (typeof doc.savedAtMs !== 'number' || !Number.isFinite(doc.savedAtMs)) {
62
+ throw new FccPolicySchemaError('savedAtMs');
63
+ }
64
+ if (typeof doc.unlock !== 'object' || doc.unlock === null || Array.isArray(doc.unlock)) {
65
+ throw new FccPolicySchemaError('unlock');
66
+ }
67
+ const unlock = {};
68
+ for (const [key, value] of Object.entries(doc.unlock)) {
69
+ if (!isFccUnlockKey(key)) {
70
+ throw new FccPolicySchemaError('unlock.key');
71
+ }
72
+ if (typeof value !== 'boolean') {
73
+ throw new FccPolicySchemaError('unlock.value');
74
+ }
75
+ unlock[key] = value;
76
+ }
77
+ return {
78
+ schemaVersion: FCC_UNLOCK_SCHEMA_VERSION,
79
+ savedAtMs: doc.savedAtMs,
80
+ unlock,
81
+ };
82
+ }
83
+ /** Classify a load failure into a metadata-only reason string (no raw content). */
84
+ function classifyFailure(error) {
85
+ if (error instanceof FccPolicySchemaError) {
86
+ return error.message;
87
+ }
88
+ if (error instanceof SyntaxError) {
89
+ const offset = /position (\d+)/.exec(error.message)?.[1];
90
+ return offset !== undefined ? `invalid-json at offset ${offset}` : 'invalid-json';
91
+ }
92
+ return 'unreadable';
93
+ }
94
+ /** The `<vid>:<pid>` models the policy positively enables. Sorted, for stability. */
95
+ export function enabledFccUnlockKeys(state) {
96
+ return Object.entries(state.unlock)
97
+ .filter(([, enabled]) => enabled)
98
+ .map(([key]) => key)
99
+ .sort();
100
+ }
101
+ /** One model's answer. An unmentioned model is NOT enabled — absence is not consent. */
102
+ export function isFccUnlockEnabled(state, key) {
103
+ return state.unlock[key] === true;
104
+ }
105
+ export function createFccUnlockPolicyFileStore(options = {}) {
106
+ const logger = options.logger ?? defaultLogger;
107
+ const path = options.path ?? FCC_UNLOCK_POLICY_PATH;
108
+ async function writeAtomic(state) {
109
+ await mkdir(dirname(path), { recursive: true });
110
+ const tmp = `${path}.tmp`;
111
+ // The trailing newline is for the shell reconciler's benefit, not decoration:
112
+ // it reads the file with `tr -d`, and a newline-terminated document is what a
113
+ // human `cat`ing it on a board expects to see.
114
+ await writeFile(tmp, `${JSON.stringify(state)}\n`);
115
+ // chmod AFTER the write (not an open flag) so mode is 0600 regardless of umask.
116
+ await chmod(tmp, 0o600);
117
+ await rename(tmp, path);
118
+ }
119
+ return {
120
+ async load(nowMs) {
121
+ let text;
122
+ try {
123
+ text = await readFile(path, 'utf8');
124
+ }
125
+ catch {
126
+ // Absent (or unreadable) → EMPTY, which activates nothing. This is the
127
+ // state on every device that never opted in, and it is the safe default.
128
+ return emptyState(nowMs);
129
+ }
130
+ try {
131
+ return validate(JSON.parse(text));
132
+ }
133
+ catch (error) {
134
+ logger({
135
+ kind: 'corrupt-policy',
136
+ bytes: Buffer.byteLength(text, 'utf8'),
137
+ reason: classifyFailure(error),
138
+ });
139
+ // Deliberately NOT rewritten — see the header. Refuse to act, keep the
140
+ // evidence, and let the next write replace it wholesale.
141
+ return emptyState(nowMs);
142
+ }
143
+ },
144
+ save: writeAtomic,
145
+ };
146
+ }
@@ -0,0 +1,32 @@
1
+ import type { FccUnlockPolicyStore, PersistedFccUnlockPolicy } from './policy-store.js';
2
+ export interface SetFccUnlockPolicyDeps {
3
+ readonly store: FccUnlockPolicyStore;
4
+ /** Injectable clock (defaults to `Date.now`). */
5
+ readonly now?: () => number;
6
+ }
7
+ export interface SetFccUnlockPolicyRequest {
8
+ readonly vid: string;
9
+ readonly pid: string;
10
+ readonly enabled: boolean;
11
+ }
12
+ export type SetFccUnlockPolicyRejection = 'invalid-vid-pid' | 'not-covered';
13
+ export type SetFccUnlockPolicyResult = {
14
+ readonly status: 'applied';
15
+ /** The dispatcher key this write is about, normalized. */
16
+ readonly key: string;
17
+ readonly enabled: boolean;
18
+ /** The whole document now on disk, so a caller need not re-read it. */
19
+ readonly policy: PersistedFccUnlockPolicy;
20
+ /**
21
+ * True when the persisted value CHANGED. A caller uses this to decide
22
+ * whether the modem needs a re-probe: ModemManager runs the dispatcher
23
+ * during initialization only, so a genuine change needs
24
+ * `mmcli -m <id> --disable && --enable` (or a replug) to take effect on an
25
+ * already-enumerated modem — and an unchanged write must not cost one.
26
+ */
27
+ readonly changed: boolean;
28
+ } | {
29
+ readonly status: 'rejected';
30
+ readonly reason: SetFccUnlockPolicyRejection;
31
+ };
32
+ export declare function setFccUnlockPolicy(request: SetFccUnlockPolicyRequest, deps: SetFccUnlockPolicyDeps): Promise<SetFccUnlockPolicyResult>;
@@ -0,0 +1,37 @@
1
+ // `setFccUnlockPolicy` — the WRITE half of the FCC auto-unlock surface.
2
+ //
3
+ // TYPED RESULTS, NEVER A THROW ON BAD INPUT. Following the `PowerHook` and
4
+ // `setUsagePolicy` precedents, a malformed key or an uncovered model is a `rejected`
5
+ // result carrying a named reason rather than an exception — this is called from an
6
+ // RPC boundary, where a throw becomes an opaque 500.
7
+ //
8
+ // THE COVERAGE CHECK IS PART OF THE WRITE, not a UI nicety. Persisting `true` for a
9
+ // model ModemManager ships no procedure for would leave the operator staring at an
10
+ // enabled toggle that provably cannot do anything: the reconciler would find no
11
+ // `<vid>:<pid>` in the available tier and skip it forever, silently. Refusing at the
12
+ // write is the only place that fact can be reported to the person who asked for it.
13
+ //
14
+ // Disabling is deliberately NOT coverage-checked. A `false` must always be
15
+ // persistable — including for a model whose coverage answer has since changed, or
16
+ // which a previous release wrote — because a fail-closed opt-OUT is not a thing.
17
+ import { normalizeVidPid, resolveFccUnlockCoverage } from './coverage.js';
18
+ export async function setFccUnlockPolicy(request, deps) {
19
+ const key = normalizeVidPid(request.vid, request.pid);
20
+ if (key === undefined) {
21
+ return { status: 'rejected', reason: 'invalid-vid-pid' };
22
+ }
23
+ if (request.enabled && resolveFccUnlockCoverage(request.vid, request.pid) !== 'present') {
24
+ return { status: 'rejected', reason: 'not-covered' };
25
+ }
26
+ const now = deps.now ?? Date.now;
27
+ const nowMs = now();
28
+ const current = await deps.store.load(nowMs);
29
+ const changed = (current.unlock[key] === true) !== request.enabled;
30
+ const next = {
31
+ schemaVersion: current.schemaVersion,
32
+ savedAtMs: nowMs,
33
+ unlock: { ...current.unlock, [key]: request.enabled },
34
+ };
35
+ await deps.store.save(next);
36
+ return { status: 'applied', key, enabled: request.enabled, policy: next, changed };
37
+ }
@@ -0,0 +1,39 @@
1
+ export declare function parseHilinkXmlValue(body: string, tag: string): string | undefined;
2
+ export type HilinkSessionDocument = {
3
+ readonly cookie: string;
4
+ readonly token: string;
5
+ };
6
+ export declare function parseHilinkSession(body: string): HilinkSessionDocument | undefined;
7
+ export type HilinkUserState = {
8
+ readonly state: string;
9
+ readonly passwordType: 3 | 4;
10
+ };
11
+ export declare function parseHilinkUserState(body: string): HilinkUserState | undefined;
12
+ export type HilinkProtocolUnknownReason = 'unsupported' | 'not-reported' | 'malformed' | 'auth-expired' | 'unreachable';
13
+ export type HilinkDataCapability = {
14
+ readonly state: 'reported';
15
+ readonly enabled: boolean;
16
+ } | {
17
+ readonly state: 'unavailable';
18
+ readonly reason: HilinkProtocolUnknownReason | 'refused';
19
+ readonly code?: string;
20
+ };
21
+ export declare function parseHilinkDataCapability(body: string): HilinkDataCapability;
22
+ export type HilinkNetModeCapability = {
23
+ readonly state: 'reported';
24
+ readonly modes: readonly {
25
+ readonly id: string;
26
+ readonly name?: string;
27
+ }[];
28
+ readonly current?: string;
29
+ } | {
30
+ readonly state: 'unavailable';
31
+ readonly reason: HilinkProtocolUnknownReason | 'refused';
32
+ readonly code?: string;
33
+ };
34
+ export declare function parseHilinkCapabilities(input: {
35
+ readonly netModeList: string;
36
+ readonly netMode?: string;
37
+ }): {
38
+ readonly net_mode: HilinkNetModeCapability;
39
+ };
@@ -0,0 +1,58 @@
1
+ export function parseHilinkXmlValue(body, tag) {
2
+ const match = body.match(new RegExp(`<${tag}>([^<]*)</${tag}>`, 'i'));
3
+ return match?.[1]?.trim();
4
+ }
5
+ export function parseHilinkSession(body) {
6
+ const cookie = parseHilinkXmlValue(body, 'SesInfo');
7
+ const token = parseHilinkXmlValue(body, 'TokInfo');
8
+ return cookie === undefined || cookie === '' || token === undefined || token === ''
9
+ ? undefined
10
+ : { cookie, token };
11
+ }
12
+ export function parseHilinkUserState(body) {
13
+ const state = parseHilinkXmlValue(body, 'State');
14
+ const rawPasswordType = parseHilinkXmlValue(body, 'password_type');
15
+ if (state === undefined || (rawPasswordType !== '3' && rawPasswordType !== '4'))
16
+ return undefined;
17
+ return { state, passwordType: rawPasswordType === '3' ? 3 : 4 };
18
+ }
19
+ export function parseHilinkDataCapability(body) {
20
+ const code = parseHilinkXmlValue(body, 'code');
21
+ if (code === '125002')
22
+ return { state: 'unavailable', reason: 'auth-expired' };
23
+ if (code !== undefined)
24
+ return { state: 'unavailable', reason: 'refused', code };
25
+ const value = parseHilinkXmlValue(body, 'dataswitch');
26
+ if (value === undefined)
27
+ return { state: 'unavailable', reason: body === '' ? 'unreachable' : 'malformed' };
28
+ if (value !== '0' && value !== '1')
29
+ return { state: 'unavailable', reason: 'malformed' };
30
+ return { state: 'reported', enabled: value === '1' };
31
+ }
32
+ export function parseHilinkCapabilities(input) {
33
+ const code = parseHilinkXmlValue(input.netModeList, 'code');
34
+ if (code === '125002')
35
+ return { net_mode: { state: 'unavailable', reason: 'auth-expired' } };
36
+ if (code !== undefined)
37
+ return { net_mode: { state: 'unavailable', reason: 'refused', code } };
38
+ if (input.netModeList.trim() === '')
39
+ return { net_mode: { state: 'unavailable', reason: 'unreachable' } };
40
+ // `malformed` is a statement about the BODY — something that is not this API
41
+ // answered. A `<response>` carrying no mode list IS this API, saying nothing,
42
+ // which is `not-reported` below.
43
+ if (!/<response>/i.test(input.netModeList))
44
+ return { net_mode: { state: 'unavailable', reason: 'malformed' } };
45
+ const modes = [...input.netModeList.matchAll(/<NetworkMode>([\s\S]*?)<\/NetworkMode>/gi)].flatMap((match) => {
46
+ const id = parseHilinkXmlValue(match[1] ?? '', 'Index');
47
+ if (id === undefined || id === '')
48
+ return [];
49
+ const name = parseHilinkXmlValue(match[1] ?? '', 'Name');
50
+ return name === undefined || name === '' ? [{ id }] : [{ id, name }];
51
+ });
52
+ if (modes.length === 0)
53
+ return { net_mode: { state: 'unavailable', reason: 'not-reported' } };
54
+ const current = parseHilinkXmlValue(input.netMode ?? '', 'NetworkMode');
55
+ return current === undefined || current === ''
56
+ ? { net_mode: { state: 'reported', modes } }
57
+ : { net_mode: { state: 'reported', modes, current } };
58
+ }
@@ -0,0 +1,3 @@
1
+ export * from '../band/index.js';
2
+ export * from '../usb-mode/index.js';
3
+ export * from './router-parsers.js';
@@ -0,0 +1,15 @@
1
+ // `@ceralive/modem-control/hardware` — the per-SKU hardware model.
2
+ //
3
+ // This subpath is the narrow entry for everything keyed on the PHYSICAL unit rather
4
+ // than on a live D-Bus object: which USB composition a SKU may be switched into and
5
+ // which transitions were certified for it (`../usb-mode`), and the MMModemBand
6
+ // vocabulary plus the per-SKU band-lock certification catalog (`../band`).
7
+ //
8
+ // Both halves are evidence-gated catalogs plus their pure lookups — no transport, no
9
+ // mutation verb. The D-Bus verbs that ACT on these facts stay where they live
10
+ // (`../backend/mm-mutations.ts`, declared on `ModemManagerPort`); a consumer that only
11
+ // needs to know what a SKU is capable of should not have to import a bus client to
12
+ // find out. That asymmetry is the reason this entry exists.
13
+ export * from '../band/index.js';
14
+ export * from '../usb-mode/index.js';
15
+ export * from './router-parsers.js';
@@ -0,0 +1,89 @@
1
+ export * from './hilink-protocol.js';
2
+ export type SimPresence = 'present' | 'absent' | 'unknown';
3
+ export type SimPresenceFacts = {
4
+ readonly sim?: string;
5
+ readonly simSlots?: readonly string[];
6
+ readonly failedReason?: string;
7
+ };
8
+ /** The mmcli spelling of `MM_MODEM_STATE_FAILED_REASON_SIM_MISSING` — the ONLY proof of absence. */
9
+ export declare const SIM_MISSING_FAILED_REASON = "sim-missing";
10
+ /** The fields a presence decision may be drawn from, in the order they are inspected. */
11
+ export declare const SIM_PRESENCE_FIELDS: readonly ["sim", "simSlots", "failedReason"];
12
+ export type SimPresenceField = (typeof SIM_PRESENCE_FIELDS)[number];
13
+ /**
14
+ * WHICH FACT decided a SIM presence — the whole point of this type.
15
+ *
16
+ * `absent` is reachable through exactly ONE member (`state-failed-reason`), so a
17
+ * consumer can prove that no code path inferred "there is no SIM" from a blank or
18
+ * missing field. A modem that exports `Sim: '/'` and says nothing else is
19
+ * `no-evidence`/`unknown`: an empty object path is ModemManager's answer for "no SIM
20
+ * object is bound right now", which a modem also reports while it is still
21
+ * initializing, while its SIM is locked out, and while a slot switch is in flight.
22
+ *
23
+ * `vendor-code-unclaimed` is the router half. HiLink, goform and HIMI each report
24
+ * their own presence code with vendor semantics no migrated decoder covers, so the
25
+ * code is NAMED here and its value stays verbatim in the diagnostics block, rather
26
+ * than being guessed into a presence.
27
+ */
28
+ export type SimPresenceEvidence = {
29
+ readonly kind: 'sim-object-path';
30
+ readonly field: 'sim';
31
+ readonly value: string;
32
+ } | {
33
+ readonly kind: 'sim-slot-object-path';
34
+ readonly field: 'simSlots';
35
+ readonly value: string;
36
+ } | {
37
+ readonly kind: 'state-failed-reason';
38
+ readonly field: 'failedReason';
39
+ readonly value: typeof SIM_MISSING_FAILED_REASON;
40
+ } | {
41
+ readonly kind: 'no-evidence';
42
+ readonly inspected: readonly SimPresenceField[];
43
+ } | {
44
+ readonly kind: 'vendor-code-unclaimed';
45
+ readonly field: string;
46
+ };
47
+ export type SimPresenceReading = {
48
+ readonly presence: SimPresence;
49
+ readonly evidence: SimPresenceEvidence;
50
+ };
51
+ /** The presence decision together with the fact that produced it. */
52
+ export declare function readSimPresence(facts: SimPresenceFacts): SimPresenceReading;
53
+ export declare function deriveSimPresence(facts: SimPresenceFacts): SimPresence;
54
+ export type RouterSignalUnknownReason = 'unsupported' | 'not-reported' | 'malformed' | 'auth-expired' | 'unreachable';
55
+ export type RouterSignalMetric = {
56
+ readonly state: 'known';
57
+ readonly value: number;
58
+ } | {
59
+ readonly state: 'unknown';
60
+ readonly reason: RouterSignalUnknownReason;
61
+ };
62
+ export type RouterSignalModel = {
63
+ readonly provenance: 'hilink-admin-api' | 'zte-goform' | 'ufi-himiapi';
64
+ readonly freshness: 'live' | 'unknown';
65
+ readonly bars: RouterSignalMetric;
66
+ readonly max_bars: RouterSignalMetric;
67
+ readonly dbm: RouterSignalMetric;
68
+ readonly rsrp: RouterSignalMetric;
69
+ readonly rsrq: RouterSignalMetric;
70
+ readonly snr: RouterSignalMetric;
71
+ readonly sinr: RouterSignalMetric;
72
+ };
73
+ export declare function parseHilinkSignal(input: {
74
+ readonly status: string;
75
+ readonly signal: string;
76
+ }): RouterSignalModel;
77
+ export declare function parseZteSignal(body: string): RouterSignalModel;
78
+ export declare function parseUfiSignal(input: {
79
+ readonly sysinfo: string;
80
+ readonly overview: string;
81
+ readonly status: string;
82
+ }): RouterSignalModel;
83
+ export type RouterDetails = Readonly<Record<string, string>>;
84
+ export declare function parseZteDetails(body: string): RouterDetails | undefined;
85
+ export declare function parseUfiDetails(input: {
86
+ readonly overview?: string;
87
+ readonly sysinfo?: string;
88
+ readonly produceInfo?: string;
89
+ }): RouterDetails | undefined;
@@ -0,0 +1,234 @@
1
+ import { z } from 'zod';
2
+ import { parseHilinkXmlValue } from './hilink-protocol.js';
3
+ export * from './hilink-protocol.js';
4
+ const SIM_OBJECT_PATH = /^\/org\/freedesktop\/ModemManager1\/SIM\/\d+$/;
5
+ /** The mmcli spelling of `MM_MODEM_STATE_FAILED_REASON_SIM_MISSING` — the ONLY proof of absence. */
6
+ export const SIM_MISSING_FAILED_REASON = 'sim-missing';
7
+ /** The fields a presence decision may be drawn from, in the order they are inspected. */
8
+ export const SIM_PRESENCE_FIELDS = ['sim', 'simSlots', 'failedReason'];
9
+ /** The presence decision together with the fact that produced it. */
10
+ export function readSimPresence(facts) {
11
+ const sim = facts.sim?.trim() ?? '';
12
+ if (SIM_OBJECT_PATH.test(sim)) {
13
+ return { presence: 'present', evidence: { kind: 'sim-object-path', field: 'sim', value: sim } };
14
+ }
15
+ const slot = facts.simSlots
16
+ ?.map((each) => each.trim())
17
+ .find((each) => SIM_OBJECT_PATH.test(each));
18
+ if (slot !== undefined) {
19
+ return {
20
+ presence: 'present',
21
+ evidence: { kind: 'sim-slot-object-path', field: 'simSlots', value: slot },
22
+ };
23
+ }
24
+ if (facts.failedReason === SIM_MISSING_FAILED_REASON) {
25
+ return {
26
+ presence: 'absent',
27
+ evidence: {
28
+ kind: 'state-failed-reason',
29
+ field: 'failedReason',
30
+ value: SIM_MISSING_FAILED_REASON,
31
+ },
32
+ };
33
+ }
34
+ return { presence: 'unknown', evidence: { kind: 'no-evidence', inspected: SIM_PRESENCE_FIELDS } };
35
+ }
36
+ export function deriveSimPresence(facts) {
37
+ return readSimPresence(facts).presence;
38
+ }
39
+ const unknown = (reason) => ({
40
+ state: 'unknown',
41
+ reason,
42
+ });
43
+ const known = (value) => ({ state: 'known', value });
44
+ function numericMetric(value) {
45
+ if (value === undefined || String(value).trim() === '')
46
+ return unknown('not-reported');
47
+ const parsed = Number.parseFloat(String(value));
48
+ return Number.isFinite(parsed) ? known(parsed) : unknown('malformed');
49
+ }
50
+ const flatRecordSchema = z.record(z.string(), z.union([z.string(), z.number()]));
51
+ const ufiBodySchema = z.object({
52
+ reply: z.string(),
53
+ params: flatRecordSchema.optional(),
54
+ });
55
+ function parseFlatRecord(body) {
56
+ const parsed = z
57
+ .string()
58
+ .transform((value, context) => {
59
+ try {
60
+ return JSON.parse(value);
61
+ }
62
+ catch (error) {
63
+ if (!(error instanceof SyntaxError))
64
+ throw error;
65
+ context.addIssue({ code: 'custom', message: 'invalid JSON' });
66
+ return z.NEVER;
67
+ }
68
+ })
69
+ .pipe(flatRecordSchema)
70
+ .safeParse(body);
71
+ return parsed.success ? parsed.data : undefined;
72
+ }
73
+ function parseUfiBody(body) {
74
+ const parsed = z
75
+ .string()
76
+ .transform((value, context) => {
77
+ try {
78
+ return JSON.parse(value);
79
+ }
80
+ catch (error) {
81
+ if (!(error instanceof SyntaxError))
82
+ throw error;
83
+ context.addIssue({ code: 'custom', message: 'invalid JSON' });
84
+ return z.NEVER;
85
+ }
86
+ })
87
+ .pipe(ufiBodySchema)
88
+ .safeParse(body);
89
+ return parsed.success ? parsed.data : undefined;
90
+ }
91
+ export function parseHilinkSignal(input) {
92
+ const authStatus = parseHilinkXmlValue(input.status, 'code') === '125002';
93
+ const authSignal = parseHilinkXmlValue(input.signal, 'code') === '125002';
94
+ const statusReason = authStatus ? 'auth-expired' : 'not-reported';
95
+ const signalReason = authSignal ? 'auth-expired' : 'not-reported';
96
+ const metric = (tag) => authSignal ? unknown(signalReason) : numericMetric(parseHilinkXmlValue(input.signal, tag));
97
+ return {
98
+ provenance: 'hilink-admin-api',
99
+ freshness: authStatus && authSignal ? 'unknown' : 'live',
100
+ bars: authStatus
101
+ ? unknown(statusReason)
102
+ : numericMetric(parseHilinkXmlValue(input.status, 'SignalIcon')),
103
+ max_bars: authStatus
104
+ ? unknown(statusReason)
105
+ : numericMetric(parseHilinkXmlValue(input.status, 'maxsignal')),
106
+ dbm: metric('rssi'),
107
+ rsrp: metric('rsrp'),
108
+ rsrq: metric('rsrq'),
109
+ snr: unknown('unsupported'),
110
+ sinr: metric('sinr'),
111
+ };
112
+ }
113
+ export function parseZteSignal(body) {
114
+ const record = parseFlatRecord(body);
115
+ const malformed = record === undefined;
116
+ const metric = (key) => malformed ? unknown('malformed') : numericMetric(record[key]);
117
+ const bars = metric('signalbar');
118
+ return {
119
+ provenance: 'zte-goform',
120
+ freshness: malformed ? 'unknown' : 'live',
121
+ bars,
122
+ max_bars: bars.state === 'known' ? known(5) : unknown('not-reported'),
123
+ dbm: metric('rssi'),
124
+ rsrp: metric('lte_rsrp'),
125
+ rsrq: metric('lte_rsrq'),
126
+ snr: metric('lte_snr'),
127
+ sinr: unknown('unsupported'),
128
+ };
129
+ }
130
+ export function parseUfiSignal(input) {
131
+ const bodies = [input.sysinfo, input.overview, input.status].map(parseUfiBody);
132
+ const authExpired = bodies.every((body) => body?.reply === 'SessionOut');
133
+ const answered = bodies.some((body) => body?.reply === 'ok');
134
+ const value = bodies[0]?.params?.SIGNAL ?? bodies[1]?.params?.SIGNAL ?? bodies[2]?.params?.signalStrength;
135
+ const dbm = authExpired
136
+ ? unknown('auth-expired')
137
+ : answered
138
+ ? numericMetric(value)
139
+ : unknown('malformed');
140
+ return {
141
+ provenance: 'ufi-himiapi',
142
+ freshness: answered ? 'live' : 'unknown',
143
+ bars: unknown('unsupported'),
144
+ max_bars: unknown('unsupported'),
145
+ dbm,
146
+ rsrp: unknown('unsupported'),
147
+ rsrq: unknown('unsupported'),
148
+ snr: unknown('unsupported'),
149
+ sinr: unknown('unsupported'),
150
+ };
151
+ }
152
+ /**
153
+ * The vendor's own "unset" placeholders. A UFI answers `-` for a WAN address, an
154
+ * IMSI and an ICCID it does not have, and the ZTE builds answer `--` / `N/A` for
155
+ * an unpopulated counter — publishing any of those as a reading puts a value on
156
+ * screen that reads like a real one.
157
+ */
158
+ const PLACEHOLDERS = new Set(['-', '--', 'n/a', 'N/A']);
159
+ function stated(value) {
160
+ if (value === undefined)
161
+ return undefined;
162
+ const normalized = String(value).trim();
163
+ return normalized === '' || PLACEHOLDERS.has(normalized) ? undefined : normalized;
164
+ }
165
+ function compact(entries) {
166
+ const result = {};
167
+ for (const [key, value] of entries)
168
+ if (value !== undefined)
169
+ result[key] = value;
170
+ return Object.keys(result).length === 0 ? undefined : result;
171
+ }
172
+ export function parseZteDetails(body) {
173
+ const value = parseFlatRecord(body);
174
+ if (value === undefined)
175
+ return undefined;
176
+ return compact([
177
+ ['network_type', stated(value.network_type)],
178
+ [
179
+ 'provider',
180
+ stated(value.network_provider_fullname) ??
181
+ stated(value.network_provider) ??
182
+ stated(value.provider),
183
+ ],
184
+ ['cell_id', stated(value.cell_id)],
185
+ ['roaming', stated(value.simcard_roam)],
186
+ ['mcc', stated(value.rmcc)],
187
+ ['mnc', stated(value.rmnc)],
188
+ ['pci', stated(value.lte_pci)],
189
+ // `band` and `network_band` are two DIFFERENT readings and must not be
190
+ // folded onto one key: `lte_band` is the serving cell's band, while
191
+ // `wan_active_band` is the band the WAN leg is active on, and the two
192
+ // disagree the moment carrier aggregation is up. Publishing either under
193
+ // the other's name reports a band the device never claimed for that leg.
194
+ ['band', stated(value.lte_band) ?? stated(value.band)],
195
+ ['network_band', stated(value.wan_active_band)],
196
+ ['carrier_aggregation', stated(value.wan_lte_ca)],
197
+ ['pcell_arfcn', stated(value.lte_ca_pcell_arfcn)],
198
+ ['pcell_band', stated(value.lte_ca_pcell_band)],
199
+ ['pcell_bandwidth', stated(value.lte_ca_pcell_bandwidth)],
200
+ ['scell_arfcn', stated(value.lte_ca_scell_arfcn)],
201
+ ['scell_band', stated(value.lte_ca_scell_band)],
202
+ ['scell_bandwidth', stated(value.lte_ca_scell_bandwidth)],
203
+ ['monthly_tx_bytes', stated(value.monthly_tx_bytes)],
204
+ ['monthly_rx_bytes', stated(value.monthly_rx_bytes)],
205
+ ['monthly_time', stated(value.monthly_time)],
206
+ ['monthly_period', stated(value.date_month)],
207
+ // Named `session_*` rather than `realtime_*`: these are cumulative counters
208
+ // and a throughput, and the vendor's own prefix reads as "live rate" for
209
+ // all five.
210
+ ['session_tx_bytes', stated(value.realtime_tx_bytes)],
211
+ ['session_rx_bytes', stated(value.realtime_rx_bytes)],
212
+ ['session_tx_rate', stated(value.realtime_tx_thrpt)],
213
+ ['session_rx_rate', stated(value.realtime_rx_thrpt)],
214
+ ['session_time', stated(value.realtime_time)],
215
+ ]);
216
+ }
217
+ export function parseUfiDetails(input) {
218
+ const overview = parseUfiBody(input.overview ?? '')?.params;
219
+ const sysinfo = parseUfiBody(input.sysinfo ?? '')?.params;
220
+ const product = parseUfiBody(input.produceInfo ?? '')?.params;
221
+ return compact([
222
+ ['product', stated(product?.productname) ?? stated(product?.ProductName)],
223
+ ['ssid', stated(overview?.SSID)],
224
+ ['wan_ip', stated(overview?.WANIP)],
225
+ ['imsi', stated(overview?.IMSI)],
226
+ ['iccid', stated(overview?.ICCID)],
227
+ ['web_version', stated(overview?.WEBVER)],
228
+ ['cell_id', stated(sysinfo?.cellid)],
229
+ ['station_id', stated(sysinfo?.bsid)],
230
+ ['cpu_temp', stated(sysinfo?.cputemp)],
231
+ ['wifi_clients', stated(sysinfo?.wifinum)],
232
+ ['eth_clients', stated(sysinfo?.ethnum)],
233
+ ]);
234
+ }