@ceralive/modem-control 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +196 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +19 -0
  146. package/dist/index.js +26 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +47 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
@@ -0,0 +1,82 @@
1
+ // The inbox row store — folds `Added` / `Deleted` / resync events into one
2
+ // newest-first, capped list.
3
+ //
4
+ // TWO PROPERTIES ARE THE WHOLE POINT OF THIS FILE, and both are failures a
5
+ // signal-driven inbox produces on its own unless something stops them.
6
+ //
7
+ // DUPLICATE SUPPRESSION. ModemManager emits `Added` for a message it is
8
+ // receiving AND again once it is stored, a re-subscribe after a bus hiccup can
9
+ // replay one, and a consumer that also re-listed would hold the same message
10
+ // twice. Rows are therefore keyed by object index and an `Added` carrying
11
+ // nothing new is a NO-OP — `apply` answers `false` and no consumer is woken.
12
+ //
13
+ // RESTART RECOVERY. A source restart cannot be folded, because the events that
14
+ // happened while it was down were never delivered. `resynced` therefore REPLACES
15
+ // the whole row set rather than merging into it: merging would keep a message
16
+ // deleted during the outage forever, and re-adding each row of a fresh list one
17
+ // `added` at a time is exactly how a restart comes to duplicate the inbox it
18
+ // already had.
19
+ import { SMS_INBOX_CAP, sortAndCapSms } from './normalize.js';
20
+ /** Two rows are the same message when every rendered field matches. */
21
+ function sameMessage(a, b) {
22
+ return (a.id === b.id &&
23
+ a.text === b.text &&
24
+ a.state === b.state &&
25
+ a.from === b.from &&
26
+ a.timestamp === b.timestamp);
27
+ }
28
+ export function createSmsInboxStore(cap = SMS_INBOX_CAP) {
29
+ const rows = new Map();
30
+ const applyAdded = (message) => {
31
+ const held = rows.get(message.id);
32
+ // A repeated Added for a row we already hold VERBATIM is the duplicate
33
+ // this store exists to swallow. An Added that genuinely differs is a
34
+ // state transition (receiving -> received) and does update the row.
35
+ if (held !== undefined && sameMessage(held, message)) {
36
+ return false;
37
+ }
38
+ rows.set(message.id, message);
39
+ return true;
40
+ };
41
+ const applyResynced = (messages) => {
42
+ const next = new Map();
43
+ for (const message of messages) {
44
+ next.set(message.id, message);
45
+ }
46
+ // Report "unchanged" when the authoritative list matches what is already
47
+ // held, so a reconnect that found nothing new broadcasts nothing.
48
+ let changed = next.size !== rows.size;
49
+ if (!changed) {
50
+ for (const [id, message] of next) {
51
+ const held = rows.get(id);
52
+ if (held === undefined || !sameMessage(held, message)) {
53
+ changed = true;
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ rows.clear();
59
+ for (const [id, message] of next) {
60
+ rows.set(id, message);
61
+ }
62
+ return changed;
63
+ };
64
+ return {
65
+ apply(event) {
66
+ switch (event.kind) {
67
+ case 'added':
68
+ return applyAdded(event.message);
69
+ case 'deleted':
70
+ return rows.delete(event.id);
71
+ case 'resynced':
72
+ return applyResynced(event.messages);
73
+ }
74
+ },
75
+ snapshot() {
76
+ return sortAndCapSms([...rows.values()], cap);
77
+ },
78
+ size() {
79
+ return rows.size;
80
+ },
81
+ };
82
+ }
@@ -0,0 +1,4 @@
1
+ export * from './dbus-messaging.js';
2
+ export * from './inbox-store.js';
3
+ export * from './mmcli-parse.js';
4
+ export * from './normalize.js';
@@ -0,0 +1,10 @@
1
+ // The read-only SMS surface: normalization, the `mmcli -K` grammar, the
2
+ // Added/Deleted fold, and the ModemManager Messaging adapter.
3
+ //
4
+ // LIST / READ and observation ONLY. Nothing exported here composes, stores,
5
+ // sends, or deletes a message; `readonly-gate.test.ts` fails the build if that
6
+ // ever stops being true.
7
+ export * from './dbus-messaging.js';
8
+ export * from './inbox-store.js';
9
+ export * from './mmcli-parse.js';
10
+ export * from './normalize.js';
@@ -0,0 +1,54 @@
1
+ import type { SmsMessage, SmsReadRefusal } from '../ports/sms.js';
2
+ /** A parse outcome. `detail` NEVER carries message content — key names only. */
3
+ export type SmsParseResult<T> = {
4
+ readonly ok: true;
5
+ readonly value: T;
6
+ } | {
7
+ readonly ok: false;
8
+ readonly reason: string;
9
+ readonly detail: string;
10
+ };
11
+ /**
12
+ * Undo `g_strescape()` on one `-K` value.
13
+ *
14
+ * mmcli does not print non-ASCII text — it prints the LITERAL ASCII characters
15
+ * of the octal escape. A Spanish message arrives on stdout as the eight
16
+ * characters `\`,`3`,`0`,`2`,`\`,`2`,`4`,`1` where the wire carried the two
17
+ * UTF-8 bytes `0xC2 0xA1` (confirmed on the bench board with `od -c`). The
18
+ * escapes are therefore per-BYTE, not per-character, and the only correct
19
+ * decode is to rebuild the byte sequence and read it back as UTF-8 — decoding
20
+ * each escape with `String.fromCharCode` instead turns `¡` into `¡`.
21
+ *
22
+ * Total and silent: it never throws and never logs, because the value it holds
23
+ * is message content. An escape outside the grammar is copied VERBATIM rather
24
+ * than dropped — mmcli cannot emit one, so meeting one means the value was
25
+ * never escaped and copying it is the only lossless answer.
26
+ */
27
+ export declare function unescapeMmcliValue(value: string): string;
28
+ /**
29
+ * Extract SMS object paths from `mmcli -K -m <id> --messaging-list-sms`.
30
+ *
31
+ * An inbox with no messages prints as `modem.messaging.sms: --`, whose `--`
32
+ * carries no entry — so an ABSENT key is a legitimate EMPTY INBOX, exactly as it
33
+ * is for `--3gpp-scan`, not drift. What IS drift, and what fails loud here, is
34
+ * output that never mentions the key at all (a renamed field, an error body
35
+ * reaching this parser): answering "no messages" to a read that never ran would
36
+ * be the worst possible lie about an inbox.
37
+ */
38
+ export declare function parseSmsListOutput(raw: string): SmsParseResult<string[]>;
39
+ /**
40
+ * Parse one `mmcli -K -s <path>` record.
41
+ *
42
+ * A malformed record reports the KEY NAMES it found and nothing else — see the
43
+ * content-free note at the top of this file.
44
+ */
45
+ export declare function parseSmsRecordOutput(raw: string): SmsParseResult<SmsMessage>;
46
+ /**
47
+ * Classify a source failure into a refusal the operator can act on.
48
+ *
49
+ * The three recognised strings are ModemManager 1.24's own, confirmed on the
50
+ * bench board: a modem with no Messaging interface, a radio that has not come up
51
+ * yet, and a selector nothing answers to. Anything else stays `read_failed`
52
+ * rather than being guessed at.
53
+ */
54
+ export declare function classifySmsFailure(description: string): SmsReadRefusal;
@@ -0,0 +1,224 @@
1
+ // The `mmcli -K` SMS grammar.
2
+ //
3
+ // WHY A CLI GRAMMAR LIVES BESIDE A D-BUS PORT: `mmcli` is a client of the SAME
4
+ // ModemManager daemon the D-Bus adapter talks to, and CeraUI has been reading
5
+ // its inbox through it on real hardware since Phase A. Owning the grammar here
6
+ // is what makes the port's output provable byte-for-byte against that reader on
7
+ // the same captured output (`mmcli-parse.parity.test.ts`) — a claim no amount of
8
+ // D-Bus-only code could support — and it is what lets a consumer move its
9
+ // parsing onto this package without also having to move its transport in the
10
+ // same change.
11
+ //
12
+ // CONTENT-FREE BY CONSTRUCTION. Unlike an ordinary parser, nothing here ever
13
+ // puts a parsed LINE into an error, a log, or a receipt. A message body is
14
+ // routinely a one-time code and a sender identifies the subscriber, so a
15
+ // malformed record reports the KEY NAMES it found and nothing else — enough to
16
+ // diagnose CLI drift, and carrying no message content. This module also never
17
+ // logs at all, which is why it does not reuse a generic key/value splitter: the
18
+ // generic one prints the offending line verbatim on any line it cannot split,
19
+ // which would put message text into a log the first time mmcli reframed a body.
20
+ import { normalizeSmsState, smsPathIndex } from './normalize.js';
21
+ const fail = (reason, detail) => ({
22
+ ok: false,
23
+ reason,
24
+ detail,
25
+ });
26
+ const SMS_LIST_KEY = 'modem.messaging.sms';
27
+ const LIST_LENGTH_SUFFIX = /\.length$/;
28
+ const LIST_VALUE_SUFFIX = /\.value\[\d+]$/;
29
+ const SMS_PATH_ANYWHERE_RE = /\/org\/freedesktop\/ModemManager1\/SMS\/(\d+)/;
30
+ /**
31
+ * The escapes `g_strescape()` emits, minus the octal form. mmcli's `-K` writer
32
+ * runs EVERY value it prints through `g_strescape (value, NULL)`
33
+ * (`cli/mmcli-output.c`), so this table plus the 3-digit octal form is the
34
+ * complete grammar — anything else can be left untouched instead of guessed at.
35
+ */
36
+ const SIMPLE_ESCAPES = {
37
+ b: 0x08,
38
+ f: 0x0c,
39
+ n: 0x0a,
40
+ r: 0x0d,
41
+ t: 0x09,
42
+ v: 0x0b,
43
+ '"': 0x22,
44
+ '\\': 0x5c,
45
+ };
46
+ /** Octal triplet first: `\302` is a byte, `\\302` is a backslash then "302". */
47
+ const ESCAPE_RE = /\\(?:([0-7]{3})|([bfnrtv"\\]))/g;
48
+ /**
49
+ * Undo `g_strescape()` on one `-K` value.
50
+ *
51
+ * mmcli does not print non-ASCII text — it prints the LITERAL ASCII characters
52
+ * of the octal escape. A Spanish message arrives on stdout as the eight
53
+ * characters `\`,`3`,`0`,`2`,`\`,`2`,`4`,`1` where the wire carried the two
54
+ * UTF-8 bytes `0xC2 0xA1` (confirmed on the bench board with `od -c`). The
55
+ * escapes are therefore per-BYTE, not per-character, and the only correct
56
+ * decode is to rebuild the byte sequence and read it back as UTF-8 — decoding
57
+ * each escape with `String.fromCharCode` instead turns `¡` into `¡`.
58
+ *
59
+ * Total and silent: it never throws and never logs, because the value it holds
60
+ * is message content. An escape outside the grammar is copied VERBATIM rather
61
+ * than dropped — mmcli cannot emit one, so meeting one means the value was
62
+ * never escaped and copying it is the only lossless answer.
63
+ */
64
+ export function unescapeMmcliValue(value) {
65
+ if (!value.includes('\\')) {
66
+ return value;
67
+ }
68
+ const encoder = new TextEncoder();
69
+ const bytes = [];
70
+ const pushText = (text) => {
71
+ for (const byte of encoder.encode(text)) {
72
+ bytes.push(byte);
73
+ }
74
+ };
75
+ let cursor = 0;
76
+ ESCAPE_RE.lastIndex = 0;
77
+ let match = ESCAPE_RE.exec(value);
78
+ while (match !== null) {
79
+ if (match.index > cursor) {
80
+ pushText(value.slice(cursor, match.index));
81
+ }
82
+ const octal = match[1];
83
+ const simple = match[2];
84
+ if (octal !== undefined) {
85
+ bytes.push(Number.parseInt(octal, 8));
86
+ }
87
+ else if (simple !== undefined) {
88
+ const byte = SIMPLE_ESCAPES[simple];
89
+ if (byte !== undefined) {
90
+ bytes.push(byte);
91
+ }
92
+ }
93
+ cursor = match.index + match[0].length;
94
+ match = ESCAPE_RE.exec(value);
95
+ }
96
+ if (cursor < value.length) {
97
+ pushText(value.slice(cursor));
98
+ }
99
+ // A byte run that is not valid UTF-8 becomes U+FFFD — an honest
100
+ // "undecodable here" mark. Failing the whole read would lose every other
101
+ // field over one bad byte.
102
+ return new TextDecoder().decode(new Uint8Array(bytes));
103
+ }
104
+ /** Collect the `modem.messaging.sms` entries, honouring mmcli's array form. */
105
+ function collectListEntries(raw) {
106
+ let scalar;
107
+ const array = [];
108
+ let sawArray = false;
109
+ for (const line of raw.split('\n')) {
110
+ const separator = line.indexOf(':');
111
+ if (separator <= 0) {
112
+ continue;
113
+ }
114
+ const key = line.slice(0, separator).trim();
115
+ // Unescaped AFTER the split, never before: a decoded byte must never be
116
+ // able to forge the `:` the line was split on.
117
+ const value = unescapeMmcliValue(line.slice(separator + 1).trim());
118
+ // mmcli renders an absent value as `--`; it carries no entry.
119
+ if (value === '--') {
120
+ continue;
121
+ }
122
+ if (key.replace(LIST_LENGTH_SUFFIX, '') === SMS_LIST_KEY && LIST_LENGTH_SUFFIX.test(key)) {
123
+ sawArray = true;
124
+ }
125
+ else if (key.replace(LIST_VALUE_SUFFIX, '') === SMS_LIST_KEY && LIST_VALUE_SUFFIX.test(key)) {
126
+ sawArray = true;
127
+ array.push(value);
128
+ }
129
+ else if (key === SMS_LIST_KEY) {
130
+ scalar = value;
131
+ }
132
+ }
133
+ if (sawArray) {
134
+ return array;
135
+ }
136
+ return scalar === undefined ? undefined : [scalar];
137
+ }
138
+ /**
139
+ * Extract SMS object paths from `mmcli -K -m <id> --messaging-list-sms`.
140
+ *
141
+ * An inbox with no messages prints as `modem.messaging.sms: --`, whose `--`
142
+ * carries no entry — so an ABSENT key is a legitimate EMPTY INBOX, exactly as it
143
+ * is for `--3gpp-scan`, not drift. What IS drift, and what fails loud here, is
144
+ * output that never mentions the key at all (a renamed field, an error body
145
+ * reaching this parser): answering "no messages" to a read that never ran would
146
+ * be the worst possible lie about an inbox.
147
+ */
148
+ export function parseSmsListOutput(raw) {
149
+ const entries = collectListEntries(raw);
150
+ if (entries === undefined) {
151
+ if (!raw.includes(SMS_LIST_KEY)) {
152
+ return fail('no modem.messaging.sms key in the mmcli output', 'no-key');
153
+ }
154
+ return { ok: true, value: [] };
155
+ }
156
+ const paths = entries.filter((entry) => SMS_PATH_ANYWHERE_RE.test(entry));
157
+ if (entries.length > 0 && paths.length === 0) {
158
+ return fail('no SMS paths matched the ModemManager path grammar', 'no-path-match');
159
+ }
160
+ return { ok: true, value: paths };
161
+ }
162
+ /**
163
+ * Parse one `mmcli -K -s <path>` record.
164
+ *
165
+ * A malformed record reports the KEY NAMES it found and nothing else — see the
166
+ * content-free note at the top of this file.
167
+ */
168
+ export function parseSmsRecordOutput(raw) {
169
+ const fields = new Map();
170
+ for (const line of raw.split('\n')) {
171
+ const separator = line.indexOf(':');
172
+ if (separator <= 0) {
173
+ continue;
174
+ }
175
+ const key = line.slice(0, separator).trim();
176
+ const value = line.slice(separator + 1).trim();
177
+ if (!key.startsWith('sms.') || value === '' || value === '--') {
178
+ continue;
179
+ }
180
+ fields.set(key, unescapeMmcliValue(value));
181
+ }
182
+ const keyNames = [...fields.keys()].join(', ');
183
+ const dbusPath = fields.get('sms.dbus-path');
184
+ if (dbusPath === undefined) {
185
+ return fail('no sms.dbus-path key in the mmcli output', keyNames);
186
+ }
187
+ const index = smsPathIndex(dbusPath);
188
+ if (Number.isNaN(index)) {
189
+ return fail('sms.dbus-path did not match the ModemManager path grammar', keyNames);
190
+ }
191
+ const from = fields.get('sms.content.number');
192
+ const timestamp = fields.get('sms.properties.timestamp');
193
+ return {
194
+ ok: true,
195
+ value: {
196
+ id: String(index),
197
+ ...(from !== undefined ? { from } : {}),
198
+ ...(timestamp !== undefined ? { timestamp } : {}),
199
+ // A data-only (WAP/PDU) message has no text at all; `''` says so honestly.
200
+ text: fields.get('sms.content.text') ?? '',
201
+ state: normalizeSmsState(fields.get('sms.properties.state')),
202
+ },
203
+ };
204
+ }
205
+ /**
206
+ * Classify a source failure into a refusal the operator can act on.
207
+ *
208
+ * The three recognised strings are ModemManager 1.24's own, confirmed on the
209
+ * bench board: a modem with no Messaging interface, a radio that has not come up
210
+ * yet, and a selector nothing answers to. Anything else stays `read_failed`
211
+ * rather than being guessed at.
212
+ */
213
+ export function classifySmsFailure(description) {
214
+ if (/no messaging capabilities/i.test(description)) {
215
+ return 'unsupported';
216
+ }
217
+ if (/not enabled yet/i.test(description)) {
218
+ return 'not_enabled';
219
+ }
220
+ if (/couldn't find modem|cannot find modem/i.test(description)) {
221
+ return 'unknown_modem';
222
+ }
223
+ return 'read_failed';
224
+ }
@@ -0,0 +1,42 @@
1
+ import type { SmsMessage, SmsState } from '../ports/sms.js';
2
+ /**
3
+ * The read cap. The list is reduced to the highest-indexed paths BEFORE any
4
+ * per-message read, so a modem holding several hundred stored messages still
5
+ * costs at most this many reads.
6
+ */
7
+ export declare const SMS_INBOX_CAP = 50;
8
+ /**
9
+ * The SMS object-path grammar.
10
+ *
11
+ * It is a SEPARATE regex from the modem-path one on purpose: a ModemManager
12
+ * modem path is anchored on `/Modem/`, so reusing it here would refuse every
13
+ * real message (`/org/freedesktop/ModemManager1/SMS/36`) on the device.
14
+ */
15
+ export declare const SMS_PATH_RE: RegExp;
16
+ /** The trailing `/SMS/<n>` index, or `Number.NaN` when the path carries none. */
17
+ export declare function smsPathIndex(path: string): number;
18
+ /**
19
+ * Fold whatever the source called the state onto the known vocabulary. An
20
+ * unrecognised value becomes `unknown` rather than being passed through — the
21
+ * state is rendered to an operator, and a raw token nobody can act on is worse
22
+ * than an honest "we do not know".
23
+ */
24
+ export declare function normalizeSmsState(raw: string | undefined): SmsState;
25
+ export declare function smsTimestampEpoch(timestamp: string): number;
26
+ /**
27
+ * Newest first, then capped.
28
+ *
29
+ * Sorted on the CARRIER timestamp because the object index is only a proxy for
30
+ * arrival order — ModemManager reuses freed indices, so a re-enumerated inbox
31
+ * can hand back a low index for the newest message. A message with no (or an
32
+ * unparseable) timestamp sorts LAST rather than first: promoting an undated
33
+ * message to the top of a "newest first" list would be a claim the device
34
+ * cannot support. Ties fall back to the index, descending.
35
+ */
36
+ export declare function sortAndCapSms(messages: readonly SmsMessage[], cap?: number): SmsMessage[];
37
+ /**
38
+ * Reduce a candidate path list to the paths worth reading: the highest-indexed
39
+ * {@link SMS_INBOX_CAP}, each of which must match the path grammar before it can
40
+ * reach a source as a selector.
41
+ */
42
+ export declare function selectReadablePaths(paths: readonly string[], cap?: number): string[];
@@ -0,0 +1,95 @@
1
+ // Device-agnostic SMS normalization — the rules that hold whichever source the
2
+ // inbox was read through (ModemManager D-Bus or the `mmcli` client of the same
3
+ // daemon).
4
+ //
5
+ // Every rule here is a PORT of a behaviour proven on the bench board through
6
+ // CeraUI's `modules/modems/mmcli-sms.ts`, and each one exists because getting it
7
+ // wrong is silent rather than loud. They are kept in one pure module so the
8
+ // D-Bus adapter and the mmcli grammar cannot drift into two different answers
9
+ // about the same inbox.
10
+ /**
11
+ * The read cap. The list is reduced to the highest-indexed paths BEFORE any
12
+ * per-message read, so a modem holding several hundred stored messages still
13
+ * costs at most this many reads.
14
+ */
15
+ export const SMS_INBOX_CAP = 50;
16
+ /**
17
+ * The SMS object-path grammar.
18
+ *
19
+ * It is a SEPARATE regex from the modem-path one on purpose: a ModemManager
20
+ * modem path is anchored on `/Modem/`, so reusing it here would refuse every
21
+ * real message (`/org/freedesktop/ModemManager1/SMS/36`) on the device.
22
+ */
23
+ export const SMS_PATH_RE = /^(?:\/org\/freedesktop\/ModemManager1\/SMS\/\d+|\d+)$/;
24
+ const SMS_PATH_INDEX_RE = /\/org\/freedesktop\/ModemManager1\/SMS\/(\d+)/;
25
+ const KNOWN_SMS_STATES = new Set([
26
+ 'unknown',
27
+ 'stored',
28
+ 'receiving',
29
+ 'received',
30
+ 'sending',
31
+ 'sent',
32
+ ]);
33
+ /** The trailing `/SMS/<n>` index, or `Number.NaN` when the path carries none. */
34
+ export function smsPathIndex(path) {
35
+ const match = path.match(SMS_PATH_INDEX_RE);
36
+ return match?.[1] === undefined ? Number.NaN : Number.parseInt(match[1], 10);
37
+ }
38
+ /**
39
+ * Fold whatever the source called the state onto the known vocabulary. An
40
+ * unrecognised value becomes `unknown` rather than being passed through — the
41
+ * state is rendered to an operator, and a raw token nobody can act on is worse
42
+ * than an honest "we do not know".
43
+ */
44
+ export function normalizeSmsState(raw) {
45
+ return raw !== undefined && KNOWN_SMS_STATES.has(raw) ? raw : 'unknown';
46
+ }
47
+ /**
48
+ * ModemManager reports the service-centre timestamp with an HOURS-ONLY UTC
49
+ * offset — `2025-08-21T17:20:16-05`, captured verbatim from the bench board.
50
+ * That is not valid ISO 8601 and `Date.parse` answers NaN for it. Left
51
+ * unhandled, EVERY message scores as undated and "newest first" silently
52
+ * degrades to object-index order, which is the one ordering this module must
53
+ * not trust. The offset is widened to `-05:00`; the anchor requires a full
54
+ * `T??:??:??` time in front of it, so a bare `YYYY-MM-DD` is never mangled.
55
+ */
56
+ const HOURS_ONLY_OFFSET_RE = /(T\d{2}:\d{2}:\d{2})([+-]\d{2})$/;
57
+ export function smsTimestampEpoch(timestamp) {
58
+ const parsedTime = Date.parse(timestamp.replace(HOURS_ONLY_OFFSET_RE, '$1$2:00'));
59
+ return Number.isNaN(parsedTime) ? Number.NEGATIVE_INFINITY : parsedTime;
60
+ }
61
+ /**
62
+ * Newest first, then capped.
63
+ *
64
+ * Sorted on the CARRIER timestamp because the object index is only a proxy for
65
+ * arrival order — ModemManager reuses freed indices, so a re-enumerated inbox
66
+ * can hand back a low index for the newest message. A message with no (or an
67
+ * unparseable) timestamp sorts LAST rather than first: promoting an undated
68
+ * message to the top of a "newest first" list would be a claim the device
69
+ * cannot support. Ties fall back to the index, descending.
70
+ */
71
+ export function sortAndCapSms(messages, cap = SMS_INBOX_CAP) {
72
+ const epoch = (message) => message.timestamp === undefined
73
+ ? Number.NEGATIVE_INFINITY
74
+ : smsTimestampEpoch(message.timestamp);
75
+ return [...messages]
76
+ .sort((a, b) => {
77
+ const delta = epoch(b) - epoch(a);
78
+ if (delta !== 0 && !Number.isNaN(delta)) {
79
+ return delta;
80
+ }
81
+ return Number(b.id) - Number(a.id);
82
+ })
83
+ .slice(0, cap);
84
+ }
85
+ /**
86
+ * Reduce a candidate path list to the paths worth reading: the highest-indexed
87
+ * {@link SMS_INBOX_CAP}, each of which must match the path grammar before it can
88
+ * reach a source as a selector.
89
+ */
90
+ export function selectReadablePaths(paths, cap = SMS_INBOX_CAP) {
91
+ return [...paths]
92
+ .sort((a, b) => smsPathIndex(b) - smsPathIndex(a))
93
+ .slice(0, cap)
94
+ .filter((path) => SMS_PATH_RE.test(path));
95
+ }
@@ -0,0 +1,58 @@
1
+ import { type DeviceGeneration, type EpochMillis, type ObservationAuthority, type ObservationEnvelope, type OperationCompletion, type OperationConfidence, type OperationDescriptor, type OperationResult, type PhysicalModemId, type SourceEpoch, type StableKey } from '../domain/index.js';
2
+ /**
3
+ * A canonical, valid `PhysicalModemId` for contract tests.
4
+ *
5
+ * It is built through the real constructor, so it can never be a value the domain
6
+ * refuses — an MM object path, an interface name, an IP address, an IMEI, or a
7
+ * subscriber identifier.
8
+ */
9
+ export declare const FAKE_PHYSICAL_MODEM_ID: PhysicalModemId;
10
+ /** The actor/storage key for {@link FAKE_PHYSICAL_MODEM_ID}. */
11
+ export declare const FAKE_STABLE_KEY: StableKey;
12
+ /** The generation every fake defaults to, so a consumer's fences have a stable anchor. */
13
+ export declare const FAKE_GENERATION: DeviceGeneration;
14
+ export type FakeObservationOptions = {
15
+ readonly stableKey?: StableKey;
16
+ readonly generation?: DeviceGeneration;
17
+ readonly source?: string;
18
+ readonly sourceEpoch?: SourceEpoch;
19
+ readonly observedAt?: EpochMillis;
20
+ readonly authority?: ObservationAuthority;
21
+ };
22
+ /** A fresh observation carrying `value`. */
23
+ export declare function fakeFreshObservation<T>(value: T, options?: FakeObservationOptions): ObservationEnvelope<T>;
24
+ /** A stale observation that RETAINS `value` — staleness never discards what was read. */
25
+ export declare function fakeStaleObservation<T>(value: T, reason?: 'source-epoch-superseded' | 'ttl-expired' | 'source-degraded', options?: FakeObservationOptions): ObservationEnvelope<T>;
26
+ /**
27
+ * An unavailable observation. It carries `value: null` by construction — there is no
28
+ * overload that lets a consumer's fake invent a value for data nobody could read.
29
+ */
30
+ export declare function fakeUnavailableObservation<T>(reason?: 'source-unavailable' | 'device-absent' | 'provider-unavailable', options?: FakeObservationOptions): ObservationEnvelope<T>;
31
+ export type FakeDescriptorOptions = {
32
+ readonly id?: string;
33
+ readonly provider?: string;
34
+ readonly authority?: 'provider' | 'controller' | 'hardware';
35
+ readonly confidence?: OperationConfidence;
36
+ };
37
+ /**
38
+ * A supported, auto-retryable idempotent READ descriptor.
39
+ *
40
+ * Routed through `defineOperationDescriptor`, so a fake that drifted into an
41
+ * unsupported/retryable combination throws instead of silently modelling a
42
+ * descriptor the domain would refuse.
43
+ */
44
+ export declare function fakeReadDescriptor<O>(options?: FakeDescriptorOptions): OperationDescriptor<never, O>;
45
+ /** A supported WRITE descriptor. Writes are never auto-retryable, so `retryClass` is `never`. */
46
+ export declare function fakeWriteDescriptor<I, O>(options?: FakeDescriptorOptions): OperationDescriptor<I, O>;
47
+ export type FakeOperationResultOptions = {
48
+ readonly operation?: 'read' | 'write';
49
+ readonly completionGeneration?: DeviceGeneration;
50
+ readonly currentGeneration?: DeviceGeneration;
51
+ };
52
+ /**
53
+ * Build an `OperationResult` by running the REAL classifier over a completion.
54
+ *
55
+ * A hand-written result literal is how a consumer's fake comes to disagree with the
56
+ * package about which completions require reconciliation; this cannot.
57
+ */
58
+ export declare function fakeOperationResult<O>(completion: OperationCompletion<O>, options?: FakeOperationResultOptions): OperationResult<O>;
@@ -0,0 +1,98 @@
1
+ import { classifyOperationCompletion, defineOperationDescriptor, deviceGeneration, epochMillis, physicalModemId, sourceEpoch, stableKeyFromPhysicalModemId, } from '../domain/index.js';
2
+ /**
3
+ * A canonical, valid `PhysicalModemId` for contract tests.
4
+ *
5
+ * It is built through the real constructor, so it can never be a value the domain
6
+ * refuses — an MM object path, an interface name, an IP address, an IMEI, or a
7
+ * subscriber identifier.
8
+ */
9
+ export const FAKE_PHYSICAL_MODEM_ID = physicalModemId('serial:ceralive-contract-fake');
10
+ /** The actor/storage key for {@link FAKE_PHYSICAL_MODEM_ID}. */
11
+ export const FAKE_STABLE_KEY = stableKeyFromPhysicalModemId(FAKE_PHYSICAL_MODEM_ID);
12
+ /** The generation every fake defaults to, so a consumer's fences have a stable anchor. */
13
+ export const FAKE_GENERATION = deviceGeneration(1);
14
+ function observationBase(options) {
15
+ return {
16
+ stableKey: options.stableKey ?? FAKE_STABLE_KEY,
17
+ generation: options.generation ?? FAKE_GENERATION,
18
+ source: options.source ?? 'contract-fake',
19
+ sourceEpoch: options.sourceEpoch ?? sourceEpoch(1),
20
+ observedAt: options.observedAt ?? epochMillis(1),
21
+ authority: options.authority ?? 'authoritative',
22
+ };
23
+ }
24
+ /** A fresh observation carrying `value`. */
25
+ export function fakeFreshObservation(value, options = {}) {
26
+ return { ...observationBase(options), freshness: { state: 'fresh' }, value };
27
+ }
28
+ /** A stale observation that RETAINS `value` — staleness never discards what was read. */
29
+ export function fakeStaleObservation(value, reason = 'ttl-expired', options = {}) {
30
+ const base = observationBase(options);
31
+ return {
32
+ ...base,
33
+ freshness: { state: 'stale', since: base.observedAt, reason },
34
+ value,
35
+ };
36
+ }
37
+ /**
38
+ * An unavailable observation. It carries `value: null` by construction — there is no
39
+ * overload that lets a consumer's fake invent a value for data nobody could read.
40
+ */
41
+ export function fakeUnavailableObservation(reason = 'source-unavailable', options = {}) {
42
+ const base = observationBase(options);
43
+ return {
44
+ ...base,
45
+ freshness: { state: 'unavailable', since: base.observedAt, reason },
46
+ value: null,
47
+ };
48
+ }
49
+ function baseDescriptor(options, support, mutationImpact, retryClass) {
50
+ return defineOperationDescriptor({
51
+ id: options.id ?? 'contract-fake-operation',
52
+ support: {
53
+ read: support.read ? { supported: true } : { supported: false, reason: 'contract-fake' },
54
+ write: support.write ? { supported: true } : { supported: false, reason: 'contract-fake' },
55
+ },
56
+ authority: options.authority ?? 'provider',
57
+ provider: options.provider ?? 'contract-fake-provider',
58
+ constraints: { kind: 'unconstrained' },
59
+ livePreconditions: [],
60
+ availability: { state: 'available' },
61
+ mutationImpact,
62
+ retryClass,
63
+ readback: { required: false },
64
+ rollback: { required: false },
65
+ journal: { required: false },
66
+ admission: { required: false },
67
+ evidence: { profiles: [], firmware: [] },
68
+ confidence: options.confidence ?? 'medium',
69
+ });
70
+ }
71
+ /**
72
+ * A supported, auto-retryable idempotent READ descriptor.
73
+ *
74
+ * Routed through `defineOperationDescriptor`, so a fake that drifted into an
75
+ * unsupported/retryable combination throws instead of silently modelling a
76
+ * descriptor the domain would refuse.
77
+ */
78
+ export function fakeReadDescriptor(options = {}) {
79
+ return baseDescriptor(options, { read: true, write: false }, 'read', 'idempotent-read');
80
+ }
81
+ /** A supported WRITE descriptor. Writes are never auto-retryable, so `retryClass` is `never`. */
82
+ export function fakeWriteDescriptor(options = {}) {
83
+ return baseDescriptor(options, { read: true, write: true }, 'write', 'never');
84
+ }
85
+ /**
86
+ * Build an `OperationResult` by running the REAL classifier over a completion.
87
+ *
88
+ * A hand-written result literal is how a consumer's fake comes to disagree with the
89
+ * package about which completions require reconciliation; this cannot.
90
+ */
91
+ export function fakeOperationResult(completion, options = {}) {
92
+ return classifyOperationCompletion({
93
+ operation: options.operation ?? 'write',
94
+ completionGeneration: options.completionGeneration ?? FAKE_GENERATION,
95
+ currentGeneration: options.currentGeneration ?? FAKE_GENERATION,
96
+ completion,
97
+ });
98
+ }
@@ -0,0 +1,2 @@
1
+ export * from './domain-fakes.js';
2
+ export * from './provider-fakes.js';