@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,74 @@
1
+ import type { MutationImpact, OperationConfidence, OperationDescriptor, OperationResult } from '../domain/index.js';
2
+ /** The current on-disk schema version for one journal line. */
3
+ export declare const MODEM_CONTROL_JOURNAL_SCHEMA_VERSION = 1;
4
+ /** The two phases the operation engine emits, mirrored one-to-one on disk. */
5
+ export type JournalPhase = 'started' | 'completed';
6
+ /**
7
+ * How an operation ended, projected from `OperationResult` WITHOUT its value.
8
+ *
9
+ * `unknown-outcome` keeps the frozen domain reason union verbatim rather than
10
+ * widening to `string`, because those three reasons are the entire vocabulary a
11
+ * recovery pass branches on and a fourth spelling would silently read as an
12
+ * ordinary failure.
13
+ */
14
+ export type JournalOutcome = {
15
+ readonly status: 'applied';
16
+ } | {
17
+ readonly status: 'refused';
18
+ readonly reason: string;
19
+ } | {
20
+ readonly status: 'failed';
21
+ readonly reason: string;
22
+ } | {
23
+ readonly status: 'unknown-outcome';
24
+ readonly reason: 'stale-generation' | 'write-reply-timed-out' | 'write-reply-dropped';
25
+ };
26
+ /**
27
+ * The descriptor facts a recovery pass needs, flattened out of the descriptor.
28
+ *
29
+ * The descriptor itself is not persisted: it carries FUNCTIONS (`readback.matches`,
30
+ * the constraint predicates) that no serialization round-trips, so storing it would
31
+ * produce a document that reads back as a different object than it was written from.
32
+ * These are the fields that answer "what was being changed, by whom, on what
33
+ * evidence" — everything a human or a reconciler needs to judge a stranded write.
34
+ */
35
+ export interface JournalDescriptorEvidence {
36
+ readonly descriptorId: string;
37
+ readonly provider: string;
38
+ readonly authority: 'provider' | 'controller' | 'hardware';
39
+ readonly mutationImpact: MutationImpact;
40
+ readonly profiles: readonly string[];
41
+ readonly firmware: readonly string[];
42
+ readonly confidence: OperationConfidence;
43
+ }
44
+ interface JournalEntryBase {
45
+ readonly schemaVersion: typeof MODEM_CONTROL_JOURNAL_SCHEMA_VERSION;
46
+ readonly operationId: string;
47
+ /** The serialized `PhysicalModemId`. Stored as text; branding is a compile-time fact. */
48
+ readonly physicalModemId: string;
49
+ /** The serialized `DeviceGeneration` the operation was fenced to. */
50
+ readonly generation: number;
51
+ readonly recordedAtMs: number;
52
+ readonly descriptor: JournalDescriptorEvidence;
53
+ }
54
+ /**
55
+ * One journal line.
56
+ *
57
+ * The two members differ in SHAPE, not just in a label: a `started` entry has no
58
+ * `outcome` KEY at all. That is the same rule `observations/reading.ts` follows —
59
+ * a consumer cannot read an outcome off a phase that has none, so "in flight" can
60
+ * never be mistaken for "ended with an unset outcome".
61
+ */
62
+ export type JournalEntry = (JournalEntryBase & {
63
+ readonly phase: 'started';
64
+ }) | (JournalEntryBase & {
65
+ readonly phase: 'completed';
66
+ readonly outcome: JournalOutcome;
67
+ });
68
+ /** Flatten a descriptor down to the serializable evidence the journal keeps. */
69
+ export declare function journalDescriptorEvidence<I, O>(descriptor: OperationDescriptor<I, O>): JournalDescriptorEvidence;
70
+ /** Project a result onto its journalable outcome, dropping the value by design. */
71
+ export declare function journalOutcome<O>(result: OperationResult<O>): JournalOutcome;
72
+ /** True when an outcome leaves the device in a state nobody has read back. */
73
+ export declare function outcomeRequiresReconciliation(outcome: JournalOutcome): boolean;
74
+ export {};
@@ -0,0 +1,56 @@
1
+ // The transaction journal's ENTRY vocabulary.
2
+ //
3
+ // The journal exists to answer ONE question after an unclean restart: which
4
+ // mutations were in flight, and which of them ended in an outcome nobody can
5
+ // read off the device. `operations/operation-engine.ts` already closes a
6
+ // per-modem gate when a write classifies `unknown-outcome`, but that gate lives
7
+ // in a `Set` on the engine instance — a process death takes it with it. Writing
8
+ // the same two facts down is what makes the gate survive the process.
9
+ //
10
+ // WHY AN EVENT LOG AND NOT A LATEST-STATE SNAPSHOT. A started event and its
11
+ // completion are two facts separated by exactly the window a crash lands in, so
12
+ // the shape has to be able to hold the first without the second. A document that
13
+ // only ever carries "the current state" cannot distinguish "we never dispatched"
14
+ // from "we dispatched and the reply never came" unless it spends a state name on
15
+ // each — which is how CeraUI's own mutation journal does it (see
16
+ // `legacy-ceraui.ts`, which reads that shape). Both are legitimate; this one is
17
+ // append-only because appending is the only write that cannot lose a prior fact.
18
+ //
19
+ // WHAT IS DELIBERATELY NOT RECORDED: the operation's INPUT and the operation's
20
+ // RETURNED VALUE. An input is routinely a PIN, a PUK, or a USSD command carrying
21
+ // a voucher code, and a returned value is routinely a message body or a location
22
+ // fix — all of them classes `../redact.ts` masks everywhere else. The journal
23
+ // records THAT an operation ran and HOW it ended, never WHAT was sent or read.
24
+ // A caller that needs a rollback payload owns persisting it beside the journal
25
+ // under its own redaction decision.
26
+ /** The current on-disk schema version for one journal line. */
27
+ export const MODEM_CONTROL_JOURNAL_SCHEMA_VERSION = 1;
28
+ /** Flatten a descriptor down to the serializable evidence the journal keeps. */
29
+ export function journalDescriptorEvidence(descriptor) {
30
+ return {
31
+ descriptorId: descriptor.id,
32
+ provider: descriptor.provider,
33
+ authority: descriptor.authority,
34
+ mutationImpact: descriptor.mutationImpact,
35
+ profiles: [...descriptor.evidence.profiles],
36
+ firmware: [...descriptor.evidence.firmware],
37
+ confidence: descriptor.confidence,
38
+ };
39
+ }
40
+ /** Project a result onto its journalable outcome, dropping the value by design. */
41
+ export function journalOutcome(result) {
42
+ switch (result.status) {
43
+ case 'applied':
44
+ return { status: 'applied' };
45
+ case 'refused':
46
+ return { status: 'refused', reason: result.reason };
47
+ case 'failed':
48
+ return { status: 'failed', reason: result.reason };
49
+ case 'unknown-outcome':
50
+ return { status: 'unknown-outcome', reason: result.reason };
51
+ }
52
+ }
53
+ /** True when an outcome leaves the device in a state nobody has read back. */
54
+ export function outcomeRequiresReconciliation(outcome) {
55
+ return outcome.status === 'unknown-outcome';
56
+ }
@@ -0,0 +1,6 @@
1
+ export * from './codec.js';
2
+ export * from './engine.js';
3
+ export * from './entry.js';
4
+ export * from './legacy-ceraui.js';
5
+ export * from './recovery.js';
6
+ export * from './store.js';
@@ -0,0 +1,6 @@
1
+ export * from './codec.js';
2
+ export * from './engine.js';
3
+ export * from './entry.js';
4
+ export * from './legacy-ceraui.js';
5
+ export * from './recovery.js';
6
+ export * from './store.js';
@@ -0,0 +1,73 @@
1
+ import { type JournalDecodeResult } from './codec.js';
2
+ import type { JournalDescriptorEvidence } from './entry.js';
3
+ import { type JournalOperationRecord, type JournalRecovery } from './recovery.js';
4
+ /** The `version` literal CeraUI's schema pins. */
5
+ export declare const LEGACY_CERAUI_JOURNAL_VERSION = 1;
6
+ /** CeraUI's cap on retained history entries per slot. */
7
+ export declare const LEGACY_CERAUI_HISTORY_CAP = 32;
8
+ export declare const LEGACY_CERAUI_MUTATION_STATES: readonly ['armed', 'executing', 'completed', 'failed', 'acknowledged', 'device-absent-quarantine', 'decommissioned', 'recommission-pending'];
9
+ export type LegacyCeraUiMutationState = (typeof LEGACY_CERAUI_MUTATION_STATES)[number];
10
+ export declare const LEGACY_CERAUI_ACK_MODES: readonly ['verified-rollback', 'force-rebaseline'];
11
+ export type LegacyCeraUiAckMode = (typeof LEGACY_CERAUI_ACK_MODES)[number];
12
+ export interface LegacyCeraUiHistoryEntry {
13
+ readonly state: LegacyCeraUiMutationState;
14
+ readonly at: number;
15
+ readonly detail?: string;
16
+ }
17
+ /** One CeraUI slot document, decoded verbatim. `preState` is kept opaque. */
18
+ export interface LegacyCeraUiMutationEntry {
19
+ readonly version: typeof LEGACY_CERAUI_JOURNAL_VERSION;
20
+ readonly stableKey: string;
21
+ readonly kind: string;
22
+ readonly state: LegacyCeraUiMutationState;
23
+ readonly attemptId: string;
24
+ readonly startedAt: number;
25
+ readonly updatedAt: number;
26
+ /** The rollback target. Opaque by design — its shape is the mutation kind's. */
27
+ readonly preState: Readonly<Record<string, unknown>>;
28
+ readonly detail?: string;
29
+ readonly acknowledgedMode?: LegacyCeraUiAckMode;
30
+ readonly history: readonly LegacyCeraUiHistoryEntry[];
31
+ }
32
+ export interface LegacyCeraUiJournalRead {
33
+ /** The same recovery model `reconstructJournalRecovery` produces. */
34
+ readonly recovery: JournalRecovery;
35
+ /** The decoded slot documents, verbatim, so `preState` survives the read. */
36
+ readonly entries: readonly LegacyCeraUiMutationEntry[];
37
+ }
38
+ export interface LegacyCeraUiJournalOptions {
39
+ /** REQUIRED. The embedding process owns where CeraUI put its journal. */
40
+ readonly dir: string;
41
+ }
42
+ /**
43
+ * The slot filename CeraUI derives from a stable key.
44
+ *
45
+ * RULE-D MIRROR of CeraUI's own helper. It is a plain lowercase-hex SHA-256 of the
46
+ * key's UTF-8 bytes; the key itself never appears in the filename in plaintext.
47
+ */
48
+ export declare function legacyMutationSlotName(stableKey: string): string;
49
+ /** Validate one slot document. Throws the codec's metadata-only schema errors. */
50
+ export declare function validateLegacyCeraUiEntry(raw: unknown): LegacyCeraUiMutationEntry;
51
+ /** Decode one slot document's text. Never throws; returns a typed failure. */
52
+ export declare function decodeLegacyCeraUiEntry(text: string): JournalDecodeResult<LegacyCeraUiMutationEntry>;
53
+ /**
54
+ * The descriptor evidence a legacy entry can honestly supply.
55
+ *
56
+ * CeraUI's journal predates `OperationDescriptor`, so there is no descriptor to
57
+ * flatten. Every field below is either a fact the file actually carries (the
58
+ * mutation kind) or an explicit statement that the file carries nothing:
59
+ * `confidence: 'unknown'` rather than a borrowed default, and empty evidence
60
+ * arrays rather than invented profiles. `mutationImpact: 'write'` is a fact, not a
61
+ * guess — CeraUI's file is a MUTATION journal and records nothing else.
62
+ */
63
+ export declare function legacyDescriptorEvidence(kind: string): JournalDescriptorEvidence;
64
+ /** Project one decoded slot onto the shared recovery record model. */
65
+ export declare function legacyOperationRecord(entry: LegacyCeraUiMutationEntry): JournalOperationRecord;
66
+ /**
67
+ * Read a whole CeraUI journal directory.
68
+ *
69
+ * An unreadable or non-conforming slot is reported as damage and LEFT IN PLACE;
70
+ * every readable slot still comes back. That is the same non-truncating contract
71
+ * the native store makes, applied to a directory instead of a file.
72
+ */
73
+ export declare function readLegacyCeraUiJournal(options: LegacyCeraUiJournalOptions): Promise<LegacyCeraUiJournalRead>;
@@ -0,0 +1,227 @@
1
+ // COMPATIBILITY READ PATH for the mutation journal CeraUI already writes.
2
+ //
3
+ // WHY THIS EXISTS AT ALL. CeraUI has kept a durable modem-mutation journal since
4
+ // long before this package had one, and devices in the field have those files on
5
+ // disk right now. The two shapes are genuinely different — this package's journal
6
+ // is an append-only EVENT LOG in one file, CeraUI's is a directory of per-modem
7
+ // LATEST-STATE SNAPSHOTS, one JSON document per modem, rewritten whole on every
8
+ // transition. Neither can be re-labelled into the other, so the bridge is a READER:
9
+ // it decodes CeraUI's shape into the SAME `JournalOperationRecord` model
10
+ // `recovery.ts` produces, so a consumer enumerates pending and unknown-outcome work
11
+ // across both without CeraUI having to change its file format first.
12
+ //
13
+ // NOTHING HERE WRITES. This module reads and decodes; it never rewrites, repairs,
14
+ // migrates in place, or deletes a slot. CeraUI's own reader leaves an unreadable
15
+ // slot on disk deliberately, and a second reader that "cleaned up" behind it would
16
+ // destroy the evidence CeraUI kept on purpose.
17
+ //
18
+ // THE DIRECTORY IS INJECTED, exactly like the native store's path — this module
19
+ // hardcodes no location, and the path-injection gate covers it.
20
+ //
21
+ // THE SLOT FILENAME IS A HASH, and `legacyMutationSlotName` mirrors that rule so a
22
+ // consumer can address ONE modem's slot without scanning. It is a RULE-D MIRROR of
23
+ // CeraUI's helper, never a shared import — the same relationship the support-claim
24
+ // ladder and the redaction key sets already have with their CeraUI twins.
25
+ //
26
+ // TWO MAPPING DECISIONS CARRY WEIGHT:
27
+ //
28
+ // * `armed` maps to `pending` and `executing` maps to `unknown-outcome`. They are
29
+ // not the same fact. `armed` says the pre-state was captured and the write had
30
+ // not been dispatched, so the device is untouched. `executing` says the write
31
+ // WAS dispatched and no terminal state was ever recorded — which is precisely
32
+ // what this package calls an unknown outcome. Collapsing them would either
33
+ // invent certainty about a dispatched write or manufacture doubt about one that
34
+ // never left.
35
+ //
36
+ // * `kind` is validated as a NON-EMPTY STRING, not against a frozen enum.
37
+ // CeraUI's runtime enum spreads its capability-module mutation kinds into the
38
+ // list, so the vocabulary grows on CeraUI's release cycle. Freezing a copy here
39
+ // would make this reader reject a perfectly valid file the day CeraUI adds a
40
+ // capability module — a compatibility reader that fails closed on new-but-valid
41
+ // input is worse than no reader at all.
42
+ import { createHash } from 'node:crypto';
43
+ import { readdir, readFile } from 'node:fs/promises';
44
+ import { join } from 'node:path';
45
+ import { decodeJournalDocument, journalSchema } from './codec.js';
46
+ import { summarizeJournalRecords, } from './recovery.js';
47
+ /** The `version` literal CeraUI's schema pins. */
48
+ export const LEGACY_CERAUI_JOURNAL_VERSION = 1;
49
+ /** CeraUI's cap on retained history entries per slot. */
50
+ export const LEGACY_CERAUI_HISTORY_CAP = 32;
51
+ export const LEGACY_CERAUI_MUTATION_STATES = [
52
+ 'armed',
53
+ 'executing',
54
+ 'completed',
55
+ 'failed',
56
+ 'acknowledged',
57
+ 'device-absent-quarantine',
58
+ 'decommissioned',
59
+ 'recommission-pending',
60
+ ];
61
+ export const LEGACY_CERAUI_ACK_MODES = ['verified-rollback', 'force-rebaseline'];
62
+ /**
63
+ * The slot filename CeraUI derives from a stable key.
64
+ *
65
+ * RULE-D MIRROR of CeraUI's own helper. It is a plain lowercase-hex SHA-256 of the
66
+ * key's UTF-8 bytes; the key itself never appears in the filename in plaintext.
67
+ */
68
+ export function legacyMutationSlotName(stableKey) {
69
+ return createHash('sha256').update(stableKey, 'utf8').digest('hex');
70
+ }
71
+ function parseHistoryEntry(raw) {
72
+ const source = journalSchema.record(raw, 'history[]');
73
+ const detail = journalSchema.optionalString(source, 'detail');
74
+ return {
75
+ state: journalSchema.member(source, 'state', LEGACY_CERAUI_MUTATION_STATES),
76
+ at: journalSchema.requiredNonNegativeInteger(source, 'at'),
77
+ ...(detail === undefined ? {} : { detail }),
78
+ };
79
+ }
80
+ /** Validate one slot document. Throws the codec's metadata-only schema errors. */
81
+ export function validateLegacyCeraUiEntry(raw) {
82
+ const source = journalSchema.record(raw, 'entry');
83
+ if (source.version !== LEGACY_CERAUI_JOURNAL_VERSION)
84
+ throw journalSchema.schemaVersionError();
85
+ const history = source.history;
86
+ if (!Array.isArray(history) || history.length > LEGACY_CERAUI_HISTORY_CAP) {
87
+ throw journalSchema.schemaError('history');
88
+ }
89
+ const detail = journalSchema.optionalString(source, 'detail');
90
+ const acknowledgedMode = source.acknowledgedMode === undefined
91
+ ? undefined
92
+ : journalSchema.member(source, 'acknowledgedMode', LEGACY_CERAUI_ACK_MODES);
93
+ return {
94
+ version: LEGACY_CERAUI_JOURNAL_VERSION,
95
+ stableKey: journalSchema.requiredString(source, 'stableKey'),
96
+ kind: journalSchema.requiredString(source, 'kind'),
97
+ state: journalSchema.member(source, 'state', LEGACY_CERAUI_MUTATION_STATES),
98
+ attemptId: journalSchema.requiredString(source, 'attemptId'),
99
+ startedAt: journalSchema.requiredNonNegativeInteger(source, 'startedAt'),
100
+ updatedAt: journalSchema.requiredNonNegativeInteger(source, 'updatedAt'),
101
+ preState: journalSchema.record(source.preState, 'preState'),
102
+ ...(detail === undefined ? {} : { detail }),
103
+ ...(acknowledgedMode === undefined ? {} : { acknowledgedMode }),
104
+ history: history.map(parseHistoryEntry),
105
+ };
106
+ }
107
+ /** Decode one slot document's text. Never throws; returns a typed failure. */
108
+ export function decodeLegacyCeraUiEntry(text) {
109
+ return decodeJournalDocument(text, validateLegacyCeraUiEntry);
110
+ }
111
+ const DISPOSITION_BY_STATE = {
112
+ // Pre-state captured, write never dispatched: the device is untouched.
113
+ armed: 'pending',
114
+ // Dispatched with no terminal record — this package's `unknown-outcome`.
115
+ executing: 'unknown-outcome',
116
+ completed: 'resolved',
117
+ // CeraUI's replay keeps these on disk and refuses further mutations until an
118
+ // operator acknowledges. That is a KNOWN bad ending, not an unknown one.
119
+ failed: 'blocked',
120
+ acknowledged: 'resolved',
121
+ 'device-absent-quarantine': 'blocked',
122
+ decommissioned: 'blocked',
123
+ 'recommission-pending': 'blocked',
124
+ };
125
+ /**
126
+ * The descriptor evidence a legacy entry can honestly supply.
127
+ *
128
+ * CeraUI's journal predates `OperationDescriptor`, so there is no descriptor to
129
+ * flatten. Every field below is either a fact the file actually carries (the
130
+ * mutation kind) or an explicit statement that the file carries nothing:
131
+ * `confidence: 'unknown'` rather than a borrowed default, and empty evidence
132
+ * arrays rather than invented profiles. `mutationImpact: 'write'` is a fact, not a
133
+ * guess — CeraUI's file is a MUTATION journal and records nothing else.
134
+ */
135
+ export function legacyDescriptorEvidence(kind) {
136
+ return {
137
+ descriptorId: kind,
138
+ provider: 'ceraui-legacy',
139
+ authority: 'controller',
140
+ mutationImpact: 'write',
141
+ profiles: [],
142
+ firmware: [],
143
+ confidence: 'unknown',
144
+ };
145
+ }
146
+ /** Project one decoded slot onto the shared recovery record model. */
147
+ export function legacyOperationRecord(entry) {
148
+ const disposition = DISPOSITION_BY_STATE[entry.state];
149
+ return {
150
+ operationId: entry.attemptId,
151
+ // CeraUI's `stableKey`, NOT a `PhysicalModemId` — see the note on the field.
152
+ physicalModemId: entry.stableKey,
153
+ // CeraUI's journal has no device-generation fence; 0 records "unfenced"
154
+ // rather than claiming a generation the file never carried.
155
+ generation: 0,
156
+ descriptor: legacyDescriptorEvidence(entry.kind),
157
+ disposition,
158
+ origin: 'legacy-ceraui',
159
+ startedAtMs: entry.startedAt,
160
+ updatedAtMs: entry.updatedAt,
161
+ // One slot is one attempt; CeraUI rewrites the slot rather than appending.
162
+ attempts: 1,
163
+ // `outcome` is deliberately ABSENT for every legacy record, including the
164
+ // `unknown-outcome` one. `JournalOutcome`'s unknown reason union is the
165
+ // frozen domain vocabulary — `stale-generation` / `write-reply-timed-out` /
166
+ // `write-reply-dropped` — and CeraUI's `executing` state asserts none of
167
+ // them: it says a write was dispatched and never concluded, not why. Naming
168
+ // one anyway would be the invented reading this package refuses everywhere
169
+ // else. The disposition carries the fact; the reason stays unclaimed.
170
+ };
171
+ }
172
+ /**
173
+ * Read a whole CeraUI journal directory.
174
+ *
175
+ * An unreadable or non-conforming slot is reported as damage and LEFT IN PLACE;
176
+ * every readable slot still comes back. That is the same non-truncating contract
177
+ * the native store makes, applied to a directory instead of a file.
178
+ */
179
+ export async function readLegacyCeraUiJournal(options) {
180
+ let names;
181
+ try {
182
+ names = await readdir(options.dir);
183
+ }
184
+ catch (error) {
185
+ if (error.code === 'ENOENT') {
186
+ return { recovery: summarizeJournalRecords([], []), entries: [] };
187
+ }
188
+ return {
189
+ recovery: summarizeJournalRecords([], [{ location: { kind: 'file' }, bytes: 0, failure: { code: 'unreadable' } }]),
190
+ entries: [],
191
+ };
192
+ }
193
+ const entries = [];
194
+ const damage = [];
195
+ // Sorted so a recovery report is stable across filesystems that do not order
196
+ // `readdir`; the slot names are hashes, so the order carries no other meaning.
197
+ for (const name of [...names].sort()) {
198
+ if (!name.endsWith('.json'))
199
+ continue;
200
+ let text;
201
+ try {
202
+ text = await readFile(join(options.dir, name), 'utf8');
203
+ }
204
+ catch {
205
+ damage.push({
206
+ location: { kind: 'slot', slot: name },
207
+ bytes: 0,
208
+ failure: { code: 'unreadable' },
209
+ });
210
+ continue;
211
+ }
212
+ const decoded = decodeLegacyCeraUiEntry(text);
213
+ if (decoded.ok) {
214
+ entries.push(decoded.value);
215
+ continue;
216
+ }
217
+ damage.push({
218
+ location: { kind: 'slot', slot: name },
219
+ bytes: Buffer.byteLength(text, 'utf8'),
220
+ failure: decoded.failure,
221
+ });
222
+ }
223
+ return {
224
+ recovery: summarizeJournalRecords(entries.map(legacyOperationRecord), damage),
225
+ entries,
226
+ };
227
+ }
@@ -0,0 +1,58 @@
1
+ import { DomainError } from '../domain/index.js';
2
+ import type { JournalDescriptorEvidence, JournalOutcome } from './entry.js';
3
+ import type { JournalDamageRecord, JournalReadResult } from './store.js';
4
+ export type JournalRecoveryDisposition = 'pending' | 'unknown-outcome' | 'resolved' | 'blocked';
5
+ /** Where a record came from. Also tells a consumer how to read `physicalModemId`. */
6
+ export type JournalRecordOrigin = 'native' | 'legacy-ceraui';
7
+ /**
8
+ * One operation reconstructed from the journal.
9
+ *
10
+ * `physicalModemId` is TEXT, not the branded `PhysicalModemId`, and that is
11
+ * deliberate: for a `legacy-ceraui` record it holds CeraUI's own `stableKey`,
12
+ * which is a different identity vocabulary and would be REFUSED by
13
+ * `physicalModemId()`'s constructor. Coercing it would either throw on a
14
+ * perfectly valid legacy file or launder a foreign identity into a branded type
15
+ * that promises it came from the serial / ID_PATH ladder. `origin` is what tells
16
+ * a consumer which vocabulary it is holding.
17
+ */
18
+ export interface JournalOperationRecord {
19
+ readonly operationId: string;
20
+ readonly physicalModemId: string;
21
+ readonly generation: number;
22
+ readonly descriptor: JournalDescriptorEvidence;
23
+ readonly disposition: JournalRecoveryDisposition;
24
+ readonly origin: JournalRecordOrigin;
25
+ readonly startedAtMs: number;
26
+ readonly updatedAtMs: number;
27
+ readonly attempts: number;
28
+ readonly outcome?: JournalOutcome;
29
+ }
30
+ export interface JournalRecovery {
31
+ /** Every reconstructed operation, in first-appearance order. */
32
+ readonly records: readonly JournalOperationRecord[];
33
+ readonly pending: readonly JournalOperationRecord[];
34
+ readonly unknownOutcome: readonly JournalOperationRecord[];
35
+ readonly blocked: readonly JournalOperationRecord[];
36
+ /** Distinct modem identities that must be reconciled before the next mutation. */
37
+ readonly reconciliationRequired: readonly string[];
38
+ readonly damage: readonly JournalDamageRecord[];
39
+ }
40
+ /** Raised only by `assertJournalIntact`; recovery itself always returns a report. */
41
+ export declare class JournalRecoveryError extends DomainError {
42
+ readonly name = "JournalRecoveryError";
43
+ readonly damage: readonly JournalDamageRecord[];
44
+ constructor(damage: readonly JournalDamageRecord[]);
45
+ }
46
+ /**
47
+ * Escalate a damaged journal to a throw, for a caller that wants fail-closed.
48
+ *
49
+ * Kept SEPARATE from `reconstructJournalRecovery` on purpose: recovery must be
50
+ * able to hand back the survivors even when part of the file is unreadable, so
51
+ * the decision to refuse to proceed belongs to the caller, after it has seen
52
+ * what did survive.
53
+ */
54
+ export declare function assertJournalIntact(recovery: JournalRecovery): void;
55
+ /** Fold decoded entries into per-operation records. Pure; no clock, no I/O. */
56
+ export declare function reconstructJournalRecovery(read: JournalReadResult): JournalRecovery;
57
+ /** Build the summary views over already-reconstructed records (native or legacy). */
58
+ export declare function summarizeJournalRecords(records: readonly JournalOperationRecord[], damage: readonly JournalDamageRecord[]): JournalRecovery;
@@ -0,0 +1,117 @@
1
+ // Replay: fold a journal back into "what was in flight when we died".
2
+ //
3
+ // This is the whole reason the journal exists. `operations/operation-engine.ts`
4
+ // keeps its uncertainty fence in a `Set<PhysicalModemId>` on the engine instance,
5
+ // so a process death drops it and the next mutation proceeds as if nothing were
6
+ // outstanding. Folding the journal reconstructs that set from disk.
7
+ //
8
+ // THE FOLD IS ORDER-SENSITIVE AND LAST-WRITE-WINS PER OPERATION. Entries are
9
+ // applied in file order, keyed by (physical modem, operation id, generation). A
10
+ // second `started` on a live key is a re-run and re-opens the record rather than
11
+ // being deduplicated away — the journal records attempts, and hiding a second
12
+ // attempt would make a retry loop invisible in exactly the forensics the file is
13
+ // kept for.
14
+ //
15
+ // FOUR DISPOSITIONS, AND TWO OF THEM MEAN "RECONCILE BEFORE MUTATING AGAIN":
16
+ //
17
+ // pending a start with no completion — the process died inside the
18
+ // operation, so whether the device changed is unknown.
19
+ // unknown-outcome a completion the engine itself classified unknown: a stale
20
+ // generation, or a write reply that timed out or was dropped.
21
+ // resolved a definite ending (applied / refused / failed). The engine
22
+ // treats a definite failure as definite; so does this.
23
+ // blocked a terminal state a human must clear. Native journals never
24
+ // produce it today — it exists because CeraUI's own mutation
25
+ // journal HAS such states (`failed`, quarantine, decommission)
26
+ // and `legacy-ceraui.ts` maps onto this same model. Folding
27
+ // those into `resolved` would report an operator-blocked device
28
+ // as healthy; folding them into `unknown-outcome` would claim
29
+ // uncertainty about an outcome that is actually known.
30
+ //
31
+ // `reconciliationRequired` deliberately covers `pending` + `unknown-outcome` only.
32
+ // A blocked record is a KNOWN bad ending awaiting acknowledgement, and answering
33
+ // it with a reconciliation pass would silently clear a state that exists precisely
34
+ // so it cannot be cleared silently.
35
+ import { DomainError } from '../domain/index.js';
36
+ /** Raised only by `assertJournalIntact`; recovery itself always returns a report. */
37
+ export class JournalRecoveryError extends DomainError {
38
+ name = 'JournalRecoveryError';
39
+ damage;
40
+ constructor(damage) {
41
+ super(`journal recovery found ${damage.length} damaged record(s)`);
42
+ this.damage = damage;
43
+ }
44
+ }
45
+ /**
46
+ * Escalate a damaged journal to a throw, for a caller that wants fail-closed.
47
+ *
48
+ * Kept SEPARATE from `reconstructJournalRecovery` on purpose: recovery must be
49
+ * able to hand back the survivors even when part of the file is unreadable, so
50
+ * the decision to refuse to proceed belongs to the caller, after it has seen
51
+ * what did survive.
52
+ */
53
+ export function assertJournalIntact(recovery) {
54
+ if (recovery.damage.length > 0)
55
+ throw new JournalRecoveryError(recovery.damage);
56
+ }
57
+ function keyOf(entry) {
58
+ return `${entry.physicalModemId}\u0000${entry.operationId}\u0000${entry.generation}`;
59
+ }
60
+ function dispositionOf(outcome) {
61
+ return outcome.status === 'unknown-outcome' ? 'unknown-outcome' : 'resolved';
62
+ }
63
+ /** Fold decoded entries into per-operation records. Pure; no clock, no I/O. */
64
+ export function reconstructJournalRecovery(read) {
65
+ const byKey = new Map();
66
+ const order = [];
67
+ for (const { entry } of read.entries) {
68
+ const key = keyOf(entry);
69
+ const previous = byKey.get(key);
70
+ if (previous === undefined)
71
+ order.push(key);
72
+ if (entry.phase === 'started') {
73
+ byKey.set(key, {
74
+ operationId: entry.operationId,
75
+ physicalModemId: entry.physicalModemId,
76
+ generation: entry.generation,
77
+ descriptor: entry.descriptor,
78
+ disposition: 'pending',
79
+ origin: 'native',
80
+ // The FIRST start is when this operation began; a re-run does not
81
+ // rewrite history, it increments the attempt count.
82
+ startedAtMs: previous?.startedAtMs ?? entry.recordedAtMs,
83
+ updatedAtMs: entry.recordedAtMs,
84
+ attempts: (previous?.attempts ?? 0) + 1,
85
+ });
86
+ continue;
87
+ }
88
+ byKey.set(key, {
89
+ operationId: entry.operationId,
90
+ physicalModemId: entry.physicalModemId,
91
+ generation: entry.generation,
92
+ descriptor: entry.descriptor,
93
+ disposition: dispositionOf(entry.outcome),
94
+ origin: 'native',
95
+ startedAtMs: previous?.startedAtMs ?? entry.recordedAtMs,
96
+ updatedAtMs: entry.recordedAtMs,
97
+ // A completion with no start is a journal whose head was never written;
98
+ // counting it as one attempt is more honest than reporting zero.
99
+ attempts: previous?.attempts ?? 1,
100
+ outcome: entry.outcome,
101
+ });
102
+ }
103
+ const records = order
104
+ .map((key) => byKey.get(key))
105
+ .filter((value) => value !== undefined);
106
+ return summarizeJournalRecords(records, read.damage);
107
+ }
108
+ /** Build the summary views over already-reconstructed records (native or legacy). */
109
+ export function summarizeJournalRecords(records, damage) {
110
+ const pending = records.filter((record) => record.disposition === 'pending');
111
+ const unknownOutcome = records.filter((record) => record.disposition === 'unknown-outcome');
112
+ const blocked = records.filter((record) => record.disposition === 'blocked');
113
+ const reconciliationRequired = [
114
+ ...new Set([...pending, ...unknownOutcome].map((record) => record.physicalModemId)),
115
+ ].sort();
116
+ return { records, pending, unknownOutcome, blocked, reconciliationRequired, damage };
117
+ }