@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,234 @@
1
+ import { z } from 'zod';
2
+ import { parseHilinkXmlValue } from './hilink-protocol.js';
3
+ export * from './hilink-protocol.js';
4
+ const SIM_OBJECT_PATH = /^\/org\/freedesktop\/ModemManager1\/SIM\/\d+$/;
5
+ /** The mmcli spelling of `MM_MODEM_STATE_FAILED_REASON_SIM_MISSING` — the ONLY proof of absence. */
6
+ export const SIM_MISSING_FAILED_REASON = 'sim-missing';
7
+ /** The fields a presence decision may be drawn from, in the order they are inspected. */
8
+ export const SIM_PRESENCE_FIELDS = ['sim', 'simSlots', 'failedReason'];
9
+ /** The presence decision together with the fact that produced it. */
10
+ export function readSimPresence(facts) {
11
+ const sim = facts.sim?.trim() ?? '';
12
+ if (SIM_OBJECT_PATH.test(sim)) {
13
+ return { presence: 'present', evidence: { kind: 'sim-object-path', field: 'sim', value: sim } };
14
+ }
15
+ const slot = facts.simSlots
16
+ ?.map((each) => each.trim())
17
+ .find((each) => SIM_OBJECT_PATH.test(each));
18
+ if (slot !== undefined) {
19
+ return {
20
+ presence: 'present',
21
+ evidence: { kind: 'sim-slot-object-path', field: 'simSlots', value: slot },
22
+ };
23
+ }
24
+ if (facts.failedReason === SIM_MISSING_FAILED_REASON) {
25
+ return {
26
+ presence: 'absent',
27
+ evidence: {
28
+ kind: 'state-failed-reason',
29
+ field: 'failedReason',
30
+ value: SIM_MISSING_FAILED_REASON,
31
+ },
32
+ };
33
+ }
34
+ return { presence: 'unknown', evidence: { kind: 'no-evidence', inspected: SIM_PRESENCE_FIELDS } };
35
+ }
36
+ export function deriveSimPresence(facts) {
37
+ return readSimPresence(facts).presence;
38
+ }
39
+ const unknown = (reason) => ({
40
+ state: 'unknown',
41
+ reason,
42
+ });
43
+ const known = (value) => ({ state: 'known', value });
44
+ function numericMetric(value) {
45
+ if (value === undefined || String(value).trim() === '')
46
+ return unknown('not-reported');
47
+ const parsed = Number.parseFloat(String(value));
48
+ return Number.isFinite(parsed) ? known(parsed) : unknown('malformed');
49
+ }
50
+ const flatRecordSchema = z.record(z.string(), z.union([z.string(), z.number()]));
51
+ const ufiBodySchema = z.object({
52
+ reply: z.string(),
53
+ params: flatRecordSchema.optional(),
54
+ });
55
+ function parseFlatRecord(body) {
56
+ const parsed = z
57
+ .string()
58
+ .transform((value, context) => {
59
+ try {
60
+ return JSON.parse(value);
61
+ }
62
+ catch (error) {
63
+ if (!(error instanceof SyntaxError))
64
+ throw error;
65
+ context.addIssue({ code: 'custom', message: 'invalid JSON' });
66
+ return z.NEVER;
67
+ }
68
+ })
69
+ .pipe(flatRecordSchema)
70
+ .safeParse(body);
71
+ return parsed.success ? parsed.data : undefined;
72
+ }
73
+ function parseUfiBody(body) {
74
+ const parsed = z
75
+ .string()
76
+ .transform((value, context) => {
77
+ try {
78
+ return JSON.parse(value);
79
+ }
80
+ catch (error) {
81
+ if (!(error instanceof SyntaxError))
82
+ throw error;
83
+ context.addIssue({ code: 'custom', message: 'invalid JSON' });
84
+ return z.NEVER;
85
+ }
86
+ })
87
+ .pipe(ufiBodySchema)
88
+ .safeParse(body);
89
+ return parsed.success ? parsed.data : undefined;
90
+ }
91
+ export function parseHilinkSignal(input) {
92
+ const authStatus = parseHilinkXmlValue(input.status, 'code') === '125002';
93
+ const authSignal = parseHilinkXmlValue(input.signal, 'code') === '125002';
94
+ const statusReason = authStatus ? 'auth-expired' : 'not-reported';
95
+ const signalReason = authSignal ? 'auth-expired' : 'not-reported';
96
+ const metric = (tag) => authSignal ? unknown(signalReason) : numericMetric(parseHilinkXmlValue(input.signal, tag));
97
+ return {
98
+ provenance: 'hilink-admin-api',
99
+ freshness: authStatus && authSignal ? 'unknown' : 'live',
100
+ bars: authStatus
101
+ ? unknown(statusReason)
102
+ : numericMetric(parseHilinkXmlValue(input.status, 'SignalIcon')),
103
+ max_bars: authStatus
104
+ ? unknown(statusReason)
105
+ : numericMetric(parseHilinkXmlValue(input.status, 'maxsignal')),
106
+ dbm: metric('rssi'),
107
+ rsrp: metric('rsrp'),
108
+ rsrq: metric('rsrq'),
109
+ snr: unknown('unsupported'),
110
+ sinr: metric('sinr'),
111
+ };
112
+ }
113
+ export function parseZteSignal(body) {
114
+ const record = parseFlatRecord(body);
115
+ const malformed = record === undefined;
116
+ const metric = (key) => malformed ? unknown('malformed') : numericMetric(record[key]);
117
+ const bars = metric('signalbar');
118
+ return {
119
+ provenance: 'zte-goform',
120
+ freshness: malformed ? 'unknown' : 'live',
121
+ bars,
122
+ max_bars: bars.state === 'known' ? known(5) : unknown('not-reported'),
123
+ dbm: metric('rssi'),
124
+ rsrp: metric('lte_rsrp'),
125
+ rsrq: metric('lte_rsrq'),
126
+ snr: metric('lte_snr'),
127
+ sinr: unknown('unsupported'),
128
+ };
129
+ }
130
+ export function parseUfiSignal(input) {
131
+ const bodies = [input.sysinfo, input.overview, input.status].map(parseUfiBody);
132
+ const authExpired = bodies.every((body) => body?.reply === 'SessionOut');
133
+ const answered = bodies.some((body) => body?.reply === 'ok');
134
+ const value = bodies[0]?.params?.SIGNAL ?? bodies[1]?.params?.SIGNAL ?? bodies[2]?.params?.signalStrength;
135
+ const dbm = authExpired
136
+ ? unknown('auth-expired')
137
+ : answered
138
+ ? numericMetric(value)
139
+ : unknown('malformed');
140
+ return {
141
+ provenance: 'ufi-himiapi',
142
+ freshness: answered ? 'live' : 'unknown',
143
+ bars: unknown('unsupported'),
144
+ max_bars: unknown('unsupported'),
145
+ dbm,
146
+ rsrp: unknown('unsupported'),
147
+ rsrq: unknown('unsupported'),
148
+ snr: unknown('unsupported'),
149
+ sinr: unknown('unsupported'),
150
+ };
151
+ }
152
+ /**
153
+ * The vendor's own "unset" placeholders. A UFI answers `-` for a WAN address, an
154
+ * IMSI and an ICCID it does not have, and the ZTE builds answer `--` / `N/A` for
155
+ * an unpopulated counter — publishing any of those as a reading puts a value on
156
+ * screen that reads like a real one.
157
+ */
158
+ const PLACEHOLDERS = new Set(['-', '--', 'n/a', 'N/A']);
159
+ function stated(value) {
160
+ if (value === undefined)
161
+ return undefined;
162
+ const normalized = String(value).trim();
163
+ return normalized === '' || PLACEHOLDERS.has(normalized) ? undefined : normalized;
164
+ }
165
+ function compact(entries) {
166
+ const result = {};
167
+ for (const [key, value] of entries)
168
+ if (value !== undefined)
169
+ result[key] = value;
170
+ return Object.keys(result).length === 0 ? undefined : result;
171
+ }
172
+ export function parseZteDetails(body) {
173
+ const value = parseFlatRecord(body);
174
+ if (value === undefined)
175
+ return undefined;
176
+ return compact([
177
+ ['network_type', stated(value.network_type)],
178
+ [
179
+ 'provider',
180
+ stated(value.network_provider_fullname) ??
181
+ stated(value.network_provider) ??
182
+ stated(value.provider),
183
+ ],
184
+ ['cell_id', stated(value.cell_id)],
185
+ ['roaming', stated(value.simcard_roam)],
186
+ ['mcc', stated(value.rmcc)],
187
+ ['mnc', stated(value.rmnc)],
188
+ ['pci', stated(value.lte_pci)],
189
+ // `band` and `network_band` are two DIFFERENT readings and must not be
190
+ // folded onto one key: `lte_band` is the serving cell's band, while
191
+ // `wan_active_band` is the band the WAN leg is active on, and the two
192
+ // disagree the moment carrier aggregation is up. Publishing either under
193
+ // the other's name reports a band the device never claimed for that leg.
194
+ ['band', stated(value.lte_band) ?? stated(value.band)],
195
+ ['network_band', stated(value.wan_active_band)],
196
+ ['carrier_aggregation', stated(value.wan_lte_ca)],
197
+ ['pcell_arfcn', stated(value.lte_ca_pcell_arfcn)],
198
+ ['pcell_band', stated(value.lte_ca_pcell_band)],
199
+ ['pcell_bandwidth', stated(value.lte_ca_pcell_bandwidth)],
200
+ ['scell_arfcn', stated(value.lte_ca_scell_arfcn)],
201
+ ['scell_band', stated(value.lte_ca_scell_band)],
202
+ ['scell_bandwidth', stated(value.lte_ca_scell_bandwidth)],
203
+ ['monthly_tx_bytes', stated(value.monthly_tx_bytes)],
204
+ ['monthly_rx_bytes', stated(value.monthly_rx_bytes)],
205
+ ['monthly_time', stated(value.monthly_time)],
206
+ ['monthly_period', stated(value.date_month)],
207
+ // Named `session_*` rather than `realtime_*`: these are cumulative counters
208
+ // and a throughput, and the vendor's own prefix reads as "live rate" for
209
+ // all five.
210
+ ['session_tx_bytes', stated(value.realtime_tx_bytes)],
211
+ ['session_rx_bytes', stated(value.realtime_rx_bytes)],
212
+ ['session_tx_rate', stated(value.realtime_tx_thrpt)],
213
+ ['session_rx_rate', stated(value.realtime_rx_thrpt)],
214
+ ['session_time', stated(value.realtime_time)],
215
+ ]);
216
+ }
217
+ export function parseUfiDetails(input) {
218
+ const overview = parseUfiBody(input.overview ?? '')?.params;
219
+ const sysinfo = parseUfiBody(input.sysinfo ?? '')?.params;
220
+ const product = parseUfiBody(input.produceInfo ?? '')?.params;
221
+ return compact([
222
+ ['product', stated(product?.productname) ?? stated(product?.ProductName)],
223
+ ['ssid', stated(overview?.SSID)],
224
+ ['wan_ip', stated(overview?.WANIP)],
225
+ ['imsi', stated(overview?.IMSI)],
226
+ ['iccid', stated(overview?.ICCID)],
227
+ ['web_version', stated(overview?.WEBVER)],
228
+ ['cell_id', stated(sysinfo?.cellid)],
229
+ ['station_id', stated(sysinfo?.bsid)],
230
+ ['cpu_temp', stated(sysinfo?.cputemp)],
231
+ ['wifi_clients', stated(sysinfo?.wifinum)],
232
+ ['eth_clients', stated(sysinfo?.ethnum)],
233
+ ]);
234
+ }
@@ -0,0 +1,20 @@
1
+ export declare const PACKAGE_NAME = "@ceralive/modem-control";
2
+ export * from './backend/index.js';
3
+ export * from './band/index.js';
4
+ export * from './capability/index.js';
5
+ export * from './domain/index.js';
6
+ export * from './fcc/index.js';
7
+ export * from './hardware/router-parsers.js';
8
+ export * from './journal/index.js';
9
+ export * from './location/index.js';
10
+ export * from './observations/index.js';
11
+ export * from './operation-ids.js';
12
+ export * from './operations/index.js';
13
+ export * from './ports/index.js';
14
+ export * from './providers/index.js';
15
+ export * from './radio/index.js';
16
+ export * from './redact.js';
17
+ export * from './safety/index.js';
18
+ export * from './sms/index.js';
19
+ export * from './usb-mode/index.js';
20
+ export * from './ussd/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ // @ceralive/modem-control — package entry point.
2
+ //
3
+ // Phase A: the domain model (identity + orthogonal state + revisions) under
4
+ // `./domain`, the MM / NM / Router port contracts + desired-state planner under
5
+ // `./ports`, the redaction module (`./redact`), and the epoch-scoped ModemManager
6
+ // D-Bus observer under `./backend`. The NetworkManager adapter, USB composition-mode
7
+ // model, and data-usage sampler land in later waves.
8
+ export const PACKAGE_NAME = '@ceralive/modem-control';
9
+ export * from './backend/index.js';
10
+ export * from './band/index.js';
11
+ export * from './capability/index.js';
12
+ export * from './domain/index.js';
13
+ export * from './fcc/index.js';
14
+ export * from './hardware/router-parsers.js';
15
+ export * from './journal/index.js';
16
+ export * from './location/index.js';
17
+ export * from './observations/index.js';
18
+ export * from './operation-ids.js';
19
+ export * from './operations/index.js';
20
+ export * from './ports/index.js';
21
+ export * from './providers/index.js';
22
+ export * from './radio/index.js';
23
+ export * from './redact.js';
24
+ export * from './safety/index.js';
25
+ export * from './sms/index.js';
26
+ export * from './usb-mode/index.js';
27
+ export * from './ussd/index.js';
@@ -0,0 +1,44 @@
1
+ import { type JournalEntry } from './entry.js';
2
+ /** Why one record could not be read. `field` names the offending key, never its value. */
3
+ export interface JournalDecodeFailure {
4
+ readonly code: 'empty' | 'invalid-json' | 'not-an-object' | 'unsupported-schema-version' | 'schema-mismatch' | 'unreadable';
5
+ readonly field?: string;
6
+ /** Byte offset for a JSON syntax error, when the runtime reported one. */
7
+ readonly offset?: number;
8
+ }
9
+ export type JournalDecodeResult<T> = {
10
+ readonly ok: true;
11
+ readonly value: T;
12
+ } | {
13
+ readonly ok: false;
14
+ readonly failure: JournalDecodeFailure;
15
+ };
16
+ declare function record(raw: unknown, field: string): Record<string, unknown>;
17
+ declare function requiredString(source: Record<string, unknown>, field: string): string;
18
+ declare function optionalString(source: Record<string, unknown>, field: string): string | undefined;
19
+ declare function requiredNonNegativeInteger(source: Record<string, unknown>, field: string): number;
20
+ declare function member<T extends string>(source: Record<string, unknown>, field: string, allowed: readonly T[]): T;
21
+ /**
22
+ * Serialize one entry to a single line WITHOUT its terminator.
23
+ *
24
+ * Keys are emitted in a fixed order because the round-trip tests compare bytes,
25
+ * and byte comparison is the only assertion that catches a field a reader silently
26
+ * dropped (`JSON.parse` + a permissive validator will happily lose one and still
27
+ * report success — the same trap the srtla telemetry byte-parity suite exists for).
28
+ */
29
+ export declare function encodeJournalEntry(entry: JournalEntry): string;
30
+ /** Decode one line. An empty/whitespace-only line is reported, never silently dropped. */
31
+ export declare function decodeJournalEntry(line: string): JournalDecodeResult<JournalEntry>;
32
+ /** Decode an arbitrary JSON document with a caller-supplied validator. */
33
+ export declare function decodeJournalDocument<T>(text: string, validate: (raw: unknown) => T): JournalDecodeResult<T>;
34
+ /** The validator helpers the legacy reader reuses, so both shapes fail the same way. */
35
+ export declare const journalSchema: {
36
+ readonly record: typeof record;
37
+ readonly requiredString: typeof requiredString;
38
+ readonly optionalString: typeof optionalString;
39
+ readonly requiredNonNegativeInteger: typeof requiredNonNegativeInteger;
40
+ readonly member: typeof member;
41
+ readonly schemaError: (field: string) => Error;
42
+ readonly schemaVersionError: () => Error;
43
+ };
44
+ export {};
@@ -0,0 +1,198 @@
1
+ // Line codec for the journal: exactly one entry per line, JSON, no wrapper.
2
+ //
3
+ // WHY DECODING RETURNS A RESULT INSTEAD OF THROWING. A journal is read at exactly
4
+ // one moment — recovery after an unclean restart — and that is the moment a throw
5
+ // is most expensive: it aborts the read at the first damaged byte and takes every
6
+ // still-valid entry after it with it. The whole point of this module is that a
7
+ // damaged record is DATA, reported alongside the records that survived, so nothing
8
+ // earlier or later is discarded to make one bad line disappear.
9
+ //
10
+ // FAILURES NAME A FIELD, NEVER CONTENT. Same rule the two policy stores follow: a
11
+ // classification carries the offending field name and a byte count, never the bytes
12
+ // themselves, because a journal line can hold provider identifiers and refusal
13
+ // reasons and a log line is the wrong place to reproduce them.
14
+ import { MODEM_CONTROL_JOURNAL_SCHEMA_VERSION, } from './entry.js';
15
+ class SchemaError extends Error {
16
+ field;
17
+ constructor(field) {
18
+ super(`schema-mismatch: ${field}`);
19
+ this.field = field;
20
+ }
21
+ }
22
+ class SchemaVersionError extends Error {
23
+ constructor() {
24
+ super('unsupported-schema-version');
25
+ }
26
+ }
27
+ function record(raw, field) {
28
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
29
+ throw new SchemaError(field);
30
+ return raw;
31
+ }
32
+ function requiredString(source, field) {
33
+ const value = source[field];
34
+ if (typeof value !== 'string' || value.length === 0)
35
+ throw new SchemaError(field);
36
+ return value;
37
+ }
38
+ function optionalString(source, field) {
39
+ const value = source[field];
40
+ if (value === undefined)
41
+ return undefined;
42
+ if (typeof value !== 'string')
43
+ throw new SchemaError(field);
44
+ return value;
45
+ }
46
+ function requiredNonNegativeInteger(source, field) {
47
+ const value = source[field];
48
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
49
+ throw new SchemaError(field);
50
+ }
51
+ return value;
52
+ }
53
+ function requiredStringArray(source, field) {
54
+ const value = source[field];
55
+ if (!Array.isArray(value) || value.some((member) => typeof member !== 'string')) {
56
+ throw new SchemaError(field);
57
+ }
58
+ return value;
59
+ }
60
+ function member(source, field, allowed) {
61
+ const value = source[field];
62
+ if (typeof value !== 'string' || !allowed.includes(value)) {
63
+ throw new SchemaError(field);
64
+ }
65
+ return value;
66
+ }
67
+ const AUTHORITIES = ['provider', 'controller', 'hardware'];
68
+ const IMPACTS = ['read', 'write', 'session', 'disruptive', 'recovery'];
69
+ const CONFIDENCES = ['high', 'medium', 'low', 'unknown'];
70
+ const UNKNOWN_OUTCOME_REASONS = [
71
+ 'stale-generation',
72
+ 'write-reply-timed-out',
73
+ 'write-reply-dropped',
74
+ ];
75
+ function parseDescriptor(raw) {
76
+ const source = record(raw, 'descriptor');
77
+ return {
78
+ descriptorId: requiredString(source, 'descriptorId'),
79
+ provider: requiredString(source, 'provider'),
80
+ authority: member(source, 'authority', AUTHORITIES),
81
+ mutationImpact: member(source, 'mutationImpact', IMPACTS),
82
+ profiles: requiredStringArray(source, 'profiles'),
83
+ firmware: requiredStringArray(source, 'firmware'),
84
+ confidence: member(source, 'confidence', CONFIDENCES),
85
+ };
86
+ }
87
+ function parseOutcome(raw) {
88
+ const source = record(raw, 'outcome');
89
+ switch (member(source, 'status', ['applied', 'refused', 'failed', 'unknown-outcome'])) {
90
+ case 'applied':
91
+ return { status: 'applied' };
92
+ case 'refused':
93
+ return { status: 'refused', reason: requiredString(source, 'reason') };
94
+ case 'failed':
95
+ return { status: 'failed', reason: requiredString(source, 'reason') };
96
+ case 'unknown-outcome':
97
+ return {
98
+ status: 'unknown-outcome',
99
+ reason: member(source, 'reason', UNKNOWN_OUTCOME_REASONS),
100
+ };
101
+ }
102
+ }
103
+ function parseEntry(raw) {
104
+ const source = record(raw, 'entry');
105
+ if (source.schemaVersion !== MODEM_CONTROL_JOURNAL_SCHEMA_VERSION)
106
+ throw new SchemaVersionError();
107
+ const base = {
108
+ schemaVersion: MODEM_CONTROL_JOURNAL_SCHEMA_VERSION,
109
+ operationId: requiredString(source, 'operationId'),
110
+ physicalModemId: requiredString(source, 'physicalModemId'),
111
+ generation: requiredNonNegativeInteger(source, 'generation'),
112
+ recordedAtMs: requiredNonNegativeInteger(source, 'recordedAtMs'),
113
+ descriptor: parseDescriptor(source.descriptor),
114
+ };
115
+ if (member(source, 'phase', ['started', 'completed']) === 'started') {
116
+ return { ...base, phase: 'started' };
117
+ }
118
+ return { ...base, phase: 'completed', outcome: parseOutcome(source.outcome) };
119
+ }
120
+ /** Classify a decode failure into a metadata-only result (never raw content). */
121
+ function classify(error) {
122
+ if (error instanceof SchemaVersionError)
123
+ return { code: 'unsupported-schema-version' };
124
+ if (error instanceof SchemaError)
125
+ return { code: 'schema-mismatch', field: error.field };
126
+ if (error instanceof SyntaxError) {
127
+ const offset = /position (\d+)/.exec(error.message)?.[1];
128
+ return offset === undefined
129
+ ? { code: 'invalid-json' }
130
+ : { code: 'invalid-json', offset: Number(offset) };
131
+ }
132
+ return { code: 'unreadable' };
133
+ }
134
+ /**
135
+ * Serialize one entry to a single line WITHOUT its terminator.
136
+ *
137
+ * Keys are emitted in a fixed order because the round-trip tests compare bytes,
138
+ * and byte comparison is the only assertion that catches a field a reader silently
139
+ * dropped (`JSON.parse` + a permissive validator will happily lose one and still
140
+ * report success — the same trap the srtla telemetry byte-parity suite exists for).
141
+ */
142
+ export function encodeJournalEntry(entry) {
143
+ const descriptor = {
144
+ descriptorId: entry.descriptor.descriptorId,
145
+ provider: entry.descriptor.provider,
146
+ authority: entry.descriptor.authority,
147
+ mutationImpact: entry.descriptor.mutationImpact,
148
+ profiles: entry.descriptor.profiles,
149
+ firmware: entry.descriptor.firmware,
150
+ confidence: entry.descriptor.confidence,
151
+ };
152
+ const base = {
153
+ schemaVersion: entry.schemaVersion,
154
+ phase: entry.phase,
155
+ operationId: entry.operationId,
156
+ physicalModemId: entry.physicalModemId,
157
+ generation: entry.generation,
158
+ recordedAtMs: entry.recordedAtMs,
159
+ descriptor,
160
+ };
161
+ return JSON.stringify(entry.phase === 'started' ? base : { ...base, outcome: entry.outcome });
162
+ }
163
+ /** Decode one line. An empty/whitespace-only line is reported, never silently dropped. */
164
+ export function decodeJournalEntry(line) {
165
+ if (line.trim().length === 0)
166
+ return { ok: false, failure: { code: 'empty' } };
167
+ try {
168
+ return { ok: true, value: parseEntry(JSON.parse(line)) };
169
+ }
170
+ catch (error) {
171
+ return { ok: false, failure: classify(error) };
172
+ }
173
+ }
174
+ /** Decode an arbitrary JSON document with a caller-supplied validator. */
175
+ export function decodeJournalDocument(text, validate) {
176
+ if (text.trim().length === 0)
177
+ return { ok: false, failure: { code: 'empty' } };
178
+ try {
179
+ return { ok: true, value: validate(JSON.parse(text)) };
180
+ }
181
+ catch (error) {
182
+ return { ok: false, failure: classify(error) };
183
+ }
184
+ }
185
+ /** The validator helpers the legacy reader reuses, so both shapes fail the same way. */
186
+ export const journalSchema = {
187
+ record,
188
+ requiredString,
189
+ optionalString,
190
+ requiredNonNegativeInteger,
191
+ member,
192
+ schemaError(field) {
193
+ return new SchemaError(field);
194
+ },
195
+ schemaVersionError() {
196
+ return new SchemaVersionError();
197
+ },
198
+ };
@@ -0,0 +1,28 @@
1
+ import type { OperationJournalEvent, OperationJournalHook } from '../operations/contracts.js';
2
+ import { type JournalRecovery } from './recovery.js';
3
+ import type { JournalStore } from './store.js';
4
+ export interface JournalEngineOptions {
5
+ /** The store — and therefore the path — is supplied by the composition root. */
6
+ readonly store: JournalStore;
7
+ readonly now?: () => number;
8
+ }
9
+ export declare class JournalEngine {
10
+ #private;
11
+ constructor(options: JournalEngineOptions);
12
+ /** Where this engine journals to; useful in a recovery log line. */
13
+ get path(): string;
14
+ /**
15
+ * The `OperationJournalHook` implementation.
16
+ *
17
+ * Generic per CALL rather than per instance, so one engine journals every
18
+ * descriptor in the process. A generic method satisfies the non-generic
19
+ * `OperationJournalHook<I, O>` member by instantiation, which is what lets an
20
+ * `OperationExecution` take the engine itself as its `journal`.
21
+ */
22
+ record<I, O>(event: OperationJournalEvent<I, O>): Promise<void>;
23
+ /** An explicitly typed hook, for a caller that prefers a narrow object. */
24
+ hook<I, O>(): OperationJournalHook<I, O>;
25
+ /** Read the journal back and reconstruct what was in flight. */
26
+ recover(): Promise<JournalRecovery>;
27
+ }
28
+ export declare function createJournalEngine(options: JournalEngineOptions): JournalEngine;
@@ -0,0 +1,68 @@
1
+ // The journal engine: the operation engine's journal hook, plus replay.
2
+ //
3
+ // `OperationExecution.journal` (todo 20's `OperationJournalHook`) is the seam the
4
+ // operation engine already calls at `started` and at `completed`, and a descriptor
5
+ // that declares `journal: { required: true }` is REFUSED outright when no hook is
6
+ // supplied. This class is the durable implementation of that seam: it flattens the
7
+ // event onto a journal entry and appends it, and it reads the same file back.
8
+ //
9
+ // THE ENGINE HOLDS NO PATH. It holds a `JournalStore`, and the store was handed a
10
+ // path by whoever composed it. That is the same injection shape todo 19 used for
11
+ // the ownership lock (`FlockResourceOwnershipOptions.lockPath` is REQUIRED and the
12
+ // adapter substitutes nothing) and the same reason: an embedding process owns its
13
+ // filesystem layout, and a library that guesses one is a library that writes to the
14
+ // wrong disk on a device it has never seen.
15
+ //
16
+ // THE CLOCK IS INJECTED TOO. `now` defaults to `Date.now`, but a test that wants
17
+ // deterministic timestamps supplies its own — the observation layer's rule that
18
+ // this package never stamps data with a time it did not come from applies here as
19
+ // well, and a replay assertion over timestamps must not be a race.
20
+ import { journalDescriptorEvidence, journalOutcome } from './entry.js';
21
+ import { reconstructJournalRecovery } from './recovery.js';
22
+ export class JournalEngine {
23
+ #store;
24
+ #now;
25
+ constructor(options) {
26
+ this.#store = options.store;
27
+ this.#now = options.now ?? (() => Date.now());
28
+ }
29
+ /** Where this engine journals to; useful in a recovery log line. */
30
+ get path() {
31
+ return this.#store.path;
32
+ }
33
+ /**
34
+ * The `OperationJournalHook` implementation.
35
+ *
36
+ * Generic per CALL rather than per instance, so one engine journals every
37
+ * descriptor in the process. A generic method satisfies the non-generic
38
+ * `OperationJournalHook<I, O>` member by instantiation, which is what lets an
39
+ * `OperationExecution` take the engine itself as its `journal`.
40
+ */
41
+ record(event) {
42
+ return this.#store.append(entryFor(event, this.#now()));
43
+ }
44
+ /** An explicitly typed hook, for a caller that prefers a narrow object. */
45
+ hook() {
46
+ return { record: (event) => this.record(event) };
47
+ }
48
+ /** Read the journal back and reconstruct what was in flight. */
49
+ async recover() {
50
+ return reconstructJournalRecovery(await this.#store.read());
51
+ }
52
+ }
53
+ function entryFor(event, recordedAtMs) {
54
+ const base = {
55
+ schemaVersion: 1,
56
+ operationId: event.operationId,
57
+ physicalModemId: event.physicalModemId,
58
+ generation: event.generation,
59
+ recordedAtMs,
60
+ descriptor: journalDescriptorEvidence(event.descriptor),
61
+ };
62
+ return event.phase === 'started'
63
+ ? { ...base, phase: 'started' }
64
+ : { ...base, phase: 'completed', outcome: journalOutcome(event.result) };
65
+ }
66
+ export function createJournalEngine(options) {
67
+ return new JournalEngine(options);
68
+ }