@ceralive/modem-control 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/README.md +341 -0
  2. package/dist/backend/at-lease.d.ts +59 -0
  3. package/dist/backend/at-lease.js +118 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +101 -0
  61. package/dist/backend/transition-preconditions.js +132 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +216 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +20 -0
  146. package/dist/index.js +27 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +61 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operation-ids.d.ts +2 -0
  196. package/dist/operation-ids.js +26 -0
  197. package/dist/operations/contracts.d.ts +72 -0
  198. package/dist/operations/contracts.js +1 -0
  199. package/dist/operations/index.d.ts +1 -0
  200. package/dist/operations/index.js +1 -0
  201. package/dist/operations/operation-engine.d.ts +16 -0
  202. package/dist/operations/operation-engine.js +194 -0
  203. package/dist/ports/index.d.ts +12 -0
  204. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  205. package/dist/ports/location.d.ts +87 -0
  206. package/dist/ports/location.js +36 -0
  207. package/dist/ports/modem-manager.d.ts +89 -0
  208. package/dist/ports/modem-manager.js +9 -0
  209. package/dist/ports/mutation-admission.d.ts +27 -0
  210. package/dist/ports/mutation-admission.js +9 -0
  211. package/dist/ports/network-manager.d.ts +68 -0
  212. package/dist/ports/network-manager.js +13 -0
  213. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  214. package/dist/ports/observation.js +7 -0
  215. package/dist/ports/ops.d.ts +44 -0
  216. package/dist/ports/ops.js +16 -0
  217. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  218. package/dist/ports/receipts.js +9 -0
  219. package/dist/ports/reconcile.d.ts +33 -0
  220. package/dist/ports/reconcile.js +200 -0
  221. package/dist/ports/resource-ownership.d.ts +29 -0
  222. package/dist/ports/resource-ownership.js +1 -0
  223. package/dist/ports/router.d.ts +19 -0
  224. package/dist/ports/router.js +7 -0
  225. package/dist/ports/sms.d.ts +64 -0
  226. package/dist/ports/sms.js +24 -0
  227. package/dist/ports/uhubctl.d.ts +6 -0
  228. package/dist/ports/uhubctl.js +1 -0
  229. package/dist/providers/contracts.d.ts +124 -0
  230. package/dist/providers/contracts.js +10 -0
  231. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  232. package/dist/providers/huawei-hilink/index.js +2 -0
  233. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  234. package/dist/providers/huawei-hilink/operations.js +56 -0
  235. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  236. package/dist/providers/huawei-hilink/provider.js +76 -0
  237. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  238. package/dist/providers/huawei-hilink/runtime.js +171 -0
  239. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  240. package/dist/providers/huawei-hilink/session.js +120 -0
  241. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  242. package/dist/providers/huawei-hilink/transport.js +1 -0
  243. package/dist/providers/index.d.ts +8 -0
  244. package/dist/providers/index.js +8 -0
  245. package/dist/providers/matcher.d.ts +3 -0
  246. package/dist/providers/matcher.js +205 -0
  247. package/dist/providers/modem-manager/errors.d.ts +7 -0
  248. package/dist/providers/modem-manager/errors.js +37 -0
  249. package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
  250. package/dist/providers/modem-manager/generic-operations.js +209 -0
  251. package/dist/providers/modem-manager/index.d.ts +4 -0
  252. package/dist/providers/modem-manager/index.js +4 -0
  253. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  254. package/dist/providers/modem-manager/module-operations.js +118 -0
  255. package/dist/providers/modem-manager/provider.d.ts +41 -0
  256. package/dist/providers/modem-manager/provider.js +155 -0
  257. package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
  258. package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
  259. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  260. package/dist/providers/modem-manager/snapshot.js +204 -0
  261. package/dist/providers/modem-manager/types.d.ts +137 -0
  262. package/dist/providers/modem-manager/types.js +1 -0
  263. package/dist/providers/network-manager/adapter.d.ts +71 -0
  264. package/dist/providers/network-manager/adapter.js +348 -0
  265. package/dist/providers/network-manager/index.d.ts +2 -0
  266. package/dist/providers/network-manager/index.js +2 -0
  267. package/dist/providers/network-manager/types.d.ts +171 -0
  268. package/dist/providers/network-manager/types.js +77 -0
  269. package/dist/providers/registry.d.ts +13 -0
  270. package/dist/providers/registry.js +33 -0
  271. package/dist/providers/ufi-himi/index.d.ts +6 -0
  272. package/dist/providers/ufi-himi/index.js +6 -0
  273. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  274. package/dist/providers/ufi-himi/operations.js +66 -0
  275. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  276. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  277. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  278. package/dist/providers/ufi-himi/provider.js +204 -0
  279. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  280. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  281. package/dist/providers/ufi-himi/session.d.ts +46 -0
  282. package/dist/providers/ufi-himi/session.js +92 -0
  283. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  284. package/dist/providers/ufi-himi/transport.js +25 -0
  285. package/dist/providers/zte-goform/index.d.ts +2 -0
  286. package/dist/providers/zte-goform/index.js +2 -0
  287. package/dist/providers/zte-goform/provider.d.ts +56 -0
  288. package/dist/providers/zte-goform/provider.js +101 -0
  289. package/dist/providers/zte-goform/session.d.ts +23 -0
  290. package/dist/providers/zte-goform/session.js +197 -0
  291. package/dist/providers/zte-goform/transport.d.ts +16 -0
  292. package/dist/providers/zte-goform/transport.js +1 -0
  293. package/dist/radio/band-truth.d.ts +50 -0
  294. package/dist/radio/band-truth.js +92 -0
  295. package/dist/radio/index.d.ts +3 -0
  296. package/dist/radio/index.js +10 -0
  297. package/dist/radio/mode-combinations.d.ts +88 -0
  298. package/dist/radio/mode-combinations.js +198 -0
  299. package/dist/radio/mode-truth.d.ts +67 -0
  300. package/dist/radio/mode-truth.js +112 -0
  301. package/dist/redact.d.ts +15 -0
  302. package/dist/redact.js +189 -0
  303. package/dist/safety/composition-root.d.ts +28 -0
  304. package/dist/safety/composition-root.js +65 -0
  305. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  306. package/dist/safety/flock-resource-ownership.js +138 -0
  307. package/dist/safety/index.d.ts +2 -0
  308. package/dist/safety/index.js +2 -0
  309. package/dist/sms/dbus-messaging.d.ts +17 -0
  310. package/dist/sms/dbus-messaging.js +185 -0
  311. package/dist/sms/inbox-store.d.ts +10 -0
  312. package/dist/sms/inbox-store.js +82 -0
  313. package/dist/sms/index.d.ts +4 -0
  314. package/dist/sms/index.js +10 -0
  315. package/dist/sms/mmcli-parse.d.ts +54 -0
  316. package/dist/sms/mmcli-parse.js +224 -0
  317. package/dist/sms/normalize.d.ts +42 -0
  318. package/dist/sms/normalize.js +95 -0
  319. package/dist/testing/domain-fakes.d.ts +58 -0
  320. package/dist/testing/domain-fakes.js +98 -0
  321. package/dist/testing/index.d.ts +2 -0
  322. package/dist/testing/index.js +17 -0
  323. package/dist/testing/provider-fakes.d.ts +45 -0
  324. package/dist/testing/provider-fakes.js +64 -0
  325. package/dist/transport/calls.d.ts +9 -0
  326. package/dist/transport/calls.js +88 -0
  327. package/dist/transport/codec.d.ts +3 -0
  328. package/dist/transport/codec.js +207 -0
  329. package/dist/transport/dbus-native.d.ts +57 -0
  330. package/dist/transport/dbus-native.js +17 -0
  331. package/dist/transport/errors.d.ts +21 -0
  332. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  333. package/dist/transport/index.d.ts +4 -0
  334. package/dist/transport/index.js +9 -0
  335. package/dist/transport/signals.d.ts +15 -0
  336. package/dist/transport/signals.js +123 -0
  337. package/dist/transport/signature.d.ts +7 -0
  338. package/dist/transport/signature.js +94 -0
  339. package/dist/transport/transport.d.ts +2 -0
  340. package/dist/transport/transport.js +202 -0
  341. package/dist/transport/types.d.ts +61 -0
  342. package/dist/transport/types.js +19 -0
  343. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  344. package/dist/usb-mode/catalog-schema.js +97 -0
  345. package/dist/usb-mode/catalog.d.ts +21 -0
  346. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  347. package/dist/usb-mode/certified-catalog.json +67 -0
  348. package/dist/usb-mode/index.d.ts +6 -0
  349. package/dist/usb-mode/index.js +16 -0
  350. package/dist/usb-mode/ingestion.d.ts +111 -0
  351. package/dist/usb-mode/ingestion.js +187 -0
  352. package/dist/usb-mode/promotion-review.d.ts +21 -0
  353. package/dist/usb-mode/promotion-review.js +87 -0
  354. package/dist/usb-mode/runtime-capability.d.ts +59 -0
  355. package/dist/usb-mode/runtime-capability.js +157 -0
  356. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  357. package/dist/usb-mode/usb-devices-parse.js +157 -0
  358. package/dist/ussd/calls.d.ts +32 -0
  359. package/dist/ussd/calls.js +96 -0
  360. package/dist/ussd/index.d.ts +5 -0
  361. package/dist/ussd/index.js +12 -0
  362. package/dist/ussd/mm-ussd.d.ts +37 -0
  363. package/dist/ussd/mm-ussd.js +205 -0
  364. package/dist/ussd/refusal.d.ts +53 -0
  365. package/dist/ussd/refusal.js +154 -0
  366. package/dist/ussd/registration.d.ts +20 -0
  367. package/dist/ussd/registration.js +101 -0
  368. package/dist/ussd/session.d.ts +106 -0
  369. package/dist/ussd/session.js +163 -0
  370. package/package.json +38 -4
  371. package/src/backend/at-lease.test.ts +0 -106
  372. package/src/backend/at-lease.ts +0 -158
  373. package/src/backend/cell-info.test.ts +0 -154
  374. package/src/backend/cell-info.ts +0 -160
  375. package/src/backend/device-classifier.test.ts +0 -168
  376. package/src/backend/device-classifier.ts +0 -248
  377. package/src/backend/enrichment.ts +0 -96
  378. package/src/backend/features.test.ts +0 -162
  379. package/src/backend/features.ts +0 -179
  380. package/src/backend/identity-ladder.test.ts +0 -117
  381. package/src/backend/identity-ladder.ts +0 -221
  382. package/src/backend/identity-registry.test.ts +0 -89
  383. package/src/backend/identity-registry.ts +0 -151
  384. package/src/backend/index.ts +0 -236
  385. package/src/backend/lifecycle-interlock.ts +0 -38
  386. package/src/backend/managed-objects.ts +0 -108
  387. package/src/backend/mapping.ts +0 -160
  388. package/src/backend/mm-backend.ts +0 -191
  389. package/src/backend/mm-mutations.ts +0 -228
  390. package/src/backend/modem-actor.test.ts +0 -95
  391. package/src/backend/modem-actor.ts +0 -112
  392. package/src/backend/nm-auto-apn.ts +0 -161
  393. package/src/backend/nm-gsm-fields.ts +0 -122
  394. package/src/backend/nmcli-nm-port.ts +0 -228
  395. package/src/backend/nmcli-runner.ts +0 -52
  396. package/src/backend/observer.ts +0 -297
  397. package/src/backend/power-contract.test.ts +0 -40
  398. package/src/backend/power-contract.ts +0 -83
  399. package/src/backend/recovery-attribution.test.ts +0 -102
  400. package/src/backend/recovery-attribution.ts +0 -86
  401. package/src/backend/recovery-budget.test.ts +0 -64
  402. package/src/backend/recovery-budget.ts +0 -84
  403. package/src/backend/recovery-ladder.test.ts +0 -257
  404. package/src/backend/recovery-ladder.ts +0 -249
  405. package/src/backend/router-ethernet.test.ts +0 -71
  406. package/src/backend/router-ethernet.ts +0 -90
  407. package/src/backend/row-store.ts +0 -105
  408. package/src/backend/signal-setup.ts +0 -112
  409. package/src/backend/sim-unlock.ts +0 -193
  410. package/src/backend/transition-preconditions.ts +0 -149
  411. package/src/backend/uhubctl-power-hook.test.ts +0 -274
  412. package/src/backend/uhubctl-power-hook.ts +0 -377
  413. package/src/backend/usage/accounting.test.ts +0 -147
  414. package/src/backend/usage/accounting.ts +0 -123
  415. package/src/backend/usage/billing-cycle.test.ts +0 -62
  416. package/src/backend/usage/billing-cycle.ts +0 -45
  417. package/src/backend/usage/index.ts +0 -60
  418. package/src/backend/usage/policy-store.test.ts +0 -164
  419. package/src/backend/usage/policy-store.ts +0 -216
  420. package/src/backend/usage/policy-write.test.ts +0 -198
  421. package/src/backend/usage/policy-write.ts +0 -207
  422. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  423. package/src/backend/usage/sampler.test.ts +0 -327
  424. package/src/backend/usage/sampler.ts +0 -282
  425. package/src/backend/usage/store.test.ts +0 -148
  426. package/src/backend/usage/store.ts +0 -177
  427. package/src/backend/usb-enumerator.test.ts +0 -87
  428. package/src/backend/usb-enumerator.ts +0 -181
  429. package/src/backend/usb-mode-transition.test.ts +0 -323
  430. package/src/backend/usb-mode-transition.ts +0 -253
  431. package/src/domain/brand.ts +0 -29
  432. package/src/domain/errors.ts +0 -77
  433. package/src/domain/guards.test.ts +0 -218
  434. package/src/domain/guards.ts +0 -144
  435. package/src/domain/identity.test.ts +0 -83
  436. package/src/domain/identity.ts +0 -165
  437. package/src/domain/index.ts +0 -12
  438. package/src/domain/snapshot.test.ts +0 -266
  439. package/src/domain/snapshot.ts +0 -120
  440. package/src/index.test.ts +0 -6
  441. package/src/index.ts +0 -15
  442. package/src/ports/README.md +0 -61
  443. package/src/ports/forbidden-surface.test.ts +0 -241
  444. package/src/ports/modem-manager.ts +0 -72
  445. package/src/ports/network-manager.ts +0 -87
  446. package/src/ports/ops.ts +0 -60
  447. package/src/ports/ops.type-test.ts +0 -39
  448. package/src/ports/receipts.test.ts +0 -153
  449. package/src/ports/reconcile.test.ts +0 -152
  450. package/src/ports/reconcile.ts +0 -338
  451. package/src/ports/router.ts +0 -29
  452. package/src/redact.test.ts +0 -82
  453. package/src/redact.ts +0 -73
  454. package/src/transport/README.md +0 -65
  455. package/src/transport/calls.ts +0 -113
  456. package/src/transport/characterization.test.ts +0 -260
  457. package/src/transport/codec.test.ts +0 -118
  458. package/src/transport/codec.ts +0 -240
  459. package/src/transport/conformance-python.test.ts +0 -152
  460. package/src/transport/conformance-same-lib.test.ts +0 -115
  461. package/src/transport/dbus-native-lib.d.ts +0 -19
  462. package/src/transport/dbus-native.ts +0 -85
  463. package/src/transport/index.ts +0 -30
  464. package/src/transport/no-library-leak.test.ts +0 -61
  465. package/src/transport/reliability.test.ts +0 -173
  466. package/src/transport/signals.ts +0 -150
  467. package/src/transport/signature.ts +0 -110
  468. package/src/transport/test-support/fake-service.ts +0 -168
  469. package/src/transport/test-support/independent-producer.py +0 -110
  470. package/src/transport/test-support/private-bus.ts +0 -66
  471. package/src/transport/transport.ts +0 -250
  472. package/src/transport/types.ts +0 -118
  473. package/src/usb-mode/catalog-schema.test.ts +0 -181
  474. package/src/usb-mode/catalog-schema.ts +0 -113
  475. package/src/usb-mode/certified-catalog.json +0 -67
  476. package/src/usb-mode/index.ts +0 -58
  477. package/src/usb-mode/ingestion.test.ts +0 -268
  478. package/src/usb-mode/ingestion.ts +0 -297
  479. package/src/usb-mode/promotion-review.ts +0 -117
  480. package/src/usb-mode/usb-devices-parse.ts +0 -196
@@ -0,0 +1,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
+ }
@@ -0,0 +1,18 @@
1
+ import { type ObservationEnvelope } from '../../domain/index.js';
2
+ import { type NormalizationContext } from '../envelope.js';
3
+ import type { NormalizedModemObservation } from '../model.js';
4
+ import { type RawFieldValue } from '../provenance.js';
5
+ /**
6
+ * The decoded ModemManager property records, one per D-Bus interface.
7
+ *
8
+ * A body left out is a body nobody read, and reads as `not-observed` rather than as a
9
+ * modem that reports nothing — that difference decides whether a consumer retries or
10
+ * hides a control.
11
+ */
12
+ export type ModemManagerObservationInput = {
13
+ readonly modem?: Readonly<Record<string, RawFieldValue>>;
14
+ readonly modem3gpp?: Readonly<Record<string, RawFieldValue>>;
15
+ readonly sim?: Readonly<Record<string, RawFieldValue>>;
16
+ readonly signal?: Readonly<Record<string, RawFieldValue>>;
17
+ };
18
+ export declare function normalizeModemManagerObservation(input: ModemManagerObservationInput, context: NormalizationContext): ObservationEnvelope<NormalizedModemObservation>;
@@ -0,0 +1,239 @@
1
+ // ModemManager → normalized observation.
2
+ //
3
+ // Every decode here is the MIGRATED logic (`domain/mm-enums.ts`,
4
+ // `domain/modem-presentation.ts`, `hardware/router-parsers.ts` for SIM presence) —
5
+ // this module adds provenance and reason-carrying unknowns, and decodes nothing
6
+ // itself. Where a decoder answers `'unknown'` or `undefined`, the distinction it
7
+ // cannot make is made here from the RAW record: a field that was never in the payload
8
+ // is `not-reported`, an interface nobody read is `not-observed`, and a field that WAS
9
+ // present but decoded to nothing is `malformed` with a diagnostic note. Collapsing
10
+ // those three into one absent value is precisely the loss this layer exists to stop.
11
+ import { decodeEsimStatus, decodeMmAccessTechnologies, decodeMmState, decodeRegistrationState, decodeSimType, decodeStateFailedReason, decodeUnlockRequired, modeMaskToLabel, modemHardwareLabel, modemHardwareName, } from '../../domain/index.js';
12
+ import { readSimPresence } from '../../hardware/router-parsers.js';
13
+ import { freshObservation, metricProvenance } from '../envelope.js';
14
+ import { knownMetric, unknownMetric } from '../metric.js';
15
+ import { createObservationDiagnostics, } from '../provenance.js';
16
+ import { hasRawField, mergeRawRecords, prefixRawRecord, rawBooleanAt, rawKey, rawNumber, rawNumberAt, rawString, rawStringArray, } from '../raw.js';
17
+ const SOURCE = 'modemmanager';
18
+ const MODEM = 'Modem';
19
+ const MODEM3GPP = 'Modem3gpp';
20
+ const SIM = 'Sim';
21
+ const SIGNAL = 'Signal';
22
+ export function normalizeModemManagerObservation(input, context) {
23
+ const raw = mergeRawRecords(prefixRawRecord(MODEM, input.modem), prefixRawRecord(MODEM3GPP, input.modem3gpp), prefixRawRecord(SIM, input.sim), prefixRawRecord(SIGNAL, input.signal));
24
+ const notes = [];
25
+ const consumed = [];
26
+ const provenance = (...fields) => {
27
+ consumed.push(...fields);
28
+ return metricProvenance(SOURCE, context, fields);
29
+ };
30
+ const hardware = normalizeHardware(raw, provenance);
31
+ const radio = normalizeRadio(raw, provenance, notes);
32
+ const signal = normalizeSignal(raw, input.signal !== undefined, provenance);
33
+ const sim = normalizeSim(raw, input.sim !== undefined, provenance, notes);
34
+ return freshObservation(SOURCE, context, {
35
+ source: SOURCE,
36
+ hardware,
37
+ radio,
38
+ signal,
39
+ sim,
40
+ diagnostics: createObservationDiagnostics({ source: SOURCE, raw, consumed, notes }),
41
+ });
42
+ }
43
+ const MODEL = rawKey(MODEM, 'Model');
44
+ const MANUFACTURER = rawKey(MODEM, 'Manufacturer');
45
+ const REVISION = rawKey(MODEM, 'Revision');
46
+ const EQUIPMENT_ID = rawKey(MODEM, 'EquipmentIdentifier');
47
+ const STATE = rawKey(MODEM, 'State');
48
+ const CURRENT_MODES = rawKey(MODEM, 'CurrentModes');
49
+ const ACCESS_TECHNOLOGIES = rawKey(MODEM, 'AccessTechnologies');
50
+ const SIGNAL_QUALITY = rawKey(MODEM, 'SignalQuality');
51
+ const SIM_PATH = rawKey(MODEM, 'Sim');
52
+ const SIM_SLOTS = rawKey(MODEM, 'SimSlots');
53
+ const FAILED_REASON = rawKey(MODEM, 'StateFailedReason');
54
+ const UNLOCK_REQUIRED = rawKey(MODEM, 'UnlockRequired');
55
+ const REGISTRATION_STATE = rawKey(MODEM3GPP, 'RegistrationState');
56
+ const SIM_TYPE = rawKey(SIM, 'SimType');
57
+ const ESIM_STATUS = rawKey(SIM, 'EsimStatus');
58
+ const RSSI = rawKey(SIGNAL, 'rssi');
59
+ const RSRP = rawKey(SIGNAL, 'rsrp');
60
+ const RSRQ = rawKey(SIGNAL, 'rsrq');
61
+ const SNR = rawKey(SIGNAL, 'snr');
62
+ function hardwareIdentity(raw) {
63
+ const model = rawString(raw, MODEL);
64
+ const manufacturer = rawString(raw, MANUFACTURER);
65
+ const firmwareRevision = rawString(raw, REVISION);
66
+ const equipmentId = rawString(raw, EQUIPMENT_ID);
67
+ return {
68
+ ...(model === undefined ? {} : { model }),
69
+ ...(manufacturer === undefined ? {} : { manufacturer }),
70
+ ...(firmwareRevision === undefined ? {} : { firmwareRevision }),
71
+ ...(equipmentId === undefined ? {} : { equipmentId }),
72
+ };
73
+ }
74
+ function normalizeHardware(raw, provenance) {
75
+ const identity = hardwareIdentity(raw);
76
+ const named = identity.model !== undefined ||
77
+ identity.manufacturer !== undefined ||
78
+ identity.firmwareRevision !== undefined;
79
+ const labelProvenance = provenance(MODEL, MANUFACTURER, REVISION);
80
+ const nameProvenance = provenance(MODEL, MANUFACTURER, REVISION, EQUIPMENT_ID);
81
+ return {
82
+ label: named
83
+ ? knownMetric(modemHardwareLabel(identity), labelProvenance)
84
+ : unknownMetric('not-reported', labelProvenance),
85
+ displayName: named
86
+ ? knownMetric(modemHardwareName(identity), nameProvenance)
87
+ : unknownMetric('not-reported', nameProvenance),
88
+ };
89
+ }
90
+ function normalizeRadio(raw, provenance, notes) {
91
+ return {
92
+ modemState: decodedLabel(raw, STATE, decodeMmState, provenance, notes),
93
+ registration: decodedLabel(raw, REGISTRATION_STATE, decodeRegistrationState, provenance, notes),
94
+ accessTechnologies: normalizeAccessTechnologies(raw, provenance, notes),
95
+ modeLabel: normalizeModeLabel(raw, provenance, notes),
96
+ };
97
+ }
98
+ /** A decoder whose "I could not place this" answer is the literal string `unknown`. */
99
+ function decodedLabel(raw, key, decode, provenance, notes) {
100
+ const source = provenance(key);
101
+ if (!hasRawField(raw, key)) {
102
+ return unknownMetric('not-reported', source);
103
+ }
104
+ const decoded = decode(rawNumber(raw, key));
105
+ if (decoded === 'unknown') {
106
+ notes.push({ code: 'field-shape-unrecognized', field: key });
107
+ return unknownMetric('malformed', source);
108
+ }
109
+ return knownMetric(decoded, source);
110
+ }
111
+ function normalizeAccessTechnologies(raw, provenance, notes) {
112
+ const source = provenance(ACCESS_TECHNOLOGIES);
113
+ if (!hasRawField(raw, ACCESS_TECHNOLOGIES)) {
114
+ return unknownMetric('not-reported', source);
115
+ }
116
+ const mask = rawNumber(raw, ACCESS_TECHNOLOGIES);
117
+ // MM's 0 is `UNKNOWN` — the modem did not say, which is not a malformed answer.
118
+ if (mask === undefined || mask <= 0) {
119
+ return unknownMetric('not-reported', source);
120
+ }
121
+ const decoded = [...decodeMmAccessTechnologies(mask)].sort();
122
+ if (decoded.length === 0) {
123
+ notes.push({ code: 'field-shape-unrecognized', field: ACCESS_TECHNOLOGIES });
124
+ return unknownMetric('malformed', source);
125
+ }
126
+ return knownMetric(decoded, source);
127
+ }
128
+ function normalizeModeLabel(raw, provenance, notes) {
129
+ const source = provenance(CURRENT_MODES);
130
+ if (!hasRawField(raw, CURRENT_MODES)) {
131
+ return unknownMetric('not-reported', source);
132
+ }
133
+ // `CurrentModes` is a `(uu)`; the ALLOWED mask is member 0. A flattened scalar
134
+ // (mmcli's shape) decodes at the same index, so both retentions work here.
135
+ const mask = rawNumberAt(raw, CURRENT_MODES, 0);
136
+ if (mask === undefined || mask <= 0) {
137
+ return unknownMetric('not-reported', source);
138
+ }
139
+ const label = modeMaskToLabel(mask);
140
+ if (label === undefined) {
141
+ notes.push({ code: 'field-shape-unrecognized', field: CURRENT_MODES });
142
+ return unknownMetric('malformed', source);
143
+ }
144
+ return knownMetric(label, source);
145
+ }
146
+ function normalizeSignal(raw, signalRead, provenance) {
147
+ const missing = signalRead ? 'not-reported' : 'not-observed';
148
+ const extended = (key) => {
149
+ const source = provenance(key);
150
+ const value = rawNumber(raw, key);
151
+ return value === undefined
152
+ ? unknownMetric(hasRawField(raw, key) ? 'malformed' : missing, source)
153
+ : knownMetric(value, source);
154
+ };
155
+ // `SignalQuality` is a `(ub)`: percentage at 0, "measured recently" at 1. Both are
156
+ // claimed; a source that retained only the percentage answers `not-reported` for the
157
+ // flag, which is a claim about the READ, never about ModemManager's capability.
158
+ const quality = rawNumberAt(raw, SIGNAL_QUALITY, 0);
159
+ const recent = rawBooleanAt(raw, SIGNAL_QUALITY, 1);
160
+ const qualityProvenance = provenance(SIGNAL_QUALITY);
161
+ return {
162
+ quality: quality === undefined
163
+ ? unknownMetric('not-reported', qualityProvenance)
164
+ : knownMetric(quality, qualityProvenance),
165
+ qualityRecent: recent === undefined
166
+ ? unknownMetric('not-reported', provenance(SIGNAL_QUALITY))
167
+ : knownMetric(recent, provenance(SIGNAL_QUALITY)),
168
+ // ModemManager reports a percentage and no bar scale. Deriving bars from the
169
+ // percentage would be inventing a reading, so this is a capability claim.
170
+ bars: unknownMetric('unsupported', metricProvenanceEmpty(provenance)),
171
+ maxBars: unknownMetric('unsupported', metricProvenanceEmpty(provenance)),
172
+ dbm: extended(RSSI),
173
+ rsrp: extended(RSRP),
174
+ rsrq: extended(RSRQ),
175
+ snr: extended(SNR),
176
+ // `Modem.Signal` exposes rssi/rsrp/rsrq/snr/ecio/io/rscp — there is no SINR member.
177
+ sinr: unknownMetric('unsupported', metricProvenanceEmpty(provenance)),
178
+ };
179
+ }
180
+ /** Provenance for a metric no raw field backs, which is what `unsupported` means. */
181
+ function metricProvenanceEmpty(provenance) {
182
+ return provenance();
183
+ }
184
+ /**
185
+ * `Modem.StateFailedReason` in the spelling the presence rule matches.
186
+ *
187
+ * D-Bus types it `u`, so a provider retaining the property verbatim holds a NUMBER,
188
+ * while the migrated rule matches the mmcli STRING. Both are accepted here — the
189
+ * number through the enum decoder, the string as it stands — because the two transports
190
+ * for the same fact must not disagree about whether a modem has a SIM. An
191
+ * unrecognized number decodes to `undefined` and simply proves nothing.
192
+ */
193
+ function failedReasonOf(raw) {
194
+ const numeric = rawNumber(raw, FAILED_REASON);
195
+ if (numeric !== undefined)
196
+ return decodeStateFailedReason(numeric);
197
+ return rawString(raw, FAILED_REASON);
198
+ }
199
+ function simPresenceFacts(raw) {
200
+ const sim = rawString(raw, SIM_PATH);
201
+ const simSlots = rawStringArray(raw, SIM_SLOTS);
202
+ const failedReason = failedReasonOf(raw);
203
+ return {
204
+ ...(sim === undefined ? {} : { sim }),
205
+ ...(simSlots === undefined ? {} : { simSlots }),
206
+ ...(failedReason === undefined ? {} : { failedReason }),
207
+ };
208
+ }
209
+ function normalizeSim(raw, simRead, provenance, notes) {
210
+ const presenceProvenance = provenance(SIM_PATH, SIM_SLOTS, FAILED_REASON);
211
+ // `absent` here comes from `state-failed-reason` evidence and from nothing else.
212
+ // A blank `Sim` object path with no failure reason stays `unknown`: ModemManager
213
+ // reports `/` while a modem is initializing and while a slot switch is in flight,
214
+ // so reading it as "no SIM" would report an absent SIM on a modem holding one.
215
+ const { presence, evidence } = readSimPresence(simPresenceFacts(raw));
216
+ const missing = simRead ? 'not-reported' : 'not-observed';
217
+ return {
218
+ presence: presence === 'unknown'
219
+ ? unknownMetric('not-reported', presenceProvenance)
220
+ : knownMetric(presence, presenceProvenance),
221
+ presenceEvidence: evidence,
222
+ lockRequired: decodedOptional(raw, UNLOCK_REQUIRED, decodeUnlockRequired, provenance, notes),
223
+ kind: decodedOptional(raw, SIM_TYPE, decodeSimType, provenance, notes, missing),
224
+ esimStatus: decodedOptional(raw, ESIM_STATUS, decodeEsimStatus, provenance, notes, missing),
225
+ };
226
+ }
227
+ /** A decoder whose "I could not place this" answer is `undefined`. */
228
+ function decodedOptional(raw, key, decode, provenance, notes, absentReason = 'not-reported') {
229
+ const source = provenance(key);
230
+ if (!hasRawField(raw, key)) {
231
+ return unknownMetric(absentReason, source);
232
+ }
233
+ const decoded = decode(rawNumber(raw, key));
234
+ if (decoded === undefined) {
235
+ notes.push({ code: 'field-shape-unrecognized', field: key });
236
+ return unknownMetric('malformed', source);
237
+ }
238
+ return knownMetric(decoded, source);
239
+ }
@@ -0,0 +1,29 @@
1
+ import { type ObservationAuthority } from '../../domain/index.js';
2
+ import { type NormalizedMetric } from '../metric.js';
3
+ import type { NormalizedHardware, NormalizedSim } from '../model.js';
4
+ import type { MetricProvenance } from '../provenance.js';
5
+ export type RouterProvenance = (fields: readonly string[], authority?: ObservationAuthority) => MetricProvenance;
6
+ export declare function unsupportedRadioMetric(provenance: RouterProvenance): NormalizedMetric<string>;
7
+ /**
8
+ * The SIM block every router source produces.
9
+ *
10
+ * `vendorPresenceField` NAMES the vendor's own presence code when the payload carried
11
+ * one, so the evidence records that a code exists and was deliberately not decoded —
12
+ * distinct from `no-evidence`, which says the payload offered nothing at all. Neither
13
+ * one is ever `absent`: claiming absence from a vendor code whose semantics no
14
+ * migrated decoder covers is precisely the invented reading this layer refuses.
15
+ */
16
+ export declare function routerSim(provenance: RouterProvenance, vendorPresenceField?: string): NormalizedSim;
17
+ /** Router admin APIs report no measurement-recency flag; that is a source capability claim. */
18
+ export declare function unsupportedQualityRecent(provenance: RouterProvenance): NormalizedMetric<boolean>;
19
+ /**
20
+ * A router's product name, screened by the migrated presentation rules.
21
+ *
22
+ * `modemHardwareLabel` falls back to a generic label when its input says nothing, and
23
+ * publishing that as a KNOWN value would be reporting a name no device gave. So an
24
+ * uninformative product string is refused up front and reads `not-reported` instead.
25
+ */
26
+ export declare function routerHardware(provenance: RouterProvenance, product: {
27
+ readonly name: string;
28
+ readonly field: string;
29
+ } | undefined): NormalizedHardware;