@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,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';
@@ -0,0 +1,17 @@
1
+ // `@ceralive/modem-control/testing` — the PUBLIC contract-fakes surface.
2
+ //
3
+ // A consumer testing against this package needs valid instances of the frozen v1.1
4
+ // domain and provider contracts (`../domain`, `../providers`). Hand-rolling them is
5
+ // how a consumer's fixtures come to disagree with the package: a hand-written
6
+ // `OperationResult` literal quietly stops matching what `classifyOperationCompletion`
7
+ // actually returns, and a hand-written envelope invents a value for an unavailable
8
+ // read. Every fake here is built through the package's own constructors and
9
+ // classifiers, so it cannot express a shape the domain refuses.
10
+ //
11
+ // This is NOT `control/test-support/`. That directory holds this repo's own heavy
12
+ // internals — an MM-faithful fake D-Bus service on a private session bus and a
13
+ // stateful `nmcli` harness. It lives outside `src`, is not published, and is not a
14
+ // reusable public surface. This entry is pure data and functions: no bus, no daemon,
15
+ // no process, no filesystem.
16
+ export * from './domain-fakes.js';
17
+ export * from './provider-fakes.js';
@@ -0,0 +1,45 @@
1
+ import type { DeviceGeneration } from '../domain/index.js';
2
+ import type { FingerprintResult, PassiveFact, ProviderDefinition, ProviderMatchRequest, ProviderOperationsSurface, ProviderTransport, UnauthenticatedProbe } from '../providers/index.js';
3
+ /** The model string every provider fake matches on unless one is named. */
4
+ export declare const FAKE_PROVIDER_MODEL = "contract-fake-model";
5
+ export type FakeMatchRequestOptions = {
6
+ readonly generation?: DeviceGeneration;
7
+ readonly transport?: ProviderTransport;
8
+ readonly model?: string;
9
+ readonly composition?: string;
10
+ readonly firmware?: string;
11
+ readonly passiveFacts?: readonly PassiveFact[];
12
+ };
13
+ /** A well-formed `ProviderMatchRequest` whose passive facts name exactly one model. */
14
+ export declare function fakeProviderMatchRequest(options?: FakeMatchRequestOptions): ProviderMatchRequest;
15
+ export type FakeProviderOptions<TObservation> = {
16
+ readonly id?: string;
17
+ readonly profile?: string;
18
+ readonly profileVersion?: string;
19
+ readonly model?: string;
20
+ readonly eligibleTransports?: readonly ProviderTransport[];
21
+ readonly access?: ProviderOperationsSurface['access'];
22
+ readonly observation?: TObservation;
23
+ readonly probes?: readonly UnauthenticatedProbe[];
24
+ };
25
+ /**
26
+ * A minimal but COMPLETE `ProviderDefinition` — one strong required passive matcher on
27
+ * the model fact, no authenticated profile, no capability readers.
28
+ *
29
+ * It registers nothing and reaches no device: `observe` answers a fresh envelope built
30
+ * from the supplied observation, and `operations` returns the access surface asked for.
31
+ * This is the shape a consumer writes its own registry/matcher tests against without
32
+ * inventing a parallel provider contract.
33
+ */
34
+ export declare function fakeProviderDefinition<TObservation>(options: FakeProviderOptions<TObservation> & {
35
+ readonly observation: TObservation;
36
+ }): ProviderDefinition<TObservation, ProviderOperationsSurface>;
37
+ export type FakeProbeOptions = {
38
+ readonly id?: string;
39
+ readonly signal?: FingerprintResult['signal'];
40
+ readonly strength?: FingerprintResult['strength'];
41
+ readonly profiles?: readonly string[];
42
+ readonly detail?: string;
43
+ };
44
+ /** A harmless unauthenticated probe that answers a fixed fingerprint. */
45
+ export declare function fakeUnauthenticatedProbe(options?: FakeProbeOptions): UnauthenticatedProbe;
@@ -0,0 +1,64 @@
1
+ import { FAKE_GENERATION, FAKE_PHYSICAL_MODEM_ID, fakeFreshObservation } from './domain-fakes.js';
2
+ /** The model string every provider fake matches on unless one is named. */
3
+ export const FAKE_PROVIDER_MODEL = 'contract-fake-model';
4
+ /** A well-formed `ProviderMatchRequest` whose passive facts name exactly one model. */
5
+ export function fakeProviderMatchRequest(options = {}) {
6
+ const model = options.model ?? FAKE_PROVIDER_MODEL;
7
+ return {
8
+ physicalModemId: FAKE_PHYSICAL_MODEM_ID,
9
+ generation: options.generation ?? FAKE_GENERATION,
10
+ transport: options.transport ?? 'network',
11
+ passiveFacts: options.passiveFacts ?? [{ kind: 'model', value: model }],
12
+ composition: options.composition ?? 'ethernet-router',
13
+ firmware: options.firmware ?? '1.0.0',
14
+ };
15
+ }
16
+ /**
17
+ * A minimal but COMPLETE `ProviderDefinition` — one strong required passive matcher on
18
+ * the model fact, no authenticated profile, no capability readers.
19
+ *
20
+ * It registers nothing and reaches no device: `observe` answers a fresh envelope built
21
+ * from the supplied observation, and `operations` returns the access surface asked for.
22
+ * This is the shape a consumer writes its own registry/matcher tests against without
23
+ * inventing a parallel provider contract.
24
+ */
25
+ export function fakeProviderDefinition(options) {
26
+ const id = options.id ?? 'contract-fake-provider';
27
+ const profile = options.profile ?? 'contract-fake-profile';
28
+ const model = options.model ?? FAKE_PROVIDER_MODEL;
29
+ const access = options.access ?? 'read-only';
30
+ const observation = options.observation;
31
+ return {
32
+ id,
33
+ profileVersion: options.profileVersion ?? '1.0.0',
34
+ eligibleTransports: options.eligibleTransports ?? ['network'],
35
+ passiveMatchers: [
36
+ {
37
+ id: `${id}-model`,
38
+ fact: 'model',
39
+ expected: [model],
40
+ profiles: [profile],
41
+ strength: 'strong',
42
+ required: true,
43
+ },
44
+ ],
45
+ unauthenticatedProbes: options.probes ?? [],
46
+ capabilityReaders: [],
47
+ observe: (_context) => Promise.resolve([fakeFreshObservation(observation, { source: id })]),
48
+ operations: (_profile) => ({ access }),
49
+ contractFixtures: [],
50
+ };
51
+ }
52
+ /** A harmless unauthenticated probe that answers a fixed fingerprint. */
53
+ export function fakeUnauthenticatedProbe(options = {}) {
54
+ const result = {
55
+ signal: options.signal ?? 'match',
56
+ strength: options.strength ?? 'moderate',
57
+ profiles: options.profiles ?? ['contract-fake-profile'],
58
+ detail: options.detail ?? 'contract-fake probe',
59
+ };
60
+ return {
61
+ id: options.id ?? 'contract-fake-probe',
62
+ run: (_context) => Promise.resolve(result),
63
+ };
64
+ }
@@ -0,0 +1,9 @@
1
+ import { type RawBus } from './dbus-native.js';
2
+ import type { MethodCall, MethodReply } from './types.js';
3
+ export declare const DEFAULT_CALL_TIMEOUT_MS = 30000;
4
+ export declare class CallDispatcher {
5
+ #private;
6
+ constructor(callTimeoutMs: number);
7
+ call(bus: RawBus | null, connected: boolean, call: MethodCall): Promise<MethodReply>;
8
+ rejectAll(cause: unknown): void;
9
+ }