@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,80 @@
1
+ import type { RadioAccessTechnology } from '../domain/index.js';
2
+ import type { SimPresenceEvidence } from '../hardware/router-parsers.js';
3
+ import type { NormalizedMetric } from './metric.js';
4
+ import type { ObservationDiagnostics, ObservationSourceKind } from './provenance.js';
5
+ export type NormalizedHardware = {
6
+ /** The operator-facing model label, sanitized by the migrated presentation rules. */
7
+ readonly label: NormalizedMetric<string>;
8
+ /**
9
+ * The label plus a short equipment-identifier tail, as CeraUI has always rendered
10
+ * it. It embeds the last five digits of an IMEI, so it is display copy and must
11
+ * not be used as a key or written to a log.
12
+ */
13
+ readonly displayName: NormalizedMetric<string>;
14
+ };
15
+ export type NormalizedRadio = {
16
+ readonly modemState: NormalizedMetric<string>;
17
+ readonly registration: NormalizedMetric<string>;
18
+ readonly accessTechnologies: NormalizedMetric<readonly RadioAccessTechnology[]>;
19
+ readonly modeLabel: NormalizedMetric<string>;
20
+ };
21
+ /**
22
+ * Signal metrics.
23
+ *
24
+ * `quality` and `bars`/`maxBars` are different measurements and both are kept:
25
+ * ModemManager reports a 0-100 percentage and no bar count, while the router admin
26
+ * APIs report a vendor bar scale and no percentage. Deriving one from the other would
27
+ * be inventing a reading, so each source reports what it has and answers
28
+ * `unsupported` — a positive claim — for what it structurally cannot.
29
+ */
30
+ export type NormalizedSignal = {
31
+ readonly quality: NormalizedMetric<number>;
32
+ /**
33
+ * ModemManager's `SignalQuality` is a `(ub)` — a percentage AND a boolean saying
34
+ * whether it was measured recently or is the last cached reading. The boolean is a
35
+ * separate fact about the same measurement, so it gets its own metric rather than
36
+ * being folded into freshness: an envelope's staleness is about when WE read, this
37
+ * is about when the MODEM last measured. The router APIs have no such flag and say
38
+ * so with `unsupported`.
39
+ */
40
+ readonly qualityRecent: NormalizedMetric<boolean>;
41
+ readonly bars: NormalizedMetric<number>;
42
+ readonly maxBars: NormalizedMetric<number>;
43
+ readonly dbm: NormalizedMetric<number>;
44
+ readonly rsrp: NormalizedMetric<number>;
45
+ readonly rsrq: NormalizedMetric<number>;
46
+ readonly snr: NormalizedMetric<number>;
47
+ readonly sinr: NormalizedMetric<number>;
48
+ };
49
+ /**
50
+ * SIM presence as a metric value is BINARY on purpose.
51
+ *
52
+ * The migrated `deriveSimPresence` answers `present | absent | unknown`; the third
53
+ * member is not a presence, it is the absence of an answer, so it becomes the
54
+ * metric's `unknown` state with a reason instead of a third value. That is what stops
55
+ * "we could not tell" from being rendered beside "there is no SIM".
56
+ */
57
+ export type SimPresenceValue = 'present' | 'absent';
58
+ export type NormalizedSim = {
59
+ readonly presence: NormalizedMetric<SimPresenceValue>;
60
+ /**
61
+ * WHICH FACT decided `presence`. Carried beside the metric rather than derived from
62
+ * it, because "absent" and "we could not tell" are read off the SAME empty fields
63
+ * and only the evidence separates them. `absent` is reachable through exactly one
64
+ * evidence kind (`state-failed-reason`), which is what makes "never inferred from a
65
+ * blank field" a property a test can assert rather than a convention.
66
+ */
67
+ readonly presenceEvidence: SimPresenceEvidence;
68
+ readonly lockRequired: NormalizedMetric<string>;
69
+ readonly kind: NormalizedMetric<'physical' | 'esim'>;
70
+ readonly esimStatus: NormalizedMetric<'no-profiles' | 'with-profiles'>;
71
+ };
72
+ export type NormalizedModemObservation = {
73
+ readonly source: ObservationSourceKind;
74
+ readonly hardware: NormalizedHardware;
75
+ readonly radio: NormalizedRadio;
76
+ readonly signal: NormalizedSignal;
77
+ readonly sim: NormalizedSim;
78
+ /** Everything the provider said, verbatim, plus what was and was not claimed. */
79
+ readonly diagnostics: ObservationDiagnostics;
80
+ };
@@ -0,0 +1,11 @@
1
+ // The normalized observation shape every source produces.
2
+ //
3
+ // It is deliberately NARROW. A field earns a slot here only when more than one source
4
+ // can express it and an operator surface acts on it; everything else stays verbatim in
5
+ // the diagnostics block rather than growing a per-vendor branch of the model. That is
6
+ // the whole trade this layer makes — one shape to render, nothing thrown away.
7
+ //
8
+ // Every leaf is a `NormalizedMetric`, so "the source cannot report this" and "the
9
+ // source did not report this on this read" are different values with different
10
+ // reasons rather than the same absent field.
11
+ export {};
@@ -0,0 +1,94 @@
1
+ import type { EpochMillis, ObservationAuthority, SourceEpoch } from '../domain/index.js';
2
+ /**
3
+ * The provider families this layer normalizes.
4
+ *
5
+ * These are SOURCE SHAPES, not live transports. Todo 18 builds the normalization
6
+ * layer; the providers that will feed it (ModemManager D-Bus, the HiLink/goform/HIMI
7
+ * HTTP sessions) are separate work and open no connection from here.
8
+ *
9
+ * `networkmanager` is here because NM is a source of a DIFFERENT fact than the other
10
+ * four: it reports which connection is in force on which interface, never a radio
11
+ * reading. Giving it its own kind is what keeps a bearer observation attributable to
12
+ * NM rather than laundered through whichever provider happened to read the modem.
13
+ */
14
+ export declare const OBSERVATION_SOURCE_KINDS: readonly ["modemmanager", "huawei-hilink", "zte-goform", "ufi-himiapi", "networkmanager"];
15
+ export type ObservationSourceKind = (typeof OBSERVATION_SOURCE_KINDS)[number];
16
+ /**
17
+ * A provider-native value, kept exactly as the provider expressed it.
18
+ *
19
+ * Arrays are part of the union because ModemManager genuinely returns them
20
+ * (`Modem.SimSlots` is an `ao`); flattening one to its first member here would be a
21
+ * silent drop of exactly the kind this module exists to prevent.
22
+ */
23
+ export type RawFieldValue = string | number | boolean | null | readonly RawFieldValue[];
24
+ /** Provider-native fields keyed by their provider-native names. */
25
+ export type RawFieldRecord = Readonly<Record<string, RawFieldValue>>;
26
+ /**
27
+ * Per-metric provenance: which source, which reading of it, and when.
28
+ *
29
+ * It is carried on EVERY metric rather than only on the envelope because one
30
+ * normalized observation routinely folds several provider reads together (HiLink
31
+ * answers `device_signal` and `monitoring_status` separately; UFI answers three
32
+ * endpoints), so a single envelope-level `observedAt` would be a claim about a
33
+ * reading no individual metric came from.
34
+ */
35
+ export type MetricProvenance = {
36
+ readonly source: ObservationSourceKind;
37
+ readonly sourceEpoch: SourceEpoch;
38
+ readonly observedAt: EpochMillis;
39
+ readonly authority: ObservationAuthority;
40
+ /** The provider-native field name(s) this value was normalized from. */
41
+ readonly rawFields: readonly string[];
42
+ };
43
+ /**
44
+ * Structural complaints about a payload. A note names the FIELD or body it concerns
45
+ * and never carries its value: a body can hold an ICCID or a one-time code, and a
46
+ * diagnostic is the one place a reviewer reads verbatim.
47
+ */
48
+ export declare const OBSERVATION_DIAGNOSTIC_CODES: readonly ["unparseable-body", "empty-body", "auth-expired", "field-shape-unrecognized"];
49
+ export type ObservationDiagnosticCode = (typeof OBSERVATION_DIAGNOSTIC_CODES)[number];
50
+ export type ObservationDiagnosticNote = {
51
+ readonly code: ObservationDiagnosticCode;
52
+ /** The provider-native field or body name, never its content. */
53
+ readonly field: string;
54
+ };
55
+ /**
56
+ * The typed diagnostics block: the provider payload, verbatim, plus which fields the
57
+ * normalizer claimed and which it did not.
58
+ *
59
+ * `raw` is a REDACTION-CLASS boundary. A vendor payload routinely carries an ICCID or
60
+ * an IMSI (the UFI overview endpoint reports both), so anything that logs, serializes
61
+ * or files a diagnostics block must route it through {@link redactObservationDiagnostics}
62
+ * first. Retention and disclosure are different decisions; this layer only guarantees
63
+ * the first.
64
+ */
65
+ export type ObservationDiagnostics = {
66
+ readonly source: ObservationSourceKind;
67
+ /** Every provider-native field, verbatim. Nothing is dropped during normalization. */
68
+ readonly raw: RawFieldRecord;
69
+ /** Fields a normalized metric claims. They remain present in `raw` as well. */
70
+ readonly consumed: readonly string[];
71
+ /** Fields in `raw` that no normalized field claims — retained, never discarded. */
72
+ readonly unmapped: readonly string[];
73
+ readonly notes: readonly ObservationDiagnosticNote[];
74
+ };
75
+ export type DiagnosticsInput = {
76
+ readonly source: ObservationSourceKind;
77
+ readonly raw: RawFieldRecord;
78
+ readonly consumed?: readonly string[];
79
+ readonly notes?: readonly ObservationDiagnosticNote[];
80
+ };
81
+ /**
82
+ * Build a diagnostics block, deriving `unmapped` rather than accepting it.
83
+ *
84
+ * Deriving it is what makes the no-drop property structural: a normalizer cannot
85
+ * declare a field mapped without a metric that names it, and a field it never
86
+ * mentions lands in `unmapped` automatically instead of vanishing.
87
+ */
88
+ export declare function createObservationDiagnostics(input: DiagnosticsInput): ObservationDiagnostics;
89
+ /**
90
+ * A copy of `diagnostics` with every sensitive raw field replaced by the shared
91
+ * redaction marker. Routed through the package's own key-based `redact`, so the
92
+ * classes it masks here are exactly the classes it masks everywhere else.
93
+ */
94
+ export declare function redactObservationDiagnostics(diagnostics: ObservationDiagnostics): ObservationDiagnostics;
@@ -0,0 +1,67 @@
1
+ // Provenance + diagnostics — where a normalized value came from, and everything the
2
+ // provider said that the normalized model has no field for.
3
+ //
4
+ // The rule this module exists to enforce: normalization NARROWS a vendor payload into
5
+ // one shape, it does not DISCARD it. Every provider-native field a normalizer reads —
6
+ // and every one it does not — is retained verbatim in `ObservationDiagnostics.raw`,
7
+ // and each normalized metric carries the provenance of the exact observation that
8
+ // produced it. A consumer can therefore always answer "which provider said this, and
9
+ // when" without the provider having to widen the normalized model.
10
+ import { redact } from '../redact.js';
11
+ /**
12
+ * The provider families this layer normalizes.
13
+ *
14
+ * These are SOURCE SHAPES, not live transports. Todo 18 builds the normalization
15
+ * layer; the providers that will feed it (ModemManager D-Bus, the HiLink/goform/HIMI
16
+ * HTTP sessions) are separate work and open no connection from here.
17
+ *
18
+ * `networkmanager` is here because NM is a source of a DIFFERENT fact than the other
19
+ * four: it reports which connection is in force on which interface, never a radio
20
+ * reading. Giving it its own kind is what keeps a bearer observation attributable to
21
+ * NM rather than laundered through whichever provider happened to read the modem.
22
+ */
23
+ export const OBSERVATION_SOURCE_KINDS = [
24
+ 'modemmanager',
25
+ 'huawei-hilink',
26
+ 'zte-goform',
27
+ 'ufi-himiapi',
28
+ 'networkmanager',
29
+ ];
30
+ /**
31
+ * Structural complaints about a payload. A note names the FIELD or body it concerns
32
+ * and never carries its value: a body can hold an ICCID or a one-time code, and a
33
+ * diagnostic is the one place a reviewer reads verbatim.
34
+ */
35
+ export const OBSERVATION_DIAGNOSTIC_CODES = [
36
+ 'unparseable-body',
37
+ 'empty-body',
38
+ 'auth-expired',
39
+ 'field-shape-unrecognized',
40
+ ];
41
+ /**
42
+ * Build a diagnostics block, deriving `unmapped` rather than accepting it.
43
+ *
44
+ * Deriving it is what makes the no-drop property structural: a normalizer cannot
45
+ * declare a field mapped without a metric that names it, and a field it never
46
+ * mentions lands in `unmapped` automatically instead of vanishing.
47
+ */
48
+ export function createObservationDiagnostics(input) {
49
+ const rawKeys = Object.keys(input.raw);
50
+ const consumed = [...new Set(input.consumed ?? [])].filter((key) => rawKeys.includes(key)).sort();
51
+ const consumedSet = new Set(consumed);
52
+ return {
53
+ source: input.source,
54
+ raw: input.raw,
55
+ consumed,
56
+ unmapped: rawKeys.filter((key) => !consumedSet.has(key)).sort(),
57
+ notes: input.notes ?? [],
58
+ };
59
+ }
60
+ /**
61
+ * A copy of `diagnostics` with every sensitive raw field replaced by the shared
62
+ * redaction marker. Routed through the package's own key-based `redact`, so the
63
+ * classes it masks here are exactly the classes it masks everywhere else.
64
+ */
65
+ export function redactObservationDiagnostics(diagnostics) {
66
+ return { ...diagnostics, raw: redact(diagnostics.raw) };
67
+ }
@@ -0,0 +1,42 @@
1
+ import type { RawFieldRecord, RawFieldValue } from './provenance.js';
2
+ export declare function rawKey(body: string, field: string): string;
3
+ /** Prefix a provider's flat record with the body it came from. */
4
+ export declare function prefixRawRecord(body: string, record: Readonly<Record<string, RawFieldValue>> | undefined): RawFieldRecord;
5
+ export declare function mergeRawRecords(...records: readonly RawFieldRecord[]): RawFieldRecord;
6
+ export declare function rawString(record: RawFieldRecord, key: string): string | undefined;
7
+ export declare function rawNumber(record: RawFieldRecord, key: string): number | undefined;
8
+ export declare function rawStringArray(record: RawFieldRecord, key: string): readonly string[] | undefined;
9
+ export declare function hasRawField(record: RawFieldRecord, key: string): boolean;
10
+ /**
11
+ * One member of a D-Bus STRUCT retained verbatim.
12
+ *
13
+ * ModemManager's `SignalQuality` is a `(ub)` and its `CurrentModes` is a `(uu)`, so
14
+ * retaining them verbatim means the raw record holds an ARRAY where a naive read
15
+ * expects a scalar. A caller that flattened the struct before retention would keep the
16
+ * first member and silently drop the second — the recency flag, and the preferred mode
17
+ * — which is the drop this layer exists to prevent. A scalar answers at index 0, so a
18
+ * source that already flattened (mmcli, or a pre-existing fixture) still decodes.
19
+ */
20
+ export declare function rawStructMember(record: RawFieldRecord, key: string, index: number): RawFieldValue | undefined;
21
+ export declare function rawNumberAt(record: RawFieldRecord, key: string, index: number): number | undefined;
22
+ export declare function rawBooleanAt(record: RawFieldRecord, key: string, index: number): boolean | undefined;
23
+ /**
24
+ * Flatten every leaf element of an XML body into `<body>.<Tag>` entries.
25
+ *
26
+ * A repeated tag — HiLink's `<NetworkMode>` list repeats `<Index>` once per mode —
27
+ * would otherwise overwrite its predecessors, so the second and later occurrences are
28
+ * suffixed `#2`, `#3`, …. Losing a repeat is exactly the silent drop this layer
29
+ * exists to prevent, and the suffix keeps the original tag name legible.
30
+ */
31
+ export declare function flattenXmlBody(body: string, name: string): RawFieldRecord;
32
+ /** Parse a JSON object body, or `undefined` when the bytes are not a JSON object. */
33
+ export declare function parseJsonObject(body: string): Readonly<Record<string, unknown>> | undefined;
34
+ /** Parse a JSON object body into a flat record, or `undefined` when it is not one. */
35
+ export declare function parseJsonRecord(body: string): Readonly<Record<string, RawFieldValue>> | undefined;
36
+ /**
37
+ * A nested object has no flat representation, so its JSON text is kept.
38
+ *
39
+ * Serializing rather than dropping is the whole contract of this layer: a payload
40
+ * this record cannot model structurally is still a payload a diagnostician can read.
41
+ */
42
+ export declare function normalizeRawValue(value: unknown): RawFieldValue;
@@ -0,0 +1,146 @@
1
+ // Raw-payload helpers — the retention half of normalization.
2
+ //
3
+ // Every normalizer builds ONE flat `RawFieldRecord` out of whatever bodies it was
4
+ // handed, keyed by `<body>.<provider-native field>`, and reads its metrics out of
5
+ // that same record. Reading from the retained copy rather than from the original
6
+ // bodies is what makes the no-drop property hold by construction instead of by
7
+ // discipline: a field a metric consumed is necessarily a field the diagnostics block
8
+ // already carries.
9
+ export function rawKey(body, field) {
10
+ return `${body}.${field}`;
11
+ }
12
+ /** Prefix a provider's flat record with the body it came from. */
13
+ export function prefixRawRecord(body, record) {
14
+ const out = {};
15
+ for (const [field, value] of Object.entries(record ?? {})) {
16
+ out[rawKey(body, field)] = value;
17
+ }
18
+ return out;
19
+ }
20
+ export function mergeRawRecords(...records) {
21
+ return Object.assign({}, ...records);
22
+ }
23
+ export function rawString(record, key) {
24
+ const value = record[key];
25
+ if (typeof value === 'string') {
26
+ const trimmed = value.trim();
27
+ return trimmed === '' ? undefined : trimmed;
28
+ }
29
+ return typeof value === 'number' || typeof value === 'boolean' ? String(value) : undefined;
30
+ }
31
+ export function rawNumber(record, key) {
32
+ const value = record[key];
33
+ if (typeof value === 'number') {
34
+ return Number.isFinite(value) ? value : undefined;
35
+ }
36
+ if (typeof value !== 'string' || value.trim() === '') {
37
+ return undefined;
38
+ }
39
+ const parsed = Number.parseFloat(value);
40
+ return Number.isFinite(parsed) ? parsed : undefined;
41
+ }
42
+ export function rawStringArray(record, key) {
43
+ const value = record[key];
44
+ return Array.isArray(value)
45
+ ? value.filter((item) => typeof item === 'string')
46
+ : undefined;
47
+ }
48
+ export function hasRawField(record, key) {
49
+ return Object.hasOwn(record, key);
50
+ }
51
+ /**
52
+ * One member of a D-Bus STRUCT retained verbatim.
53
+ *
54
+ * ModemManager's `SignalQuality` is a `(ub)` and its `CurrentModes` is a `(uu)`, so
55
+ * retaining them verbatim means the raw record holds an ARRAY where a naive read
56
+ * expects a scalar. A caller that flattened the struct before retention would keep the
57
+ * first member and silently drop the second — the recency flag, and the preferred mode
58
+ * — which is the drop this layer exists to prevent. A scalar answers at index 0, so a
59
+ * source that already flattened (mmcli, or a pre-existing fixture) still decodes.
60
+ */
61
+ export function rawStructMember(record, key, index) {
62
+ const value = record[key];
63
+ if (Array.isArray(value))
64
+ return value[index];
65
+ return index === 0 ? value : undefined;
66
+ }
67
+ export function rawNumberAt(record, key, index) {
68
+ const member = rawStructMember(record, key, index);
69
+ if (typeof member === 'number')
70
+ return Number.isFinite(member) ? member : undefined;
71
+ if (typeof member !== 'string' || member.trim() === '')
72
+ return undefined;
73
+ const parsed = Number.parseFloat(member);
74
+ return Number.isFinite(parsed) ? parsed : undefined;
75
+ }
76
+ export function rawBooleanAt(record, key, index) {
77
+ const member = rawStructMember(record, key, index);
78
+ return typeof member === 'boolean' ? member : undefined;
79
+ }
80
+ const XML_LEAF = /<([A-Za-z_][\w.-]*)>([^<]*)<\/\1>/g;
81
+ /**
82
+ * Flatten every leaf element of an XML body into `<body>.<Tag>` entries.
83
+ *
84
+ * A repeated tag — HiLink's `<NetworkMode>` list repeats `<Index>` once per mode —
85
+ * would otherwise overwrite its predecessors, so the second and later occurrences are
86
+ * suffixed `#2`, `#3`, …. Losing a repeat is exactly the silent drop this layer
87
+ * exists to prevent, and the suffix keeps the original tag name legible.
88
+ */
89
+ export function flattenXmlBody(body, name) {
90
+ const out = {};
91
+ const seen = new Map();
92
+ for (const match of body.matchAll(XML_LEAF)) {
93
+ const tag = match[1] ?? '';
94
+ const count = (seen.get(tag) ?? 0) + 1;
95
+ seen.set(tag, count);
96
+ out[rawKey(name, count === 1 ? tag : `${tag}#${count}`)] = (match[2] ?? '').trim();
97
+ }
98
+ return out;
99
+ }
100
+ /** Parse a JSON object body, or `undefined` when the bytes are not a JSON object. */
101
+ export function parseJsonObject(body) {
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(body);
105
+ }
106
+ catch (error) {
107
+ if (!(error instanceof SyntaxError)) {
108
+ throw error;
109
+ }
110
+ return undefined;
111
+ }
112
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
113
+ ? parsed
114
+ : undefined;
115
+ }
116
+ /** Parse a JSON object body into a flat record, or `undefined` when it is not one. */
117
+ export function parseJsonRecord(body) {
118
+ const parsed = parseJsonObject(body);
119
+ if (parsed === undefined) {
120
+ return undefined;
121
+ }
122
+ const out = {};
123
+ for (const [field, value] of Object.entries(parsed)) {
124
+ out[field] = normalizeRawValue(value);
125
+ }
126
+ return out;
127
+ }
128
+ /**
129
+ * A nested object has no flat representation, so its JSON text is kept.
130
+ *
131
+ * Serializing rather than dropping is the whole contract of this layer: a payload
132
+ * this record cannot model structurally is still a payload a diagnostician can read.
133
+ */
134
+ export function normalizeRawValue(value) {
135
+ if (value === null) {
136
+ return null;
137
+ }
138
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
139
+ return value;
140
+ }
141
+ if (Array.isArray(value)) {
142
+ return value.map(normalizeRawValue);
143
+ }
144
+ // A nested object has no flat representation; keep its JSON text rather than drop it.
145
+ return JSON.stringify(value);
146
+ }
@@ -0,0 +1,51 @@
1
+ import type { DeviceGeneration, EpochMillis, ObservationAuthority, ObservationEnvelope, SourceEpoch, StableKey } from '../domain/index.js';
2
+ import { type ObservationStaleReason, type ObservationUnavailableReason } from './envelope.js';
3
+ import type { MetricUnknownReason, NormalizedMetric } from './metric.js';
4
+ import type { MetricProvenance } from './provenance.js';
5
+ /** The envelope-level provenance every reading carries, whatever its state. */
6
+ export type EnvelopeProvenance = {
7
+ readonly stableKey: StableKey;
8
+ readonly generation: DeviceGeneration;
9
+ readonly source: string;
10
+ readonly sourceEpoch: SourceEpoch;
11
+ readonly observedAt: EpochMillis;
12
+ readonly authority: ObservationAuthority;
13
+ };
14
+ export declare function envelopeProvenance<T>(envelope: ObservationEnvelope<T>): EnvelopeProvenance;
15
+ type ReadingBase = {
16
+ readonly envelope: EnvelopeProvenance;
17
+ };
18
+ export type ObservationReading<T> = (ReadingBase & {
19
+ readonly state: 'fresh';
20
+ readonly value: T;
21
+ readonly provenance: MetricProvenance;
22
+ }) | (ReadingBase & {
23
+ readonly state: 'stale';
24
+ readonly value: T;
25
+ readonly since: EpochMillis;
26
+ readonly reason: ObservationStaleReason;
27
+ readonly provenance: MetricProvenance;
28
+ }) | (ReadingBase & {
29
+ readonly state: 'unavailable';
30
+ readonly since: EpochMillis;
31
+ readonly reason: ObservationUnavailableReason;
32
+ }) | (ReadingBase & {
33
+ readonly state: 'unknown';
34
+ readonly reason: MetricUnknownReason;
35
+ readonly provenance: MetricProvenance;
36
+ });
37
+ export declare const OBSERVATION_READING_STATES: readonly ["fresh", "stale", "unavailable", "unknown"];
38
+ export type ObservationReadingState = (typeof OBSERVATION_READING_STATES)[number];
39
+ /**
40
+ * Project one metric out of an envelope.
41
+ *
42
+ * An unknown metric inside a STALE envelope reads `unknown`, not `stale`: staleness is
43
+ * a statement about a value's age, and there is no value here to have aged. The
44
+ * envelope's own age remains readable through `reading.envelope.observedAt`.
45
+ */
46
+ export declare function readMetric<T, V>(envelope: ObservationEnvelope<T>, select: (value: T) => NormalizedMetric<V>): ObservationReading<V>;
47
+ /** Whether a reading carries a value a consumer may render. */
48
+ export declare function hasReadableValue<T>(reading: ObservationReading<T>): reading is Extract<ObservationReading<T>, {
49
+ readonly state: 'fresh' | 'stale';
50
+ }>;
51
+ export {};
@@ -0,0 +1,65 @@
1
+ // Reading — the projection a consumer renders from.
2
+ //
3
+ // It is the one place the envelope's freshness and the metric's knownness are folded
4
+ // together, and it keeps FOUR genuinely distinct outcomes rather than a value plus a
5
+ // flag:
6
+ //
7
+ // `fresh` — a current value.
8
+ // `stale` — a RETAINED value that has aged out, with when and why.
9
+ // `unavailable` — there is no observation at all; no value exists to report.
10
+ // `unknown` — there IS an observation and this field is not in it, with a reason
11
+ // that says whether the source CANNOT report it or merely DID NOT.
12
+ //
13
+ // They differ in shape, not only in label: `unavailable` and `unknown` carry no
14
+ // `value` field at all, so no consumer can read one off a state that has none, and
15
+ // `unavailable` carries no metric provenance because no metric was produced.
16
+ import { viewEnvelope, } from './envelope.js';
17
+ export function envelopeProvenance(envelope) {
18
+ return {
19
+ stableKey: envelope.stableKey,
20
+ generation: envelope.generation,
21
+ source: envelope.source,
22
+ sourceEpoch: envelope.sourceEpoch,
23
+ observedAt: envelope.observedAt,
24
+ authority: envelope.authority,
25
+ };
26
+ }
27
+ export const OBSERVATION_READING_STATES = ['fresh', 'stale', 'unavailable', 'unknown'];
28
+ /**
29
+ * Project one metric out of an envelope.
30
+ *
31
+ * An unknown metric inside a STALE envelope reads `unknown`, not `stale`: staleness is
32
+ * a statement about a value's age, and there is no value here to have aged. The
33
+ * envelope's own age remains readable through `reading.envelope.observedAt`.
34
+ */
35
+ export function readMetric(envelope, select) {
36
+ const base = { envelope: envelopeProvenance(envelope) };
37
+ const view = viewEnvelope(envelope);
38
+ if (view.kind === 'unavailable') {
39
+ return {
40
+ ...base,
41
+ state: 'unavailable',
42
+ since: view.freshness.since,
43
+ reason: view.freshness.reason,
44
+ };
45
+ }
46
+ const metric = select(view.value);
47
+ if (metric.state === 'unknown') {
48
+ return { ...base, state: 'unknown', reason: metric.reason, provenance: metric.provenance };
49
+ }
50
+ if (view.freshness.state === 'stale') {
51
+ return {
52
+ ...base,
53
+ state: 'stale',
54
+ value: metric.value,
55
+ since: view.freshness.since,
56
+ reason: view.freshness.reason,
57
+ provenance: metric.provenance,
58
+ };
59
+ }
60
+ return { ...base, state: 'fresh', value: metric.value, provenance: metric.provenance };
61
+ }
62
+ /** Whether a reading carries a value a consumer may render. */
63
+ export function hasReadableValue(reading) {
64
+ return reading.state === 'fresh' || reading.state === 'stale';
65
+ }
@@ -0,0 +1,10 @@
1
+ import type { ObservationEnvelope } from '../../domain/index.js';
2
+ import { type NormalizationContext } from '../envelope.js';
3
+ import type { NormalizedModemObservation } from '../model.js';
4
+ export type HilinkObservationInput = {
5
+ readonly status: string;
6
+ readonly signal: string;
7
+ readonly netModeList?: string;
8
+ readonly netMode?: string;
9
+ };
10
+ export declare function normalizeHilinkObservation(input: HilinkObservationInput, context: NormalizationContext): ObservationEnvelope<NormalizedModemObservation>;
@@ -0,0 +1,84 @@
1
+ // Huawei HiLink admin API → normalized observation.
2
+ //
3
+ // The decoding is the migrated `parseHilinkSignal` / `parseHilinkCapabilities`; this
4
+ // module adds provenance, retains both XML bodies field-for-field, and states the
5
+ // capability boundary honestly. HiLink is a ROUTER admin API, not a modem stack: it
6
+ // has no ModemManager state, no 3GPP registration state and no access-technology
7
+ // bitmask, so those read `unsupported` — a claim about this source, which stays true
8
+ // on the next poll — while a field the API could carry and did not reads
9
+ // `not-reported`.
10
+ //
11
+ // Its `<ConnectionStatus>` and `<SimStatus>` are NOT folded into `radio.registration`
12
+ // or `sim.presence`. They are vendor codes with vendor semantics and no migrated
13
+ // decoder claims them, so they stay verbatim in the diagnostics block instead of
14
+ // being guessed into a normalized field.
15
+ import { parseHilinkCapabilities, parseHilinkSignal } from '../../hardware/router-parsers.js';
16
+ import { freshObservation, metricProvenance } from '../envelope.js';
17
+ import { knownMetric, metricFromRouterSignal, metricUnknownReasonFromRouter, unknownMetric, } from '../metric.js';
18
+ import { createObservationDiagnostics } from '../provenance.js';
19
+ import { flattenXmlBody, hasRawField, mergeRawRecords, rawKey } from '../raw.js';
20
+ import { routerHardware, routerSim, unsupportedQualityRecent, unsupportedRadioMetric, } from './router-shared.js';
21
+ const SOURCE = 'huawei-hilink';
22
+ const STATUS = 'monitoring-status';
23
+ const SIGNAL = 'device-signal';
24
+ const NET_MODE_LIST = 'net-mode-list';
25
+ const NET_MODE = 'net-mode';
26
+ /** HiLink's own SIM presence code. Named as evidence, never decoded into a presence. */
27
+ const SIM_STATUS = rawKey(STATUS, 'SimStatus');
28
+ const AUTH_REFUSAL_CODE = '125002';
29
+ export function normalizeHilinkObservation(input, context) {
30
+ const raw = mergeRawRecords(flattenXmlBody(input.status, STATUS), flattenXmlBody(input.signal, SIGNAL), flattenXmlBody(input.netModeList ?? '', NET_MODE_LIST), flattenXmlBody(input.netMode ?? '', NET_MODE));
31
+ const notes = [];
32
+ const consumed = [];
33
+ const provenance = (fields, authority) => {
34
+ consumed.push(...fields);
35
+ return authority === undefined
36
+ ? metricProvenance(SOURCE, context, fields)
37
+ : metricProvenance(SOURCE, context, fields, authority);
38
+ };
39
+ for (const [body, text] of [
40
+ [STATUS, input.status],
41
+ [SIGNAL, input.signal],
42
+ ]) {
43
+ if (text.includes(`<code>${AUTH_REFUSAL_CODE}</code>`)) {
44
+ notes.push({ code: 'auth-expired', field: rawKey(body, 'code') });
45
+ }
46
+ }
47
+ const signalModel = parseHilinkSignal({ status: input.status, signal: input.signal });
48
+ const capabilities = parseHilinkCapabilities({
49
+ netModeList: input.netModeList ?? '',
50
+ ...(input.netMode === undefined ? {} : { netMode: input.netMode }),
51
+ });
52
+ return freshObservation(SOURCE, context, {
53
+ source: SOURCE,
54
+ hardware: routerHardware(provenance, undefined),
55
+ radio: {
56
+ modemState: unsupportedRadioMetric(provenance),
57
+ registration: unsupportedRadioMetric(provenance),
58
+ accessTechnologies: unknownMetric('unsupported', provenance([])),
59
+ modeLabel: normalizeModeLabel(capabilities.net_mode, provenance),
60
+ },
61
+ signal: {
62
+ quality: unknownMetric('unsupported', provenance([])),
63
+ qualityRecent: unsupportedQualityRecent(provenance),
64
+ bars: metricFromRouterSignal(signalModel.bars, provenance([rawKey(STATUS, 'SignalIcon')], 'derived')),
65
+ maxBars: metricFromRouterSignal(signalModel.max_bars, provenance([rawKey(STATUS, 'maxsignal')], 'derived')),
66
+ dbm: metricFromRouterSignal(signalModel.dbm, provenance([rawKey(SIGNAL, 'rssi')])),
67
+ rsrp: metricFromRouterSignal(signalModel.rsrp, provenance([rawKey(SIGNAL, 'rsrp')])),
68
+ rsrq: metricFromRouterSignal(signalModel.rsrq, provenance([rawKey(SIGNAL, 'rsrq')])),
69
+ snr: metricFromRouterSignal(signalModel.snr, provenance([])),
70
+ sinr: metricFromRouterSignal(signalModel.sinr, provenance([rawKey(SIGNAL, 'sinr')])),
71
+ },
72
+ sim: routerSim(provenance, hasRawField(raw, SIM_STATUS) ? SIM_STATUS : undefined),
73
+ diagnostics: createObservationDiagnostics({ source: SOURCE, raw, consumed, notes }),
74
+ });
75
+ }
76
+ function normalizeModeLabel(capability, provenance) {
77
+ const source = provenance([rawKey(NET_MODE, 'NetworkMode')]);
78
+ if (capability.state === 'unavailable') {
79
+ return unknownMetric(metricUnknownReasonFromRouter(capability.reason), source);
80
+ }
81
+ return capability.current === undefined
82
+ ? unknownMetric('not-reported', source)
83
+ : knownMetric(capability.current, source);
84
+ }