@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.
- package/README.md +341 -0
- package/dist/backend/at-lease.d.ts +59 -0
- package/dist/backend/at-lease.js +118 -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 +101 -0
- package/dist/backend/transition-preconditions.js +132 -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 +216 -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 +20 -0
- package/dist/index.js +27 -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 +61 -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/operation-ids.d.ts +2 -0
- package/dist/operation-ids.js +26 -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 +10 -0
- package/dist/providers/modem-manager/generic-operations.js +209 -0
- package/dist/providers/modem-manager/index.d.ts +4 -0
- package/dist/providers/modem-manager/index.js +4 -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 +41 -0
- package/dist/providers/modem-manager/provider.js +155 -0
- package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
- package/dist/providers/modem-manager/runtime-composition-operation.js +151 -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 +137 -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 +171 -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 +56 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +23 -0
- package/dist/providers/zte-goform/session.js +197 -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 +88 -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 +138 -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 +6 -0
- package/dist/usb-mode/index.js +16 -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/runtime-capability.d.ts +59 -0
- package/dist/usb-mode/runtime-capability.js +157 -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 +106 -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 -236
- 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/uhubctl-power-hook.test.ts +0 -274
- package/src/backend/uhubctl-power-hook.ts +0 -377
- 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 -60
- package/src/backend/usage/policy-store.test.ts +0 -164
- package/src/backend/usage/policy-store.ts +0 -216
- package/src/backend/usage/policy-write.test.ts +0 -198
- package/src/backend/usage/policy-write.ts +0 -207
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -327
- package/src/backend/usage/sampler.ts +0 -282
- 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 -58
- package/src/usb-mode/ingestion.test.ts +0 -268
- package/src/usb-mode/ingestion.ts +0 -297
- package/src/usb-mode/promotion-review.ts +0 -117
- package/src/usb-mode/usb-devices-parse.ts +0 -196
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// The certified USB-mode transition transaction.
|
|
2
|
+
//
|
|
3
|
+
// A mode switch is destructive: the modem physically re-enumerates, its interface
|
|
4
|
+
// name changes, and MM briefly loses sight of it. This transaction runs the switch
|
|
5
|
+
// through A3.3's shared per-modem `ModemActor` (keyed on stableKey, so it serialises
|
|
6
|
+
// behind every other disruptive op) in a FIXED order:
|
|
7
|
+
//
|
|
8
|
+
// 1 NM-quiesce → 2 inhibit-by-cached-UID → 3 AT command → 3b catalog `applyCommand`
|
|
9
|
+
// (only for a SKU whose AT command writes NV without re-enumerating) → 4 expected
|
|
10
|
+
// port-drop → 5 uninhibit → 6 await SAME physical UID → 7 POSTCONDITION →
|
|
11
|
+
// 8 resolve new ifname → 9 reactivate (uuid, newIfname) → 10 release interlock
|
|
12
|
+
// (finally, always).
|
|
13
|
+
//
|
|
14
|
+
// THE POSTCONDITION IS THE ONLY PROOF OF SUCCESS. An AT `OK` proves nothing. Tier 1 is
|
|
15
|
+
// the strongest proof and remains unchanged: a reviewed catalog transition must match
|
|
16
|
+
// both descriptors and canonical mode. Tier 2 exists only when no reviewed transition
|
|
17
|
+
// matches: the re-enumerated device must report the raw target through its own vendor READ.
|
|
18
|
+
// Tier 2 is explicitly weaker because it proves reported mode, not descriptor composition.
|
|
19
|
+
// On a postcondition MISMATCH the whole transaction fails `degraded`, does NOT
|
|
20
|
+
// reactivate, and still releases the interlock via `finally`. A hung command trips the
|
|
21
|
+
// AT watchdog, which force-uninhibits so the system reprobes rather than wedging.
|
|
22
|
+
import { deviceIfname } from '../ports/index.js';
|
|
23
|
+
import { CERTIFIED_CATALOG, readRuntimeCompositionCurrent, } from '../usb-mode/index.js';
|
|
24
|
+
import { AtCommandLease, computeAtAllowlist, } from './at-lease.js';
|
|
25
|
+
import { descriptorsMatch, detectUsbMode } from './device-classifier.js';
|
|
26
|
+
import { ALLOW_ALL_TRANSITION_INTERLOCK, checkTransitionPreconditions, } from './transition-preconditions.js';
|
|
27
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
+
const DEFAULT_WATCHDOG_MS = 30_000;
|
|
29
|
+
const DEFAULT_REENUM_TIMEOUT_MS = 60_000;
|
|
30
|
+
const DEFAULT_POLL_INTERVAL_MS = 250;
|
|
31
|
+
function defaultResolveIfname(device) {
|
|
32
|
+
return device.ifname !== undefined && device.ifname !== ''
|
|
33
|
+
? deviceIfname(device.ifname)
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
/** The USB-mode transition transaction. One instance is reusable across requests. */
|
|
37
|
+
export class UsbModeTransition {
|
|
38
|
+
#actor;
|
|
39
|
+
#nm;
|
|
40
|
+
#modemManager;
|
|
41
|
+
#atSender;
|
|
42
|
+
#enumerate;
|
|
43
|
+
#interlock;
|
|
44
|
+
#catalog;
|
|
45
|
+
#audit;
|
|
46
|
+
#resolveIfname;
|
|
47
|
+
#watchdogMs;
|
|
48
|
+
#reenumMs;
|
|
49
|
+
#pollMs;
|
|
50
|
+
constructor(deps) {
|
|
51
|
+
this.#actor = deps.actor;
|
|
52
|
+
this.#nm = deps.nm;
|
|
53
|
+
this.#modemManager = deps.modemManager;
|
|
54
|
+
this.#atSender = deps.atSender;
|
|
55
|
+
this.#enumerate = deps.enumerate;
|
|
56
|
+
this.#interlock = deps.interlock ?? ALLOW_ALL_TRANSITION_INTERLOCK;
|
|
57
|
+
this.#catalog = deps.catalog ?? CERTIFIED_CATALOG;
|
|
58
|
+
this.#audit = deps.audit;
|
|
59
|
+
this.#resolveIfname = deps.resolveIfname ?? defaultResolveIfname;
|
|
60
|
+
this.#watchdogMs = deps.watchdogMs ?? DEFAULT_WATCHDOG_MS;
|
|
61
|
+
this.#reenumMs = deps.reenumerationTimeoutMs ?? DEFAULT_REENUM_TIMEOUT_MS;
|
|
62
|
+
this.#pollMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
63
|
+
}
|
|
64
|
+
/** Run one transition. Preconditions are checked at entry, then again in-actor. */
|
|
65
|
+
async execute(request) {
|
|
66
|
+
const steps = [];
|
|
67
|
+
// ENTRY check — a doomed request NEVER enters the actor (TIER A: zero calls).
|
|
68
|
+
const entry = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
|
|
69
|
+
if (!entry.ok) {
|
|
70
|
+
return { status: 'refused', stage: 'entry', reason: entry.reason, steps };
|
|
71
|
+
}
|
|
72
|
+
return this.#actor.run(request.stableKey, () => this.#inActor(request, steps));
|
|
73
|
+
}
|
|
74
|
+
async #inActor(request, steps) {
|
|
75
|
+
steps.push('actor-enter');
|
|
76
|
+
// IN-ACTOR re-check — catches a race that closed a gate while queued (TIER B).
|
|
77
|
+
const recheck = await checkTransitionPreconditions(request, this.#catalog, this.#interlock);
|
|
78
|
+
if (!recheck.ok) {
|
|
79
|
+
return { status: 'refused', stage: 'in-actor', reason: recheck.reason, steps };
|
|
80
|
+
}
|
|
81
|
+
const hold = await this.#interlock.hold({ stableKey: request.stableKey });
|
|
82
|
+
try {
|
|
83
|
+
return await this.#runTransaction(request, recheck.allowlistedCommands, recheck.plan, steps);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
steps.push('release-interlock');
|
|
87
|
+
await hold.release().catch(() => undefined);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async #runTransaction(request, allowlistedCommands, plan, steps) {
|
|
91
|
+
let inhibit;
|
|
92
|
+
let reactivated = false;
|
|
93
|
+
const forceUninhibit = async () => {
|
|
94
|
+
if (inhibit === undefined) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const held = inhibit;
|
|
98
|
+
inhibit = undefined;
|
|
99
|
+
steps.push('force-uninhibit');
|
|
100
|
+
await this.#modemManager.uninhibit(held).catch(() => undefined);
|
|
101
|
+
};
|
|
102
|
+
const lease = new AtCommandLease({
|
|
103
|
+
sender: this.#atSender,
|
|
104
|
+
allowlist: computeAtAllowlist(allowlistedCommands),
|
|
105
|
+
timeoutMs: this.#watchdogMs,
|
|
106
|
+
onWatchdog: forceUninhibit,
|
|
107
|
+
...(this.#audit !== undefined ? { audit: this.#audit } : {}),
|
|
108
|
+
});
|
|
109
|
+
steps.push('nm-quiesce');
|
|
110
|
+
const quiesce = await this.#nm.acquireQuiesceLease(request.connectionId, request.deviceIfname);
|
|
111
|
+
try {
|
|
112
|
+
steps.push('inhibit');
|
|
113
|
+
inhibit = await this.#modemManager.inhibit(request.inhibitUid);
|
|
114
|
+
// AT `OK` is IGNORED for success — only the postcondition below decides.
|
|
115
|
+
steps.push('at-command');
|
|
116
|
+
await lease.run(plan.atCommand, { inhibitUid: request.inhibitUid });
|
|
117
|
+
if (plan.applyCommand !== undefined) {
|
|
118
|
+
steps.push('apply-command');
|
|
119
|
+
await lease.run(plan.applyCommand, { inhibitUid: request.inhibitUid });
|
|
120
|
+
}
|
|
121
|
+
steps.push('await-port-drop');
|
|
122
|
+
await this.#awaitPortDrop(request.cachedPhysicalUid);
|
|
123
|
+
steps.push('uninhibit');
|
|
124
|
+
if (inhibit !== undefined) {
|
|
125
|
+
const held = inhibit;
|
|
126
|
+
inhibit = undefined;
|
|
127
|
+
await this.#modemManager.uninhibit(held);
|
|
128
|
+
}
|
|
129
|
+
steps.push('await-reenumeration');
|
|
130
|
+
const device = await this.#awaitReenumeration(request.cachedPhysicalUid);
|
|
131
|
+
steps.push('postcondition');
|
|
132
|
+
if (plan.proof.tier === 'catalog-descriptors') {
|
|
133
|
+
const observedMode = detectUsbMode(device);
|
|
134
|
+
const descriptorsOk = descriptorsMatch(device, plan.proof.transition.expectedDescriptors);
|
|
135
|
+
if (observedMode !== plan.proof.transition.to || !descriptorsOk) {
|
|
136
|
+
return {
|
|
137
|
+
status: 'failed',
|
|
138
|
+
degraded: true,
|
|
139
|
+
reason: `postcondition mismatch: observed ${observedMode ?? 'unknown'} vs target ${plan.proof.transition.to}; descriptors ${descriptorsOk ? 'ok' : 'mismatch'}`,
|
|
140
|
+
steps,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
steps.push('postcondition-runtime-read');
|
|
146
|
+
const response = await lease.run(plan.proof.currentQuery, {
|
|
147
|
+
inhibitUid: request.inhibitUid,
|
|
148
|
+
});
|
|
149
|
+
const observed = readRuntimeCompositionCurrent(plan.proof.vendor, response.raw);
|
|
150
|
+
if (!Object.is(observed, plan.proof.target)) {
|
|
151
|
+
return {
|
|
152
|
+
status: 'failed',
|
|
153
|
+
degraded: true,
|
|
154
|
+
reason: `runtime readback mismatch: observed ${observed ?? 'unknown'} vs target ${plan.proof.target}`,
|
|
155
|
+
steps,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
steps.push('resolve-ifname');
|
|
160
|
+
const newIfname = this.#resolveIfname(device);
|
|
161
|
+
if (newIfname === undefined) {
|
|
162
|
+
return { status: 'failed', degraded: true, reason: 'could not resolve new ifname', steps };
|
|
163
|
+
}
|
|
164
|
+
steps.push('reactivate');
|
|
165
|
+
await this.#nm.activate(request.connectionId, newIfname);
|
|
166
|
+
reactivated = true;
|
|
167
|
+
return { status: 'succeeded', newIfname, steps };
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
await forceUninhibit();
|
|
171
|
+
await this.#reprobe();
|
|
172
|
+
return {
|
|
173
|
+
status: 'failed',
|
|
174
|
+
degraded: true,
|
|
175
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
176
|
+
steps,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await forceUninhibit();
|
|
181
|
+
if (!reactivated) {
|
|
182
|
+
// Failure path: restore the old connection. On success the new-ifname
|
|
183
|
+
// activation supersedes the quiesce lease (old ifname is gone).
|
|
184
|
+
steps.push('release-quiesce');
|
|
185
|
+
await this.#nm.releaseQuiesceLease(quiesce).catch(() => undefined);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async #awaitPortDrop(uid) {
|
|
190
|
+
const deadline = Date.now() + this.#reenumMs;
|
|
191
|
+
while (Date.now() < deadline) {
|
|
192
|
+
const devices = await this.#enumerate();
|
|
193
|
+
if (!devices.some((d) => d.physicalUid === uid)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
await sleep(this.#pollMs);
|
|
197
|
+
}
|
|
198
|
+
throw new Error(`control port did not drop within ${this.#reenumMs}ms (uid ${uid})`);
|
|
199
|
+
}
|
|
200
|
+
async #awaitReenumeration(uid) {
|
|
201
|
+
const deadline = Date.now() + this.#reenumMs;
|
|
202
|
+
while (Date.now() < deadline) {
|
|
203
|
+
const devices = await this.#enumerate();
|
|
204
|
+
const device = devices.find((d) => d.physicalUid === uid);
|
|
205
|
+
if (device !== undefined) {
|
|
206
|
+
return device;
|
|
207
|
+
}
|
|
208
|
+
await sleep(this.#pollMs);
|
|
209
|
+
}
|
|
210
|
+
throw new Error(`device did not re-enumerate within ${this.#reenumMs}ms (uid ${uid})`);
|
|
211
|
+
}
|
|
212
|
+
/** Best-effort state re-read after a crash — the transaction still fails degraded. */
|
|
213
|
+
async #reprobe() {
|
|
214
|
+
await this.#enumerate().then(() => undefined, () => undefined);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** A band as every operator-facing surface spells it. Opaque; compare by equality. */
|
|
2
|
+
export type BandName = string;
|
|
3
|
+
/** `MM_MODEM_BAND_UNKNOWN`. Never a member of a supported/current set we act on. */
|
|
4
|
+
export declare const BAND_UNKNOWN = "unknown";
|
|
5
|
+
/**
|
|
6
|
+
* `MM_MODEM_BAND_ANY` (256) — "let the modem choose", i.e. the reset value.
|
|
7
|
+
* Setting exactly this is how a band lock is released; there is no separate
|
|
8
|
+
* ModemManager verb for it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const BAND_ANY = "any";
|
|
11
|
+
/** Decode one `MMModemBand` value. Total: an unknown value round-trips. */
|
|
12
|
+
export declare function bandName(value: number): BandName;
|
|
13
|
+
/** Encode one band name. `undefined` for a name this build cannot place. */
|
|
14
|
+
export declare function bandValue(name: BandName): number | undefined;
|
|
15
|
+
/** True when this build recognises the name (a `band-<n>` passthrough does not). */
|
|
16
|
+
export declare function isNamedBand(name: BandName): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Decode a `SupportedBands` / `CurrentBands` property value.
|
|
19
|
+
*
|
|
20
|
+
* Non-numeric members are DROPPED rather than coerced — a malformed member says
|
|
21
|
+
* nothing about the radio, and coercing it would invent a band. `unknown` is
|
|
22
|
+
* dropped for the same reason: MM emits it for "the modem did not say", which is
|
|
23
|
+
* not a band an operator can select.
|
|
24
|
+
*/
|
|
25
|
+
export declare function decodeBandList(value: unknown): readonly BandName[];
|
|
26
|
+
/**
|
|
27
|
+
* Encode a band selection for `SetCurrentBands`.
|
|
28
|
+
*
|
|
29
|
+
* FAILS CLOSED AS A WHOLE. One unplaceable name rejects the entire request
|
|
30
|
+
* rather than silently narrowing the selection: a partial band set is a
|
|
31
|
+
* DIFFERENT lock from the one the operator asked for, and applying it would
|
|
32
|
+
* strand the radio on bands they never chose.
|
|
33
|
+
*/
|
|
34
|
+
export declare function encodeBandList(names: readonly BandName[]): {
|
|
35
|
+
readonly ok: true;
|
|
36
|
+
readonly values: number[];
|
|
37
|
+
} | {
|
|
38
|
+
readonly ok: false;
|
|
39
|
+
readonly unknown: BandName;
|
|
40
|
+
};
|
|
41
|
+
/** True when the selection is exactly the reset value (`any`, alone). */
|
|
42
|
+
export declare function isResetSelection(names: readonly BandName[]): boolean;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// MMModemBand ↔ band NAME, in both directions.
|
|
2
|
+
//
|
|
3
|
+
// The D-Bus surface speaks numbers: `Modem.SupportedBands` and
|
|
4
|
+
// `Modem.CurrentBands` are `au`, and `Modem.SetCurrentBands` takes `au`. Every
|
|
5
|
+
// operator-facing surface — mmcli's `--set-current-bands`, this package's API,
|
|
6
|
+
// CeraUI's selector — speaks the NAME (`eutran-3`, `utran-1`, `egsm`, `any`).
|
|
7
|
+
// One mapping, here, so the two can never disagree.
|
|
8
|
+
//
|
|
9
|
+
// WHERE THE NUMBERS COME FROM. ModemManager's `MMModemBand` (`mm-enums.h`) is
|
|
10
|
+
// two different things stitched together, and this module reproduces exactly
|
|
11
|
+
// that shape rather than flattening it into one big table:
|
|
12
|
+
//
|
|
13
|
+
// * the GSM/UTRAN block (1..20) is IRREGULAR — `UTRAN_2` is 12 while `UTRAN_6`
|
|
14
|
+
// is 8 — because MM appended bands in the order they were needed, so it is
|
|
15
|
+
// an explicit table and can only ever be an explicit table;
|
|
16
|
+
// * every later block is ARITHMETIC, by MM's own construction: `EUTRAN_n` is
|
|
17
|
+
// `30 + n`, `CDMA_BCn` is `128 + n`, `NGRAN_n` is `300 + n`.
|
|
18
|
+
//
|
|
19
|
+
// Deriving the arithmetic blocks rather than transcribing ~350 constants is the
|
|
20
|
+
// point: a transcription is where a wrong band number would hide, and a wrong
|
|
21
|
+
// band number sets a radio to a band the SIM's network does not operate on.
|
|
22
|
+
//
|
|
23
|
+
// A value this module does not recognise is NEVER dropped and NEVER guessed at.
|
|
24
|
+
// It round-trips as `band-<value>`, so an unfamiliar band a future ModemManager
|
|
25
|
+
// reports is still shown to the operator, still comparable, and still settable —
|
|
26
|
+
// the same "unknown is an answer about the read" discipline `detect.ts` follows.
|
|
27
|
+
/** `MM_MODEM_BAND_UNKNOWN`. Never a member of a supported/current set we act on. */
|
|
28
|
+
export const BAND_UNKNOWN = 'unknown';
|
|
29
|
+
/**
|
|
30
|
+
* `MM_MODEM_BAND_ANY` (256) — "let the modem choose", i.e. the reset value.
|
|
31
|
+
* Setting exactly this is how a band lock is released; there is no separate
|
|
32
|
+
* ModemManager verb for it.
|
|
33
|
+
*/
|
|
34
|
+
export const BAND_ANY = 'any';
|
|
35
|
+
const BAND_ANY_VALUE = 256;
|
|
36
|
+
/**
|
|
37
|
+
* The irregular head of the enum (`MM_MODEM_BAND_EGSM` = 1 … `G810` = 20),
|
|
38
|
+
* transcribed because it cannot be derived.
|
|
39
|
+
*/
|
|
40
|
+
const IRREGULAR = {
|
|
41
|
+
0: BAND_UNKNOWN,
|
|
42
|
+
1: 'egsm',
|
|
43
|
+
2: 'dcs',
|
|
44
|
+
3: 'pcs',
|
|
45
|
+
4: 'g850',
|
|
46
|
+
5: 'utran-1',
|
|
47
|
+
6: 'utran-3',
|
|
48
|
+
7: 'utran-4',
|
|
49
|
+
8: 'utran-6',
|
|
50
|
+
9: 'utran-5',
|
|
51
|
+
10: 'utran-8',
|
|
52
|
+
11: 'utran-9',
|
|
53
|
+
12: 'utran-2',
|
|
54
|
+
13: 'utran-7',
|
|
55
|
+
14: 'g450',
|
|
56
|
+
15: 'g480',
|
|
57
|
+
16: 'g750',
|
|
58
|
+
17: 'g380',
|
|
59
|
+
18: 'g410',
|
|
60
|
+
19: 'g710',
|
|
61
|
+
20: 'g810',
|
|
62
|
+
[BAND_ANY_VALUE]: BAND_ANY,
|
|
63
|
+
};
|
|
64
|
+
/** Arithmetic blocks: `[prefix, base, firstIndex, lastIndex]`. */
|
|
65
|
+
const ARITHMETIC = [
|
|
66
|
+
// MM_MODEM_BAND_EUTRAN_1 = 31 … EUTRAN_71 = 101.
|
|
67
|
+
['eutran-', 30, 1, 71],
|
|
68
|
+
// MM_MODEM_BAND_CDMA_BC0 = 128 … CDMA_BC19 = 147.
|
|
69
|
+
['cdma-bc', 128, 0, 19],
|
|
70
|
+
// MM_MODEM_BAND_NGRAN_1 = 301 … the 5G NR block.
|
|
71
|
+
['ngran-', 300, 1, 261],
|
|
72
|
+
];
|
|
73
|
+
const NUMBER_TO_NAME = new Map();
|
|
74
|
+
const NAME_TO_NUMBER = new Map();
|
|
75
|
+
for (const [value, name] of Object.entries(IRREGULAR)) {
|
|
76
|
+
NUMBER_TO_NAME.set(Number(value), name);
|
|
77
|
+
NAME_TO_NUMBER.set(name, Number(value));
|
|
78
|
+
}
|
|
79
|
+
for (const [prefix, base, first, last] of ARITHMETIC) {
|
|
80
|
+
for (let index = first; index <= last; index += 1) {
|
|
81
|
+
const name = `${prefix}${index}`;
|
|
82
|
+
NUMBER_TO_NAME.set(base + index, name);
|
|
83
|
+
NAME_TO_NUMBER.set(name, base + index);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** The passthrough spelling for a band value this build does not name. */
|
|
87
|
+
const PASSTHROUGH_RE = /^band-(\d+)$/;
|
|
88
|
+
/** Decode one `MMModemBand` value. Total: an unknown value round-trips. */
|
|
89
|
+
export function bandName(value) {
|
|
90
|
+
return NUMBER_TO_NAME.get(value) ?? `band-${value}`;
|
|
91
|
+
}
|
|
92
|
+
/** Encode one band name. `undefined` for a name this build cannot place. */
|
|
93
|
+
export function bandValue(name) {
|
|
94
|
+
const known = NAME_TO_NUMBER.get(name);
|
|
95
|
+
if (known !== undefined)
|
|
96
|
+
return known;
|
|
97
|
+
const passthrough = PASSTHROUGH_RE.exec(name);
|
|
98
|
+
if (passthrough?.[1] === undefined)
|
|
99
|
+
return undefined;
|
|
100
|
+
const parsed = Number(passthrough[1]);
|
|
101
|
+
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
102
|
+
}
|
|
103
|
+
/** True when this build recognises the name (a `band-<n>` passthrough does not). */
|
|
104
|
+
export function isNamedBand(name) {
|
|
105
|
+
return NAME_TO_NUMBER.has(name);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Decode a `SupportedBands` / `CurrentBands` property value.
|
|
109
|
+
*
|
|
110
|
+
* Non-numeric members are DROPPED rather than coerced — a malformed member says
|
|
111
|
+
* nothing about the radio, and coercing it would invent a band. `unknown` is
|
|
112
|
+
* dropped for the same reason: MM emits it for "the modem did not say", which is
|
|
113
|
+
* not a band an operator can select.
|
|
114
|
+
*/
|
|
115
|
+
export function decodeBandList(value) {
|
|
116
|
+
if (!Array.isArray(value))
|
|
117
|
+
return [];
|
|
118
|
+
const names = [];
|
|
119
|
+
for (const member of value) {
|
|
120
|
+
if (typeof member !== 'number' || !Number.isSafeInteger(member))
|
|
121
|
+
continue;
|
|
122
|
+
const name = bandName(member);
|
|
123
|
+
if (name === BAND_UNKNOWN)
|
|
124
|
+
continue;
|
|
125
|
+
names.push(name);
|
|
126
|
+
}
|
|
127
|
+
return names;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Encode a band selection for `SetCurrentBands`.
|
|
131
|
+
*
|
|
132
|
+
* FAILS CLOSED AS A WHOLE. One unplaceable name rejects the entire request
|
|
133
|
+
* rather than silently narrowing the selection: a partial band set is a
|
|
134
|
+
* DIFFERENT lock from the one the operator asked for, and applying it would
|
|
135
|
+
* strand the radio on bands they never chose.
|
|
136
|
+
*/
|
|
137
|
+
export function encodeBandList(names) {
|
|
138
|
+
const values = [];
|
|
139
|
+
for (const name of names) {
|
|
140
|
+
const value = bandValue(name);
|
|
141
|
+
if (value === undefined)
|
|
142
|
+
return { ok: false, unknown: name };
|
|
143
|
+
values.push(value);
|
|
144
|
+
}
|
|
145
|
+
return { ok: true, values };
|
|
146
|
+
}
|
|
147
|
+
/** True when the selection is exactly the reset value (`any`, alone). */
|
|
148
|
+
export function isResetSelection(names) {
|
|
149
|
+
return names.length === 1 && names[0] === BAND_ANY;
|
|
150
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The device a certification is about. Firmware is a PREFIX for the same reason
|
|
4
|
+
* the USB-mode catalog matches one: an entry certifies a firmware FAMILY, and
|
|
5
|
+
* where the family ends is a per-SKU judgement a reviewer makes, not something a
|
|
6
|
+
* device can compute.
|
|
7
|
+
*/
|
|
8
|
+
export declare const bandSkuSchema: z.ZodObject<{
|
|
9
|
+
vidPid: z.ZodString;
|
|
10
|
+
model: z.ZodString;
|
|
11
|
+
firmwarePrefix: z.ZodString;
|
|
12
|
+
}, z.core.$strict>;
|
|
13
|
+
export type BandSku = z.infer<typeof bandSkuSchema>;
|
|
14
|
+
export declare const bandProofSchema: z.ZodObject<{
|
|
15
|
+
supportedRead: z.ZodLiteral<true>;
|
|
16
|
+
set: z.ZodLiteral<true>;
|
|
17
|
+
readback: z.ZodLiteral<true>;
|
|
18
|
+
reset: z.ZodLiteral<true>;
|
|
19
|
+
}, z.core.$strict>;
|
|
20
|
+
/**
|
|
21
|
+
* All four proofs are `z.literal(true)`, so a HALF-certified entry cannot be
|
|
22
|
+
* expressed at all. A reviewer with three of four proofs has an uncertified SKU
|
|
23
|
+
* and the file must say so by omitting it — a `false` field would read as a
|
|
24
|
+
* catalog row, and a catalog row is what surfaces the control.
|
|
25
|
+
*/
|
|
26
|
+
export declare const bandCertificationEntrySchema: z.ZodObject<{
|
|
27
|
+
vidPid: z.ZodString;
|
|
28
|
+
model: z.ZodString;
|
|
29
|
+
firmwarePrefix: z.ZodString;
|
|
30
|
+
evidence: z.ZodString;
|
|
31
|
+
proofs: z.ZodObject<{
|
|
32
|
+
supportedRead: z.ZodLiteral<true>;
|
|
33
|
+
set: z.ZodLiteral<true>;
|
|
34
|
+
readback: z.ZodLiteral<true>;
|
|
35
|
+
reset: z.ZodLiteral<true>;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
provenBands: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
38
|
+
}, z.core.$strict>;
|
|
39
|
+
export type BandCertificationEntry = z.infer<typeof bandCertificationEntrySchema>;
|
|
40
|
+
export declare const bandCertificationCatalogSchema: z.ZodObject<{
|
|
41
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
42
|
+
entries: z.ZodArray<z.ZodObject<{
|
|
43
|
+
vidPid: z.ZodString;
|
|
44
|
+
model: z.ZodString;
|
|
45
|
+
firmwarePrefix: z.ZodString;
|
|
46
|
+
evidence: z.ZodString;
|
|
47
|
+
proofs: z.ZodObject<{
|
|
48
|
+
supportedRead: z.ZodLiteral<true>;
|
|
49
|
+
set: z.ZodLiteral<true>;
|
|
50
|
+
readback: z.ZodLiteral<true>;
|
|
51
|
+
reset: z.ZodLiteral<true>;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
provenBands: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
export type BandCertificationCatalog = z.infer<typeof bandCertificationCatalogSchema>;
|
|
57
|
+
/** Parse an untrusted value as a band catalog. Throws with a precise path. */
|
|
58
|
+
export declare function loadBandCertificationCatalog(value: unknown): BandCertificationCatalog;
|
|
59
|
+
/** The catalog shipped in this package, validated at module load. */
|
|
60
|
+
export declare const BAND_CERTIFICATION_CATALOG: BandCertificationCatalog;
|
|
61
|
+
/**
|
|
62
|
+
* The entry certifying this exact device, or `undefined`.
|
|
63
|
+
*
|
|
64
|
+
* All three discriminators must match, and the firmware match is a PREFIX of the
|
|
65
|
+
* device's FULL revision — never a truncation of the device's revision to the
|
|
66
|
+
* entry's length, which would certify a family the reviewer never looked at.
|
|
67
|
+
*/
|
|
68
|
+
export declare function findBandCertification(catalog: BandCertificationCatalog, sku: BandSku): BandCertificationEntry | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Whether a band-lock control may be OFFERED for this device.
|
|
71
|
+
*
|
|
72
|
+
* Fail-closed in every direction: an unknown SKU, a SKU with no entry, and a SKU
|
|
73
|
+
* whose entry predates a proof step all answer `false`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isBandControlCertified(catalog: BandCertificationCatalog, sku: BandSku | undefined): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Narrow an advertised band set to what the certification proves is settable.
|
|
78
|
+
*
|
|
79
|
+
* An entry with no `provenBands` proves the whole advertised set (the drill swept
|
|
80
|
+
* it); an entry that names bands offers ONLY those, intersected with what the
|
|
81
|
+
* modem advertises right now — a certified band the device no longer advertises
|
|
82
|
+
* is not offerable, and the device's own answer outranks the catalog.
|
|
83
|
+
*/
|
|
84
|
+
export declare function offerableBands(entry: BandCertificationEntry | undefined, supported: readonly string[]): readonly string[];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// The band-lock certification catalog — what has actually been PROVEN, per SKU.
|
|
2
|
+
//
|
|
3
|
+
// A band lock is the one capability module that can take a working uplink off the
|
|
4
|
+
// air and leave it there: a band the SIM's network does not operate on registers
|
|
5
|
+
// nowhere, and a modem that does not honour a reset leaves the operator with no
|
|
6
|
+
// way back short of a replug they may not be able to reach. So band-lock is
|
|
7
|
+
// deliberately STRICTER than the framework floor in `support-claim.ts`. The
|
|
8
|
+
// framework offers a control at `capable` (the modem advertises the capability)
|
|
9
|
+
// because hiding an uncertified-but-working control puts hardware behind a
|
|
10
|
+
// paperwork gate. For this module the paperwork IS the safety argument, so the
|
|
11
|
+
// control stays HIDDEN until an entry here proves the whole round trip.
|
|
12
|
+
//
|
|
13
|
+
// FOUR STEPS, ALL FOUR REQUIRED, and they are separate booleans rather than one
|
|
14
|
+
// `certified: true` because each can fail on its own and each failure means a
|
|
15
|
+
// different thing:
|
|
16
|
+
//
|
|
17
|
+
// supportedRead — `SupportedBands` was read and named real bands. Without it
|
|
18
|
+
// there is nothing to offer.
|
|
19
|
+
// set — `SetCurrentBands` was accepted for a band in that set.
|
|
20
|
+
// readback — `CurrentBands` afterwards reported exactly what was asked
|
|
21
|
+
// for. An accepted-but-ignored write is the failure mode that
|
|
22
|
+
// looks like success from the call site alone.
|
|
23
|
+
// reset — setting `any` restored the baseline. This is the escape
|
|
24
|
+
// hatch; a SKU that cannot be shown to reset must never be
|
|
25
|
+
// locked in the first place.
|
|
26
|
+
//
|
|
27
|
+
// THE SHIPPED CATALOG IS EMPTY, and that is the honest state. Nothing in the
|
|
28
|
+
// fleet has been through the drill — see the phase-C band-lock evidence for why
|
|
29
|
+
// (the bench Quectel's SIM never registers, so "re-registration proven" cannot
|
|
30
|
+
// be claimed today). An entry is added by a human-reviewed commit carrying the
|
|
31
|
+
// bench transcript, exactly like `usb-mode/certified-catalog.json`.
|
|
32
|
+
import { z } from 'zod';
|
|
33
|
+
import rawCatalog from './certified-bands.json' with { type: 'json' };
|
|
34
|
+
/**
|
|
35
|
+
* The device a certification is about. Firmware is a PREFIX for the same reason
|
|
36
|
+
* the USB-mode catalog matches one: an entry certifies a firmware FAMILY, and
|
|
37
|
+
* where the family ends is a per-SKU judgement a reviewer makes, not something a
|
|
38
|
+
* device can compute.
|
|
39
|
+
*/
|
|
40
|
+
export const bandSkuSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
vidPid: z.string().regex(/^[0-9a-f]{4}:[0-9a-f]{4}$/),
|
|
43
|
+
model: z.string().min(1),
|
|
44
|
+
firmwarePrefix: z.string().min(1),
|
|
45
|
+
})
|
|
46
|
+
.strict();
|
|
47
|
+
export const bandProofSchema = z
|
|
48
|
+
.object({
|
|
49
|
+
supportedRead: z.literal(true),
|
|
50
|
+
set: z.literal(true),
|
|
51
|
+
readback: z.literal(true),
|
|
52
|
+
reset: z.literal(true),
|
|
53
|
+
})
|
|
54
|
+
.strict();
|
|
55
|
+
/**
|
|
56
|
+
* All four proofs are `z.literal(true)`, so a HALF-certified entry cannot be
|
|
57
|
+
* expressed at all. A reviewer with three of four proofs has an uncertified SKU
|
|
58
|
+
* and the file must say so by omitting it — a `false` field would read as a
|
|
59
|
+
* catalog row, and a catalog row is what surfaces the control.
|
|
60
|
+
*/
|
|
61
|
+
export const bandCertificationEntrySchema = z
|
|
62
|
+
.object({
|
|
63
|
+
vidPid: z.string().regex(/^[0-9a-f]{4}:[0-9a-f]{4}$/),
|
|
64
|
+
model: z.string().min(1),
|
|
65
|
+
firmwarePrefix: z.string().min(1),
|
|
66
|
+
/** Free-form: where the transcript proving the four steps lives. */
|
|
67
|
+
evidence: z.string().min(1),
|
|
68
|
+
proofs: bandProofSchema,
|
|
69
|
+
/**
|
|
70
|
+
* Bands the reviewer proved were individually settable AND resettable.
|
|
71
|
+
* An EMPTY list means "the whole advertised set", which is what a drill
|
|
72
|
+
* that swept the set records; a non-empty list NARROWS what is offered.
|
|
73
|
+
*/
|
|
74
|
+
provenBands: z.array(z.string()).default([]),
|
|
75
|
+
})
|
|
76
|
+
.strict();
|
|
77
|
+
export const bandCertificationCatalogSchema = z
|
|
78
|
+
.object({
|
|
79
|
+
schemaVersion: z.literal(1),
|
|
80
|
+
entries: z.array(bandCertificationEntrySchema),
|
|
81
|
+
})
|
|
82
|
+
.strict();
|
|
83
|
+
/** Parse an untrusted value as a band catalog. Throws with a precise path. */
|
|
84
|
+
export function loadBandCertificationCatalog(value) {
|
|
85
|
+
return bandCertificationCatalogSchema.parse(value);
|
|
86
|
+
}
|
|
87
|
+
/** The catalog shipped in this package, validated at module load. */
|
|
88
|
+
export const BAND_CERTIFICATION_CATALOG = loadBandCertificationCatalog(rawCatalog);
|
|
89
|
+
/**
|
|
90
|
+
* The entry certifying this exact device, or `undefined`.
|
|
91
|
+
*
|
|
92
|
+
* All three discriminators must match, and the firmware match is a PREFIX of the
|
|
93
|
+
* device's FULL revision — never a truncation of the device's revision to the
|
|
94
|
+
* entry's length, which would certify a family the reviewer never looked at.
|
|
95
|
+
*/
|
|
96
|
+
export function findBandCertification(catalog, sku) {
|
|
97
|
+
return catalog.entries.find((entry) => entry.vidPid === sku.vidPid &&
|
|
98
|
+
entry.model === sku.model &&
|
|
99
|
+
sku.firmwarePrefix.startsWith(entry.firmwarePrefix));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether a band-lock control may be OFFERED for this device.
|
|
103
|
+
*
|
|
104
|
+
* Fail-closed in every direction: an unknown SKU, a SKU with no entry, and a SKU
|
|
105
|
+
* whose entry predates a proof step all answer `false`.
|
|
106
|
+
*/
|
|
107
|
+
export function isBandControlCertified(catalog, sku) {
|
|
108
|
+
if (sku === undefined)
|
|
109
|
+
return false;
|
|
110
|
+
return findBandCertification(catalog, sku) !== undefined;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Narrow an advertised band set to what the certification proves is settable.
|
|
114
|
+
*
|
|
115
|
+
* An entry with no `provenBands` proves the whole advertised set (the drill swept
|
|
116
|
+
* it); an entry that names bands offers ONLY those, intersected with what the
|
|
117
|
+
* modem advertises right now — a certified band the device no longer advertises
|
|
118
|
+
* is not offerable, and the device's own answer outranks the catalog.
|
|
119
|
+
*/
|
|
120
|
+
export function offerableBands(entry, supported) {
|
|
121
|
+
if (entry === undefined)
|
|
122
|
+
return [];
|
|
123
|
+
if (entry.provenBands.length === 0)
|
|
124
|
+
return supported;
|
|
125
|
+
const proven = new Set(entry.provenBands);
|
|
126
|
+
return supported.filter((band) => proven.has(band));
|
|
127
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The band-lock capability module: the MMModemBand vocabulary and the
|
|
2
|
+
// certification catalog that gates whether a control may be offered at all.
|
|
3
|
+
//
|
|
4
|
+
// The D-Bus verbs themselves live with the other ModemManager mutations
|
|
5
|
+
// (`backend/mm-mutations.ts`), declared on `ModemManagerPort` — a band change is
|
|
6
|
+
// a radio mutation like a mode change, not a subsystem of its own.
|
|
7
|
+
export * from './band-names.js';
|
|
8
|
+
export * from './certification.js';
|