@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
package/src/backend/features.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
// ModemManager feature detection — what a given MM daemon can actually do.
|
|
2
|
-
//
|
|
3
|
-
// A3.2 must run against MM 1.20 (the bookworm floor), 1.22, 1.24, and versions
|
|
4
|
-
// nobody has shipped yet. The plan is explicit that this is NOT a version-string
|
|
5
|
-
// whitelist: a version we have never seen must degrade gracefully by PROBING the
|
|
6
|
-
// observed property set, never throw. So detection combines two signals:
|
|
7
|
-
//
|
|
8
|
-
// - the daemon `Version` string (a floor: e.g. eSIM read model is 1.20+), and
|
|
9
|
-
// - the properties actually present on the observed modem (authoritative for
|
|
10
|
-
// `physdev` — the plan requires `physdev` be true ONLY when the `Physdev`
|
|
11
|
-
// property is really there, never merely inferred from a version number).
|
|
12
|
-
//
|
|
13
|
-
// Property facts pinned by the reviewed design (draft §Oracle round-2 #9 / round-4):
|
|
14
|
-
// - `Modem.Device` carries the udev slot UID on ALL versions.
|
|
15
|
-
// - `Modem.Physdev` is the physical topology path, 1.22+ ONLY.
|
|
16
|
-
// - there is NO `PhysdevUid` property anywhere — do not look for one.
|
|
17
|
-
|
|
18
|
-
import { MODEM_IFACE, MODEM3GPP_IFACE, SIM_IFACE } from './constants';
|
|
19
|
-
import { type DecodedManagedObjects, findInterface, followObjectPath } from './managed-objects';
|
|
20
|
-
|
|
21
|
-
/** The capabilities the D-Bus backend gates its behaviour on. */
|
|
22
|
-
export interface MmFeatures {
|
|
23
|
-
/** `Modem.Physdev` present (physical topology path, 1.22+). Property-probed. */
|
|
24
|
-
readonly physdev: boolean;
|
|
25
|
-
/**
|
|
26
|
-
* Cell-info tier:
|
|
27
|
-
* - `none` — below 1.20, or the modem advertises no cell-info capability.
|
|
28
|
-
* - `basic` — 1.20/1.21 `GetCellInfo`.
|
|
29
|
-
* - `rich` — 1.22+ (serving-type / bandwidth fields).
|
|
30
|
-
*/
|
|
31
|
-
readonly cellInfo: 'none' | 'basic' | 'rich';
|
|
32
|
-
/** eSIM read model (`Sim.SimType` / `Sim.EsimStatus`), 1.20+. */
|
|
33
|
-
readonly esimStatus: boolean;
|
|
34
|
-
/** Per-modem operation serialization is available (the shared disruptive actor). */
|
|
35
|
-
readonly opSerialization: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* What A3.2 observed about ONE modem's property surface.
|
|
40
|
-
*
|
|
41
|
-
* `properties` is the union of property NAMES seen across the modem's interfaces
|
|
42
|
-
* (built by `probeModemProperties` from a decoded `GetManagedObjects` tree). The
|
|
43
|
-
* two optional fields let a caller feed richer cell-info evidence than the static
|
|
44
|
-
* object tree carries — MM does not advertise cell-info richness as a Modem
|
|
45
|
-
* property (it comes back from the `GetCellInfo` method), so a backend that has
|
|
46
|
-
* probed cell info can pass what it saw; absent that, detection falls back to the
|
|
47
|
-
* version + `Physdev` co-introduction signal.
|
|
48
|
-
*/
|
|
49
|
-
export interface MmPropertyProbe {
|
|
50
|
-
/** Property names present on the observed modem (Modem + Modem3gpp + active SIM). */
|
|
51
|
-
readonly properties: ReadonlySet<string>;
|
|
52
|
-
/** Explicit "modem advertises cell info": `false` forces `cellInfo: 'none'`. */
|
|
53
|
-
readonly cellInfoAvailable?: boolean;
|
|
54
|
-
/** Cell-info field names seen in a probe; `serving-type`/`bandwidth` ⇒ `rich`. */
|
|
55
|
-
readonly cellInfoFields?: ReadonlySet<string>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** A parsed `major.minor` MM version. `null` when the string is not parseable. */
|
|
59
|
-
export interface MmVersion {
|
|
60
|
-
readonly major: number;
|
|
61
|
-
readonly minor: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const VERSION_PATTERN = /^(\d+)\.(\d+)(?:\.\d+)*/;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Parse an MM `Version` string (`1.20.0`, `1.24`, `1.26.0-rc1`, …) to `major.minor`.
|
|
68
|
-
* Returns `null` on anything unparseable — callers must degrade, never throw.
|
|
69
|
-
*/
|
|
70
|
-
export function parseMmVersion(version: string): MmVersion | null {
|
|
71
|
-
const match = VERSION_PATTERN.exec(version.trim());
|
|
72
|
-
if (match === null) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
const major = Number(match[1]);
|
|
76
|
-
const minor = Number(match[2]);
|
|
77
|
-
if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor)) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
return { major, minor };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Whether `version` is at least `major.minor` (false when unparseable). */
|
|
84
|
-
function atLeast(version: MmVersion | null, major: number, minor: number): boolean {
|
|
85
|
-
if (version === null) {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
return version.major > major || (version.major === major && version.minor >= minor);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** MM baseline this stack supports — eSIM read model, `GetCellInfo`, Signal.Setup. */
|
|
92
|
-
const MM_MIN = { major: 1, minor: 20 } as const;
|
|
93
|
-
/** Physdev + rich cell-info were co-introduced at 1.22. */
|
|
94
|
-
const MM_RICH = { major: 1, minor: 22 } as const;
|
|
95
|
-
|
|
96
|
-
function detectCellInfo(
|
|
97
|
-
probe: MmPropertyProbe,
|
|
98
|
-
supported: boolean,
|
|
99
|
-
modern: boolean,
|
|
100
|
-
physdev: boolean,
|
|
101
|
-
): MmFeatures['cellInfo'] {
|
|
102
|
-
// Version floor first: below 1.20 there is no GetCellInfo at all.
|
|
103
|
-
if (!supported) {
|
|
104
|
-
return 'none';
|
|
105
|
-
}
|
|
106
|
-
// A modem that explicitly advertises no cell-info capability degrades to none,
|
|
107
|
-
// even on a version that would otherwise support it.
|
|
108
|
-
if (probe.cellInfoAvailable === false) {
|
|
109
|
-
return 'none';
|
|
110
|
-
}
|
|
111
|
-
const fields = probe.cellInfoFields;
|
|
112
|
-
if (fields !== undefined && (fields.has('serving-type') || fields.has('bandwidth'))) {
|
|
113
|
-
return 'rich';
|
|
114
|
-
}
|
|
115
|
-
// 1.22 co-introduced `Physdev` and the rich cell-info fields, so either signal
|
|
116
|
-
// promotes the tier — this is what makes an unseen future version (e.g. 1.26)
|
|
117
|
-
// resolve to `rich` by probing rather than by matching a whitelist.
|
|
118
|
-
if (modern || physdev) {
|
|
119
|
-
return 'rich';
|
|
120
|
-
}
|
|
121
|
-
return 'basic';
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Detect what an MM daemon can do from its version string AND the properties
|
|
126
|
-
* observed on a modem. Pure — no I/O, never throws (an unparseable version yields
|
|
127
|
-
* the safe floor: property-probed `physdev`, everything else off).
|
|
128
|
-
*/
|
|
129
|
-
export function detectMmFeatures(version: string, probe: MmPropertyProbe): MmFeatures {
|
|
130
|
-
const parsed = parseMmVersion(version);
|
|
131
|
-
const supported = atLeast(parsed, MM_MIN.major, MM_MIN.minor);
|
|
132
|
-
const modern = atLeast(parsed, MM_RICH.major, MM_RICH.minor);
|
|
133
|
-
|
|
134
|
-
// `physdev` is property-authoritative: present iff the `Physdev` property is
|
|
135
|
-
// really in the observed set (never inferred from the version alone).
|
|
136
|
-
const physdev = probe.properties.has('Physdev');
|
|
137
|
-
|
|
138
|
-
const esimStatus =
|
|
139
|
-
probe.properties.has('SimType') || probe.properties.has('EsimStatus') || supported;
|
|
140
|
-
|
|
141
|
-
const cellInfo = detectCellInfo(probe, supported, modern, physdev);
|
|
142
|
-
|
|
143
|
-
return { physdev, cellInfo, esimStatus, opSerialization: supported };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Build an `MmPropertyProbe` for one modem from a decoded `GetManagedObjects` tree:
|
|
148
|
-
* the union of property names on its `Modem`, `Modem.Modem3gpp`, and active `Sim`
|
|
149
|
-
* interfaces. Cell-info fields are not carried by the static tree, so the returned
|
|
150
|
-
* probe leaves them unset (detection falls back to version + `Physdev`).
|
|
151
|
-
*/
|
|
152
|
-
export function probeModemProperties(
|
|
153
|
-
tree: DecodedManagedObjects,
|
|
154
|
-
modemPath: string,
|
|
155
|
-
): MmPropertyProbe {
|
|
156
|
-
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
157
|
-
const modem3gpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
|
|
158
|
-
const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
|
|
159
|
-
|
|
160
|
-
const properties = new Set<string>();
|
|
161
|
-
for (const source of [modem, modem3gpp, sim]) {
|
|
162
|
-
if (source === undefined) {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
for (const [name] of source) {
|
|
166
|
-
properties.add(name);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return { properties };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Convenience: detect features straight off a decoded tree for one modem. */
|
|
173
|
-
export function detectModemFeatures(
|
|
174
|
-
version: string,
|
|
175
|
-
tree: DecodedManagedObjects,
|
|
176
|
-
modemPath: string,
|
|
177
|
-
): MmFeatures {
|
|
178
|
-
return detectMmFeatures(version, probeModemProperties(tree, modemPath));
|
|
179
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
canBindPolicy,
|
|
4
|
-
imeiEquipmentId,
|
|
5
|
-
logicalSlotId,
|
|
6
|
-
NO_EQUIPMENT_ID,
|
|
7
|
-
PolicyBindingRefusedError,
|
|
8
|
-
policyBindingKey,
|
|
9
|
-
} from '../domain';
|
|
10
|
-
import {
|
|
11
|
-
looksLikeSlotUid,
|
|
12
|
-
type ModemIdentityFacts,
|
|
13
|
-
resolveModemIdentities,
|
|
14
|
-
resolveModemIdentity,
|
|
15
|
-
} from './identity-ladder';
|
|
16
|
-
|
|
17
|
-
const IMEI_A = '490154203237518';
|
|
18
|
-
const IMEI_B = '356938035643809';
|
|
19
|
-
const PATH = '/org/freedesktop/ModemManager1/Modem/0';
|
|
20
|
-
|
|
21
|
-
function facts(overrides: Partial<ModemIdentityFacts> = {}): ModemIdentityFacts {
|
|
22
|
-
return { runtimePath: PATH, equipmentId: imeiEquipmentId(IMEI_A), ...overrides };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe('looksLikeSlotUid', () => {
|
|
26
|
-
test('our slot-* naming is detected', () => {
|
|
27
|
-
expect(looksLikeSlotUid('slot-usb2-1')).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('MM path-shaped default is not a slot UID', () => {
|
|
31
|
-
expect(looksLikeSlotUid('/sys/devices/pci0000:00/usb2')).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('the bare prefix alone is not a slot UID', () => {
|
|
35
|
-
expect(looksLikeSlotUid('slot-')).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('an absent Device is not a slot UID', () => {
|
|
39
|
-
expect(looksLikeSlotUid(undefined)).toBe(false);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('identity ladder rungs (in priority order)', () => {
|
|
44
|
-
test('rung 1: a slot-UID Device wins over Physdev and ports', () => {
|
|
45
|
-
const resolved = resolveModemIdentity(
|
|
46
|
-
facts({ device: 'slot-usb2-1', physdev: '/sys/devices/usb2', ports: ['ttyUSB0'] }),
|
|
47
|
-
);
|
|
48
|
-
expect(resolved.slotSource).toBe('device-slot-uid');
|
|
49
|
-
expect(resolved.confidence).toBe('high');
|
|
50
|
-
expect(resolved.identity.logicalSlotId).toBe(logicalSlotId('slot-usb2-1'));
|
|
51
|
-
expect(resolved.stableKey).toBe('slot:slot-usb2-1');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('rung 2: Physdev is used when Device is a path-shaped default', () => {
|
|
55
|
-
const resolved = resolveModemIdentity(
|
|
56
|
-
facts({ device: '/sys/devices/usb2', physdev: '/sys/devices/pci/usb2/2-1' }),
|
|
57
|
-
);
|
|
58
|
-
expect(resolved.slotSource).toBe('physdev');
|
|
59
|
-
expect(resolved.confidence).toBe('high');
|
|
60
|
-
expect(resolved.stableKey).toBe('physdev:/sys/devices/pci/usb2/2-1');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('rung 3: a ports-derived sysfs walk when no slot UID or Physdev', () => {
|
|
64
|
-
const resolved = resolveModemIdentity(facts({ ports: ['wwan0', 'ttyUSB2', 'ttyUSB0'] }));
|
|
65
|
-
expect(resolved.slotSource).toBe('sysfs-walk');
|
|
66
|
-
expect(resolved.confidence).toBe('medium');
|
|
67
|
-
expect(resolved.stableKey).toBe('sysfs:ttyUSB0+ttyUSB2+wwan0');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('rung 4: equipment fallback is low confidence with no slot', () => {
|
|
71
|
-
const resolved = resolveModemIdentity(facts());
|
|
72
|
-
expect(resolved.slotSource).toBe('equipment-fallback');
|
|
73
|
-
expect(resolved.confidence).toBe('low');
|
|
74
|
-
expect(resolved.identity.logicalSlotId).toBeUndefined();
|
|
75
|
-
expect(resolved.stableKey).toBe(`equip:${IMEI_A}`);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('rung 4 with no equipment id keys off the transient path', () => {
|
|
79
|
-
const resolved = resolveModemIdentity(facts({ equipmentId: NO_EQUIPMENT_ID }));
|
|
80
|
-
expect(resolved.stableKey).toBe(`path:${PATH}`);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('durable policy binding through the ladder', () => {
|
|
85
|
-
test('a slot-resolved unique modem may bind durable policy', () => {
|
|
86
|
-
const resolved = resolveModemIdentity(facts({ device: 'slot-usb2-1' }));
|
|
87
|
-
expect(canBindPolicy(resolved.identity)).toBe(true);
|
|
88
|
-
expect(policyBindingKey(resolved.identity).logicalSlotId).toBe(logicalSlotId('slot-usb2-1'));
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('duplicate-IMEI fixture: the ladder falls to low-confidence equipment fallback and refuses binding', () => {
|
|
92
|
-
const resolved = resolveModemIdentities([
|
|
93
|
-
facts({
|
|
94
|
-
runtimePath: '/org/freedesktop/ModemManager1/Modem/0',
|
|
95
|
-
equipmentId: imeiEquipmentId(IMEI_A),
|
|
96
|
-
}),
|
|
97
|
-
facts({
|
|
98
|
-
runtimePath: '/org/freedesktop/ModemManager1/Modem/1',
|
|
99
|
-
equipmentId: imeiEquipmentId(IMEI_A),
|
|
100
|
-
}),
|
|
101
|
-
]);
|
|
102
|
-
for (const each of resolved) {
|
|
103
|
-
expect(each.slotSource).toBe('equipment-fallback');
|
|
104
|
-
expect(each.identity.equipmentId.confidence).toBe('low');
|
|
105
|
-
expect(canBindPolicy(each.identity)).toBe(false);
|
|
106
|
-
expect(() => policyBindingKey(each.identity)).toThrow(PolicyBindingRefusedError);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('unique IMEIs are NOT demoted in a batch resolve', () => {
|
|
111
|
-
const resolved = resolveModemIdentities([
|
|
112
|
-
facts({ device: 'slot-a', equipmentId: imeiEquipmentId(IMEI_A) }),
|
|
113
|
-
facts({ device: 'slot-b', equipmentId: imeiEquipmentId(IMEI_B) }),
|
|
114
|
-
]);
|
|
115
|
-
expect(resolved.every((each) => each.identity.equipmentId.confidence === 'high')).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
// The identity ladder — resolving a STABLE slot handle for a live modem.
|
|
2
|
-
//
|
|
3
|
-
// A modem's D-Bus object path (`/org/freedesktop/ModemManager1/Modem/3`) is a
|
|
4
|
-
// per-boot, per-plug value: it changes on replug and daemon restart, so it can
|
|
5
|
-
// never be the durable key. The ladder derives a stable key by trying, in order:
|
|
6
|
-
//
|
|
7
|
-
// 1. slot-UID in `Modem.Device` — when OUR udev rules label the slot they set
|
|
8
|
-
// `Device` to a `slot-*` UID (e.g. `slot-usb2-1`). That is stable and ours,
|
|
9
|
-
// distinguishable from MM's own path-shaped default (`/sys/devices/...`).
|
|
10
|
-
// 2. `Modem.Physdev` (1.22+) — the physical USB topology path. Stable across
|
|
11
|
-
// replug into the same port; used when no slot-UID is present.
|
|
12
|
-
// 3. a ports-derived sysfs walk — a deterministic key from the modem's port
|
|
13
|
-
// list when neither of the above is available (MM 1.20, no udev rule yet).
|
|
14
|
-
// 4. equipment-id fallback — LOW confidence. Per A2.1's ambiguity rules a
|
|
15
|
-
// low-confidence identity must never bind durable policy, so this rung sets
|
|
16
|
-
// `confidence: 'low'` and `canBindPolicy` refuses on an ambiguous equipment id.
|
|
17
|
-
//
|
|
18
|
-
// Phase A note: no image-pipeline udev rules ship yet, so on the bench `Device`
|
|
19
|
-
// is usually MM's path-shaped default and the ladder lands on Physdev / sysfs.
|
|
20
|
-
// The `slot-*` heuristic is documented here so a bench operator can label a slot
|
|
21
|
-
// by hand and see rung 1 fire.
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
demoteToLowConfidence,
|
|
25
|
-
type EquipmentId,
|
|
26
|
-
type IdentityConfidence,
|
|
27
|
-
imeiEquipmentId,
|
|
28
|
-
type LogicalSlotId,
|
|
29
|
-
logicalSlotId,
|
|
30
|
-
type ModemIdentity,
|
|
31
|
-
runtimePath,
|
|
32
|
-
} from '../domain';
|
|
33
|
-
import { MODEM_IFACE, MODEM3GPP_IFACE } from './constants';
|
|
34
|
-
import {
|
|
35
|
-
type DecodedManagedObjects,
|
|
36
|
-
findInterface,
|
|
37
|
-
propValue,
|
|
38
|
-
stringProp,
|
|
39
|
-
} from './managed-objects';
|
|
40
|
-
|
|
41
|
-
/** Which rung of the ladder produced the stable key. */
|
|
42
|
-
export type SlotSource = 'device-slot-uid' | 'physdev' | 'sysfs-walk' | 'equipment-fallback';
|
|
43
|
-
|
|
44
|
-
/** The prefix OUR udev rules use to label a slot in `Modem.Device`. */
|
|
45
|
-
export const SLOT_UID_PREFIX = 'slot-';
|
|
46
|
-
|
|
47
|
-
/** The raw facts the ladder resolves from — one modem's observed identity inputs. */
|
|
48
|
-
export interface ModemIdentityFacts {
|
|
49
|
-
/** The live MM object path (becomes `runtimePath`; never a durable key). */
|
|
50
|
-
readonly runtimePath: string;
|
|
51
|
-
/** `Modem.Device` — a `slot-*` UID (ours) or MM's path-shaped default. */
|
|
52
|
-
readonly device?: string;
|
|
53
|
-
/** `Modem.Physdev` — physical topology path (1.22+; absent on 1.20). */
|
|
54
|
-
readonly physdev?: string;
|
|
55
|
-
/** The modem's port device names (e.g. `ttyUSB0`, `wwan0`) for the sysfs walk. */
|
|
56
|
-
readonly ports?: readonly string[];
|
|
57
|
-
/** IMEI/serial equipment id with its own confidence (A2.1). */
|
|
58
|
-
readonly equipmentId: EquipmentId;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** The ladder's output: a resolved identity plus how (and how firmly) we got it. */
|
|
62
|
-
export interface ResolvedIdentity {
|
|
63
|
-
/** The four-part identity; `logicalSlotId` is set for rungs 1-3 only. */
|
|
64
|
-
readonly identity: ModemIdentity;
|
|
65
|
-
/** Which rung produced the result. */
|
|
66
|
-
readonly slotSource: SlotSource;
|
|
67
|
-
/** Confidence in the SLOT resolution (rung 4 is always `low`). */
|
|
68
|
-
readonly confidence: IdentityConfidence;
|
|
69
|
-
/** The durable row key — stable across replug for rungs 1-3 (and unique-IMEI rung 4). */
|
|
70
|
-
readonly stableKey: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Whether a `Modem.Device` value is one of OUR slot UIDs (shaped `slot-*`) rather
|
|
75
|
-
* than MM's path-shaped default. This is the documented Phase-A heuristic that
|
|
76
|
-
* keeps a hand-labelled slot preferred over a `/sys/devices/...` path.
|
|
77
|
-
*/
|
|
78
|
-
export function looksLikeSlotUid(device: string | undefined): boolean {
|
|
79
|
-
if (device === undefined) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
return device.startsWith(SLOT_UID_PREFIX) && device.length > SLOT_UID_PREFIX.length;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Derive a deterministic key from a modem's port list (sorted, stable across replug). */
|
|
86
|
-
function sysfsWalkKey(ports: readonly string[]): string | undefined {
|
|
87
|
-
const named = ports.map((p) => p.trim()).filter((p) => p.length > 0);
|
|
88
|
-
if (named.length === 0) {
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
return [...named].sort().join('+');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function buildIdentity(facts: ModemIdentityFacts, slot: LogicalSlotId | undefined): ModemIdentity {
|
|
95
|
-
return {
|
|
96
|
-
equipmentId: facts.equipmentId,
|
|
97
|
-
runtimePath: runtimePath(facts.runtimePath),
|
|
98
|
-
...(slot !== undefined ? { logicalSlotId: slot } : {}),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Resolve ONE modem's stable identity by walking the ladder. Pure — no I/O.
|
|
104
|
-
*
|
|
105
|
-
* Rungs 1-3 yield a `logicalSlotId` and a stable key that survives replug into the
|
|
106
|
-
* same slot. Rung 4 (equipment fallback) yields NO slot and `confidence: 'low'`,
|
|
107
|
-
* so a durable policy bound via `policyBindingKey` is refused for an ambiguous
|
|
108
|
-
* (duplicate/zero) equipment id.
|
|
109
|
-
*/
|
|
110
|
-
export function resolveModemIdentity(facts: ModemIdentityFacts): ResolvedIdentity {
|
|
111
|
-
// Rung 1 — our slot-UID in `Device`.
|
|
112
|
-
if (looksLikeSlotUid(facts.device)) {
|
|
113
|
-
const device = facts.device as string;
|
|
114
|
-
const slot = logicalSlotId(device);
|
|
115
|
-
return {
|
|
116
|
-
identity: buildIdentity(facts, slot),
|
|
117
|
-
slotSource: 'device-slot-uid',
|
|
118
|
-
confidence: 'high',
|
|
119
|
-
stableKey: `slot:${device}`,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Rung 2 — `Physdev` physical topology path (1.22+).
|
|
124
|
-
const physdev = facts.physdev?.trim();
|
|
125
|
-
if (physdev !== undefined && physdev.length > 0) {
|
|
126
|
-
const slot = logicalSlotId(`physdev:${physdev}`);
|
|
127
|
-
return {
|
|
128
|
-
identity: buildIdentity(facts, slot),
|
|
129
|
-
slotSource: 'physdev',
|
|
130
|
-
confidence: 'high',
|
|
131
|
-
stableKey: `physdev:${physdev}`,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Rung 3 — ports-derived sysfs walk.
|
|
136
|
-
const walk = facts.ports !== undefined ? sysfsWalkKey(facts.ports) : undefined;
|
|
137
|
-
if (walk !== undefined) {
|
|
138
|
-
const slot = logicalSlotId(`sysfs:${walk}`);
|
|
139
|
-
return {
|
|
140
|
-
identity: buildIdentity(facts, slot),
|
|
141
|
-
slotSource: 'sysfs-walk',
|
|
142
|
-
confidence: 'medium',
|
|
143
|
-
stableKey: `sysfs:${walk}`,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Rung 4 — equipment fallback, LOW confidence, no durable slot.
|
|
148
|
-
const equip = facts.equipmentId;
|
|
149
|
-
const stableKey =
|
|
150
|
-
equip.provenance !== 'none' && equip.value.trim().length > 0
|
|
151
|
-
? `equip:${equip.value}`
|
|
152
|
-
: `path:${facts.runtimePath}`;
|
|
153
|
-
return {
|
|
154
|
-
identity: buildIdentity(facts, undefined),
|
|
155
|
-
slotSource: 'equipment-fallback',
|
|
156
|
-
confidence: 'low',
|
|
157
|
-
stableKey,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Resolve a batch of modems, demoting any equipment id that appears on more than
|
|
163
|
-
* one modem to LOW confidence FIRST (cross-modem duplicate detection A2.1 cannot do
|
|
164
|
-
* from a single value). A duplicate IMEI thus reaches rung 4 with a low-confidence
|
|
165
|
-
* equipment id, and `canBindPolicy` refuses durable binding on it.
|
|
166
|
-
*/
|
|
167
|
-
export function resolveModemIdentities(
|
|
168
|
-
factsList: readonly ModemIdentityFacts[],
|
|
169
|
-
): ResolvedIdentity[] {
|
|
170
|
-
const counts = new Map<string, number>();
|
|
171
|
-
for (const facts of factsList) {
|
|
172
|
-
const { equipmentId } = facts;
|
|
173
|
-
if (equipmentId.provenance !== 'none' && equipmentId.value.trim().length > 0) {
|
|
174
|
-
counts.set(equipmentId.value, (counts.get(equipmentId.value) ?? 0) + 1);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return factsList.map((facts) => {
|
|
178
|
-
const { equipmentId } = facts;
|
|
179
|
-
const duplicated =
|
|
180
|
-
equipmentId.provenance !== 'none' && (counts.get(equipmentId.value) ?? 0) > 1;
|
|
181
|
-
const resolvedFacts: ModemIdentityFacts = duplicated
|
|
182
|
-
? { ...facts, equipmentId: demoteToLowConfidence(equipmentId) }
|
|
183
|
-
: facts;
|
|
184
|
-
return resolveModemIdentity(resolvedFacts);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** MM `Modem.Ports` is `a(su)`: entries of `[portName, portType]`. */
|
|
189
|
-
function portNames(value: ReturnType<typeof propValue>): string[] | undefined {
|
|
190
|
-
if (!Array.isArray(value)) {
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
const names = value
|
|
194
|
-
.map((entry) => (Array.isArray(entry) ? entry[0] : undefined))
|
|
195
|
-
.filter((name): name is string => typeof name === 'string');
|
|
196
|
-
return names.length > 0 ? names : undefined;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Extract one modem's `ModemIdentityFacts` from a decoded `GetManagedObjects` tree —
|
|
201
|
-
* the bridge real backends use to feed the ladder from an observed snapshot.
|
|
202
|
-
*/
|
|
203
|
-
export function modemIdentityFactsFromTree(
|
|
204
|
-
tree: DecodedManagedObjects,
|
|
205
|
-
modemPath: string,
|
|
206
|
-
): ModemIdentityFacts {
|
|
207
|
-
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
208
|
-
const modem3gpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
|
|
209
|
-
const equipment = stringProp(modem, 'EquipmentIdentifier') ?? stringProp(modem3gpp, 'Imei') ?? '';
|
|
210
|
-
const device = stringProp(modem, 'Device');
|
|
211
|
-
const physdev = stringProp(modem, 'Physdev');
|
|
212
|
-
const ports = portNames(propValue(modem, 'Ports'));
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
runtimePath: modemPath,
|
|
216
|
-
equipmentId: imeiEquipmentId(equipment),
|
|
217
|
-
...(device !== undefined ? { device } : {}),
|
|
218
|
-
...(physdev !== undefined ? { physdev } : {}),
|
|
219
|
-
...(ports !== undefined ? { ports } : {}),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { imeiEquipmentId, logicalSlotId, runtimePath } from '../domain';
|
|
3
|
-
import { type ModemIdentityFacts, resolveModemIdentity } from './identity-ladder';
|
|
4
|
-
import { IdentityRegistry } from './identity-registry';
|
|
5
|
-
|
|
6
|
-
const IMEI_A = '490154203237518';
|
|
7
|
-
const IMEI_B = '356938035643809';
|
|
8
|
-
|
|
9
|
-
function resolvedAt(
|
|
10
|
-
device: string,
|
|
11
|
-
imei: string,
|
|
12
|
-
path: string,
|
|
13
|
-
extra: Partial<ModemIdentityFacts> = {},
|
|
14
|
-
) {
|
|
15
|
-
return resolveModemIdentity({
|
|
16
|
-
runtimePath: path,
|
|
17
|
-
device,
|
|
18
|
-
equipmentId: imeiEquipmentId(imei),
|
|
19
|
-
...extra,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('IdentityRegistry transitions', () => {
|
|
24
|
-
test('a fresh stable key attaches a new row', () => {
|
|
25
|
-
const registry = new IdentityRegistry();
|
|
26
|
-
const transition = registry.apply(resolvedAt('slot-a', IMEI_A, '/Modem/0'));
|
|
27
|
-
expect(transition.kind).toBe('attached');
|
|
28
|
-
expect(registry.rows).toHaveLength(1);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('replug into the SAME slot keeps ONE row and updates the path', () => {
|
|
32
|
-
const registry = new IdentityRegistry();
|
|
33
|
-
registry.apply(resolvedAt('slot-a', IMEI_A, '/Modem/0'));
|
|
34
|
-
const transition = registry.apply(resolvedAt('slot-a', IMEI_A, '/Modem/7'));
|
|
35
|
-
|
|
36
|
-
expect(transition.kind).toBe('replugged');
|
|
37
|
-
expect(registry.rows).toHaveLength(1);
|
|
38
|
-
expect(registry.rows[0]?.runtimePath).toBe(runtimePath('/Modem/7'));
|
|
39
|
-
if (transition.kind === 'replugged') {
|
|
40
|
-
expect(transition.previousPath).toBe(runtimePath('/Modem/0'));
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('a different modem in the SAME slot is an equipment-swap (slot inherits policy)', () => {
|
|
45
|
-
const registry = new IdentityRegistry();
|
|
46
|
-
registry.apply(resolvedAt('slot-a', IMEI_A, '/Modem/0'));
|
|
47
|
-
const transition = registry.apply(resolvedAt('slot-a', IMEI_B, '/Modem/1'));
|
|
48
|
-
|
|
49
|
-
expect(transition.kind).toBe('equipment-swapped-in-slot');
|
|
50
|
-
expect(registry.rows).toHaveLength(1);
|
|
51
|
-
expect(registry.rows[0]?.logicalSlotId).toBe(logicalSlotId('slot-a'));
|
|
52
|
-
expect(
|
|
53
|
-
registry.rows[0]?.equipmentId.provenance === 'imei' && registry.rows[0]?.equipmentId.value,
|
|
54
|
-
).toBe(IMEI_B);
|
|
55
|
-
if (transition.kind === 'equipment-swapped-in-slot') {
|
|
56
|
-
expect(
|
|
57
|
-
transition.previousEquipment.provenance === 'imei' && transition.previousEquipment.value,
|
|
58
|
-
).toBe(IMEI_A);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('the SAME equipment in a different slot is an equipment-move', () => {
|
|
63
|
-
const registry = new IdentityRegistry();
|
|
64
|
-
registry.apply(resolvedAt('slot-a', IMEI_A, '/Modem/0'));
|
|
65
|
-
const transition = registry.apply(resolvedAt('slot-b', IMEI_A, '/Modem/2'));
|
|
66
|
-
|
|
67
|
-
expect(transition.kind).toBe('equipment-moved');
|
|
68
|
-
expect(registry.rows).toHaveLength(1);
|
|
69
|
-
expect(registry.rows[0]?.logicalSlotId).toBe(logicalSlotId('slot-b'));
|
|
70
|
-
if (transition.kind === 'equipment-moved') {
|
|
71
|
-
expect(transition.previousKey).toBe('slot:slot-a');
|
|
72
|
-
expect(transition.previousSlot).toBe(logicalSlotId('slot-a'));
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('two distinct slots keep two independent rows', () => {
|
|
77
|
-
const registry = new IdentityRegistry();
|
|
78
|
-
registry.apply(resolvedAt('slot-a', IMEI_A, '/Modem/0'));
|
|
79
|
-
registry.apply(resolvedAt('slot-b', IMEI_B, '/Modem/1'));
|
|
80
|
-
expect(registry.rows).toHaveLength(2);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('remove drops a row by stable key', () => {
|
|
84
|
-
const registry = new IdentityRegistry();
|
|
85
|
-
registry.apply(resolvedAt('slot-a', IMEI_A, '/Modem/0'));
|
|
86
|
-
registry.remove('slot:slot-a');
|
|
87
|
-
expect(registry.rows).toHaveLength(0);
|
|
88
|
-
});
|
|
89
|
-
});
|