@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,55 @@
1
+ import { type JournalDecodeFailure } from './codec.js';
2
+ import type { JournalEntry } from './entry.js';
3
+ /** Default file mode. `0600` for the same reason the two policy stores use it. */
4
+ export declare const JOURNAL_FILE_MODE = 384;
5
+ /** Where a damaged record was found. Line-based, slot-based, or the whole file. */
6
+ export type JournalDamageLocation = {
7
+ readonly kind: 'line';
8
+ readonly line: number;
9
+ readonly trailing: boolean;
10
+ } | {
11
+ readonly kind: 'slot';
12
+ readonly slot: string;
13
+ } | {
14
+ readonly kind: 'file';
15
+ };
16
+ /** One record that could not be read, reported rather than dropped. */
17
+ export interface JournalDamageRecord {
18
+ readonly location: JournalDamageLocation;
19
+ readonly bytes: number;
20
+ readonly failure: JournalDecodeFailure;
21
+ }
22
+ /** One decoded entry with the 1-based line it came from. */
23
+ export interface JournalLineRecord {
24
+ readonly line: number;
25
+ readonly entry: JournalEntry;
26
+ }
27
+ export interface JournalReadResult {
28
+ readonly entries: readonly JournalLineRecord[];
29
+ readonly damage: readonly JournalDamageRecord[];
30
+ }
31
+ export interface JournalStore {
32
+ /** The injected path, echoed back so a caller can report where it recovered from. */
33
+ readonly path: string;
34
+ append(entry: JournalEntry): Promise<void>;
35
+ read(): Promise<JournalReadResult>;
36
+ }
37
+ export interface FileJournalStoreOptions {
38
+ /** REQUIRED. There is no default and no fallback. */
39
+ readonly path: string;
40
+ readonly mode?: number;
41
+ }
42
+ export declare class JournalPathError extends Error {
43
+ readonly name = "JournalPathError";
44
+ constructor();
45
+ }
46
+ /**
47
+ * Decode a whole journal document.
48
+ *
49
+ * Exported because the corruption fixtures assert against it directly — proving
50
+ * the "survivors are kept" property without needing a filesystem to prove it.
51
+ * Blank lines carry no record and are skipped rather than reported: a trailing
52
+ * newline is how every line ends, so the final split member is always empty.
53
+ */
54
+ export declare function decodeJournalText(text: string): JournalReadResult;
55
+ export declare function createFileJournalStore(options: FileJournalStoreOptions): JournalStore;
@@ -0,0 +1,150 @@
1
+ // The append-only journal store. THE PATH IS INJECTED AND HAS NO DEFAULT.
2
+ //
3
+ // This package never learns where a journal lives. The embedding process owns that
4
+ // decision because it owns the filesystem contract: on a CeraLive device the
5
+ // update-surviving partition is the right home, on a bench box a scratch directory
6
+ // is, and in a test a `mkdtemp` directory is. A default here would be a policy this
7
+ // library has no standing to set, and — worse — a default is what turns "the
8
+ // embedder forgot to configure it" into "we silently wrote somewhere plausible".
9
+ // `journal-path-injection.test.ts` fails the build if an absolute path literal ever
10
+ // appears in this directory's executable source.
11
+ //
12
+ // THREE PROPERTIES THIS STORE GUARANTEES, ALL LOAD-BEARING:
13
+ //
14
+ // 1. APPEND-ONLY. There is no verb here that rewrites or truncates the file.
15
+ // A rewrite is the one operation that can lose a fact that was already
16
+ // durable, and a journal that can lose a fact answers nothing after a crash.
17
+ //
18
+ // 2. A DAMAGED RECORD NEVER DISCARDS ITS NEIGHBOURS. `read()` decodes every line
19
+ // independently and returns the survivors alongside a typed damage report.
20
+ // Stopping at the first bad line — the natural thing a `for` loop with a throw
21
+ // does — silently truncates the journal to its first corruption, which is
22
+ // exactly the failure mode this store exists to make impossible.
23
+ //
24
+ // 3. A TORN TRAILING LINE IS CLOSED BEFORE THE NEXT APPEND. A process killed
25
+ // mid-write leaves a final line with no terminator. Appending straight onto it
26
+ // would glue the new entry to the garbage and corrupt a SECOND record — one
27
+ // that was never in flight when the crash happened. The store probes the last
28
+ // byte once and emits a leading terminator when the file does not end in one,
29
+ // so the damage stays confined to the record that actually tore.
30
+ import { appendFile, chmod, mkdir, open, readFile } from 'node:fs/promises';
31
+ import { dirname } from 'node:path';
32
+ import { decodeJournalEntry, encodeJournalEntry } from './codec.js';
33
+ const NEWLINE = 0x0a;
34
+ /** Default file mode. `0600` for the same reason the two policy stores use it. */
35
+ export const JOURNAL_FILE_MODE = 0o600;
36
+ export class JournalPathError extends Error {
37
+ name = 'JournalPathError';
38
+ constructor() {
39
+ super('journal path refused: an explicit, non-empty path must be injected');
40
+ }
41
+ }
42
+ /**
43
+ * Decode a whole journal document.
44
+ *
45
+ * Exported because the corruption fixtures assert against it directly — proving
46
+ * the "survivors are kept" property without needing a filesystem to prove it.
47
+ * Blank lines carry no record and are skipped rather than reported: a trailing
48
+ * newline is how every line ends, so the final split member is always empty.
49
+ */
50
+ export function decodeJournalText(text) {
51
+ const lines = text.split('\n');
52
+ let lastPopulated = -1;
53
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
54
+ if ((lines[index] ?? '').trim().length > 0) {
55
+ lastPopulated = index;
56
+ break;
57
+ }
58
+ }
59
+ const entries = [];
60
+ const damage = [];
61
+ for (const [index, raw] of lines.entries()) {
62
+ if (raw.trim().length === 0)
63
+ continue;
64
+ const decoded = decodeJournalEntry(raw);
65
+ if (decoded.ok) {
66
+ entries.push({ line: index + 1, entry: decoded.value });
67
+ continue;
68
+ }
69
+ damage.push({
70
+ location: { kind: 'line', line: index + 1, trailing: index === lastPopulated },
71
+ bytes: Buffer.byteLength(raw, 'utf8'),
72
+ failure: decoded.failure,
73
+ });
74
+ }
75
+ return { entries, damage };
76
+ }
77
+ class FileJournalStore {
78
+ path;
79
+ #mode;
80
+ #tail = Promise.resolve();
81
+ /** Undefined until the first append probes the existing file's final byte. */
82
+ #terminated;
83
+ constructor(options) {
84
+ if (typeof options.path !== 'string' || options.path.trim().length === 0) {
85
+ throw new JournalPathError();
86
+ }
87
+ this.path = options.path;
88
+ this.#mode = options.mode ?? JOURNAL_FILE_MODE;
89
+ }
90
+ append(entry) {
91
+ // Chained so two concurrent appends cannot interleave, and recovered from so
92
+ // one failed append does not poison every later one with its rejection.
93
+ const next = this.#tail.catch(() => undefined).then(() => this.#appendNow(entry));
94
+ this.#tail = next.catch(() => undefined);
95
+ return next;
96
+ }
97
+ async read() {
98
+ let text;
99
+ try {
100
+ text = await readFile(this.path, 'utf8');
101
+ }
102
+ catch (error) {
103
+ // An ABSENT journal is an empty one — nothing was ever written. Anything
104
+ // else (permissions, a directory in the way) is reported as damage: a
105
+ // journal we could not open is not evidence that no mutation was pending.
106
+ if (error.code === 'ENOENT') {
107
+ return { entries: [], damage: [] };
108
+ }
109
+ return {
110
+ entries: [],
111
+ damage: [{ location: { kind: 'file' }, bytes: 0, failure: { code: 'unreadable' } }],
112
+ };
113
+ }
114
+ return decodeJournalText(text);
115
+ }
116
+ async #appendNow(entry) {
117
+ await mkdir(dirname(this.path), { recursive: true });
118
+ this.#terminated ??= await this.#probeTerminated();
119
+ const line = `${this.#terminated ? '' : '\n'}${encodeJournalEntry(entry)}\n`;
120
+ await appendFile(this.path, line, { mode: this.#mode });
121
+ // chmod AFTER the write, not as an open flag, so the mode holds regardless of
122
+ // umask — the same reason the usage and FCC policy stores do it this way.
123
+ await chmod(this.path, this.#mode);
124
+ this.#terminated = true;
125
+ }
126
+ async #probeTerminated() {
127
+ let handle;
128
+ try {
129
+ handle = await open(this.path, 'r');
130
+ }
131
+ catch {
132
+ // No file yet: the first line starts the document, nothing to close.
133
+ return true;
134
+ }
135
+ try {
136
+ const { size } = await handle.stat();
137
+ if (size === 0)
138
+ return true;
139
+ const tail = Buffer.alloc(1);
140
+ await handle.read(tail, 0, 1, size - 1);
141
+ return tail[0] === NEWLINE;
142
+ }
143
+ finally {
144
+ await handle.close();
145
+ }
146
+ }
147
+ }
148
+ export function createFileJournalStore(options) {
149
+ return new FileJournalStore(options);
150
+ }
@@ -0,0 +1,53 @@
1
+ import type { EpochMillis } from '../domain/index.js';
2
+ import type { FixRead, GnssFix } from '../ports/location.js';
3
+ export interface GnssFixStateConfig {
4
+ /** How long acquisition may run before the state turns to an honest `no-fix`. */
5
+ readonly acquireTimeoutMs: number;
6
+ /** How long a fix stays current before it expires and is dropped. */
7
+ readonly fixTtlMs: number;
8
+ }
9
+ /**
10
+ * Defaults chosen against the fleet's own numbers: a cold GNSS start on the
11
+ * bench modems is a low-minutes affair, so 120 s is long enough to be a fair
12
+ * attempt and short enough that a missing antenna is reported inside a support
13
+ * call rather than after one. A fix older than 30 s is not "current" on a moving
14
+ * vehicle, which is the only context this display has.
15
+ */
16
+ export declare const DEFAULT_FIX_STATE_CONFIG: GnssFixStateConfig;
17
+ export type NoFixReason = 'acquire-timeout' | 'reported-no-fix' | 'fix-expired';
18
+ export type GnssFixState = {
19
+ readonly kind: 'off';
20
+ } | {
21
+ readonly kind: 'acquiring';
22
+ readonly since: EpochMillis;
23
+ } | {
24
+ readonly kind: 'no-fix';
25
+ readonly since: EpochMillis;
26
+ readonly reason: NoFixReason;
27
+ } | {
28
+ readonly kind: 'fix';
29
+ readonly fix: GnssFix;
30
+ } | {
31
+ readonly kind: 'unavailable';
32
+ readonly reason: string;
33
+ };
34
+ export type GnssFixEvent = {
35
+ readonly kind: 'gnss-enabled';
36
+ readonly at: EpochMillis;
37
+ } | {
38
+ readonly kind: 'gnss-disabled';
39
+ } | {
40
+ readonly kind: 'read';
41
+ readonly at: EpochMillis;
42
+ readonly read: FixRead;
43
+ } | {
44
+ readonly kind: 'tick';
45
+ readonly at: EpochMillis;
46
+ };
47
+ export declare const GNSS_OFF: GnssFixState;
48
+ /** A fix is reachable ONLY here, and only while the state actually holds one. */
49
+ export declare function renderableFix(state: GnssFixState): GnssFix | undefined;
50
+ /** True while a bounded wait is legitimately in progress — the only spinner state. */
51
+ export declare function isAcquiring(state: GnssFixState): boolean;
52
+ /** Pure, total transition. Every exit from `fix` drops the coordinates. */
53
+ export declare function advanceGnssFixState(state: GnssFixState, event: GnssFixEvent, config?: GnssFixStateConfig): GnssFixState;
@@ -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>;