@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
@@ -6,55 +6,33 @@
6
6
  // an alternate file (e.g. A6.2's `certify` tool checking a candidate entry) uses
7
7
  // `loadCertifiedCatalog`. The evidence bundles the entries reference are NOT read
8
8
  // here — only the catalog metadata is.
9
-
10
- import {
11
- type CatalogEntry,
12
- type CertifiedCatalog,
13
- certifiedCatalogSchema,
14
- type MmUsbMode,
15
- type PermittedTransition,
16
- type SkuDiscriminator,
17
- } from './catalog-schema';
9
+ import { certifiedCatalogSchema, } from './catalog-schema.js';
18
10
  import rawCatalog from './certified-catalog.json' with { type: 'json' };
19
-
20
11
  /**
21
12
  * Parse and validate an untrusted value as a certified catalog. Throws a `ZodError`
22
13
  * with a precise path if the value violates the schema (unknown field, an MM↔router
23
14
  * transition, a bad VID:PID, etc.).
24
15
  */
25
- export function loadCertifiedCatalog(value: unknown): CertifiedCatalog {
26
- return certifiedCatalogSchema.parse(value);
16
+ export function loadCertifiedCatalog(value) {
17
+ return certifiedCatalogSchema.parse(value);
27
18
  }
28
-
29
19
  /** The catalog shipped in this package, validated at module load. */
30
- export const CERTIFIED_CATALOG: CertifiedCatalog = loadCertifiedCatalog(rawCatalog);
31
-
20
+ export const CERTIFIED_CATALOG = loadCertifiedCatalog(rawCatalog);
32
21
  /**
33
22
  * Find the catalog entry matching a live device's SKU discriminator. All three
34
23
  * discriminators (VID:PID, model, firmware prefix) must match — a partial match is
35
24
  * NOT a certified device. Returns `undefined` for an uncertified SKU (never a guess).
36
25
  */
37
- export function findCatalogEntry(
38
- catalog: CertifiedCatalog,
39
- sku: SkuDiscriminator,
40
- ): CatalogEntry | undefined {
41
- return catalog.entries.find(
42
- (entry) =>
43
- entry.vidPid === sku.vidPid &&
44
- entry.model === sku.model &&
45
- entry.firmwarePrefix === sku.firmwarePrefix,
46
- );
26
+ export function findCatalogEntry(catalog, sku) {
27
+ return catalog.entries.find((entry) => entry.vidPid === sku.vidPid &&
28
+ entry.model === sku.model &&
29
+ entry.firmwarePrefix === sku.firmwarePrefix);
47
30
  }
48
-
49
31
  /**
50
32
  * Find the permitted transition `from → to` in a catalog entry. Returns `undefined`
51
33
  * when the entry declares no such transition — the caller MUST treat that as "not
52
34
  * permitted", never as "permitted with no command".
53
35
  */
54
- export function findPermittedTransition(
55
- entry: CatalogEntry,
56
- from: MmUsbMode,
57
- to: MmUsbMode,
58
- ): PermittedTransition | undefined {
59
- return entry.permittedTransitions.find((t) => t.from === from && t.to === to);
36
+ export function findPermittedTransition(entry, from, to) {
37
+ return entry.permittedTransitions.find((t) => t.from === from && t.to === to);
60
38
  }
@@ -0,0 +1,67 @@
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
+ }
@@ -0,0 +1,6 @@
1
+ export { CERTIFIED_CATALOG, findCatalogEntry, findPermittedTransition, loadCertifiedCatalog, } from './catalog.js';
2
+ export { CANONICAL_USB_MODES, type CanonicalUsbMode, type CatalogEntry, type CertifiedCatalog, catalogEntrySchema, certifiedCatalogSchema, type ExpectedDescriptors, expectedDescriptorsSchema, MM_USB_MODES, type MmUsbMode, type PermittedTransition, permittedTransitionSchema, type SkuDiscriminator, } from './catalog-schema.js';
3
+ export { buildCatalogEntryCandidate, buildClassifierFixture, type CatalogClaim, CLAIMABLE_CANONICAL_MODES, type ClassifierFixture, type EvidenceBundleView, evidenceBundleViewSchema, type FixtureProvenance, type IngestionOutcome, type IngestionRefusal, type IngestionRefusalReason, type IngestionRequest, parseIngestionRequest, } from './ingestion.js';
4
+ export { type PromotionContext, type PromotionRequest, renderPromotionReview, } from './promotion-review.js';
5
+ export { buildRuntimeCompositionSetCommand, isRuntimeCompositionVendor, RUNTIME_COMPOSITION_QUERY_REGISTRY, RUNTIME_COMPOSITION_SET_REGISTRY, RUNTIME_COMPOSITION_VENDORS, type RuntimeCompositionCapability, type RuntimeCompositionMode, type RuntimeCompositionQuery, type RuntimeCompositionResponse, type RuntimeCompositionSetCommand, type RuntimeCompositionVendor, readRuntimeCompositionCurrent, resolveRuntimeCompositionCapability, } from './runtime-capability.js';
6
+ export { type ParsedUsbDevice, type ParsedUsbInterface, parseUsbDevices, selectUniqueDevice, } from './usb-devices-parse.js';
@@ -0,0 +1,16 @@
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
+ export { CERTIFIED_CATALOG, findCatalogEntry, findPermittedTransition, loadCertifiedCatalog, } from './catalog.js';
12
+ export { CANONICAL_USB_MODES, catalogEntrySchema, certifiedCatalogSchema, expectedDescriptorsSchema, MM_USB_MODES, permittedTransitionSchema, } from './catalog-schema.js';
13
+ export { buildCatalogEntryCandidate, buildClassifierFixture, CLAIMABLE_CANONICAL_MODES, evidenceBundleViewSchema, parseIngestionRequest, } from './ingestion.js';
14
+ export { renderPromotionReview, } from './promotion-review.js';
15
+ export { buildRuntimeCompositionSetCommand, isRuntimeCompositionVendor, RUNTIME_COMPOSITION_QUERY_REGISTRY, RUNTIME_COMPOSITION_SET_REGISTRY, RUNTIME_COMPOSITION_VENDORS, readRuntimeCompositionCurrent, resolveRuntimeCompositionCapability, } from './runtime-capability.js';
16
+ export { parseUsbDevices, selectUniqueDevice, } from './usb-devices-parse.js';
@@ -0,0 +1,111 @@
1
+ import { z } from 'zod';
2
+ import type { UsbDeviceSnapshot } from '../backend/device-classifier.js';
3
+ import { type CanonicalUsbMode, type CatalogEntry } from './catalog-schema.js';
4
+ /** The ingestion VIEW of a certification bundle — non-strict on purpose (see header). */
5
+ export declare const evidenceBundleViewSchema: z.ZodObject<{
6
+ schemaVersion: z.ZodLiteral<1>;
7
+ synthetic: z.ZodBoolean;
8
+ capturedAtMs: z.ZodNumber;
9
+ slot: z.ZodString;
10
+ sku: z.ZodOptional<z.ZodObject<{
11
+ vidPid: z.ZodString;
12
+ model: z.ZodString;
13
+ firmwarePrefix: z.ZodString;
14
+ }, z.core.$strip>>;
15
+ usb: z.ZodObject<{
16
+ usbDevices: z.ZodString;
17
+ udevProperties: z.ZodRecord<z.ZodString, z.ZodString>;
18
+ }, z.core.$strip>;
19
+ transition: z.ZodOptional<z.ZodObject<{
20
+ from: z.ZodEnum<{
21
+ "ecm-ncm": "ecm-ncm";
22
+ mbim: "mbim";
23
+ qmi: "qmi";
24
+ }>;
25
+ to: z.ZodEnum<{
26
+ "ecm-ncm": "ecm-ncm";
27
+ mbim: "mbim";
28
+ qmi: "qmi";
29
+ }>;
30
+ atCommand: z.ZodString;
31
+ expectedResponse: z.ZodString;
32
+ expectsPortDrop: z.ZodBoolean;
33
+ afterDescriptors: z.ZodObject<{
34
+ deviceClass: z.ZodNumber;
35
+ interfaces: z.ZodArray<z.ZodObject<{
36
+ interfaceClass: z.ZodNumber;
37
+ interfaceSubClass: z.ZodNumber;
38
+ interfaceProtocol: z.ZodNumber;
39
+ }, z.core.$strict>>;
40
+ }, z.core.$strict>;
41
+ }, z.core.$strip>>;
42
+ }, z.core.$strip>;
43
+ export type EvidenceBundleView = z.infer<typeof evidenceBundleViewSchema>;
44
+ /** Every way ingestion can refuse. Each is a named, actionable condition — never a throw. */
45
+ export type IngestionRefusalReason = 'bundle-malformed' | 'sha256-malformed' | 'sku-missing' | 'device-not-in-capture' | 'device-ambiguous' | 'no-interfaces-captured' | 'synthetic-bundle' | 'transition-mode-mismatch' | 'entry-schema-invalid';
46
+ /** A typed refusal. `detail` is for a human reviewer; `reason` is for a machine. */
47
+ export interface IngestionRefusal {
48
+ readonly ok: false;
49
+ readonly reason: IngestionRefusalReason;
50
+ readonly detail: string;
51
+ }
52
+ /** A refusal or a value — ingestion never throws and never returns a partial result. */
53
+ export type IngestionOutcome<T> = {
54
+ readonly ok: true;
55
+ readonly value: T;
56
+ } | IngestionRefusal;
57
+ /** Where a fixture came from — stamped onto every fixture, honest about synthetic input. */
58
+ export interface FixtureProvenance {
59
+ readonly bundleSha256: string;
60
+ /** `true` when the source bundle was synthetic — such a fixture is test data only. */
61
+ readonly synthetic: boolean;
62
+ readonly slot: string;
63
+ readonly capturedAtMs: number;
64
+ }
65
+ /** A classifier fixture: the snapshot `classifyDevice` consumes, plus its provenance. */
66
+ export interface ClassifierFixture {
67
+ readonly snapshot: UsbDeviceSnapshot;
68
+ readonly provenance: FixtureProvenance;
69
+ }
70
+ /** One bundle plus the sha256 `certify` printed for it — the two halves are inseparable. */
71
+ export interface IngestionRequest {
72
+ /** The bundle JSON, already `JSON.parse`d. Validated here against the view schema. */
73
+ readonly bundle: unknown;
74
+ /** The `CERTIFY OK: sha256=…` value. Becomes the entry's `evidenceBundleSha256`. */
75
+ readonly bundleSha256: string;
76
+ }
77
+ /**
78
+ * The claim a REVIEWER makes about the SKU. `canonicalMode` is stated, never inferred:
79
+ * a machine reading descriptors could guess it, but a catalog entry is an assertion a
80
+ * human signs, and a stage-2 bundle's `transition.from` is cross-checked against it.
81
+ */
82
+ export interface CatalogClaim {
83
+ readonly canonicalMode: CanonicalUsbMode;
84
+ }
85
+ /** Validate a request's bundle + sha, or refuse with a precise reason. */
86
+ export declare function parseIngestionRequest(request: IngestionRequest): IngestionOutcome<EvidenceBundleView>;
87
+ /**
88
+ * Build a classifier test fixture from a bundle — the real udev shape, not a hand-typed
89
+ * approximation. Descriptors and per-interface DRIVERS come from the bundle's
90
+ * `usb-devices` text (the only structured descriptor source a base bundle carries);
91
+ * identity comes from the bundle's SKU; `physicalUid` / `ifname` come from the captured
92
+ * udev properties. A synthetic bundle is ACCEPTED here and the provenance says so.
93
+ */
94
+ export declare function buildClassifierFixture(request: IngestionRequest): IngestionOutcome<ClassifierFixture>;
95
+ /**
96
+ * Build a CANDIDATE catalog entry from a bundle. The entry is a review artifact: it is
97
+ * returned, never written.
98
+ *
99
+ * REFUSES a `synthetic: true` bundle — a catalog entry asserts a certified hardware
100
+ * fact, so synthetic evidence can never back one (`docs/BENCH.md` Must-NOT-Have 7).
101
+ *
102
+ * A stage-1 (base) bundle yields `permittedTransitions: []`. A stage-2 bundle — one
103
+ * captured with `certify --transition` — yields exactly ONE permitted transition, whose
104
+ * `expectedDescriptors` is the captured `afterDescriptors` and whose
105
+ * `evidenceBundleSha256` is this bundle's hash. The reviewer's stated `canonicalMode`
106
+ * must equal the captured `transition.from`; a mismatch is refused rather than silently
107
+ * resolved in either direction.
108
+ */
109
+ export declare function buildCatalogEntryCandidate(request: IngestionRequest, claim: CatalogClaim): IngestionOutcome<CatalogEntry>;
110
+ /** The canonical-mode vocabulary a reviewer's claim may use — re-exported for callers. */
111
+ export declare const CLAIMABLE_CANONICAL_MODES: readonly ["qmi", "mbim", "ecm-ncm", "rndis", "router-ethernet"];
@@ -0,0 +1,187 @@
1
+ // The evidence-bundle ingestion seam — turning ONE real `certify` bundle into (i) a
2
+ // classifier test fixture in the real udev shape and (ii) a candidate catalog entry.
3
+ //
4
+ // This is the documented path between the bench and the catalog. It is deliberately a
5
+ // PURE TRANSFORM that produces a REVIEW ARTIFACT: nothing here writes a file, mutates
6
+ // `certified-catalog.json`, or promotes anything. A catalog addition stays what Phase A
7
+ // made it — a human-reviewed commit — and this seam only removes the hand-transcription
8
+ // step between the bundle and that commit.
9
+ //
10
+ // THE ONE RULE THE CODE ENFORCES, NOT THE REVIEWER:
11
+ // A bundle marked `synthetic: true` is REFUSED for catalog promotion, with a typed
12
+ // reason. Synthetic bundles are legitimate test data — `buildClassifierFixture` accepts
13
+ // them and stamps the fixture's provenance with `synthetic: true` — but a catalog entry
14
+ // asserts a certified hardware fact, and no synthetic capture may ever back one.
15
+ //
16
+ // SHAPE COMPATIBILITY, not shape duplication: the bundle is validated here through a
17
+ // deliberately NON-strict VIEW schema. The authoritative bundle schema lives beside the
18
+ // `certify` command in the CLI, which depends on this package and not the reverse, so
19
+ // this file describes only the subset ingestion reads and ignores the rest (`lsusb`,
20
+ // `modemManager`, the transition timeline). Adding a field to the bundle can therefore
21
+ // never break ingestion — which is the point of a view.
22
+ import { z } from 'zod';
23
+ import { CANONICAL_USB_MODES, catalogEntrySchema, expectedDescriptorsSchema, MM_USB_MODES, } from './catalog-schema.js';
24
+ import { parseUsbDevices, selectUniqueDevice } from './usb-devices-parse.js';
25
+ const mmMode = z.enum(MM_USB_MODES);
26
+ /** The ingestion VIEW of a certification bundle — non-strict on purpose (see header). */
27
+ export const evidenceBundleViewSchema = z.object({
28
+ schemaVersion: z.literal(1),
29
+ synthetic: z.boolean(),
30
+ capturedAtMs: z.number(),
31
+ slot: z.string().min(1),
32
+ sku: z
33
+ .object({
34
+ vidPid: z.string().regex(/^[0-9a-f]{4}:[0-9a-f]{4}$/),
35
+ model: z.string().min(1),
36
+ firmwarePrefix: z.string().min(1),
37
+ })
38
+ .optional(),
39
+ usb: z.object({
40
+ usbDevices: z.string().min(1),
41
+ udevProperties: z.record(z.string(), z.string()),
42
+ }),
43
+ transition: z
44
+ .object({
45
+ from: mmMode,
46
+ to: mmMode,
47
+ atCommand: z.string().min(1),
48
+ expectedResponse: z.string().min(1),
49
+ expectsPortDrop: z.boolean(),
50
+ afterDescriptors: expectedDescriptorsSchema,
51
+ })
52
+ .optional(),
53
+ });
54
+ const refuse = (reason, detail) => ({
55
+ ok: false,
56
+ reason,
57
+ detail,
58
+ });
59
+ const SHA256_RE = /^[0-9a-f]{64}$/;
60
+ /** Validate a request's bundle + sha, or refuse with a precise reason. */
61
+ export function parseIngestionRequest(request) {
62
+ if (!SHA256_RE.test(request.bundleSha256)) {
63
+ return refuse('sha256-malformed', `bundle sha256 must be 64 lowercase hex characters, got '${request.bundleSha256}'`);
64
+ }
65
+ const parsed = evidenceBundleViewSchema.safeParse(request.bundle);
66
+ if (!parsed.success) {
67
+ return refuse('bundle-malformed', z.prettifyError(parsed.error));
68
+ }
69
+ return { ok: true, value: parsed.data };
70
+ }
71
+ /**
72
+ * Build a classifier test fixture from a bundle — the real udev shape, not a hand-typed
73
+ * approximation. Descriptors and per-interface DRIVERS come from the bundle's
74
+ * `usb-devices` text (the only structured descriptor source a base bundle carries);
75
+ * identity comes from the bundle's SKU; `physicalUid` / `ifname` come from the captured
76
+ * udev properties. A synthetic bundle is ACCEPTED here and the provenance says so.
77
+ */
78
+ export function buildClassifierFixture(request) {
79
+ const parsed = parseIngestionRequest(request);
80
+ if (!parsed.ok) {
81
+ return parsed;
82
+ }
83
+ const bundle = parsed.value;
84
+ const sku = bundle.sku;
85
+ if (sku === undefined) {
86
+ // Blocker B2 in `docs/BENCH.md` produces exactly this: an unmatched USB device
87
+ // yields a bundle with no SKU at all.
88
+ return refuse('sku-missing', 'bundle carries no `sku` — the capture did not match a USB device to the slot');
89
+ }
90
+ const selected = selectUniqueDevice(parseUsbDevices(bundle.usb.usbDevices), sku.vidPid);
91
+ if (!('device' in selected)) {
92
+ return selected.ambiguousMatches === 0
93
+ ? refuse('device-not-in-capture', `no device with vidPid ${sku.vidPid} in the bundle's usb-devices capture`)
94
+ : refuse('device-ambiguous', `${selected.ambiguousMatches} devices share vidPid ${sku.vidPid} in this capture; a fixture must name one physical device`);
95
+ }
96
+ const device = selected.device;
97
+ if (device.interfaces.length === 0) {
98
+ return refuse('no-interfaces-captured', `device ${sku.vidPid} has no parsed interface lines; a classifier fixture with no interfaces classifies nothing`);
99
+ }
100
+ const [vendorId, productId] = sku.vidPid.split(':');
101
+ const props = bundle.usb.udevProperties;
102
+ const physicalUid = props.ID_PATH;
103
+ const ifname = props.INTERFACE;
104
+ return {
105
+ ok: true,
106
+ value: {
107
+ snapshot: {
108
+ vendorId,
109
+ productId,
110
+ model: sku.model,
111
+ firmwareRevision: sku.firmwarePrefix,
112
+ bDeviceClass: device.bDeviceClass,
113
+ interfaces: device.interfaces,
114
+ udevProperties: props,
115
+ ...(physicalUid !== undefined ? { physicalUid } : {}),
116
+ ...(ifname !== undefined ? { ifname } : {}),
117
+ },
118
+ provenance: {
119
+ bundleSha256: request.bundleSha256,
120
+ synthetic: bundle.synthetic,
121
+ slot: bundle.slot,
122
+ capturedAtMs: bundle.capturedAtMs,
123
+ },
124
+ },
125
+ };
126
+ }
127
+ /**
128
+ * Build a CANDIDATE catalog entry from a bundle. The entry is a review artifact: it is
129
+ * returned, never written.
130
+ *
131
+ * REFUSES a `synthetic: true` bundle — a catalog entry asserts a certified hardware
132
+ * fact, so synthetic evidence can never back one (`docs/BENCH.md` Must-NOT-Have 7).
133
+ *
134
+ * A stage-1 (base) bundle yields `permittedTransitions: []`. A stage-2 bundle — one
135
+ * captured with `certify --transition` — yields exactly ONE permitted transition, whose
136
+ * `expectedDescriptors` is the captured `afterDescriptors` and whose
137
+ * `evidenceBundleSha256` is this bundle's hash. The reviewer's stated `canonicalMode`
138
+ * must equal the captured `transition.from`; a mismatch is refused rather than silently
139
+ * resolved in either direction.
140
+ */
141
+ export function buildCatalogEntryCandidate(request, claim) {
142
+ const parsed = parseIngestionRequest(request);
143
+ if (!parsed.ok) {
144
+ return parsed;
145
+ }
146
+ const bundle = parsed.value;
147
+ if (bundle.synthetic) {
148
+ return refuse('synthetic-bundle', `bundle for slot '${bundle.slot}' is marked synthetic:true; a catalog entry requires a real capture (synthetic:false)`);
149
+ }
150
+ const sku = bundle.sku;
151
+ if (sku === undefined) {
152
+ return refuse('sku-missing', 'bundle carries no `sku` — a catalog entry needs all three discriminators (vidPid, model, firmwarePrefix)');
153
+ }
154
+ const transition = bundle.transition;
155
+ if (transition !== undefined && transition.from !== claim.canonicalMode) {
156
+ return refuse('transition-mode-mismatch', `claimed canonicalMode '${claim.canonicalMode}' contradicts the captured transition.from '${transition.from}'`);
157
+ }
158
+ const candidate = {
159
+ vidPid: sku.vidPid,
160
+ model: sku.model,
161
+ firmwarePrefix: sku.firmwarePrefix,
162
+ canonicalMode: claim.canonicalMode,
163
+ permittedTransitions: transition === undefined
164
+ ? []
165
+ : [
166
+ {
167
+ from: transition.from,
168
+ to: transition.to,
169
+ atCommand: transition.atCommand,
170
+ expectedResponse: transition.expectedResponse,
171
+ expectsPortDrop: transition.expectsPortDrop,
172
+ expectedDescriptors: transition.afterDescriptors,
173
+ evidenceBundleSha256: request.bundleSha256,
174
+ },
175
+ ],
176
+ };
177
+ // The candidate is re-validated through the AUTHORITATIVE entry schema, so an
178
+ // impossible combination (a router-mode SKU declaring a transition, say) is refused
179
+ // here rather than at review time.
180
+ const entry = catalogEntrySchema.safeParse(candidate);
181
+ if (!entry.success) {
182
+ return refuse('entry-schema-invalid', z.prettifyError(entry.error));
183
+ }
184
+ return { ok: true, value: entry.data };
185
+ }
186
+ /** The canonical-mode vocabulary a reviewer's claim may use — re-exported for callers. */
187
+ export const CLAIMABLE_CANONICAL_MODES = CANONICAL_USB_MODES;
@@ -0,0 +1,21 @@
1
+ import type { CatalogEntry } from './catalog-schema.js';
2
+ import type { ClassifierFixture, IngestionOutcome } from './ingestion.js';
3
+ /** Everything the rendered comment needs beyond the two ingestion outcomes. */
4
+ export interface PromotionContext {
5
+ /** The runbook that captured the bundle (`RB-11` …) — the evidence's provenance. */
6
+ readonly runbook: string;
7
+ /** The repo-local evidence path the bundle was written to. */
8
+ readonly evidencePath: string;
9
+ }
10
+ /** A promotion request: the two ingestion outcomes plus where the evidence came from. */
11
+ export interface PromotionRequest {
12
+ readonly context: PromotionContext;
13
+ readonly entry: IngestionOutcome<CatalogEntry>;
14
+ readonly fixture: IngestionOutcome<ClassifierFixture>;
15
+ }
16
+ /**
17
+ * Render the review comment for a promotion request. Always returns a comment: a
18
+ * refusal renders a refusal block, so a run that produced nothing promotable still
19
+ * leaves a visible, auditable trace.
20
+ */
21
+ export declare function renderPromotionReview(request: PromotionRequest): string;
@@ -0,0 +1,87 @@
1
+ // Rendering the REVIEW ARTIFACT for a catalog promotion — the PR-comment template.
2
+ //
3
+ // Catalog additions are human-reviewed commits (Phase-A rule). This module renders what
4
+ // a reviewer reads: the proposed entry, the classifier fixture derived from the same
5
+ // bundle, and a checklist whose boxes a machine cannot tick. It renders a REFUSAL with
6
+ // equal prominence — a refused promotion produces a comment that says so, never silence,
7
+ // because a silently-absent comment is indistinguishable from a forgotten run.
8
+ //
9
+ // Nothing here writes a file or opens a PR. The output is text.
10
+ function refusalBlock(what, refusal) {
11
+ return [
12
+ `### ❌ ${what} — REFUSED`,
13
+ '',
14
+ `**Reason:** \`${refusal.reason}\``,
15
+ '',
16
+ `> ${refusal.detail}`,
17
+ '',
18
+ 'This is a typed refusal from the ingestion seam, not a review opinion. Fix the',
19
+ 'capture and re-run the runbook; do not hand-author the artifact around it.',
20
+ ].join('\n');
21
+ }
22
+ function entryBlock(entry) {
23
+ return [
24
+ '### Proposed `certified-catalog.json` entry',
25
+ '',
26
+ '```json',
27
+ JSON.stringify(entry, null, 2),
28
+ '```',
29
+ ].join('\n');
30
+ }
31
+ function fixtureBlock(fixture) {
32
+ const { snapshot, provenance } = fixture;
33
+ const syntheticNote = provenance.synthetic
34
+ ? '> ⚠️ Derived from a **synthetic** bundle — valid as test data, never as certification evidence.'
35
+ : `> Derived from a real capture, bundle sha256 \`${provenance.bundleSha256}\`.`;
36
+ return [
37
+ '### Proposed classifier fixture (`control/src/backend/device-classifier.test.ts`)',
38
+ '',
39
+ syntheticNote,
40
+ '',
41
+ '```ts',
42
+ `const FIXTURE: UsbDeviceSnapshot = ${JSON.stringify(snapshot, null, 2)};`,
43
+ '```',
44
+ ].join('\n');
45
+ }
46
+ function checklistBlock(context, entry) {
47
+ const transitions = entry.permittedTransitions.length;
48
+ return [
49
+ '### Reviewer checklist (every box is a human judgement)',
50
+ '',
51
+ `- [ ] The bundle at \`${context.evidencePath}\` was captured by **${context.runbook}** on real hardware, and its \`CERTIFY OK\` line reads \`synthetic=false\`.`,
52
+ '- [ ] The bundle sha256 in the entry matches the sha256 the capture printed — recomputed, not copied from this comment.',
53
+ `- [ ] \`canonicalMode: "${entry.canonicalMode}"\` is the mode the device was actually observed in, not the mode it was expected to be in.`,
54
+ transitions === 0
55
+ ? '- [ ] `permittedTransitions: []` is correct for this stage — a stage-1 entry never declares a transition.'
56
+ : '- [ ] The declared transition was OBSERVED end to end: the AT command executed, the port dropped if `expectsPortDrop`, and the device re-enumerated presenting `expectedDescriptors`.',
57
+ '- [ ] No claim in `docs/MODEM-SUPPORT-MATRIX.md` is being changed by this commit without its own evidence.',
58
+ ].join('\n');
59
+ }
60
+ /**
61
+ * Render the review comment for a promotion request. Always returns a comment: a
62
+ * refusal renders a refusal block, so a run that produced nothing promotable still
63
+ * leaves a visible, auditable trace.
64
+ */
65
+ export function renderPromotionReview(request) {
66
+ const { context, entry, fixture } = request;
67
+ const parts = [
68
+ `## Catalog promotion review — ${context.runbook}`,
69
+ '',
70
+ `Evidence: \`${context.evidencePath}\``,
71
+ '',
72
+ 'Generated by the `control/src/usb-mode/` ingestion seam. **This comment promotes',
73
+ 'nothing** — the promotion is the human-reviewed commit that follows it.',
74
+ '',
75
+ ];
76
+ parts.push(entry.ok ? entryBlock(entry.value) : refusalBlock('Catalog entry', entry));
77
+ parts.push('');
78
+ parts.push(fixture.ok ? fixtureBlock(fixture.value) : refusalBlock('Classifier fixture', fixture));
79
+ parts.push('');
80
+ if (entry.ok) {
81
+ parts.push(checklistBlock(context, entry.value));
82
+ }
83
+ else {
84
+ parts.push('### No checklist', '', 'The catalog entry was refused, so there is nothing to review. A checklist here', 'would invite a reviewer to approve an artifact that does not exist.');
85
+ }
86
+ return `${parts.join('\n')}\n`;
87
+ }
@@ -0,0 +1,59 @@
1
+ export declare const RUNTIME_COMPOSITION_VENDORS: readonly ['fibocom', 'quectel', 'simcom', 'sierra'];
2
+ export type RuntimeCompositionVendor = (typeof RUNTIME_COMPOSITION_VENDORS)[number];
3
+ export type RuntimeCompositionMode = number | string;
4
+ export type RuntimeCompositionQuery = {
5
+ readonly current: string;
6
+ readonly enumerate: string;
7
+ };
8
+ export type RuntimeCompositionSetCommand = (target: RuntimeCompositionMode) => string | undefined;
9
+ /** Commands only: selecting a port, sending, deadlines, and retries belong to the provider. */
10
+ export declare const RUNTIME_COMPOSITION_QUERY_REGISTRY: Readonly<{
11
+ fibocom: Readonly<{
12
+ current: "AT+GTUSBMODE?";
13
+ enumerate: "AT+GTUSBMODE=?";
14
+ }>;
15
+ quectel: Readonly<{
16
+ current: "AT+QCFG=\"usbnet\"";
17
+ enumerate: "AT+QCFG=?";
18
+ }>;
19
+ simcom: Readonly<{
20
+ current: "AT+CUSBPIDSWITCH?";
21
+ enumerate: "AT+CUSBPIDSWITCH=?";
22
+ }>;
23
+ sierra: Readonly<{
24
+ current: "AT!USBCOMP?";
25
+ enumerate: "AT!USBCOMP=?";
26
+ }>;
27
+ }>;
28
+ /** Exact reviewed SET forms. Callers still allowlist only the one enumerated target selected. */
29
+ export declare const RUNTIME_COMPOSITION_SET_REGISTRY: Readonly<{
30
+ fibocom: RuntimeCompositionSetCommand;
31
+ quectel: RuntimeCompositionSetCommand;
32
+ simcom: (target: RuntimeCompositionMode) => string | undefined;
33
+ sierra: RuntimeCompositionSetCommand;
34
+ }>;
35
+ export type RuntimeCompositionCapability = {
36
+ readonly status: 'available';
37
+ readonly current: RuntimeCompositionMode;
38
+ readonly enumerated: readonly RuntimeCompositionMode[];
39
+ readonly returnPathProven: boolean;
40
+ readonly offerable: readonly RuntimeCompositionMode[];
41
+ } | {
42
+ readonly status: 'unknown';
43
+ readonly current: null;
44
+ readonly enumerated: readonly [];
45
+ readonly returnPathProven: false;
46
+ readonly offerable: readonly [];
47
+ readonly reason: 'vendor-unsupported' | 'malformed-response';
48
+ };
49
+ export type RuntimeCompositionResponse = {
50
+ readonly vendor: string;
51
+ readonly currentResponse: string;
52
+ readonly enumerationResponse: string;
53
+ };
54
+ export declare function isRuntimeCompositionVendor(vendor: string): vendor is RuntimeCompositionVendor;
55
+ export declare function buildRuntimeCompositionSetCommand(vendor: string, target: RuntimeCompositionMode): string | undefined;
56
+ /** Parse only the vendor READ response used by the weaker post-switch proof tier. */
57
+ export declare function readRuntimeCompositionCurrent(vendor: string, response: string): RuntimeCompositionMode | undefined;
58
+ /** Derive controls exclusively from the device's current and enumerated response text. */
59
+ export declare function resolveRuntimeCompositionCapability(input: RuntimeCompositionResponse): RuntimeCompositionCapability;