@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,116 @@
1
+ // The evidence-gated recovery ladder — disabled by default.
2
+ //
3
+ // A bounded four-rung ladder [1 nm-cycle: NM deactivate→reactivate exact pair; 2
4
+ // mm-cycle: MM disable→enable; 3 reset: MM Reset(); 4 power-cycle: power hook (only
5
+ // `none` → always unsupported)]. Every disruptive rung routes through A3.3's shared
6
+ // per-modem `ModemActor` (serialised behind all other disruptive ops) and consults
7
+ // the `LifecycleInterlock` first so it never disrupts a streaming modem. GATES, in
8
+ // order: recovery.enabled=false → zero steps · attribution≠modem-fault → zero steps ·
9
+ // budget spent / cooldown → zero steps · per-rung allowDisruptive · interlock.
10
+ // Disabled or un-attributed, NOT ONE side-effecting call is made.
11
+ import { ALLOW_ALL_INTERLOCK } from './lifecycle-interlock.js';
12
+ import { NONE_POWER_HOOK } from './power-contract.js';
13
+ import { beginAttempt, DEFAULT_RECOVERY_BUDGET, INITIAL_BUDGET_STATE, markRecovered, } from './recovery-budget.js';
14
+ export const LADDER_ORDER = ['nmCycle', 'mmCycle', 'reset', 'powerCycle'];
15
+ const ALLOW = { allowDisruptive: true };
16
+ /** Default config: every rung permitted, default budget. */
17
+ export const DEFAULT_LADDER_CONFIG = {
18
+ nmCycle: ALLOW,
19
+ mmCycle: ALLOW,
20
+ reset: ALLOW,
21
+ powerCycle: ALLOW,
22
+ budget: DEFAULT_RECOVERY_BUDGET,
23
+ };
24
+ /**
25
+ * The recovery ladder. Holds per-modem budget state keyed by stable key so the
26
+ * loop-stop can latch a flapping modem `degraded` across invocations.
27
+ */
28
+ export class RecoveryLadder {
29
+ #actor;
30
+ #steps;
31
+ #powerHook;
32
+ #interlock;
33
+ #config;
34
+ #states = new Map();
35
+ constructor(deps) {
36
+ this.#actor = deps.actor;
37
+ this.#steps = deps.steps;
38
+ this.#powerHook = deps.powerHook ?? NONE_POWER_HOOK;
39
+ this.#interlock = deps.interlock ?? ALLOW_ALL_INTERLOCK;
40
+ this.#config = deps.config ?? DEFAULT_LADDER_CONFIG;
41
+ }
42
+ /** The current budget state for a modem (for observability / assertions). */
43
+ budgetStateFor(stableKey) {
44
+ return this.#states.get(stableKey) ?? INITIAL_BUDGET_STATE;
45
+ }
46
+ /** Clear a modem's budget / degraded latch (operator un-degrade). */
47
+ clear(stableKey) {
48
+ this.#states.delete(stableKey);
49
+ }
50
+ /** Run one recovery attempt. Enforces every gate before any side effect. */
51
+ async run(recovery, request) {
52
+ const { stableKey, attribution } = request;
53
+ // GATE 1 — master switch. Disabled ⇒ literally zero steps, zero side effects.
54
+ if (!recovery.enabled) {
55
+ return this.#end('disabled', request, [], 'recovery disabled by policy');
56
+ }
57
+ // GATE 2 — attribution. Only a confident modem-fault may ever be disruptive.
58
+ if (attribution !== 'modem-fault') {
59
+ return this.#end('not-attributed', request, [], `attribution '${attribution}' is never disruptive`);
60
+ }
61
+ // GATE 3 — budget / cooldown / loop-stop.
62
+ const decision = beginAttempt(this.budgetStateFor(stableKey), this.#config.budget, request.now);
63
+ if (decision.kind === 'loop-stop') {
64
+ this.#states.set(stableKey, decision.state);
65
+ return this.#end('loop-stop', request, [], 'recovery budget exhausted — modem marked degraded');
66
+ }
67
+ if (decision.kind === 'cooldown') {
68
+ return this.#end('cooldown', request, [], 'within cooldown window — attempt refused');
69
+ }
70
+ this.#states.set(stableKey, decision.state);
71
+ return this.#runRungs(request);
72
+ }
73
+ async #runRungs(request) {
74
+ const { stableKey } = request;
75
+ const context = { stableKey, modem: request.modem, at: request.now };
76
+ const steps = [];
77
+ for (const rung of LADDER_ORDER) {
78
+ if (!this.#config[rung].allowDisruptive) {
79
+ steps.push({ rung, status: 'skipped', reason: 'allowDisruptive is false' });
80
+ continue;
81
+ }
82
+ // Interlock BEFORE any disruptive step — never disrupt a streaming modem.
83
+ const verdict = await this.#interlock.canDisrupt({ stableKey });
84
+ if (!verdict.allow) {
85
+ steps.push({ rung, status: 'blocked', reason: verdict.reason });
86
+ return this.#end('interlock-blocked', request, steps, `interlock blocked '${rung}': ${verdict.reason}`);
87
+ }
88
+ const report = await this.#runRung(rung, context);
89
+ steps.push(report);
90
+ if (report.status === 'applied' && (await request.probeHealthy())) {
91
+ this.#states.set(stableKey, markRecovered());
92
+ return this.#end('recovered', request, steps, `recovered at rung '${rung}'`);
93
+ }
94
+ }
95
+ return this.#end('exhausted', request, steps, 'ladder exhausted without restoring health');
96
+ }
97
+ async #runRung(rung, context) {
98
+ if (rung === 'powerCycle') {
99
+ const result = await this.#powerHook.cycle({ stableKey: context.stableKey, at: context.at });
100
+ return { rung, status: result.status, reason: result.reason };
101
+ }
102
+ // Rungs 1–3 route through the shared per-modem actor for serialisation.
103
+ const step = this.#steps[rung];
104
+ const outcome = await this.#actor.run(context.stableKey, () => step(context));
105
+ return { rung, status: outcome.status, reason: outcome.reason };
106
+ }
107
+ #end(kind, request, steps, reason) {
108
+ return {
109
+ kind,
110
+ attribution: request.attribution,
111
+ steps,
112
+ degraded: this.budgetStateFor(request.stableKey).degraded,
113
+ reason,
114
+ };
115
+ }
116
+ }
@@ -0,0 +1,19 @@
1
+ import { type EpochMillis } from '../domain/index.js';
2
+ import type { DeviceIfname, RouterPort } from '../ports/index.js';
3
+ /** Injectable probes — each defaults to a `Bun.spawn` shell-out; all advisory. */
4
+ export interface RouterEthernetProbeDeps {
5
+ readonly now?: () => EpochMillis;
6
+ /** Whether `ifname` exists and is administratively up. */
7
+ readonly checkLinkUp?: (ifname: DeviceIfname) => Promise<boolean>;
8
+ /** The DHCP-assigned default gateway on `ifname`, if any. */
9
+ readonly resolveGateway?: (ifname: DeviceIfname) => Promise<string | undefined>;
10
+ /** Whether `host` is reachable via `ifname` (ICMP). */
11
+ readonly ping?: (host: string, ifname: DeviceIfname) => Promise<boolean>;
12
+ }
13
+ /**
14
+ * Create a router-ethernet probe implementing the advisory `RouterPort`. Presence is
15
+ * link-up; health additionally reports DHCP-gateway reachability and a basic egress
16
+ * probe — all informational, never a gate. `checkHealth` swallows every error into a
17
+ * `false` field so it can never throw.
18
+ */
19
+ export declare function createRouterEthernetProbe(deps?: RouterEthernetProbeDeps): RouterPort;
@@ -0,0 +1,66 @@
1
+ // Generic router-ethernet detection — presence, DHCP-gateway reachability, and a
2
+ // basic egress-health probe for uplinks ModemManager cannot control (HiLink, RNDIS
3
+ // tether, full router firmware).
4
+ //
5
+ // EVERYTHING here is ADVISORY (ports/router.ts, matrix §1-R): health degradation is
6
+ // informational only. `checkHealth` NEVER throws and NEVER gates anything — a
7
+ // degraded router stays in the routing set; the controller merely reports what it
8
+ // observed. Each probe is an injectable seam (default `Bun.spawn` of `ip` / `ping`)
9
+ // so tests run with no real network.
10
+ import { epochMillis } from '../domain/index.js';
11
+ /** The host used for the basic egress-health probe (public DNS anycast). */
12
+ const EGRESS_PROBE_HOST = '1.1.1.1';
13
+ async function spawnSucceeds(command) {
14
+ try {
15
+ const proc = Bun.spawn([...command], { stdout: 'pipe', stderr: 'pipe' });
16
+ return (await proc.exited) === 0;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ async function spawnOutput(command) {
23
+ try {
24
+ const proc = Bun.spawn([...command], { stdout: 'pipe', stderr: 'pipe' });
25
+ const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
26
+ return code === 0 ? out : '';
27
+ }
28
+ catch {
29
+ return '';
30
+ }
31
+ }
32
+ function defaultCheckLinkUp(ifname) {
33
+ return spawnSucceeds(['ip', 'link', 'show', 'up', 'dev', String(ifname)]);
34
+ }
35
+ async function defaultResolveGateway(ifname) {
36
+ // `ip -o route show default dev <ifname>` → "default via 192.168.8.1 dev <ifname> …".
37
+ const out = await spawnOutput(['ip', '-o', 'route', 'show', 'default', 'dev', String(ifname)]);
38
+ const match = out.match(/default via (\S+)/);
39
+ return match?.[1];
40
+ }
41
+ function defaultPing(host, ifname) {
42
+ return spawnSucceeds(['ping', '-c', '1', '-W', '2', '-I', String(ifname), host]);
43
+ }
44
+ /**
45
+ * Create a router-ethernet probe implementing the advisory `RouterPort`. Presence is
46
+ * link-up; health additionally reports DHCP-gateway reachability and a basic egress
47
+ * probe — all informational, never a gate. `checkHealth` swallows every error into a
48
+ * `false` field so it can never throw.
49
+ */
50
+ export function createRouterEthernetProbe(deps = {}) {
51
+ const now = deps.now ?? (() => epochMillis(Date.now()));
52
+ const checkLinkUp = deps.checkLinkUp ?? defaultCheckLinkUp;
53
+ const resolveGateway = deps.resolveGateway ?? defaultResolveGateway;
54
+ const ping = deps.ping ?? defaultPing;
55
+ async function probePresence(ifname) {
56
+ return (await checkLinkUp(ifname).catch(() => false)) ? 'present' : 'absent';
57
+ }
58
+ async function checkHealth(ifname) {
59
+ const presence = await probePresence(ifname);
60
+ const gateway = presence === 'present' ? await resolveGateway(ifname).catch(() => undefined) : undefined;
61
+ const gatewayReachable = gateway !== undefined && (await ping(gateway, ifname).catch(() => false));
62
+ const egressHealthy = gatewayReachable && (await ping(EGRESS_PROBE_HOST, ifname).catch(() => false));
63
+ return { presence, gatewayReachable, egressHealthy, observedAt: now() };
64
+ }
65
+ return { probePresence, checkHealth };
66
+ }
@@ -0,0 +1,13 @@
1
+ import type { ObservationFailureReason, ObservationList } from '../ports/index.js';
2
+ import type { DecodedManagedObjects } from './managed-objects.js';
3
+ export declare class ObservationRowStore {
4
+ #private;
5
+ /** Reconcile the authoritative tree into the rows. Returns whether rows changed.
6
+ * This is the SOLE removal path — an omission from a current-epoch snapshot. */
7
+ reconcile(tree: DecodedManagedObjects): boolean;
8
+ /** Mark the source healthy after a successful reconcile. Returns whether health flipped. */
9
+ markHealthy(): boolean;
10
+ /** Flag every row stale (retained, never removed). Returns whether an emission is due. */
11
+ markUnavailable(reason: ObservationFailureReason): boolean;
12
+ list(): ObservationList;
13
+ }
@@ -0,0 +1,82 @@
1
+ // The observer's row bookkeeping — revisions, source health, and the discriminated
2
+ // list result — split out of the observer so each file stays focused.
3
+ //
4
+ // Removal lives ONLY in `reconcile`: a modem is dropped exactly when a current-epoch
5
+ // authoritative snapshot omits it. `markUnavailable` never removes — it flags rows
6
+ // stale and retains them, so the `ObservationList` failure arm always carries its rows.
7
+ import { createSnapshot, revision as makeRevision, markSourceUnavailable, } from '../domain/index.js';
8
+ import { fingerprint, mapModem, modemPaths } from './mapping.js';
9
+ export class ObservationRowStore {
10
+ #rows = new Map();
11
+ #revCounter = 0;
12
+ #sourceHealthy = false;
13
+ #failureReason = 'not-started';
14
+ /** Reconcile the authoritative tree into the rows. Returns whether rows changed.
15
+ * This is the SOLE removal path — an omission from a current-epoch snapshot. */
16
+ reconcile(tree) {
17
+ let changed = false;
18
+ const seen = new Set();
19
+ for (const path of modemPaths(tree)) {
20
+ seen.add(path);
21
+ if (this.#upsert(path, mapModem(tree, path))) {
22
+ changed = true;
23
+ }
24
+ }
25
+ for (const path of [...this.#rows.keys()]) {
26
+ if (!seen.has(path)) {
27
+ this.#rows.delete(path);
28
+ changed = true;
29
+ }
30
+ }
31
+ return changed;
32
+ }
33
+ /** Mark the source healthy after a successful reconcile. Returns whether health flipped. */
34
+ markHealthy() {
35
+ const flipped = !this.#sourceHealthy;
36
+ this.#sourceHealthy = true;
37
+ return flipped;
38
+ }
39
+ /** Flag every row stale (retained, never removed). Returns whether an emission is due. */
40
+ markUnavailable(reason) {
41
+ const wasHealthy = this.#sourceHealthy;
42
+ let changed = false;
43
+ for (const [path, row] of this.#rows) {
44
+ if (row.snapshot.sourceHealth === 'sourceUnavailable') {
45
+ continue;
46
+ }
47
+ this.#rows.set(path, {
48
+ snapshot: markSourceUnavailable(row.snapshot),
49
+ fingerprint: row.fingerprint,
50
+ });
51
+ changed = true;
52
+ }
53
+ this.#sourceHealthy = false;
54
+ this.#failureReason = reason;
55
+ return changed || wasHealthy;
56
+ }
57
+ list() {
58
+ const rows = [...this.#rows.values()].map((row) => row.snapshot);
59
+ if (this.#sourceHealthy) {
60
+ return { ok: true, rows };
61
+ }
62
+ return { ok: false, reason: this.#failureReason, rows };
63
+ }
64
+ #upsert(path, mapped) {
65
+ const fp = fingerprint(mapped);
66
+ const existing = this.#rows.get(path);
67
+ if (existing !== undefined &&
68
+ existing.fingerprint === fp &&
69
+ existing.snapshot.sourceHealth === 'live') {
70
+ return false;
71
+ }
72
+ this.#rows.set(path, {
73
+ snapshot: createSnapshot({ ...mapped, revision: this.#nextRevision() }),
74
+ fingerprint: fp,
75
+ });
76
+ return true;
77
+ }
78
+ #nextRevision() {
79
+ this.#revCounter += 1;
80
+ return makeRevision(this.#revCounter);
81
+ }
82
+ }
@@ -0,0 +1,30 @@
1
+ import type { DbusTransport } from '../transport/index.js';
2
+ import type { DecodedManagedObjects } from './managed-objects.js';
3
+ /** Whether periodic signal reporting is configured for a modem. */
4
+ export type SignalCadence = 'active' | 'unsupported' | 'unknown';
5
+ /** MM's default reporting rate is seconds; callers pass whole seconds. */
6
+ export declare const DEFAULT_SIGNAL_INTERVAL_SECONDS = 5;
7
+ export interface SignalSetupManagerOptions {
8
+ readonly transport: DbusTransport;
9
+ /** MM bus name override (defaults to `org.freedesktop.ModemManager1`). */
10
+ readonly destination?: string;
11
+ /** Reporting interval in seconds passed to `Signal.Setup`. */
12
+ readonly intervalSeconds?: number;
13
+ }
14
+ /**
15
+ * Drives `Signal.Setup` across the modem fleet, keyed to the observer's epochs. Feed
16
+ * it every `onEpochRefresh` event; it applies setup to each modem exactly once per
17
+ * epoch, re-applies to survivors on a new epoch, and never calls for an old one.
18
+ */
19
+ export declare class SignalSetupManager {
20
+ #private;
21
+ constructor(options: SignalSetupManagerOptions);
22
+ /** The last-known cadence for a modem path (`'unknown'` until first applied). */
23
+ cadenceFor(modemPath: string): SignalCadence;
24
+ /**
25
+ * Apply `Signal.Setup` for the current epoch's modems. New epoch ⇒ every survivor
26
+ * is re-applied; an already-applied (epoch, modem) is skipped. Modems absent from
27
+ * this snapshot keep their last cadence but are not re-driven.
28
+ */
29
+ applyForEpoch(epoch: string, tree: DecodedManagedObjects): void;
30
+ }
@@ -0,0 +1,92 @@
1
+ // Signal.Setup(interval) full-lifecycle management, epoch-scoped.
2
+ //
3
+ // ModemManager's `Modem.Signal.Setup(rate)` turns on periodic extended signal
4
+ // reporting. Its lifecycle mirrors the observer's epochs (A3.1):
5
+ //
6
+ // - applied once when the backend starts observing a modem;
7
+ // - applied to a hot-plugged modem the moment it appears (`InterfacesAdded`);
8
+ // - RE-APPLIED to every surviving modem after every owner-epoch change (a fresh MM
9
+ // owner has none of the previous owner's cadence configured);
10
+ // - NEVER applied for an OLD epoch — a setup scheduled for an epoch that is no
11
+ // longer current is dropped before the call ever goes out.
12
+ //
13
+ // A modem that lacks the `Modem.Signal` interface entirely reports
14
+ // `signalCadence: 'unsupported'` and is never called — a soft capability gap, never
15
+ // a start failure. The manager is driven purely by the observer's `onEpochRefresh`
16
+ // hook: every current-epoch snapshot re-drives it, and it de-dupes per (epoch, modem)
17
+ // so a modem is set up exactly once per epoch.
18
+ import { MM_BUS_NAME, MODEM_IFACE } from './constants.js';
19
+ import { hasInterface, pathsWithInterface } from './managed-objects.js';
20
+ /** The `Modem.Signal` interface — absent means signal cadence is unsupported. */
21
+ const SIGNAL_IFACE = 'org.freedesktop.ModemManager1.Modem.Signal';
22
+ /** MM's default reporting rate is seconds; callers pass whole seconds. */
23
+ export const DEFAULT_SIGNAL_INTERVAL_SECONDS = 5;
24
+ /**
25
+ * Drives `Signal.Setup` across the modem fleet, keyed to the observer's epochs. Feed
26
+ * it every `onEpochRefresh` event; it applies setup to each modem exactly once per
27
+ * epoch, re-applies to survivors on a new epoch, and never calls for an old one.
28
+ */
29
+ export class SignalSetupManager {
30
+ #transport;
31
+ #destination;
32
+ #interval;
33
+ #currentEpoch;
34
+ // Guards double-issue within an epoch: `${epoch}\u0000${modemPath}`.
35
+ #applied = new Set();
36
+ #cadence = new Map();
37
+ constructor(options) {
38
+ this.#transport = options.transport;
39
+ this.#destination = options.destination ?? MM_BUS_NAME;
40
+ this.#interval = options.intervalSeconds ?? DEFAULT_SIGNAL_INTERVAL_SECONDS;
41
+ }
42
+ /** The last-known cadence for a modem path (`'unknown'` until first applied). */
43
+ cadenceFor(modemPath) {
44
+ return this.#cadence.get(modemPath) ?? 'unknown';
45
+ }
46
+ /**
47
+ * Apply `Signal.Setup` for the current epoch's modems. New epoch ⇒ every survivor
48
+ * is re-applied; an already-applied (epoch, modem) is skipped. Modems absent from
49
+ * this snapshot keep their last cadence but are not re-driven.
50
+ */
51
+ applyForEpoch(epoch, tree) {
52
+ this.#currentEpoch = epoch;
53
+ for (const modemPath of pathsWithInterface(tree, MODEM_IFACE)) {
54
+ const key = `${epoch}\u0000${modemPath}`;
55
+ if (this.#applied.has(key)) {
56
+ continue;
57
+ }
58
+ this.#applied.add(key);
59
+ void this.#setupOne(epoch, modemPath, tree);
60
+ }
61
+ }
62
+ async #setupOne(epoch, modemPath, tree) {
63
+ // Epoch changed out from under us before we could issue the call — drop it, so
64
+ // no Signal.Setup ever fires for a superseded epoch.
65
+ if (this.#currentEpoch !== epoch) {
66
+ return;
67
+ }
68
+ if (!hasInterface(tree, modemPath, SIGNAL_IFACE)) {
69
+ this.#cadence.set(modemPath, 'unsupported');
70
+ return;
71
+ }
72
+ if (this.#currentEpoch !== epoch) {
73
+ return;
74
+ }
75
+ try {
76
+ await this.#transport.callMethod({
77
+ destination: this.#destination,
78
+ path: modemPath,
79
+ interface: SIGNAL_IFACE,
80
+ member: 'Setup',
81
+ signature: 'u',
82
+ args: [this.#interval],
83
+ });
84
+ this.#cadence.set(modemPath, 'active');
85
+ }
86
+ catch {
87
+ // A Setup call that errors is a soft gap — surfaced as unsupported, never a
88
+ // start failure.
89
+ this.#cadence.set(modemPath, 'unsupported');
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,13 @@
1
+ import type { SimPukUnlockResult, SimUnlockResult } from '../ports/index.js';
2
+ import type { DbusTransport } from '../transport/index.js';
3
+ /**
4
+ * Submit a SIM PIN read-before-submit and exactly-once. On a wrong PIN the lock state
5
+ * is re-read (never resubmitted) to report remaining attempts or a resulting PUK lock.
6
+ */
7
+ export declare function sendSimPin(transport: DbusTransport, destination: string, modemPath: string, pin: string): Promise<SimUnlockResult>;
8
+ /**
9
+ * Submit a SIM PUK + new PIN read-before-submit and exactly-once. On a wrong PUK the
10
+ * remaining PUK attempts are re-read (never resubmitted); zero remaining is a
11
+ * permanent block.
12
+ */
13
+ export declare function sendSimPuk(transport: DbusTransport, destination: string, modemPath: string, puk: string, newPin: string): Promise<SimPukUnlockResult>;
@@ -0,0 +1,153 @@
1
+ // SIM PIN / PUK unlock — read-before-submit, exactly-once, CeraUI-taxonomy-faithful.
2
+ //
3
+ // The ordering is the whole point (mirrors CeraUI `mmcli.ts` unlockSimPin/Puk): the
4
+ // lock state is READ first, and the secret is submitted EXACTLY ONCE. A blind
5
+ // resubmit walks the SIM toward an irreversible PUK / permanent lockout, so on a
6
+ // failure we only RE-READ the lock state to report remaining attempts — we never
7
+ // resubmit. A PUK cannot be cleared with a PIN, so a PUK-locked SIM is surfaced, not
8
+ // submitted into.
9
+ //
10
+ // The result outcomes map onto A2.2's `SimUnlockResult` / `SimPukUnlockResult`, which
11
+ // mirror CeraUI's own shapes so Phase-B adoption is a rename, not a rewrite:
12
+ // success → unlocked · wrong-pin → incorrect-pin(+remaining) · puk-required →
13
+ // sim-puk-required · wrong-puk → incorrect-puk(+remaining) · locked(0) →
14
+ // permanently-blocked · read failure → error · nothing-to-unlock → unlocked.
15
+ // The secret is passed only as a call arg and is NEVER placed in a `reason` string.
16
+ import { MODEM_IFACE, SIM_IFACE } from './constants.js';
17
+ import { fetchManagedObjects, findInterface, numberProp, propValue, stringProp, } from './managed-objects.js';
18
+ // MMModemLock values we branch on.
19
+ const LOCK_SIM_PIN = 2;
20
+ const LOCK_SIM_PUK = 4;
21
+ const LOCK_SIM_PUK2 = 5;
22
+ function readLockState(tree, modemPath) {
23
+ const modem = findInterface(tree, modemPath, MODEM_IFACE);
24
+ if (modem === undefined) {
25
+ return undefined;
26
+ }
27
+ const required = numberProp(modem, 'UnlockRequired') ?? 0;
28
+ const retries = new Map();
29
+ const raw = propValue(modem, 'UnlockRetries');
30
+ if (Array.isArray(raw)) {
31
+ for (const entry of raw) {
32
+ if (Array.isArray(entry) && typeof entry[0] === 'number' && typeof entry[1] === 'number') {
33
+ retries.set(entry[0], entry[1]);
34
+ }
35
+ }
36
+ }
37
+ return { required, retries };
38
+ }
39
+ /** The object path of a modem's active SIM (its `Sim` property), when present. */
40
+ function activeSimPath(tree, modemPath) {
41
+ const path = stringProp(findInterface(tree, modemPath, MODEM_IFACE), 'Sim');
42
+ return path?.startsWith('/') && path !== '/' ? path : undefined;
43
+ }
44
+ const isPukLock = (lock) => lock === LOCK_SIM_PUK || lock === LOCK_SIM_PUK2;
45
+ /**
46
+ * Submit a SIM PIN read-before-submit and exactly-once. On a wrong PIN the lock state
47
+ * is re-read (never resubmitted) to report remaining attempts or a resulting PUK lock.
48
+ */
49
+ export async function sendSimPin(transport, destination, modemPath, pin) {
50
+ let tree;
51
+ try {
52
+ tree = await fetchManagedObjects(transport, destination);
53
+ }
54
+ catch {
55
+ return { outcome: 'error', reason: 'could not read modem lock state before PIN submit' };
56
+ }
57
+ const state = readLockState(tree, modemPath);
58
+ if (state === undefined) {
59
+ return { outcome: 'error', reason: 'modem not found while reading lock state' };
60
+ }
61
+ if (isPukLock(state.required)) {
62
+ return { outcome: 'sim-puk-required', reason: 'SIM is PUK-locked; a PIN cannot clear it' };
63
+ }
64
+ if (state.required !== LOCK_SIM_PIN) {
65
+ return { outcome: 'unlocked', reason: 'no SIM PIN is pending' };
66
+ }
67
+ const simPath = activeSimPath(tree, modemPath);
68
+ if (simPath === undefined) {
69
+ return { outcome: 'error', reason: 'no active SIM object to submit the PIN to' };
70
+ }
71
+ try {
72
+ await transport.callMethod({
73
+ destination,
74
+ path: simPath,
75
+ interface: SIM_IFACE,
76
+ member: 'SendPin',
77
+ signature: 's',
78
+ args: [pin],
79
+ });
80
+ return { outcome: 'unlocked', reason: 'PIN accepted' };
81
+ }
82
+ catch {
83
+ return classifyPinFailure(transport, destination, modemPath);
84
+ }
85
+ }
86
+ async function classifyPinFailure(transport, destination, modemPath) {
87
+ const state = await readLockState(await fetchManagedObjects(transport, destination), modemPath);
88
+ if (state !== undefined && isPukLock(state.required)) {
89
+ return { outcome: 'sim-puk-required', reason: 'wrong PIN tripped the SIM into a PUK lock' };
90
+ }
91
+ const remaining = state?.retries.get(LOCK_SIM_PIN);
92
+ return {
93
+ outcome: 'incorrect-pin',
94
+ ...(remaining !== undefined ? { remainingAttempts: remaining } : {}),
95
+ reason: 'PIN was rejected',
96
+ };
97
+ }
98
+ /**
99
+ * Submit a SIM PUK + new PIN read-before-submit and exactly-once. On a wrong PUK the
100
+ * remaining PUK attempts are re-read (never resubmitted); zero remaining is a
101
+ * permanent block.
102
+ */
103
+ export async function sendSimPuk(transport, destination, modemPath, puk, newPin) {
104
+ let tree;
105
+ try {
106
+ tree = await fetchManagedObjects(transport, destination);
107
+ }
108
+ catch {
109
+ return { outcome: 'error', reason: 'could not read modem lock state before PUK submit' };
110
+ }
111
+ const state = readLockState(tree, modemPath);
112
+ if (state === undefined) {
113
+ return { outcome: 'error', reason: 'modem not found while reading lock state' };
114
+ }
115
+ if (!isPukLock(state.required)) {
116
+ return { outcome: 'unlocked', reason: 'no SIM PUK is pending' };
117
+ }
118
+ const pukKind = state.required;
119
+ const simPath = activeSimPath(tree, modemPath);
120
+ if (simPath === undefined) {
121
+ return { outcome: 'error', reason: 'no active SIM object to submit the PUK to' };
122
+ }
123
+ try {
124
+ await transport.callMethod({
125
+ destination,
126
+ path: simPath,
127
+ interface: SIM_IFACE,
128
+ member: 'SendPuk',
129
+ signature: 'ss',
130
+ args: [puk, newPin],
131
+ });
132
+ return { outcome: 'unlocked', reason: 'PUK accepted; new PIN set' };
133
+ }
134
+ catch {
135
+ return classifyPukFailure(transport, destination, modemPath, pukKind);
136
+ }
137
+ }
138
+ async function classifyPukFailure(transport, destination, modemPath, pukKind) {
139
+ const state = await readLockState(await fetchManagedObjects(transport, destination), modemPath);
140
+ const remaining = state?.retries.get(pukKind);
141
+ if (remaining === 0) {
142
+ return {
143
+ outcome: 'permanently-blocked',
144
+ remainingAttempts: 0,
145
+ reason: 'PUK attempts exhausted; SIM is permanently locked',
146
+ };
147
+ }
148
+ return {
149
+ outcome: 'incorrect-puk',
150
+ ...(remaining !== undefined ? { remainingAttempts: remaining } : {}),
151
+ reason: 'PUK was rejected',
152
+ };
153
+ }