@ceralive/modem-control 0.2.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +76 -0
- package/dist/backend/transition-preconditions.js +64 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +196 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +26 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +47 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +39 -0
- package/dist/providers/modem-manager/provider.js +152 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +135 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +163 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +78 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +151 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +5 -0
- package/dist/usb-mode/index.js +15 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +84 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -221
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -27
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
import { z } from 'zod';
|
|
18
|
+
/**
|
|
19
|
+
* The full canonical USB composition-mode vocabulary. A SKU's `canonicalMode` may be
|
|
20
|
+
* any of these; only the first three are ModemManager-manageable. (Structurally
|
|
21
|
+
* identical to `PreferredUsbMode` in the power contract — A4.2 owns this vocabulary.)
|
|
22
|
+
*/
|
|
23
|
+
export const CANONICAL_USB_MODES = ['qmi', 'mbim', 'ecm-ncm', 'rndis', 'router-ethernet'];
|
|
24
|
+
/**
|
|
25
|
+
* The ModemManager-manageable modes — the ONLY modes a transition may move between.
|
|
26
|
+
* `rndis` and `router-ethernet` are deliberately absent: a device in either is not
|
|
27
|
+
* MM-managed, so switching to/from them crosses the MM↔router line the schema forbids.
|
|
28
|
+
*/
|
|
29
|
+
export const MM_USB_MODES = ['qmi', 'mbim', 'ecm-ncm'];
|
|
30
|
+
const canonicalMode = z.enum(CANONICAL_USB_MODES);
|
|
31
|
+
const mmMode = z.enum(MM_USB_MODES);
|
|
32
|
+
/** A `bInterfaceClass`/`bInterfaceSubClass`/`bInterfaceProtocol` byte triple. */
|
|
33
|
+
const usbByte = z.number().int().min(0).max(255);
|
|
34
|
+
/** The USB descriptors a device must present AFTER a transition — the postcondition. */
|
|
35
|
+
export const expectedDescriptorsSchema = z.strictObject({
|
|
36
|
+
deviceClass: usbByte,
|
|
37
|
+
interfaces: z
|
|
38
|
+
.array(z.strictObject({
|
|
39
|
+
interfaceClass: usbByte,
|
|
40
|
+
interfaceSubClass: usbByte,
|
|
41
|
+
interfaceProtocol: usbByte,
|
|
42
|
+
}))
|
|
43
|
+
.min(1),
|
|
44
|
+
});
|
|
45
|
+
/** One permitted, certified transition between two MM modes. */
|
|
46
|
+
export const permittedTransitionSchema = z
|
|
47
|
+
.strictObject({
|
|
48
|
+
from: mmMode,
|
|
49
|
+
to: mmMode,
|
|
50
|
+
/** The EXACT AT command that performs the switch (allowlisted at send time). */
|
|
51
|
+
atCommand: z.string().min(1),
|
|
52
|
+
/**
|
|
53
|
+
* The EXACT AT command that COMMITS the switch on a SKU whose `atCommand` only
|
|
54
|
+
* writes non-volatile configuration; omitted for a SKU that re-enumerates on
|
|
55
|
+
* `atCommand` alone. Sent through the SAME allowlisted lease.
|
|
56
|
+
*
|
|
57
|
+
* Declaring it is a per-SKU hardware FACT, not a retry. Measured on the bench
|
|
58
|
+
* RM530N-GL: `AT+QCFG="usbnet",<n>` answers `OK`, reads back the new value, and
|
|
59
|
+
* leaves the device in the OLD composition for the whole port-drop budget — having
|
|
60
|
+
* already committed to NV. Undeclared, such a SKU fails on a timeout AFTER it was
|
|
61
|
+
* silently changed, then lands in the new composition at the next unrelated reboot.
|
|
62
|
+
*/
|
|
63
|
+
applyCommand: z.string().min(1).optional(),
|
|
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
|
+
/** One certified SKU: its discriminator, canonical mode, and permitted transitions. */
|
|
78
|
+
export const catalogEntrySchema = z
|
|
79
|
+
.strictObject({
|
|
80
|
+
/** VID:PID, lowercase hex, `xxxx:xxxx`. */
|
|
81
|
+
vidPid: z.string().regex(/^[0-9a-f]{4}:[0-9a-f]{4}$/),
|
|
82
|
+
/** The server-derived model string (a discriminator, not free text). */
|
|
83
|
+
model: z.string().min(1),
|
|
84
|
+
/** The firmware-revision prefix that discriminates this personality. */
|
|
85
|
+
firmwarePrefix: z.string().min(1),
|
|
86
|
+
/** The mode this SKU speaks as certified. */
|
|
87
|
+
canonicalMode,
|
|
88
|
+
/** The certified transitions — empty for a non-MM (router/rndis) SKU. */
|
|
89
|
+
permittedTransitions: z.array(permittedTransitionSchema),
|
|
90
|
+
})
|
|
91
|
+
.refine((e) => e.permittedTransitions.length === 0 ||
|
|
92
|
+
MM_USB_MODES.includes(e.canonicalMode), { message: 'only an MM-mode SKU (qmi/mbim/ecm-ncm) may declare permitted transitions' });
|
|
93
|
+
/** The whole certified catalog file. */
|
|
94
|
+
export const certifiedCatalogSchema = z.strictObject({
|
|
95
|
+
schemaVersion: z.literal(1),
|
|
96
|
+
entries: z.array(catalogEntrySchema),
|
|
97
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CatalogEntry, type CertifiedCatalog, type MmUsbMode, type PermittedTransition, type SkuDiscriminator } from './catalog-schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse and validate an untrusted value as a certified catalog. Throws a `ZodError`
|
|
4
|
+
* with a precise path if the value violates the schema (unknown field, an MM↔router
|
|
5
|
+
* transition, a bad VID:PID, etc.).
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadCertifiedCatalog(value: unknown): CertifiedCatalog;
|
|
8
|
+
/** The catalog shipped in this package, validated at module load. */
|
|
9
|
+
export declare const CERTIFIED_CATALOG: CertifiedCatalog;
|
|
10
|
+
/**
|
|
11
|
+
* Find the catalog entry matching a live device's SKU discriminator. All three
|
|
12
|
+
* discriminators (VID:PID, model, firmware prefix) must match — a partial match is
|
|
13
|
+
* NOT a certified device. Returns `undefined` for an uncertified SKU (never a guess).
|
|
14
|
+
*/
|
|
15
|
+
export declare function findCatalogEntry(catalog: CertifiedCatalog, sku: SkuDiscriminator): CatalogEntry | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Find the permitted transition `from → to` in a catalog entry. Returns `undefined`
|
|
18
|
+
* when the entry declares no such transition — the caller MUST treat that as "not
|
|
19
|
+
* permitted", never as "permitted with no command".
|
|
20
|
+
*/
|
|
21
|
+
export declare function findPermittedTransition(entry: CatalogEntry, from: MmUsbMode, to: MmUsbMode): PermittedTransition | undefined;
|
|
@@ -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
|
|
26
|
-
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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,5 @@
|
|
|
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 { type ParsedUsbDevice, type ParsedUsbInterface, parseUsbDevices, selectUniqueDevice, } from './usb-devices-parse.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
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 { 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
|
+
qmi: "qmi";
|
|
22
|
+
mbim: "mbim";
|
|
23
|
+
"ecm-ncm": "ecm-ncm";
|
|
24
|
+
}>;
|
|
25
|
+
to: z.ZodEnum<{
|
|
26
|
+
qmi: "qmi";
|
|
27
|
+
mbim: "mbim";
|
|
28
|
+
"ecm-ncm": "ecm-ncm";
|
|
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;
|