@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,75 @@
1
+ // The GNSS display state machine — bounded acquisition and stale-fix expiry.
2
+ //
3
+ // It exists to make two dishonest renders impossible rather than merely unlikely:
4
+ //
5
+ // 1. An endless "acquiring…" spinner. A modem with no antenna answers "no fix"
6
+ // forever, quite correctly, and a naive UI waits forever. Acquisition here is
7
+ // BOUNDED — past `acquireTimeoutMs` the state becomes `no-fix`, which is a
8
+ // terminal render, not a spinner.
9
+ // 2. A stale coordinate shown as current. A fix is only reachable through
10
+ // `renderableFix`, which returns one ONLY in the `fix` state; every exit from
11
+ // that state DROPS the fix rather than carrying it, so there is no code path
12
+ // that can render a position the modem is no longer reporting.
13
+ //
14
+ // Pure and total: no clock, no I/O. The caller supplies `at` on every event, which
15
+ // is what makes both bounds testable without waiting for real time to pass.
16
+ /**
17
+ * Defaults chosen against the fleet's own numbers: a cold GNSS start on the
18
+ * bench modems is a low-minutes affair, so 120 s is long enough to be a fair
19
+ * attempt and short enough that a missing antenna is reported inside a support
20
+ * call rather than after one. A fix older than 30 s is not "current" on a moving
21
+ * vehicle, which is the only context this display has.
22
+ */
23
+ export const DEFAULT_FIX_STATE_CONFIG = {
24
+ acquireTimeoutMs: 120_000,
25
+ fixTtlMs: 30_000,
26
+ };
27
+ export const GNSS_OFF = { kind: 'off' };
28
+ /** A fix is reachable ONLY here, and only while the state actually holds one. */
29
+ export function renderableFix(state) {
30
+ return state.kind === 'fix' ? state.fix : undefined;
31
+ }
32
+ /** True while a bounded wait is legitimately in progress — the only spinner state. */
33
+ export function isAcquiring(state) {
34
+ return state.kind === 'acquiring';
35
+ }
36
+ function expireIfDue(state, at, config) {
37
+ if (state.kind === 'acquiring' && at - state.since >= config.acquireTimeoutMs) {
38
+ return { kind: 'no-fix', since: at, reason: 'acquire-timeout' };
39
+ }
40
+ if (state.kind === 'fix' && at - state.fix.observedAt >= config.fixTtlMs) {
41
+ return { kind: 'no-fix', since: at, reason: 'fix-expired' };
42
+ }
43
+ return state;
44
+ }
45
+ function applyRead(state, at, read, config) {
46
+ switch (read.outcome) {
47
+ case 'fix':
48
+ return { kind: 'fix', fix: read.fix };
49
+ case 'no-fix':
50
+ // Still inside the bound, the modem simply has not acquired yet — that is
51
+ // what `acquiring` means, so a report of no-fix does not end the wait. Any
52
+ // other state (including a held fix) drops to an honest `no-fix`.
53
+ return state.kind === 'acquiring'
54
+ ? expireIfDue(state, at, config)
55
+ : { kind: 'no-fix', since: at, reason: 'reported-no-fix' };
56
+ case 'disabled':
57
+ return GNSS_OFF;
58
+ case 'unsupported':
59
+ case 'error':
60
+ return { kind: 'unavailable', reason: read.reason };
61
+ }
62
+ }
63
+ /** Pure, total transition. Every exit from `fix` drops the coordinates. */
64
+ export function advanceGnssFixState(state, event, config = DEFAULT_FIX_STATE_CONFIG) {
65
+ switch (event.kind) {
66
+ case 'gnss-enabled':
67
+ return state.kind === 'fix' ? state : { kind: 'acquiring', since: event.at };
68
+ case 'gnss-disabled':
69
+ return GNSS_OFF;
70
+ case 'read':
71
+ return applyRead(state, event.at, event.read, config);
72
+ case 'tick':
73
+ return expireIfDue(state, event.at, config);
74
+ }
75
+ }
@@ -0,0 +1,2 @@
1
+ export * from './fix-state.js';
2
+ export * from './nmea.js';
@@ -0,0 +1,8 @@
1
+ // The GPS/location module — the GNSS display state machine and NMEA decoding.
2
+ //
3
+ // PRIVACY FENCE (a product rule, not a phase limitation): this module reads the
4
+ // CURRENT fix and holds it in memory for a live display. There is no history, no
5
+ // track log, no persistence, and no upload — and none may be added here. The port
6
+ // contract in `../ports/location.ts` is guarded by `location-fence.test.ts`.
7
+ export * from './fix-state.js';
8
+ export * from './nmea.js';
@@ -0,0 +1,10 @@
1
+ export interface NmeaFix {
2
+ readonly latitude: number;
3
+ readonly longitude: number;
4
+ readonly altitude?: number;
5
+ }
6
+ /**
7
+ * The LAST valid GGA fix in a block of sentences, or `undefined` when none of them
8
+ * carries one. Never throws — a truncated or corrupt blob is simply not a fix.
9
+ */
10
+ export declare function parseNmeaFix(text: string): NmeaFix | undefined;
@@ -0,0 +1,89 @@
1
+ // Minimal NMEA decoding — GGA only, checksum-verified.
2
+ //
3
+ // It exists because `gps-nmea` is the one GNSS source EVERY GNSS-capable modem on
4
+ // the fleet advertises, while `gps-raw` (MM's pre-decoded dict) is not guaranteed:
5
+ // a `gps-unmanaged`-style device hands over sentences and nothing else. Without this
6
+ // the module would have to answer "no fix" to a modem that is reporting one, which
7
+ // is exactly the dishonesty the rest of this module is built to avoid.
8
+ //
9
+ // GGA is the only sentence read: it is the only standard sentence carrying fix
10
+ // QUALITY alongside the position, so "the receiver has not locked on" is decodable
11
+ // rather than inferred. RMC's A/V validity flag would do, but no fleet modem emits
12
+ // RMC without also emitting GGA.
13
+ /** XOR of every character between `$` and `*` — the NMEA checksum. */
14
+ function checksumOf(body) {
15
+ let sum = 0;
16
+ for (let i = 0; i < body.length; i += 1) {
17
+ sum ^= body.charCodeAt(i);
18
+ }
19
+ return sum;
20
+ }
21
+ function verifiedBody(sentence) {
22
+ const trimmed = sentence.trim();
23
+ if (!trimmed.startsWith('$')) {
24
+ return undefined;
25
+ }
26
+ const star = trimmed.lastIndexOf('*');
27
+ if (star < 0) {
28
+ return undefined;
29
+ }
30
+ const body = trimmed.slice(1, star);
31
+ const declared = Number.parseInt(trimmed.slice(star + 1, star + 3), 16);
32
+ return Number.isNaN(declared) || checksumOf(body) !== declared ? undefined : body;
33
+ }
34
+ /** `ddmm.mmmm` + hemisphere → signed degrees. `degreeDigits` is 2 for lat, 3 for lon. */
35
+ function toDegrees(value, hemisphere, degreeDigits) {
36
+ if (value.length < degreeDigits + 1) {
37
+ return undefined;
38
+ }
39
+ const degrees = Number.parseFloat(value.slice(0, degreeDigits));
40
+ const minutes = Number.parseFloat(value.slice(degreeDigits));
41
+ if (!Number.isFinite(degrees) || !Number.isFinite(minutes)) {
42
+ return undefined;
43
+ }
44
+ const magnitude = degrees + minutes / 60;
45
+ const negative = hemisphere === 'S' || hemisphere === 'W';
46
+ return negative ? -magnitude : magnitude;
47
+ }
48
+ function parseGga(fields) {
49
+ const quality = Number.parseInt(fields[6] ?? '', 10);
50
+ // Quality 0 is "fix not available". A receiver that is searching emits GGA with
51
+ // empty position fields and quality 0, so this is the honest no-fix signal.
52
+ if (!Number.isFinite(quality) || quality <= 0) {
53
+ return undefined;
54
+ }
55
+ const latitude = toDegrees(fields[2] ?? '', fields[3] ?? '', 2);
56
+ const longitude = toDegrees(fields[4] ?? '', fields[5] ?? '', 3);
57
+ if (latitude === undefined || longitude === undefined) {
58
+ return undefined;
59
+ }
60
+ if (Math.abs(latitude) > 90 || Math.abs(longitude) > 180) {
61
+ return undefined;
62
+ }
63
+ const altitude = Number.parseFloat(fields[9] ?? '');
64
+ return {
65
+ latitude,
66
+ longitude,
67
+ ...(Number.isFinite(altitude) ? { altitude } : {}),
68
+ };
69
+ }
70
+ /**
71
+ * The LAST valid GGA fix in a block of sentences, or `undefined` when none of them
72
+ * carries one. Never throws — a truncated or corrupt blob is simply not a fix.
73
+ */
74
+ export function parseNmeaFix(text) {
75
+ let latest;
76
+ for (const line of text.split(/[\r\n]+/)) {
77
+ const body = verifiedBody(line);
78
+ if (body === undefined) {
79
+ continue;
80
+ }
81
+ const fields = body.split(',');
82
+ const type = fields[0] ?? '';
83
+ if (type.length !== 5 || !type.endsWith('GGA')) {
84
+ continue;
85
+ }
86
+ latest = parseGga(fields) ?? latest;
87
+ }
88
+ return latest;
89
+ }
@@ -0,0 +1,63 @@
1
+ import type { DeviceGeneration, EpochMillis, ObservationAuthority, ObservationEnvelope, ObservationFreshness, SourceEpoch, StableKey } from '../domain/index.js';
2
+ import type { MetricProvenance, ObservationSourceKind } from './provenance.js';
3
+ export type ObservationStaleReason = Extract<ObservationFreshness, {
4
+ state: 'stale';
5
+ }>['reason'];
6
+ export type ObservationUnavailableReason = Extract<ObservationFreshness, {
7
+ state: 'unavailable';
8
+ }>['reason'];
9
+ export type EnvelopeView<T> = {
10
+ readonly kind: 'valued';
11
+ readonly value: T;
12
+ readonly freshness: Extract<ObservationFreshness, {
13
+ state: 'fresh' | 'stale';
14
+ }>;
15
+ } | {
16
+ readonly kind: 'unavailable';
17
+ readonly freshness: Extract<ObservationFreshness, {
18
+ state: 'unavailable';
19
+ }>;
20
+ };
21
+ /**
22
+ * Split an envelope into its two representable shapes.
23
+ *
24
+ * The package's only cast over an envelope lives here. `ObservationEnvelope<T>` pairs
25
+ * `value: T` with fresh|stale and `value: null` with unavailable BY CONSTRUCTION, but
26
+ * the discriminant sits one level down (`freshness.state`) and TypeScript narrows a
27
+ * union only on a direct property — so the pairing the type already guarantees has to
28
+ * be restated once, here, instead of at every call site.
29
+ */
30
+ export declare function viewEnvelope<T>(envelope: ObservationEnvelope<T>): EnvelopeView<T>;
31
+ /**
32
+ * Everything a normalizer needs that it cannot derive from the payload.
33
+ *
34
+ * There is no clock and no epoch counter in this layer: `observedAt` and
35
+ * `sourceEpoch` are supplied by whoever performed the read, so a normalizer cannot
36
+ * stamp a payload with a time it did not come from.
37
+ */
38
+ export type NormalizationContext = {
39
+ readonly stableKey: StableKey;
40
+ readonly generation: DeviceGeneration;
41
+ readonly sourceEpoch: SourceEpoch;
42
+ readonly observedAt: EpochMillis;
43
+ readonly authority?: ObservationAuthority;
44
+ };
45
+ export declare function contextAuthority(context: NormalizationContext): ObservationAuthority;
46
+ /**
47
+ * Provenance for one metric.
48
+ *
49
+ * `authority` is per-METRIC rather than inherited wholesale, because one payload can
50
+ * mix classes: a router's RSRP is a measurement the modem reported, while its bar
51
+ * count is a vendor rendering of that measurement, and calling both `authoritative`
52
+ * would let a consumer treat a marketing scale as a reading.
53
+ */
54
+ export declare function metricProvenance(source: ObservationSourceKind, context: NormalizationContext, rawFields: readonly string[], authority?: ObservationAuthority): MetricProvenance;
55
+ /** A fresh envelope around a normalized value. */
56
+ export declare function freshObservation<T>(source: ObservationSourceKind, context: NormalizationContext, value: T): ObservationEnvelope<T>;
57
+ /**
58
+ * An envelope for a read that produced no payload at all.
59
+ *
60
+ * It carries `value: null` by construction — there is no overload that could invent
61
+ * one — which is also why a normalizer holding vendor bytes must not use it.
62
+ */
63
+ export declare function unavailableObservation<T>(source: ObservationSourceKind, context: NormalizationContext, reason: ObservationUnavailableReason): ObservationEnvelope<T>;
@@ -0,0 +1,77 @@
1
+ // Envelope construction for normalizers.
2
+ //
3
+ // One rule shapes this module: **a payload that arrived is an OBSERVATION, however
4
+ // little of it could be read.** A refused HiLink session, an unparseable goform body
5
+ // and a UFI endpoint that answered without the field all produce a FRESH envelope
6
+ // whose metrics are `unknown` with a reason — not an `unavailable` one. That matters
7
+ // for a reason beyond taxonomy: `ObservationEnvelope` pairs `unavailable` with
8
+ // `value: null`, so emitting `unavailable` for a payload we did hold would throw the
9
+ // diagnostics block away with it, and the raw vendor fields with that.
10
+ //
11
+ // `unavailable` is therefore reserved for the case where there is no payload at all —
12
+ // the device is gone, or the provider could not be engaged.
13
+ /**
14
+ * Split an envelope into its two representable shapes.
15
+ *
16
+ * The package's only cast over an envelope lives here. `ObservationEnvelope<T>` pairs
17
+ * `value: T` with fresh|stale and `value: null` with unavailable BY CONSTRUCTION, but
18
+ * the discriminant sits one level down (`freshness.state`) and TypeScript narrows a
19
+ * union only on a direct property — so the pairing the type already guarantees has to
20
+ * be restated once, here, instead of at every call site.
21
+ */
22
+ export function viewEnvelope(envelope) {
23
+ return envelope.freshness.state === 'unavailable'
24
+ ? { kind: 'unavailable', freshness: envelope.freshness }
25
+ : { kind: 'valued', value: envelope.value, freshness: envelope.freshness };
26
+ }
27
+ export function contextAuthority(context) {
28
+ return context.authority ?? 'authoritative';
29
+ }
30
+ /**
31
+ * Provenance for one metric.
32
+ *
33
+ * `authority` is per-METRIC rather than inherited wholesale, because one payload can
34
+ * mix classes: a router's RSRP is a measurement the modem reported, while its bar
35
+ * count is a vendor rendering of that measurement, and calling both `authoritative`
36
+ * would let a consumer treat a marketing scale as a reading.
37
+ */
38
+ export function metricProvenance(source, context, rawFields, authority) {
39
+ return {
40
+ source,
41
+ sourceEpoch: context.sourceEpoch,
42
+ observedAt: context.observedAt,
43
+ authority: authority ?? contextAuthority(context),
44
+ rawFields,
45
+ };
46
+ }
47
+ /** A fresh envelope around a normalized value. */
48
+ export function freshObservation(source, context, value) {
49
+ return {
50
+ stableKey: context.stableKey,
51
+ generation: context.generation,
52
+ source,
53
+ sourceEpoch: context.sourceEpoch,
54
+ observedAt: context.observedAt,
55
+ authority: contextAuthority(context),
56
+ freshness: { state: 'fresh' },
57
+ value,
58
+ };
59
+ }
60
+ /**
61
+ * An envelope for a read that produced no payload at all.
62
+ *
63
+ * It carries `value: null` by construction — there is no overload that could invent
64
+ * one — which is also why a normalizer holding vendor bytes must not use it.
65
+ */
66
+ export function unavailableObservation(source, context, reason) {
67
+ return {
68
+ stableKey: context.stableKey,
69
+ generation: context.generation,
70
+ source,
71
+ sourceEpoch: context.sourceEpoch,
72
+ observedAt: context.observedAt,
73
+ authority: contextAuthority(context),
74
+ freshness: { state: 'unavailable', since: context.observedAt, reason },
75
+ value: null,
76
+ };
77
+ }
@@ -0,0 +1,28 @@
1
+ import { type DeviceGeneration, type EpochMillis, type ObservationEnvelope, type SourceEpoch } from '../domain/index.js';
2
+ /** How long a retained value is reported fresh before it ages into `stale`. */
3
+ export type FreshnessWindow = {
4
+ readonly ttlMs: number;
5
+ };
6
+ export type FreshnessEvaluation = {
7
+ /** The caller's current time. This module never reads a clock of its own. */
8
+ readonly at: EpochMillis;
9
+ readonly window: FreshnessWindow;
10
+ /** The source's current epoch; an older envelope epoch is superseded. */
11
+ readonly currentSourceEpoch?: SourceEpoch;
12
+ /** The device's current generation; an envelope from an older one is fenced out. */
13
+ readonly currentGeneration?: DeviceGeneration;
14
+ /** `false` marks a source that is answering but degraded (e.g. a reconnecting bus). */
15
+ readonly sourceHealthy?: boolean;
16
+ };
17
+ /** How long ago an envelope was observed, floored at zero for a clock that moved back. */
18
+ export declare function observationAgeMs<T>(envelope: ObservationEnvelope<T>, at: EpochMillis): number;
19
+ /**
20
+ * Re-classify an envelope against the current time, epoch, generation and source
21
+ * health.
22
+ *
23
+ * Trigger precedence, when several apply at once: superseded generation → superseded
24
+ * source epoch → degraded source → TTL expiry. The first three are positive statements
25
+ * that the reading has been overtaken by a newer reality; TTL expiry only says nobody
26
+ * has looked recently, and reporting it over a supersession would understate why.
27
+ */
28
+ export declare function evaluateFreshness<T>(envelope: ObservationEnvelope<T>, evaluation: FreshnessEvaluation): ObservationEnvelope<T>;
@@ -0,0 +1,76 @@
1
+ // Freshness evaluation — how a retained observation ages, and what it never becomes.
2
+ //
3
+ // Three rules carry the weight here:
4
+ //
5
+ // 1. **Staleness keeps the value.** An aged reading is the last thing the device
6
+ // actually said; discarding it leaves an operator with a blank field and no way
7
+ // to tell "we lost contact" from "the modem reports nothing".
8
+ // 2. **Unavailable is terminal on re-evaluation.** An envelope that carries no value
9
+ // cannot become stale, because there is nothing to age. Re-classifying it would
10
+ // have to invent a value to be stale about.
11
+ // 3. **Staleness is monotonic.** A stale envelope is returned UNCHANGED, so its
12
+ // `since` and `reason` record the FIRST cause. Freshness comes from a new read,
13
+ // never from re-evaluating an old one.
14
+ //
15
+ // This module has no clock: the caller supplies `at` on every evaluation, which is
16
+ // what makes the whole window testable without waiting. Same discipline as
17
+ // `location/fix-state.ts`.
18
+ import { epochMillis, isCurrentGeneration, } from '../domain/index.js';
19
+ import { viewEnvelope } from './envelope.js';
20
+ /** How long ago an envelope was observed, floored at zero for a clock that moved back. */
21
+ export function observationAgeMs(envelope, at) {
22
+ return Math.max(0, at - envelope.observedAt);
23
+ }
24
+ /**
25
+ * Re-classify an envelope against the current time, epoch, generation and source
26
+ * health.
27
+ *
28
+ * Trigger precedence, when several apply at once: superseded generation → superseded
29
+ * source epoch → degraded source → TTL expiry. The first three are positive statements
30
+ * that the reading has been overtaken by a newer reality; TTL expiry only says nobody
31
+ * has looked recently, and reporting it over a supersession would understate why.
32
+ */
33
+ export function evaluateFreshness(envelope, evaluation) {
34
+ const view = viewEnvelope(envelope);
35
+ if (view.kind === 'unavailable' || view.freshness.state !== 'fresh') {
36
+ return envelope;
37
+ }
38
+ const reason = staleReason(envelope, evaluation);
39
+ if (reason === undefined) {
40
+ return envelope;
41
+ }
42
+ return {
43
+ ...envelope,
44
+ freshness: { state: 'stale', since: staleSince(envelope, evaluation, reason), reason },
45
+ value: view.value,
46
+ };
47
+ }
48
+ function staleReason(envelope, evaluation) {
49
+ if (evaluation.currentGeneration !== undefined &&
50
+ !isCurrentGeneration(envelope.generation, evaluation.currentGeneration)) {
51
+ return 'source-epoch-superseded';
52
+ }
53
+ if (evaluation.currentSourceEpoch !== undefined &&
54
+ envelope.sourceEpoch !== evaluation.currentSourceEpoch) {
55
+ return 'source-epoch-superseded';
56
+ }
57
+ if (evaluation.sourceHealthy === false) {
58
+ return 'source-degraded';
59
+ }
60
+ return observationAgeMs(envelope, evaluation.at) > evaluation.window.ttlMs
61
+ ? 'ttl-expired'
62
+ : undefined;
63
+ }
64
+ /**
65
+ * When the reading became stale.
66
+ *
67
+ * A TTL expiry has an exact moment — one window after it was observed — and reporting
68
+ * the evaluation time instead would make a reading that expired an hour ago look like
69
+ * it just went stale. A supersession or a degradation is only known at the moment it
70
+ * is evaluated, so those honestly report `at`.
71
+ */
72
+ function staleSince(envelope, evaluation, reason) {
73
+ return reason === 'ttl-expired'
74
+ ? epochMillis(envelope.observedAt + evaluation.window.ttlMs)
75
+ : evaluation.at;
76
+ }
@@ -0,0 +1,13 @@
1
+ export * from './envelope.js';
2
+ export * from './freshness.js';
3
+ export * from './metric.js';
4
+ export * from './model.js';
5
+ export * from './provenance.js';
6
+ export * from './raw.js';
7
+ export * from './reading.js';
8
+ export * from './sources/hilink.js';
9
+ export * from './sources/modemmanager.js';
10
+ export * from './sources/router-shared.js';
11
+ export * from './sources/ufi.js';
12
+ export * from './sources/zte.js';
13
+ export * from './state-separation.js';
@@ -0,0 +1,24 @@
1
+ // The observation layer — normalization, provenance, freshness.
2
+ //
3
+ // It sits directly on top of the migrated pure logic (`domain/mm-enums.ts`,
4
+ // `domain/modem-presentation.ts`, `hardware/router-parsers.ts`) and turns a raw
5
+ // per-vendor payload into ONE `ObservationEnvelope<NormalizedModemObservation>` that
6
+ // carries where every value came from and why any value is missing. It opens no
7
+ // transport: a provider performs the read, this layer explains the result.
8
+ //
9
+ // Reachable through the package root, deliberately not through a new subpath — the
10
+ // public specifier set is a frozen contract and this is normalization, which the root
11
+ // entry already owns.
12
+ export * from './envelope.js';
13
+ export * from './freshness.js';
14
+ export * from './metric.js';
15
+ export * from './model.js';
16
+ export * from './provenance.js';
17
+ export * from './raw.js';
18
+ export * from './reading.js';
19
+ export * from './sources/hilink.js';
20
+ export * from './sources/modemmanager.js';
21
+ export * from './sources/router-shared.js';
22
+ export * from './sources/ufi.js';
23
+ export * from './sources/zte.js';
24
+ export * from './state-separation.js';
@@ -0,0 +1,47 @@
1
+ import type { RouterSignalMetric, RouterSignalUnknownReason } from '../hardware/router-parsers.js';
2
+ import type { MetricProvenance } from './provenance.js';
3
+ export declare const METRIC_UNKNOWN_REASONS: readonly ["unsupported", "not-reported", "not-observed", "malformed", "auth-expired", "refused", "unreachable"];
4
+ export type MetricUnknownReason = (typeof METRIC_UNKNOWN_REASONS)[number];
5
+ /**
6
+ * The two classes an unknown reason falls into.
7
+ *
8
+ * `capability` is a durable statement about the source; `read` is a statement about
9
+ * one attempt and may differ on the next one. A consumer deciding whether to HIDE a
10
+ * control (capability) or show it as pending (read) must branch on this, never on the
11
+ * bare fact that a value is missing.
12
+ */
13
+ export type MetricUnknownClass = 'capability' | 'read';
14
+ export declare function metricUnknownClass(reason: MetricUnknownReason): MetricUnknownClass;
15
+ /** Whether a reason is the positive "this source cannot report it" claim. */
16
+ export declare function isCapabilityUnknown(reason: MetricUnknownReason): reason is 'unsupported';
17
+ export type NormalizedMetric<T> = {
18
+ readonly state: 'known';
19
+ readonly value: T;
20
+ readonly provenance: MetricProvenance;
21
+ } | {
22
+ readonly state: 'unknown';
23
+ readonly reason: MetricUnknownReason;
24
+ readonly provenance: MetricProvenance;
25
+ };
26
+ export declare function knownMetric<T>(value: T, provenance: MetricProvenance): NormalizedMetric<T>;
27
+ export declare function unknownMetric<T>(reason: MetricUnknownReason, provenance: MetricProvenance): NormalizedMetric<T>;
28
+ /**
29
+ * Lift an optional decode result into a metric.
30
+ *
31
+ * `reason` is supplied by the caller precisely so this helper cannot pick one: the
32
+ * difference between "the provider omitted the field" and "this provider has no such
33
+ * field" is knowledge the decoder does not have and the call site does.
34
+ */
35
+ export declare function metricFromOptional<T>(value: T | undefined, reason: MetricUnknownReason, provenance: MetricProvenance): NormalizedMetric<T>;
36
+ /**
37
+ * The migrated router parsers' own unknown vocabulary, carried across UNCHANGED.
38
+ *
39
+ * Every member of `RouterSignalUnknownReason` is a member of `MetricUnknownReason`
40
+ * with the same meaning, so this is a widening and never a re-classification — in
41
+ * particular `not-reported` stays `not-reported` and does not become `unsupported`.
42
+ */
43
+ export declare function metricUnknownReasonFromRouter(reason: RouterSignalUnknownReason | 'refused'): MetricUnknownReason;
44
+ /** Wrap one migrated router signal metric with its provenance. */
45
+ export declare function metricFromRouterSignal(metric: RouterSignalMetric, provenance: MetricProvenance): NormalizedMetric<number>;
46
+ /** Transform a known value while preserving state, reason and provenance. */
47
+ export declare function mapMetric<T, U>(metric: NormalizedMetric<T>, transform: (value: T) => U): NormalizedMetric<U>;
@@ -0,0 +1,73 @@
1
+ // A normalized metric: a value, or an explicit reason there is none — with the
2
+ // provenance of the exact reading that produced it.
3
+ //
4
+ // `unknown` is a FIRST-CLASS state here, and its reason is what keeps it from
5
+ // collapsing into `unsupported`. Those two answer different questions: `unsupported`
6
+ // is a positive claim about the SOURCE ("this provider cannot express this datum at
7
+ // all"), while every other reason is a claim about one READ ("the provider could have
8
+ // said, and did not / could not be asked / answered nonsense"). Folding the second
9
+ // class into the first is how a control disappears from an operator's screen because
10
+ // one poll came back empty.
11
+ export const METRIC_UNKNOWN_REASONS = [
12
+ /** A positive claim about the SOURCE: it cannot express this datum at all. */
13
+ 'unsupported',
14
+ /** The source answered and simply did not include this field. */
15
+ 'not-reported',
16
+ /** Nobody has read this yet. Says nothing about the source or the device. */
17
+ 'not-observed',
18
+ /** The source answered with something this layer could not decode. */
19
+ 'malformed',
20
+ /** The source refused the read; the session needs re-authentication. */
21
+ 'auth-expired',
22
+ /** The source answered with an explicit refusal code for this field. */
23
+ 'refused',
24
+ /** The source could not be reached for this read. */
25
+ 'unreachable',
26
+ ];
27
+ export function metricUnknownClass(reason) {
28
+ return reason === 'unsupported' ? 'capability' : 'read';
29
+ }
30
+ /** Whether a reason is the positive "this source cannot report it" claim. */
31
+ export function isCapabilityUnknown(reason) {
32
+ return metricUnknownClass(reason) === 'capability';
33
+ }
34
+ export function knownMetric(value, provenance) {
35
+ return { state: 'known', value, provenance };
36
+ }
37
+ export function unknownMetric(reason, provenance) {
38
+ return { state: 'unknown', reason, provenance };
39
+ }
40
+ /**
41
+ * Lift an optional decode result into a metric.
42
+ *
43
+ * `reason` is supplied by the caller precisely so this helper cannot pick one: the
44
+ * difference between "the provider omitted the field" and "this provider has no such
45
+ * field" is knowledge the decoder does not have and the call site does.
46
+ */
47
+ export function metricFromOptional(value, reason, provenance) {
48
+ return value === undefined
49
+ ? unknownMetric(reason, provenance)
50
+ : knownMetric(value, provenance);
51
+ }
52
+ /**
53
+ * The migrated router parsers' own unknown vocabulary, carried across UNCHANGED.
54
+ *
55
+ * Every member of `RouterSignalUnknownReason` is a member of `MetricUnknownReason`
56
+ * with the same meaning, so this is a widening and never a re-classification — in
57
+ * particular `not-reported` stays `not-reported` and does not become `unsupported`.
58
+ */
59
+ export function metricUnknownReasonFromRouter(reason) {
60
+ return reason;
61
+ }
62
+ /** Wrap one migrated router signal metric with its provenance. */
63
+ export function metricFromRouterSignal(metric, provenance) {
64
+ return metric.state === 'known'
65
+ ? knownMetric(metric.value, provenance)
66
+ : unknownMetric(metricUnknownReasonFromRouter(metric.reason), provenance);
67
+ }
68
+ /** Transform a known value while preserving state, reason and provenance. */
69
+ export function mapMetric(metric, transform) {
70
+ return metric.state === 'known'
71
+ ? knownMetric(transform(metric.value), metric.provenance)
72
+ : unknownMetric(metric.reason, metric.provenance);
73
+ }