@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
@@ -1,181 +0,0 @@
1
- // The catalog schema is a SAFETY BOUNDARY: the two invariants it enforces (only
2
- // within-MM transitions are expressible; no unknown field slips through) are what
3
- // keep an uncertified or MM↔router switch from ever becoming valid catalog data.
4
-
5
- import { describe, expect, test } from 'bun:test';
6
- import {
7
- CERTIFIED_CATALOG,
8
- findCatalogEntry,
9
- findPermittedTransition,
10
- loadCertifiedCatalog,
11
- } from './catalog';
12
- import { catalogEntrySchema, certifiedCatalogSchema } from './catalog-schema';
13
-
14
- function validCatalog(): unknown {
15
- return {
16
- schemaVersion: 1,
17
- entries: [
18
- {
19
- vidPid: '2c7c:0125',
20
- model: 'TEST-SKU',
21
- firmwarePrefix: 'FW01',
22
- canonicalMode: 'qmi',
23
- permittedTransitions: [
24
- {
25
- from: 'qmi',
26
- to: 'mbim',
27
- atCommand: 'AT+QCFG="usbnet",2',
28
- expectedResponse: 'OK',
29
- expectsPortDrop: true,
30
- expectedDescriptors: {
31
- deviceClass: 0,
32
- interfaces: [{ interfaceClass: 2, interfaceSubClass: 14, interfaceProtocol: 0 }],
33
- },
34
- },
35
- ],
36
- },
37
- ],
38
- };
39
- }
40
-
41
- function firstTransition(catalog: ReturnType<typeof validCatalog>): Record<string, unknown> {
42
- // biome-ignore lint/suspicious/noExplicitAny: test helper reaching into a plain fixture
43
- return (catalog as any).entries[0].permittedTransitions[0];
44
- }
45
-
46
- function firstEntry(catalog: ReturnType<typeof validCatalog>): Record<string, unknown> {
47
- // biome-ignore lint/suspicious/noExplicitAny: test helper reaching into a plain fixture
48
- return (catalog as any).entries[0];
49
- }
50
-
51
- describe('certified catalog schema — accepts valid data', () => {
52
- test('a well-formed catalog parses', () => {
53
- expect(certifiedCatalogSchema.safeParse(validCatalog()).success).toBe(true);
54
- });
55
-
56
- test('a router-ethernet SKU with NO transitions is valid', () => {
57
- const catalog = validCatalog() as { entries: Array<Record<string, unknown>> };
58
- catalog.entries[0] = {
59
- vidPid: '12d1:14db',
60
- model: 'HILINK-STICK',
61
- firmwarePrefix: 'HILINK',
62
- canonicalMode: 'router-ethernet',
63
- permittedTransitions: [],
64
- };
65
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(true);
66
- });
67
- });
68
-
69
- describe('certified catalog schema — rejects MM↔router transitions (schema-invalid)', () => {
70
- test('a transition TO router-ethernet fails to parse', () => {
71
- const catalog = validCatalog();
72
- firstTransition(catalog).to = 'router-ethernet';
73
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
74
- });
75
-
76
- test('a transition FROM router-ethernet fails to parse', () => {
77
- const catalog = validCatalog();
78
- firstTransition(catalog).from = 'router-ethernet';
79
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
80
- });
81
-
82
- test('a transition touching rndis fails to parse', () => {
83
- const catalog = validCatalog();
84
- firstTransition(catalog).to = 'rndis';
85
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
86
- });
87
-
88
- test('a router-ethernet SKU may NOT declare any transition', () => {
89
- const catalog = validCatalog();
90
- firstEntry(catalog).canonicalMode = 'router-ethernet';
91
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
92
- });
93
- });
94
-
95
- describe('certified catalog schema — strict mode rejects unknown fields', () => {
96
- test('an unknown field on the catalog root is rejected', () => {
97
- const catalog = validCatalog() as Record<string, unknown>;
98
- catalog.smuggled = true;
99
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
100
- });
101
-
102
- test('an unknown field on an entry is rejected', () => {
103
- const catalog = validCatalog();
104
- firstEntry(catalog).hidden = 'x';
105
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
106
- });
107
-
108
- test('an unknown field on a transition is rejected', () => {
109
- const catalog = validCatalog();
110
- firstTransition(catalog).sneaky = 1;
111
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
112
- });
113
- });
114
-
115
- describe('certified catalog schema — field validation', () => {
116
- test('a self-transition (from === to) is rejected', () => {
117
- const catalog = validCatalog();
118
- firstTransition(catalog).to = 'qmi';
119
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
120
- });
121
-
122
- test('a malformed VID:PID is rejected', () => {
123
- const catalog = validCatalog();
124
- firstEntry(catalog).vidPid = '2c7c-0125';
125
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
126
- });
127
-
128
- test('a schemaVersion other than 1 is rejected', () => {
129
- const catalog = validCatalog() as Record<string, unknown>;
130
- catalog.schemaVersion = 2;
131
- expect(certifiedCatalogSchema.safeParse(catalog).success).toBe(false);
132
- });
133
-
134
- test('a valid evidence-bundle sha256 is accepted; a malformed one is rejected', () => {
135
- const good = validCatalog();
136
- firstTransition(good).evidenceBundleSha256 = 'a'.repeat(64);
137
- expect(certifiedCatalogSchema.safeParse(good).success).toBe(true);
138
-
139
- const bad = validCatalog();
140
- firstTransition(bad).evidenceBundleSha256 = 'nothex';
141
- expect(certifiedCatalogSchema.safeParse(bad).success).toBe(false);
142
- });
143
-
144
- test('loadCertifiedCatalog throws on invalid input', () => {
145
- expect(() => loadCertifiedCatalog({ schemaVersion: 1 })).toThrow();
146
- });
147
-
148
- test('a lone entry also validates against catalogEntrySchema', () => {
149
- const entry = (validCatalog() as { entries: unknown[] }).entries[0];
150
- expect(catalogEntrySchema.safeParse(entry).success).toBe(true);
151
- });
152
- });
153
-
154
- describe('the shipped certified catalog', () => {
155
- test('validates and exposes the synthetic test SKU with within-MM transitions', () => {
156
- const sku = {
157
- vidPid: '2c7c:0125',
158
- model: 'CERALIVE-SYNTHETIC-TEST-SKU',
159
- firmwarePrefix: 'SYNTHETICFW01',
160
- };
161
- const entry = findCatalogEntry(CERTIFIED_CATALOG, sku);
162
- expect(entry).toBeDefined();
163
- expect(entry?.canonicalMode).toBe('qmi');
164
- const transition =
165
- entry !== undefined ? findPermittedTransition(entry, 'qmi', 'mbim') : undefined;
166
- expect(transition?.atCommand).toBe('AT+QCFG="usbnet",2');
167
- // A transition the catalog does NOT declare is absent (never a guess).
168
- expect(
169
- entry !== undefined ? findPermittedTransition(entry, 'mbim', 'ecm-ncm') : undefined,
170
- ).toBeUndefined();
171
- });
172
-
173
- test('an unmatched SKU discriminator returns undefined (uncertified)', () => {
174
- const entry = findCatalogEntry(CERTIFIED_CATALOG, {
175
- vidPid: '2c7c:0125',
176
- model: 'CERALIVE-SYNTHETIC-TEST-SKU',
177
- firmwarePrefix: 'WRONGFW',
178
- });
179
- expect(entry).toBeUndefined();
180
- });
181
- });
@@ -1,113 +0,0 @@
1
- // The certified USB-mode catalog schema — the contract that gates a mode switch.
2
- //
3
- // A catalog ENTRY is one server-derived SKU discriminator (VID:PID + model +
4
- // firmware prefix) plus everything a certified transition needs: the canonical mode
5
- // the SKU speaks, the AT commands that switch it, the expected AT response and
6
- // port-drop behaviour, the USB descriptors it should present AFTER the switch, and a
7
- // forward-reference to the evidence bundle that certified it. Certification is a
8
- // human-reviewed commit that adds an entry — evidence bundles are inputs to that
9
- // review, never read at runtime.
10
- //
11
- // TWO safety invariants are enforced by the SCHEMA ITSELF, not by convention:
12
- // 1. Permitted transitions are WITHIN-ModemManager only — `qmi ↔ mbim ↔ ecm-ncm`.
13
- // A transition's `from`/`to` are typed to the MM-mode enum, so any attempt to
14
- // declare an `MM ↔ router` (or `rndis`) transition FAILS to parse.
15
- // 2. `.strict()` everywhere — an entry carrying an unknown field is rejected, so a
16
- // typo or a smuggled extra field can never slip past review as valid data.
17
-
18
- import { z } from 'zod';
19
-
20
- /**
21
- * The full canonical USB composition-mode vocabulary. A SKU's `canonicalMode` may be
22
- * any of these; only the first three are ModemManager-manageable. (Structurally
23
- * identical to `PreferredUsbMode` in the power contract — A4.2 owns this vocabulary.)
24
- */
25
- export const CANONICAL_USB_MODES = ['qmi', 'mbim', 'ecm-ncm', 'rndis', 'router-ethernet'] as const;
26
- export type CanonicalUsbMode = (typeof CANONICAL_USB_MODES)[number];
27
-
28
- /**
29
- * The ModemManager-manageable modes — the ONLY modes a transition may move between.
30
- * `rndis` and `router-ethernet` are deliberately absent: a device in either is not
31
- * MM-managed, so switching to/from them crosses the MM↔router line the schema forbids.
32
- */
33
- export const MM_USB_MODES = ['qmi', 'mbim', 'ecm-ncm'] as const;
34
- export type MmUsbMode = (typeof MM_USB_MODES)[number];
35
-
36
- const canonicalMode = z.enum(CANONICAL_USB_MODES);
37
- const mmMode = z.enum(MM_USB_MODES);
38
-
39
- /** A `bInterfaceClass`/`bInterfaceSubClass`/`bInterfaceProtocol` byte triple. */
40
- const usbByte = z.number().int().min(0).max(255);
41
-
42
- /** The USB descriptors a device must present AFTER a transition — the postcondition. */
43
- export const expectedDescriptorsSchema = z.strictObject({
44
- deviceClass: usbByte,
45
- interfaces: z
46
- .array(
47
- z.strictObject({
48
- interfaceClass: usbByte,
49
- interfaceSubClass: usbByte,
50
- interfaceProtocol: usbByte,
51
- }),
52
- )
53
- .min(1),
54
- });
55
- export type ExpectedDescriptors = z.infer<typeof expectedDescriptorsSchema>;
56
-
57
- /** One permitted, certified transition between two MM modes. */
58
- export const permittedTransitionSchema = z
59
- .strictObject({
60
- from: mmMode,
61
- to: mmMode,
62
- /** The EXACT AT command that performs the switch (allowlisted at send time). */
63
- atCommand: z.string().min(1),
64
- /** The AT response expected on success (e.g. `OK`) — never proof on its own. */
65
- expectedResponse: z.string().min(1),
66
- /** Whether the control port is expected to drop after the command is written. */
67
- expectsPortDrop: z.boolean(),
68
- /** The descriptors the device must present after re-enumerating — the postcondition. */
69
- expectedDescriptors: expectedDescriptorsSchema,
70
- /** sha256 of the evidence bundle that certified this transition (A6.2 fills it). */
71
- evidenceBundleSha256: z
72
- .string()
73
- .regex(/^[0-9a-f]{64}$/)
74
- .optional(),
75
- })
76
- .refine((t) => t.from !== t.to, { message: 'a transition must change mode (from !== to)' });
77
- export type PermittedTransition = z.infer<typeof permittedTransitionSchema>;
78
-
79
- /** One certified SKU: its discriminator, canonical mode, and permitted transitions. */
80
- export const catalogEntrySchema = z
81
- .strictObject({
82
- /** VID:PID, lowercase hex, `xxxx:xxxx`. */
83
- vidPid: z.string().regex(/^[0-9a-f]{4}:[0-9a-f]{4}$/),
84
- /** The server-derived model string (a discriminator, not free text). */
85
- model: z.string().min(1),
86
- /** The firmware-revision prefix that discriminates this personality. */
87
- firmwarePrefix: z.string().min(1),
88
- /** The mode this SKU speaks as certified. */
89
- canonicalMode,
90
- /** The certified transitions — empty for a non-MM (router/rndis) SKU. */
91
- permittedTransitions: z.array(permittedTransitionSchema),
92
- })
93
- .refine(
94
- (e) =>
95
- e.permittedTransitions.length === 0 ||
96
- (MM_USB_MODES as readonly string[]).includes(e.canonicalMode),
97
- { message: 'only an MM-mode SKU (qmi/mbim/ecm-ncm) may declare permitted transitions' },
98
- );
99
- export type CatalogEntry = z.infer<typeof catalogEntrySchema>;
100
-
101
- /** The whole certified catalog file. */
102
- export const certifiedCatalogSchema = z.strictObject({
103
- schemaVersion: z.literal(1),
104
- entries: z.array(catalogEntrySchema),
105
- });
106
- export type CertifiedCatalog = z.infer<typeof certifiedCatalogSchema>;
107
-
108
- /** A live device's SKU discriminator — matched against the catalog. */
109
- export interface SkuDiscriminator {
110
- readonly vidPid: string;
111
- readonly model: string;
112
- readonly firmwarePrefix: string;
113
- }
@@ -1,67 +0,0 @@
1
- {
2
- "schemaVersion": 1,
3
- "entries": [
4
- {
5
- "vidPid": "2c7c:0125",
6
- "model": "CERALIVE-SYNTHETIC-TEST-SKU",
7
- "firmwarePrefix": "SYNTHETICFW01",
8
- "canonicalMode": "qmi",
9
- "permittedTransitions": [
10
- {
11
- "from": "qmi",
12
- "to": "mbim",
13
- "atCommand": "AT+QCFG=\"usbnet\",2",
14
- "expectedResponse": "OK",
15
- "expectsPortDrop": true,
16
- "expectedDescriptors": {
17
- "deviceClass": 0,
18
- "interfaces": [
19
- { "interfaceClass": 2, "interfaceSubClass": 14, "interfaceProtocol": 0 },
20
- { "interfaceClass": 10, "interfaceSubClass": 0, "interfaceProtocol": 2 }
21
- ]
22
- }
23
- },
24
- {
25
- "from": "mbim",
26
- "to": "qmi",
27
- "atCommand": "AT+QCFG=\"usbnet\",0",
28
- "expectedResponse": "OK",
29
- "expectsPortDrop": true,
30
- "expectedDescriptors": {
31
- "deviceClass": 0,
32
- "interfaces": [
33
- { "interfaceClass": 255, "interfaceSubClass": 255, "interfaceProtocol": 255 }
34
- ]
35
- }
36
- },
37
- {
38
- "from": "qmi",
39
- "to": "ecm-ncm",
40
- "atCommand": "AT+QCFG=\"usbnet\",1",
41
- "expectedResponse": "OK",
42
- "expectsPortDrop": true,
43
- "expectedDescriptors": {
44
- "deviceClass": 0,
45
- "interfaces": [
46
- { "interfaceClass": 2, "interfaceSubClass": 6, "interfaceProtocol": 0 },
47
- { "interfaceClass": 255, "interfaceSubClass": 255, "interfaceProtocol": 255 }
48
- ]
49
- }
50
- },
51
- {
52
- "from": "ecm-ncm",
53
- "to": "qmi",
54
- "atCommand": "AT+QCFG=\"usbnet\",0",
55
- "expectedResponse": "OK",
56
- "expectsPortDrop": true,
57
- "expectedDescriptors": {
58
- "deviceClass": 0,
59
- "interfaces": [
60
- { "interfaceClass": 255, "interfaceSubClass": 255, "interfaceProtocol": 255 }
61
- ]
62
- }
63
- }
64
- ]
65
- }
66
- ]
67
- }
@@ -1,58 +0,0 @@
1
- // The certified USB-mode catalog — schema, data, lookups, and the evidence-bundle
2
- // ingestion seam.
3
- //
4
- // A6.1's bench CLI (`set-usb-mode`) and A6.2's `certify` tool both consume this: the
5
- // CLI looks up the permitted transition for a target mode, `certify` validates a
6
- // candidate entry against the schema before a human commits it.
7
- //
8
- // The ingestion seam (`./ingestion`, `./promotion-review`) is the documented path from a
9
- // captured `certify` bundle to a reviewed catalog commit — see `docs/CATALOG-INGESTION.md`.
10
- // It refuses a `synthetic: true` bundle for catalog promotion, by construction.
11
-
12
- export {
13
- CERTIFIED_CATALOG,
14
- findCatalogEntry,
15
- findPermittedTransition,
16
- loadCertifiedCatalog,
17
- } from './catalog';
18
- export {
19
- CANONICAL_USB_MODES,
20
- type CanonicalUsbMode,
21
- type CatalogEntry,
22
- type CertifiedCatalog,
23
- catalogEntrySchema,
24
- certifiedCatalogSchema,
25
- type ExpectedDescriptors,
26
- expectedDescriptorsSchema,
27
- MM_USB_MODES,
28
- type MmUsbMode,
29
- type PermittedTransition,
30
- permittedTransitionSchema,
31
- type SkuDiscriminator,
32
- } from './catalog-schema';
33
- export {
34
- buildCatalogEntryCandidate,
35
- buildClassifierFixture,
36
- type CatalogClaim,
37
- CLAIMABLE_CANONICAL_MODES,
38
- type ClassifierFixture,
39
- type EvidenceBundleView,
40
- evidenceBundleViewSchema,
41
- type FixtureProvenance,
42
- type IngestionOutcome,
43
- type IngestionRefusal,
44
- type IngestionRefusalReason,
45
- type IngestionRequest,
46
- parseIngestionRequest,
47
- } from './ingestion';
48
- export {
49
- type PromotionContext,
50
- type PromotionRequest,
51
- renderPromotionReview,
52
- } from './promotion-review';
53
- export {
54
- type ParsedUsbDevice,
55
- type ParsedUsbInterface,
56
- parseUsbDevices,
57
- selectUniqueDevice,
58
- } from './usb-devices-parse';
@@ -1,268 +0,0 @@
1
- // The ingestion seam's contract: a bundle round-trips into a schema-valid catalog entry
2
- // and a real-shaped classifier fixture, the sha256 links the two, and a synthetic bundle
3
- // is REFUSED for catalog promotion with a typed reason rather than silently accepted.
4
-
5
- import { describe, expect, test } from 'bun:test';
6
- import { classifyDevice, detectUsbMode } from '../backend/device-classifier';
7
- import { catalogEntrySchema } from './catalog-schema';
8
- import {
9
- buildCatalogEntryCandidate,
10
- buildClassifierFixture,
11
- type IngestionRequest,
12
- parseIngestionRequest,
13
- } from './ingestion';
14
- import { renderPromotionReview } from './promotion-review';
15
- import { parseUsbDevices, selectUniqueDevice } from './usb-devices-parse';
16
-
17
- const SHA = 'a'.repeat(64);
18
-
19
- /** Verbatim-shaped `usb-devices` output for a QMI stick plus one unrelated hub. */
20
- const USB_DEVICES = `
21
- T: Bus=04 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=5000 MxCh= 4
22
- D: Ver= 3.20 Cls=09(hub ) Sub=00 Prot=03 MxPS= 9 #Cfgs= 1
23
- P: Vendor=0bda ProdID=0411 Rev=01.01
24
- S: Manufacturer=Generic
25
- S: Product=USB3.2 Hub
26
- I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
27
-
28
- T: Bus=04 Lev=03 Prnt=04 Port=03 Cnt=01 Dev#= 7 Spd=480 MxCh= 0
29
- D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
30
- P: Vendor=2c7c ProdID=0125 Rev=03.18
31
- S: Manufacturer=Quectel
32
- S: Product=SYNTHETIC-BENCH-STICK
33
- I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=00 Driver=option
34
- I: If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=qmi_wwan
35
- `;
36
-
37
- function bundle(overrides: Record<string, unknown> = {}): Record<string, unknown> {
38
- return {
39
- schemaVersion: 1,
40
- synthetic: false,
41
- capturedAtMs: 1_760_000_000_000,
42
- slot: 'Modem/2',
43
- sku: {
44
- vidPid: '2c7c:0125',
45
- model: 'CERALIVE-SYNTHETIC-TEST-SKU',
46
- firmwarePrefix: 'SYNTHETICFW01',
47
- },
48
- usb: {
49
- usbDevices: USB_DEVICES,
50
- udevProperties: {
51
- ID_PATH: 'platform-xhci-hcd.0.auto-usb-0:1.4.4',
52
- ID_VENDOR_ID: '2c7c',
53
- ID_MODEL_ID: '0125',
54
- INTERFACE: 'wwan0',
55
- },
56
- },
57
- // Fields the real bundle carries and the ingestion VIEW deliberately ignores.
58
- usbExtra: { lsusb: 'Device Descriptor:' },
59
- modemManager: { mmcliKeyfile: {}, managedObjects: {}, signalWindow: [] },
60
- ...overrides,
61
- };
62
- }
63
-
64
- const request = (overrides: Record<string, unknown> = {}, sha = SHA): IngestionRequest => ({
65
- bundle: bundle(overrides),
66
- bundleSha256: sha,
67
- });
68
-
69
- const TRANSITION = {
70
- from: 'qmi',
71
- to: 'mbim',
72
- atCommand: 'AT+QCFG="usbnet",2',
73
- expectedResponse: 'OK',
74
- expectsPortDrop: true,
75
- afterDescriptors: {
76
- deviceClass: 0,
77
- interfaces: [
78
- { interfaceClass: 2, interfaceSubClass: 14, interfaceProtocol: 0 },
79
- { interfaceClass: 10, interfaceSubClass: 0, interfaceProtocol: 2 },
80
- ],
81
- },
82
- timeline: [{ event: 'command-sent', atMs: 1 }],
83
- };
84
-
85
- describe('parseUsbDevices — the descriptor source a base bundle actually carries', () => {
86
- test('parses every device, its bDeviceClass, and each interface driver', () => {
87
- const devices = parseUsbDevices(USB_DEVICES);
88
- expect(devices).toHaveLength(2);
89
- const stick = devices[1];
90
- expect(stick?.vidPid).toBe('2c7c:0125');
91
- expect(stick?.bDeviceClass).toBe(0);
92
- expect(stick?.product).toBe('SYNTHETIC-BENCH-STICK');
93
- expect(stick?.interfaces).toEqual([
94
- { interfaceClass: 0xff, interfaceSubClass: 0x00, interfaceProtocol: 0x00, driver: 'option' },
95
- {
96
- interfaceClass: 0xff,
97
- interfaceSubClass: 0xff,
98
- interfaceProtocol: 0xff,
99
- driver: 'qmi_wwan',
100
- },
101
- ]);
102
- });
103
-
104
- test('a block with no P: line yields no record — identity is never invented', () => {
105
- expect(parseUsbDevices('T: Bus=01\nI: If#= 0 Cls=ff Sub=ff Prot=ff Driver=x')).toEqual([]);
106
- });
107
-
108
- test('a duplicate VID:PID is AMBIGUOUS, not first-wins (the Huawei HiLink pair)', () => {
109
- const pair = `${USB_DEVICES}\nT: Bus=01 Lev=01\nD: Cls=00\nP: Vendor=2c7c ProdID=0125 Rev=03.18\nI: If#= 0 Cls=ff Sub=ff Prot=ff Driver=qmi_wwan\n`;
110
- const selected = selectUniqueDevice(parseUsbDevices(pair), '2c7c:0125');
111
- expect(selected).toEqual({ ambiguousMatches: 2 });
112
- });
113
- });
114
-
115
- describe('buildClassifierFixture — the real udev shape, not a hand-typed approximation', () => {
116
- test('produces a snapshot the REAL classifier classifies correctly', () => {
117
- const outcome = buildClassifierFixture(request());
118
- expect(outcome.ok).toBe(true);
119
- if (!outcome.ok) {
120
- return;
121
- }
122
- const { snapshot, provenance } = outcome.value;
123
- expect(snapshot.vendorId).toBe('2c7c');
124
- expect(snapshot.productId).toBe('0125');
125
- expect(snapshot.model).toBe('CERALIVE-SYNTHETIC-TEST-SKU');
126
- expect(snapshot.firmwareRevision).toBe('SYNTHETICFW01');
127
- expect(snapshot.physicalUid).toBe('platform-xhci-hcd.0.auto-usb-0:1.4.4');
128
- expect(snapshot.ifname).toBe('wwan0');
129
- // The whole point of deriving from real capture text: the fixture must survive
130
- // the production classifier, not merely typecheck.
131
- expect(classifyDevice(snapshot).deviceClass).toBe('mm-managed');
132
- expect(detectUsbMode(snapshot)).toBe('qmi');
133
- expect(provenance.bundleSha256).toBe(SHA);
134
- expect(provenance.synthetic).toBe(false);
135
- });
136
-
137
- test('a SYNTHETIC bundle still yields a fixture, stamped synthetic in provenance', () => {
138
- const outcome = buildClassifierFixture(request({ synthetic: true }));
139
- expect(outcome.ok).toBe(true);
140
- if (outcome.ok) {
141
- expect(outcome.value.provenance.synthetic).toBe(true);
142
- }
143
- });
144
-
145
- test('refuses a bundle with no sku (blocker B2 shape) rather than inventing one', () => {
146
- const outcome = buildClassifierFixture(request({ sku: undefined }));
147
- expect(outcome).toMatchObject({ ok: false, reason: 'sku-missing' });
148
- });
149
-
150
- test('refuses when the SKU is absent from the usb-devices capture', () => {
151
- const outcome = buildClassifierFixture(
152
- request({ sku: { ...(bundle().sku as object), vidPid: '1199:9071' } }),
153
- );
154
- expect(outcome).toMatchObject({ ok: false, reason: 'device-not-in-capture' });
155
- });
156
- });
157
-
158
- describe('buildCatalogEntryCandidate — schema round-trip and sha linkage', () => {
159
- test('a stage-1 bundle yields an entry with NO permitted transitions', () => {
160
- const outcome = buildCatalogEntryCandidate(request(), { canonicalMode: 'qmi' });
161
- expect(outcome.ok).toBe(true);
162
- if (!outcome.ok) {
163
- return;
164
- }
165
- expect(outcome.value.permittedTransitions).toEqual([]);
166
- // Round-trip through the AUTHORITATIVE schema, not the builder's own view.
167
- expect(catalogEntrySchema.parse(outcome.value)).toEqual(outcome.value);
168
- });
169
-
170
- test('a stage-2 bundle links the transition to THIS bundle sha256', () => {
171
- const sha = 'b'.repeat(64);
172
- const outcome = buildCatalogEntryCandidate(
173
- { bundle: bundle({ transition: TRANSITION }), bundleSha256: sha },
174
- { canonicalMode: 'qmi' },
175
- );
176
- expect(outcome.ok).toBe(true);
177
- if (!outcome.ok) {
178
- return;
179
- }
180
- const [transition] = outcome.value.permittedTransitions;
181
- expect(transition?.evidenceBundleSha256).toBe(sha);
182
- expect(transition?.expectedDescriptors).toEqual(TRANSITION.afterDescriptors);
183
- expect(transition?.atCommand).toBe('AT+QCFG="usbnet",2');
184
- expect(catalogEntrySchema.parse(outcome.value)).toEqual(outcome.value);
185
- });
186
-
187
- test('REFUSES a synthetic:true bundle for catalog promotion — typed, not silent', () => {
188
- const outcome = buildCatalogEntryCandidate(request({ synthetic: true }), {
189
- canonicalMode: 'qmi',
190
- });
191
- expect(outcome.ok).toBe(false);
192
- if (outcome.ok) {
193
- return;
194
- }
195
- expect(outcome.reason).toBe('synthetic-bundle');
196
- expect(outcome.detail).toContain('synthetic:true');
197
- });
198
-
199
- test('refuses a claimed mode that contradicts the captured transition.from', () => {
200
- const outcome = buildCatalogEntryCandidate(
201
- { bundle: bundle({ transition: TRANSITION }), bundleSha256: SHA },
202
- { canonicalMode: 'mbim' },
203
- );
204
- expect(outcome).toMatchObject({ ok: false, reason: 'transition-mode-mismatch' });
205
- });
206
-
207
- test('refuses a router-mode SKU that carries a transition (schema invariant)', () => {
208
- const outcome = buildCatalogEntryCandidate(
209
- { bundle: bundle({ transition: TRANSITION }), bundleSha256: SHA },
210
- { canonicalMode: 'router-ethernet' },
211
- );
212
- // The mode cross-check fires first; either refusal is correct, neither is an accept.
213
- expect(outcome.ok).toBe(false);
214
- });
215
-
216
- test('accepts a router-mode SKU with no transitions (the RB-15 shape)', () => {
217
- const outcome = buildCatalogEntryCandidate(request(), { canonicalMode: 'router-ethernet' });
218
- expect(outcome.ok).toBe(true);
219
- if (outcome.ok) {
220
- expect(outcome.value.canonicalMode).toBe('router-ethernet');
221
- expect(outcome.value.permittedTransitions).toEqual([]);
222
- }
223
- });
224
-
225
- test('refuses a malformed sha256 before reading the bundle at all', () => {
226
- expect(parseIngestionRequest({ bundle: bundle(), bundleSha256: 'nope' })).toMatchObject({
227
- ok: false,
228
- reason: 'sha256-malformed',
229
- });
230
- });
231
-
232
- test('refuses a bundle that fails the view schema', () => {
233
- expect(
234
- parseIngestionRequest({ bundle: { schemaVersion: 2 }, bundleSha256: SHA }),
235
- ).toMatchObject({ ok: false, reason: 'bundle-malformed' });
236
- });
237
- });
238
-
239
- describe('renderPromotionReview — the review artifact, including for refusals', () => {
240
- test('renders the entry, the fixture, and a checklist on success', () => {
241
- const req = request();
242
- const comment = renderPromotionReview({
243
- context: { runbook: 'RB-11', evidencePath: 'test-results/modem-phase-b/08/x/bundle.json' },
244
- entry: buildCatalogEntryCandidate(req, { canonicalMode: 'qmi' }),
245
- fixture: buildClassifierFixture(req),
246
- });
247
- expect(comment).toContain('Proposed `certified-catalog.json` entry');
248
- expect(comment).toContain('Proposed classifier fixture');
249
- expect(comment).toContain('Reviewer checklist');
250
- expect(comment).toContain('RB-11');
251
- expect(comment).toContain('**This comment promotes');
252
- });
253
-
254
- test('a refused promotion renders the refusal and NO checklist', () => {
255
- const req = request({ synthetic: true });
256
- const comment = renderPromotionReview({
257
- context: { runbook: 'RB-11', evidencePath: 'x.json' },
258
- entry: buildCatalogEntryCandidate(req, { canonicalMode: 'qmi' }),
259
- fixture: buildClassifierFixture(req),
260
- });
261
- expect(comment).toContain('Catalog entry — REFUSED');
262
- expect(comment).toContain('`synthetic-bundle`');
263
- expect(comment).toContain('### No checklist');
264
- expect(comment).not.toContain('Reviewer checklist');
265
- // The fixture half still renders — synthetic fixtures are legitimate test data.
266
- expect(comment).toContain('Derived from a **synthetic** bundle');
267
- });
268
- });