@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,146 @@
|
|
|
1
|
+
// Raw-payload helpers — the retention half of normalization.
|
|
2
|
+
//
|
|
3
|
+
// Every normalizer builds ONE flat `RawFieldRecord` out of whatever bodies it was
|
|
4
|
+
// handed, keyed by `<body>.<provider-native field>`, and reads its metrics out of
|
|
5
|
+
// that same record. Reading from the retained copy rather than from the original
|
|
6
|
+
// bodies is what makes the no-drop property hold by construction instead of by
|
|
7
|
+
// discipline: a field a metric consumed is necessarily a field the diagnostics block
|
|
8
|
+
// already carries.
|
|
9
|
+
export function rawKey(body, field) {
|
|
10
|
+
return `${body}.${field}`;
|
|
11
|
+
}
|
|
12
|
+
/** Prefix a provider's flat record with the body it came from. */
|
|
13
|
+
export function prefixRawRecord(body, record) {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const [field, value] of Object.entries(record ?? {})) {
|
|
16
|
+
out[rawKey(body, field)] = value;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
export function mergeRawRecords(...records) {
|
|
21
|
+
return Object.assign({}, ...records);
|
|
22
|
+
}
|
|
23
|
+
export function rawString(record, key) {
|
|
24
|
+
const value = record[key];
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
return trimmed === '' ? undefined : trimmed;
|
|
28
|
+
}
|
|
29
|
+
return typeof value === 'number' || typeof value === 'boolean' ? String(value) : undefined;
|
|
30
|
+
}
|
|
31
|
+
export function rawNumber(record, key) {
|
|
32
|
+
const value = record[key];
|
|
33
|
+
if (typeof value === 'number') {
|
|
34
|
+
return Number.isFinite(value) ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const parsed = Number.parseFloat(value);
|
|
40
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
41
|
+
}
|
|
42
|
+
export function rawStringArray(record, key) {
|
|
43
|
+
const value = record[key];
|
|
44
|
+
return Array.isArray(value)
|
|
45
|
+
? value.filter((item) => typeof item === 'string')
|
|
46
|
+
: undefined;
|
|
47
|
+
}
|
|
48
|
+
export function hasRawField(record, key) {
|
|
49
|
+
return Object.hasOwn(record, key);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One member of a D-Bus STRUCT retained verbatim.
|
|
53
|
+
*
|
|
54
|
+
* ModemManager's `SignalQuality` is a `(ub)` and its `CurrentModes` is a `(uu)`, so
|
|
55
|
+
* retaining them verbatim means the raw record holds an ARRAY where a naive read
|
|
56
|
+
* expects a scalar. A caller that flattened the struct before retention would keep the
|
|
57
|
+
* first member and silently drop the second — the recency flag, and the preferred mode
|
|
58
|
+
* — which is the drop this layer exists to prevent. A scalar answers at index 0, so a
|
|
59
|
+
* source that already flattened (mmcli, or a pre-existing fixture) still decodes.
|
|
60
|
+
*/
|
|
61
|
+
export function rawStructMember(record, key, index) {
|
|
62
|
+
const value = record[key];
|
|
63
|
+
if (Array.isArray(value))
|
|
64
|
+
return value[index];
|
|
65
|
+
return index === 0 ? value : undefined;
|
|
66
|
+
}
|
|
67
|
+
export function rawNumberAt(record, key, index) {
|
|
68
|
+
const member = rawStructMember(record, key, index);
|
|
69
|
+
if (typeof member === 'number')
|
|
70
|
+
return Number.isFinite(member) ? member : undefined;
|
|
71
|
+
if (typeof member !== 'string' || member.trim() === '')
|
|
72
|
+
return undefined;
|
|
73
|
+
const parsed = Number.parseFloat(member);
|
|
74
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
75
|
+
}
|
|
76
|
+
export function rawBooleanAt(record, key, index) {
|
|
77
|
+
const member = rawStructMember(record, key, index);
|
|
78
|
+
return typeof member === 'boolean' ? member : undefined;
|
|
79
|
+
}
|
|
80
|
+
const XML_LEAF = /<([A-Za-z_][\w.-]*)>([^<]*)<\/\1>/g;
|
|
81
|
+
/**
|
|
82
|
+
* Flatten every leaf element of an XML body into `<body>.<Tag>` entries.
|
|
83
|
+
*
|
|
84
|
+
* A repeated tag — HiLink's `<NetworkMode>` list repeats `<Index>` once per mode —
|
|
85
|
+
* would otherwise overwrite its predecessors, so the second and later occurrences are
|
|
86
|
+
* suffixed `#2`, `#3`, …. Losing a repeat is exactly the silent drop this layer
|
|
87
|
+
* exists to prevent, and the suffix keeps the original tag name legible.
|
|
88
|
+
*/
|
|
89
|
+
export function flattenXmlBody(body, name) {
|
|
90
|
+
const out = {};
|
|
91
|
+
const seen = new Map();
|
|
92
|
+
for (const match of body.matchAll(XML_LEAF)) {
|
|
93
|
+
const tag = match[1] ?? '';
|
|
94
|
+
const count = (seen.get(tag) ?? 0) + 1;
|
|
95
|
+
seen.set(tag, count);
|
|
96
|
+
out[rawKey(name, count === 1 ? tag : `${tag}#${count}`)] = (match[2] ?? '').trim();
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/** Parse a JSON object body, or `undefined` when the bytes are not a JSON object. */
|
|
101
|
+
export function parseJsonObject(body) {
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(body);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (!(error instanceof SyntaxError)) {
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
113
|
+
? parsed
|
|
114
|
+
: undefined;
|
|
115
|
+
}
|
|
116
|
+
/** Parse a JSON object body into a flat record, or `undefined` when it is not one. */
|
|
117
|
+
export function parseJsonRecord(body) {
|
|
118
|
+
const parsed = parseJsonObject(body);
|
|
119
|
+
if (parsed === undefined) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const [field, value] of Object.entries(parsed)) {
|
|
124
|
+
out[field] = normalizeRawValue(value);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A nested object has no flat representation, so its JSON text is kept.
|
|
130
|
+
*
|
|
131
|
+
* Serializing rather than dropping is the whole contract of this layer: a payload
|
|
132
|
+
* this record cannot model structurally is still a payload a diagnostician can read.
|
|
133
|
+
*/
|
|
134
|
+
export function normalizeRawValue(value) {
|
|
135
|
+
if (value === null) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
return value.map(normalizeRawValue);
|
|
143
|
+
}
|
|
144
|
+
// A nested object has no flat representation; keep its JSON text rather than drop it.
|
|
145
|
+
return JSON.stringify(value);
|
|
146
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { DeviceGeneration, EpochMillis, ObservationAuthority, ObservationEnvelope, SourceEpoch, StableKey } from '../domain/index.js';
|
|
2
|
+
import { type ObservationStaleReason, type ObservationUnavailableReason } from './envelope.js';
|
|
3
|
+
import type { MetricUnknownReason, NormalizedMetric } from './metric.js';
|
|
4
|
+
import type { MetricProvenance } from './provenance.js';
|
|
5
|
+
/** The envelope-level provenance every reading carries, whatever its state. */
|
|
6
|
+
export type EnvelopeProvenance = {
|
|
7
|
+
readonly stableKey: StableKey;
|
|
8
|
+
readonly generation: DeviceGeneration;
|
|
9
|
+
readonly source: string;
|
|
10
|
+
readonly sourceEpoch: SourceEpoch;
|
|
11
|
+
readonly observedAt: EpochMillis;
|
|
12
|
+
readonly authority: ObservationAuthority;
|
|
13
|
+
};
|
|
14
|
+
export declare function envelopeProvenance<T>(envelope: ObservationEnvelope<T>): EnvelopeProvenance;
|
|
15
|
+
type ReadingBase = {
|
|
16
|
+
readonly envelope: EnvelopeProvenance;
|
|
17
|
+
};
|
|
18
|
+
export type ObservationReading<T> = (ReadingBase & {
|
|
19
|
+
readonly state: 'fresh';
|
|
20
|
+
readonly value: T;
|
|
21
|
+
readonly provenance: MetricProvenance;
|
|
22
|
+
}) | (ReadingBase & {
|
|
23
|
+
readonly state: 'stale';
|
|
24
|
+
readonly value: T;
|
|
25
|
+
readonly since: EpochMillis;
|
|
26
|
+
readonly reason: ObservationStaleReason;
|
|
27
|
+
readonly provenance: MetricProvenance;
|
|
28
|
+
}) | (ReadingBase & {
|
|
29
|
+
readonly state: 'unavailable';
|
|
30
|
+
readonly since: EpochMillis;
|
|
31
|
+
readonly reason: ObservationUnavailableReason;
|
|
32
|
+
}) | (ReadingBase & {
|
|
33
|
+
readonly state: 'unknown';
|
|
34
|
+
readonly reason: MetricUnknownReason;
|
|
35
|
+
readonly provenance: MetricProvenance;
|
|
36
|
+
});
|
|
37
|
+
export declare const OBSERVATION_READING_STATES: readonly ['fresh', 'stale', 'unavailable', 'unknown'];
|
|
38
|
+
export type ObservationReadingState = (typeof OBSERVATION_READING_STATES)[number];
|
|
39
|
+
/**
|
|
40
|
+
* Project one metric out of an envelope.
|
|
41
|
+
*
|
|
42
|
+
* An unknown metric inside a STALE envelope reads `unknown`, not `stale`: staleness is
|
|
43
|
+
* a statement about a value's age, and there is no value here to have aged. The
|
|
44
|
+
* envelope's own age remains readable through `reading.envelope.observedAt`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readMetric<T, V>(envelope: ObservationEnvelope<T>, select: (value: T) => NormalizedMetric<V>): ObservationReading<V>;
|
|
47
|
+
/** Whether a reading carries a value a consumer may render. */
|
|
48
|
+
export declare function hasReadableValue<T>(reading: ObservationReading<T>): reading is Extract<ObservationReading<T>, {
|
|
49
|
+
readonly state: 'fresh' | 'stale';
|
|
50
|
+
}>;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Reading — the projection a consumer renders from.
|
|
2
|
+
//
|
|
3
|
+
// It is the one place the envelope's freshness and the metric's knownness are folded
|
|
4
|
+
// together, and it keeps FOUR genuinely distinct outcomes rather than a value plus a
|
|
5
|
+
// flag:
|
|
6
|
+
//
|
|
7
|
+
// `fresh` — a current value.
|
|
8
|
+
// `stale` — a RETAINED value that has aged out, with when and why.
|
|
9
|
+
// `unavailable` — there is no observation at all; no value exists to report.
|
|
10
|
+
// `unknown` — there IS an observation and this field is not in it, with a reason
|
|
11
|
+
// that says whether the source CANNOT report it or merely DID NOT.
|
|
12
|
+
//
|
|
13
|
+
// They differ in shape, not only in label: `unavailable` and `unknown` carry no
|
|
14
|
+
// `value` field at all, so no consumer can read one off a state that has none, and
|
|
15
|
+
// `unavailable` carries no metric provenance because no metric was produced.
|
|
16
|
+
import { viewEnvelope, } from './envelope.js';
|
|
17
|
+
export function envelopeProvenance(envelope) {
|
|
18
|
+
return {
|
|
19
|
+
stableKey: envelope.stableKey,
|
|
20
|
+
generation: envelope.generation,
|
|
21
|
+
source: envelope.source,
|
|
22
|
+
sourceEpoch: envelope.sourceEpoch,
|
|
23
|
+
observedAt: envelope.observedAt,
|
|
24
|
+
authority: envelope.authority,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export const OBSERVATION_READING_STATES = ['fresh', 'stale', 'unavailable', 'unknown'];
|
|
28
|
+
/**
|
|
29
|
+
* Project one metric out of an envelope.
|
|
30
|
+
*
|
|
31
|
+
* An unknown metric inside a STALE envelope reads `unknown`, not `stale`: staleness is
|
|
32
|
+
* a statement about a value's age, and there is no value here to have aged. The
|
|
33
|
+
* envelope's own age remains readable through `reading.envelope.observedAt`.
|
|
34
|
+
*/
|
|
35
|
+
export function readMetric(envelope, select) {
|
|
36
|
+
const base = { envelope: envelopeProvenance(envelope) };
|
|
37
|
+
const view = viewEnvelope(envelope);
|
|
38
|
+
if (view.kind === 'unavailable') {
|
|
39
|
+
return {
|
|
40
|
+
...base,
|
|
41
|
+
state: 'unavailable',
|
|
42
|
+
since: view.freshness.since,
|
|
43
|
+
reason: view.freshness.reason,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const metric = select(view.value);
|
|
47
|
+
if (metric.state === 'unknown') {
|
|
48
|
+
return { ...base, state: 'unknown', reason: metric.reason, provenance: metric.provenance };
|
|
49
|
+
}
|
|
50
|
+
if (view.freshness.state === 'stale') {
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
state: 'stale',
|
|
54
|
+
value: metric.value,
|
|
55
|
+
since: view.freshness.since,
|
|
56
|
+
reason: view.freshness.reason,
|
|
57
|
+
provenance: metric.provenance,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { ...base, state: 'fresh', value: metric.value, provenance: metric.provenance };
|
|
61
|
+
}
|
|
62
|
+
/** Whether a reading carries a value a consumer may render. */
|
|
63
|
+
export function hasReadableValue(reading) {
|
|
64
|
+
return reading.state === 'fresh' || reading.state === 'stale';
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ObservationEnvelope } from '../../domain/index.js';
|
|
2
|
+
import { type NormalizationContext } from '../envelope.js';
|
|
3
|
+
import type { NormalizedModemObservation } from '../model.js';
|
|
4
|
+
export type HilinkObservationInput = {
|
|
5
|
+
readonly status: string;
|
|
6
|
+
readonly signal: string;
|
|
7
|
+
readonly netModeList?: string;
|
|
8
|
+
readonly netMode?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function normalizeHilinkObservation(input: HilinkObservationInput, context: NormalizationContext): ObservationEnvelope<NormalizedModemObservation>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Huawei HiLink admin API → normalized observation.
|
|
2
|
+
//
|
|
3
|
+
// The decoding is the migrated `parseHilinkSignal` / `parseHilinkCapabilities`; this
|
|
4
|
+
// module adds provenance, retains both XML bodies field-for-field, and states the
|
|
5
|
+
// capability boundary honestly. HiLink is a ROUTER admin API, not a modem stack: it
|
|
6
|
+
// has no ModemManager state, no 3GPP registration state and no access-technology
|
|
7
|
+
// bitmask, so those read `unsupported` — a claim about this source, which stays true
|
|
8
|
+
// on the next poll — while a field the API could carry and did not reads
|
|
9
|
+
// `not-reported`.
|
|
10
|
+
//
|
|
11
|
+
// Its `<ConnectionStatus>` and `<SimStatus>` are NOT folded into `radio.registration`
|
|
12
|
+
// or `sim.presence`. They are vendor codes with vendor semantics and no migrated
|
|
13
|
+
// decoder claims them, so they stay verbatim in the diagnostics block instead of
|
|
14
|
+
// being guessed into a normalized field.
|
|
15
|
+
import { parseHilinkCapabilities, parseHilinkSignal } from '../../hardware/router-parsers.js';
|
|
16
|
+
import { freshObservation, metricProvenance } from '../envelope.js';
|
|
17
|
+
import { knownMetric, metricFromRouterSignal, metricUnknownReasonFromRouter, unknownMetric, } from '../metric.js';
|
|
18
|
+
import { createObservationDiagnostics } from '../provenance.js';
|
|
19
|
+
import { flattenXmlBody, hasRawField, mergeRawRecords, rawKey } from '../raw.js';
|
|
20
|
+
import { routerHardware, routerSim, unsupportedQualityRecent, unsupportedRadioMetric, } from './router-shared.js';
|
|
21
|
+
const SOURCE = 'huawei-hilink';
|
|
22
|
+
const STATUS = 'monitoring-status';
|
|
23
|
+
const SIGNAL = 'device-signal';
|
|
24
|
+
const NET_MODE_LIST = 'net-mode-list';
|
|
25
|
+
const NET_MODE = 'net-mode';
|
|
26
|
+
/** HiLink's own SIM presence code. Named as evidence, never decoded into a presence. */
|
|
27
|
+
const SIM_STATUS = rawKey(STATUS, 'SimStatus');
|
|
28
|
+
const AUTH_REFUSAL_CODE = '125002';
|
|
29
|
+
export function normalizeHilinkObservation(input, context) {
|
|
30
|
+
const raw = mergeRawRecords(flattenXmlBody(input.status, STATUS), flattenXmlBody(input.signal, SIGNAL), flattenXmlBody(input.netModeList ?? '', NET_MODE_LIST), flattenXmlBody(input.netMode ?? '', NET_MODE));
|
|
31
|
+
const notes = [];
|
|
32
|
+
const consumed = [];
|
|
33
|
+
const provenance = (fields, authority) => {
|
|
34
|
+
consumed.push(...fields);
|
|
35
|
+
return authority === undefined
|
|
36
|
+
? metricProvenance(SOURCE, context, fields)
|
|
37
|
+
: metricProvenance(SOURCE, context, fields, authority);
|
|
38
|
+
};
|
|
39
|
+
for (const [body, text] of [
|
|
40
|
+
[STATUS, input.status],
|
|
41
|
+
[SIGNAL, input.signal],
|
|
42
|
+
]) {
|
|
43
|
+
if (text.includes(`<code>${AUTH_REFUSAL_CODE}</code>`)) {
|
|
44
|
+
notes.push({ code: 'auth-expired', field: rawKey(body, 'code') });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const signalModel = parseHilinkSignal({ status: input.status, signal: input.signal });
|
|
48
|
+
const capabilities = parseHilinkCapabilities({
|
|
49
|
+
netModeList: input.netModeList ?? '',
|
|
50
|
+
...(input.netMode === undefined ? {} : { netMode: input.netMode }),
|
|
51
|
+
});
|
|
52
|
+
return freshObservation(SOURCE, context, {
|
|
53
|
+
source: SOURCE,
|
|
54
|
+
hardware: routerHardware(provenance, undefined),
|
|
55
|
+
radio: {
|
|
56
|
+
modemState: unsupportedRadioMetric(provenance),
|
|
57
|
+
registration: unsupportedRadioMetric(provenance),
|
|
58
|
+
accessTechnologies: unknownMetric('unsupported', provenance([])),
|
|
59
|
+
modeLabel: normalizeModeLabel(capabilities.net_mode, provenance),
|
|
60
|
+
},
|
|
61
|
+
signal: {
|
|
62
|
+
quality: unknownMetric('unsupported', provenance([])),
|
|
63
|
+
qualityRecent: unsupportedQualityRecent(provenance),
|
|
64
|
+
bars: metricFromRouterSignal(signalModel.bars, provenance([rawKey(STATUS, 'SignalIcon')], 'derived')),
|
|
65
|
+
maxBars: metricFromRouterSignal(signalModel.max_bars, provenance([rawKey(STATUS, 'maxsignal')], 'derived')),
|
|
66
|
+
dbm: metricFromRouterSignal(signalModel.dbm, provenance([rawKey(SIGNAL, 'rssi')])),
|
|
67
|
+
rsrp: metricFromRouterSignal(signalModel.rsrp, provenance([rawKey(SIGNAL, 'rsrp')])),
|
|
68
|
+
rsrq: metricFromRouterSignal(signalModel.rsrq, provenance([rawKey(SIGNAL, 'rsrq')])),
|
|
69
|
+
snr: metricFromRouterSignal(signalModel.snr, provenance([])),
|
|
70
|
+
sinr: metricFromRouterSignal(signalModel.sinr, provenance([rawKey(SIGNAL, 'sinr')])),
|
|
71
|
+
},
|
|
72
|
+
sim: routerSim(provenance, hasRawField(raw, SIM_STATUS) ? SIM_STATUS : undefined),
|
|
73
|
+
diagnostics: createObservationDiagnostics({ source: SOURCE, raw, consumed, notes }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function normalizeModeLabel(capability, provenance) {
|
|
77
|
+
const source = provenance([rawKey(NET_MODE, 'NetworkMode')]);
|
|
78
|
+
if (capability.state === 'unavailable') {
|
|
79
|
+
return unknownMetric(metricUnknownReasonFromRouter(capability.reason), source);
|
|
80
|
+
}
|
|
81
|
+
return capability.current === undefined
|
|
82
|
+
? unknownMetric('not-reported', source)
|
|
83
|
+
: knownMetric(capability.current, source);
|
|
84
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ObservationEnvelope } from '../../domain/index.js';
|
|
2
|
+
import { type NormalizationContext } from '../envelope.js';
|
|
3
|
+
import type { NormalizedModemObservation } from '../model.js';
|
|
4
|
+
import { type RawFieldValue } from '../provenance.js';
|
|
5
|
+
/**
|
|
6
|
+
* The decoded ModemManager property records, one per D-Bus interface.
|
|
7
|
+
*
|
|
8
|
+
* A body left out is a body nobody read, and reads as `not-observed` rather than as a
|
|
9
|
+
* modem that reports nothing — that difference decides whether a consumer retries or
|
|
10
|
+
* hides a control.
|
|
11
|
+
*/
|
|
12
|
+
export type ModemManagerObservationInput = {
|
|
13
|
+
readonly modem?: Readonly<Record<string, RawFieldValue>>;
|
|
14
|
+
readonly modem3gpp?: Readonly<Record<string, RawFieldValue>>;
|
|
15
|
+
readonly sim?: Readonly<Record<string, RawFieldValue>>;
|
|
16
|
+
readonly signal?: Readonly<Record<string, RawFieldValue>>;
|
|
17
|
+
};
|
|
18
|
+
export declare function normalizeModemManagerObservation(input: ModemManagerObservationInput, context: NormalizationContext): ObservationEnvelope<NormalizedModemObservation>;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// ModemManager → normalized observation.
|
|
2
|
+
//
|
|
3
|
+
// Every decode here is the MIGRATED logic (`domain/mm-enums.ts`,
|
|
4
|
+
// `domain/modem-presentation.ts`, `hardware/router-parsers.ts` for SIM presence) —
|
|
5
|
+
// this module adds provenance and reason-carrying unknowns, and decodes nothing
|
|
6
|
+
// itself. Where a decoder answers `'unknown'` or `undefined`, the distinction it
|
|
7
|
+
// cannot make is made here from the RAW record: a field that was never in the payload
|
|
8
|
+
// is `not-reported`, an interface nobody read is `not-observed`, and a field that WAS
|
|
9
|
+
// present but decoded to nothing is `malformed` with a diagnostic note. Collapsing
|
|
10
|
+
// those three into one absent value is precisely the loss this layer exists to stop.
|
|
11
|
+
import { decodeEsimStatus, decodeMmAccessTechnologies, decodeMmState, decodeRegistrationState, decodeSimType, decodeStateFailedReason, decodeUnlockRequired, modeMaskToLabel, modemHardwareLabel, modemHardwareName, } from '../../domain/index.js';
|
|
12
|
+
import { readSimPresence } from '../../hardware/router-parsers.js';
|
|
13
|
+
import { freshObservation, metricProvenance } from '../envelope.js';
|
|
14
|
+
import { knownMetric, unknownMetric } from '../metric.js';
|
|
15
|
+
import { createObservationDiagnostics, } from '../provenance.js';
|
|
16
|
+
import { hasRawField, mergeRawRecords, prefixRawRecord, rawBooleanAt, rawKey, rawNumber, rawNumberAt, rawString, rawStringArray, } from '../raw.js';
|
|
17
|
+
const SOURCE = 'modemmanager';
|
|
18
|
+
const MODEM = 'Modem';
|
|
19
|
+
const MODEM3GPP = 'Modem3gpp';
|
|
20
|
+
const SIM = 'Sim';
|
|
21
|
+
const SIGNAL = 'Signal';
|
|
22
|
+
export function normalizeModemManagerObservation(input, context) {
|
|
23
|
+
const raw = mergeRawRecords(prefixRawRecord(MODEM, input.modem), prefixRawRecord(MODEM3GPP, input.modem3gpp), prefixRawRecord(SIM, input.sim), prefixRawRecord(SIGNAL, input.signal));
|
|
24
|
+
const notes = [];
|
|
25
|
+
const consumed = [];
|
|
26
|
+
const provenance = (...fields) => {
|
|
27
|
+
consumed.push(...fields);
|
|
28
|
+
return metricProvenance(SOURCE, context, fields);
|
|
29
|
+
};
|
|
30
|
+
const hardware = normalizeHardware(raw, provenance);
|
|
31
|
+
const radio = normalizeRadio(raw, provenance, notes);
|
|
32
|
+
const signal = normalizeSignal(raw, input.signal !== undefined, provenance);
|
|
33
|
+
const sim = normalizeSim(raw, input.sim !== undefined, provenance, notes);
|
|
34
|
+
return freshObservation(SOURCE, context, {
|
|
35
|
+
source: SOURCE,
|
|
36
|
+
hardware,
|
|
37
|
+
radio,
|
|
38
|
+
signal,
|
|
39
|
+
sim,
|
|
40
|
+
diagnostics: createObservationDiagnostics({ source: SOURCE, raw, consumed, notes }),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const MODEL = rawKey(MODEM, 'Model');
|
|
44
|
+
const MANUFACTURER = rawKey(MODEM, 'Manufacturer');
|
|
45
|
+
const REVISION = rawKey(MODEM, 'Revision');
|
|
46
|
+
const EQUIPMENT_ID = rawKey(MODEM, 'EquipmentIdentifier');
|
|
47
|
+
const STATE = rawKey(MODEM, 'State');
|
|
48
|
+
const CURRENT_MODES = rawKey(MODEM, 'CurrentModes');
|
|
49
|
+
const ACCESS_TECHNOLOGIES = rawKey(MODEM, 'AccessTechnologies');
|
|
50
|
+
const SIGNAL_QUALITY = rawKey(MODEM, 'SignalQuality');
|
|
51
|
+
const SIM_PATH = rawKey(MODEM, 'Sim');
|
|
52
|
+
const SIM_SLOTS = rawKey(MODEM, 'SimSlots');
|
|
53
|
+
const FAILED_REASON = rawKey(MODEM, 'StateFailedReason');
|
|
54
|
+
const UNLOCK_REQUIRED = rawKey(MODEM, 'UnlockRequired');
|
|
55
|
+
const REGISTRATION_STATE = rawKey(MODEM3GPP, 'RegistrationState');
|
|
56
|
+
const SIM_TYPE = rawKey(SIM, 'SimType');
|
|
57
|
+
const ESIM_STATUS = rawKey(SIM, 'EsimStatus');
|
|
58
|
+
const RSSI = rawKey(SIGNAL, 'rssi');
|
|
59
|
+
const RSRP = rawKey(SIGNAL, 'rsrp');
|
|
60
|
+
const RSRQ = rawKey(SIGNAL, 'rsrq');
|
|
61
|
+
const SNR = rawKey(SIGNAL, 'snr');
|
|
62
|
+
function hardwareIdentity(raw) {
|
|
63
|
+
const model = rawString(raw, MODEL);
|
|
64
|
+
const manufacturer = rawString(raw, MANUFACTURER);
|
|
65
|
+
const firmwareRevision = rawString(raw, REVISION);
|
|
66
|
+
const equipmentId = rawString(raw, EQUIPMENT_ID);
|
|
67
|
+
return {
|
|
68
|
+
...(model === undefined ? {} : { model }),
|
|
69
|
+
...(manufacturer === undefined ? {} : { manufacturer }),
|
|
70
|
+
...(firmwareRevision === undefined ? {} : { firmwareRevision }),
|
|
71
|
+
...(equipmentId === undefined ? {} : { equipmentId }),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function normalizeHardware(raw, provenance) {
|
|
75
|
+
const identity = hardwareIdentity(raw);
|
|
76
|
+
const named = identity.model !== undefined ||
|
|
77
|
+
identity.manufacturer !== undefined ||
|
|
78
|
+
identity.firmwareRevision !== undefined;
|
|
79
|
+
const labelProvenance = provenance(MODEL, MANUFACTURER, REVISION);
|
|
80
|
+
const nameProvenance = provenance(MODEL, MANUFACTURER, REVISION, EQUIPMENT_ID);
|
|
81
|
+
return {
|
|
82
|
+
label: named
|
|
83
|
+
? knownMetric(modemHardwareLabel(identity), labelProvenance)
|
|
84
|
+
: unknownMetric('not-reported', labelProvenance),
|
|
85
|
+
displayName: named
|
|
86
|
+
? knownMetric(modemHardwareName(identity), nameProvenance)
|
|
87
|
+
: unknownMetric('not-reported', nameProvenance),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function normalizeRadio(raw, provenance, notes) {
|
|
91
|
+
return {
|
|
92
|
+
modemState: decodedLabel(raw, STATE, decodeMmState, provenance, notes),
|
|
93
|
+
registration: decodedLabel(raw, REGISTRATION_STATE, decodeRegistrationState, provenance, notes),
|
|
94
|
+
accessTechnologies: normalizeAccessTechnologies(raw, provenance, notes),
|
|
95
|
+
modeLabel: normalizeModeLabel(raw, provenance, notes),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** A decoder whose "I could not place this" answer is the literal string `unknown`. */
|
|
99
|
+
function decodedLabel(raw, key, decode, provenance, notes) {
|
|
100
|
+
const source = provenance(key);
|
|
101
|
+
if (!hasRawField(raw, key)) {
|
|
102
|
+
return unknownMetric('not-reported', source);
|
|
103
|
+
}
|
|
104
|
+
const decoded = decode(rawNumber(raw, key));
|
|
105
|
+
if (decoded === 'unknown') {
|
|
106
|
+
notes.push({ code: 'field-shape-unrecognized', field: key });
|
|
107
|
+
return unknownMetric('malformed', source);
|
|
108
|
+
}
|
|
109
|
+
return knownMetric(decoded, source);
|
|
110
|
+
}
|
|
111
|
+
function normalizeAccessTechnologies(raw, provenance, notes) {
|
|
112
|
+
const source = provenance(ACCESS_TECHNOLOGIES);
|
|
113
|
+
if (!hasRawField(raw, ACCESS_TECHNOLOGIES)) {
|
|
114
|
+
return unknownMetric('not-reported', source);
|
|
115
|
+
}
|
|
116
|
+
const mask = rawNumber(raw, ACCESS_TECHNOLOGIES);
|
|
117
|
+
// MM's 0 is `UNKNOWN` — the modem did not say, which is not a malformed answer.
|
|
118
|
+
if (mask === undefined || mask <= 0) {
|
|
119
|
+
return unknownMetric('not-reported', source);
|
|
120
|
+
}
|
|
121
|
+
const decoded = [...decodeMmAccessTechnologies(mask)].sort();
|
|
122
|
+
if (decoded.length === 0) {
|
|
123
|
+
notes.push({ code: 'field-shape-unrecognized', field: ACCESS_TECHNOLOGIES });
|
|
124
|
+
return unknownMetric('malformed', source);
|
|
125
|
+
}
|
|
126
|
+
return knownMetric(decoded, source);
|
|
127
|
+
}
|
|
128
|
+
function normalizeModeLabel(raw, provenance, notes) {
|
|
129
|
+
const source = provenance(CURRENT_MODES);
|
|
130
|
+
if (!hasRawField(raw, CURRENT_MODES)) {
|
|
131
|
+
return unknownMetric('not-reported', source);
|
|
132
|
+
}
|
|
133
|
+
// `CurrentModes` is a `(uu)`; the ALLOWED mask is member 0. A flattened scalar
|
|
134
|
+
// (mmcli's shape) decodes at the same index, so both retentions work here.
|
|
135
|
+
const mask = rawNumberAt(raw, CURRENT_MODES, 0);
|
|
136
|
+
if (mask === undefined || mask <= 0) {
|
|
137
|
+
return unknownMetric('not-reported', source);
|
|
138
|
+
}
|
|
139
|
+
const label = modeMaskToLabel(mask);
|
|
140
|
+
if (label === undefined) {
|
|
141
|
+
notes.push({ code: 'field-shape-unrecognized', field: CURRENT_MODES });
|
|
142
|
+
return unknownMetric('malformed', source);
|
|
143
|
+
}
|
|
144
|
+
return knownMetric(label, source);
|
|
145
|
+
}
|
|
146
|
+
function normalizeSignal(raw, signalRead, provenance) {
|
|
147
|
+
const missing = signalRead ? 'not-reported' : 'not-observed';
|
|
148
|
+
const extended = (key) => {
|
|
149
|
+
const source = provenance(key);
|
|
150
|
+
const value = rawNumber(raw, key);
|
|
151
|
+
return value === undefined
|
|
152
|
+
? unknownMetric(hasRawField(raw, key) ? 'malformed' : missing, source)
|
|
153
|
+
: knownMetric(value, source);
|
|
154
|
+
};
|
|
155
|
+
// `SignalQuality` is a `(ub)`: percentage at 0, "measured recently" at 1. Both are
|
|
156
|
+
// claimed; a source that retained only the percentage answers `not-reported` for the
|
|
157
|
+
// flag, which is a claim about the READ, never about ModemManager's capability.
|
|
158
|
+
const quality = rawNumberAt(raw, SIGNAL_QUALITY, 0);
|
|
159
|
+
const recent = rawBooleanAt(raw, SIGNAL_QUALITY, 1);
|
|
160
|
+
const qualityProvenance = provenance(SIGNAL_QUALITY);
|
|
161
|
+
return {
|
|
162
|
+
quality: quality === undefined
|
|
163
|
+
? unknownMetric('not-reported', qualityProvenance)
|
|
164
|
+
: knownMetric(quality, qualityProvenance),
|
|
165
|
+
qualityRecent: recent === undefined
|
|
166
|
+
? unknownMetric('not-reported', provenance(SIGNAL_QUALITY))
|
|
167
|
+
: knownMetric(recent, provenance(SIGNAL_QUALITY)),
|
|
168
|
+
// ModemManager reports a percentage and no bar scale. Deriving bars from the
|
|
169
|
+
// percentage would be inventing a reading, so this is a capability claim.
|
|
170
|
+
bars: unknownMetric('unsupported', metricProvenanceEmpty(provenance)),
|
|
171
|
+
maxBars: unknownMetric('unsupported', metricProvenanceEmpty(provenance)),
|
|
172
|
+
dbm: extended(RSSI),
|
|
173
|
+
rsrp: extended(RSRP),
|
|
174
|
+
rsrq: extended(RSRQ),
|
|
175
|
+
snr: extended(SNR),
|
|
176
|
+
// `Modem.Signal` exposes rssi/rsrp/rsrq/snr/ecio/io/rscp — there is no SINR member.
|
|
177
|
+
sinr: unknownMetric('unsupported', metricProvenanceEmpty(provenance)),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Provenance for a metric no raw field backs, which is what `unsupported` means. */
|
|
181
|
+
function metricProvenanceEmpty(provenance) {
|
|
182
|
+
return provenance();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* `Modem.StateFailedReason` in the spelling the presence rule matches.
|
|
186
|
+
*
|
|
187
|
+
* D-Bus types it `u`, so a provider retaining the property verbatim holds a NUMBER,
|
|
188
|
+
* while the migrated rule matches the mmcli STRING. Both are accepted here — the
|
|
189
|
+
* number through the enum decoder, the string as it stands — because the two transports
|
|
190
|
+
* for the same fact must not disagree about whether a modem has a SIM. An
|
|
191
|
+
* unrecognized number decodes to `undefined` and simply proves nothing.
|
|
192
|
+
*/
|
|
193
|
+
function failedReasonOf(raw) {
|
|
194
|
+
const numeric = rawNumber(raw, FAILED_REASON);
|
|
195
|
+
if (numeric !== undefined)
|
|
196
|
+
return decodeStateFailedReason(numeric);
|
|
197
|
+
return rawString(raw, FAILED_REASON);
|
|
198
|
+
}
|
|
199
|
+
function simPresenceFacts(raw) {
|
|
200
|
+
const sim = rawString(raw, SIM_PATH);
|
|
201
|
+
const simSlots = rawStringArray(raw, SIM_SLOTS);
|
|
202
|
+
const failedReason = failedReasonOf(raw);
|
|
203
|
+
return {
|
|
204
|
+
...(sim === undefined ? {} : { sim }),
|
|
205
|
+
...(simSlots === undefined ? {} : { simSlots }),
|
|
206
|
+
...(failedReason === undefined ? {} : { failedReason }),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function normalizeSim(raw, simRead, provenance, notes) {
|
|
210
|
+
const presenceProvenance = provenance(SIM_PATH, SIM_SLOTS, FAILED_REASON);
|
|
211
|
+
// `absent` here comes from `state-failed-reason` evidence and from nothing else.
|
|
212
|
+
// A blank `Sim` object path with no failure reason stays `unknown`: ModemManager
|
|
213
|
+
// reports `/` while a modem is initializing and while a slot switch is in flight,
|
|
214
|
+
// so reading it as "no SIM" would report an absent SIM on a modem holding one.
|
|
215
|
+
const { presence, evidence } = readSimPresence(simPresenceFacts(raw));
|
|
216
|
+
const missing = simRead ? 'not-reported' : 'not-observed';
|
|
217
|
+
return {
|
|
218
|
+
presence: presence === 'unknown'
|
|
219
|
+
? unknownMetric('not-reported', presenceProvenance)
|
|
220
|
+
: knownMetric(presence, presenceProvenance),
|
|
221
|
+
presenceEvidence: evidence,
|
|
222
|
+
lockRequired: decodedOptional(raw, UNLOCK_REQUIRED, decodeUnlockRequired, provenance, notes),
|
|
223
|
+
kind: decodedOptional(raw, SIM_TYPE, decodeSimType, provenance, notes, missing),
|
|
224
|
+
esimStatus: decodedOptional(raw, ESIM_STATUS, decodeEsimStatus, provenance, notes, missing),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** A decoder whose "I could not place this" answer is `undefined`. */
|
|
228
|
+
function decodedOptional(raw, key, decode, provenance, notes, absentReason = 'not-reported') {
|
|
229
|
+
const source = provenance(key);
|
|
230
|
+
if (!hasRawField(raw, key)) {
|
|
231
|
+
return unknownMetric(absentReason, source);
|
|
232
|
+
}
|
|
233
|
+
const decoded = decode(rawNumber(raw, key));
|
|
234
|
+
if (decoded === undefined) {
|
|
235
|
+
notes.push({ code: 'field-shape-unrecognized', field: key });
|
|
236
|
+
return unknownMetric('malformed', source);
|
|
237
|
+
}
|
|
238
|
+
return knownMetric(decoded, source);
|
|
239
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ObservationAuthority } from '../../domain/index.js';
|
|
2
|
+
import { type NormalizedMetric } from '../metric.js';
|
|
3
|
+
import type { NormalizedHardware, NormalizedSim } from '../model.js';
|
|
4
|
+
import type { MetricProvenance } from '../provenance.js';
|
|
5
|
+
export type RouterProvenance = (fields: readonly string[], authority?: ObservationAuthority) => MetricProvenance;
|
|
6
|
+
export declare function unsupportedRadioMetric(provenance: RouterProvenance): NormalizedMetric<string>;
|
|
7
|
+
/**
|
|
8
|
+
* The SIM block every router source produces.
|
|
9
|
+
*
|
|
10
|
+
* `vendorPresenceField` NAMES the vendor's own presence code when the payload carried
|
|
11
|
+
* one, so the evidence records that a code exists and was deliberately not decoded —
|
|
12
|
+
* distinct from `no-evidence`, which says the payload offered nothing at all. Neither
|
|
13
|
+
* one is ever `absent`: claiming absence from a vendor code whose semantics no
|
|
14
|
+
* migrated decoder covers is precisely the invented reading this layer refuses.
|
|
15
|
+
*/
|
|
16
|
+
export declare function routerSim(provenance: RouterProvenance, vendorPresenceField?: string): NormalizedSim;
|
|
17
|
+
/** Router admin APIs report no measurement-recency flag; that is a source capability claim. */
|
|
18
|
+
export declare function unsupportedQualityRecent(provenance: RouterProvenance): NormalizedMetric<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* A router's product name, screened by the migrated presentation rules.
|
|
21
|
+
*
|
|
22
|
+
* `modemHardwareLabel` falls back to a generic label when its input says nothing, and
|
|
23
|
+
* publishing that as a KNOWN value would be reporting a name no device gave. So an
|
|
24
|
+
* uninformative product string is refused up front and reads `not-reported` instead.
|
|
25
|
+
*/
|
|
26
|
+
export declare function routerHardware(provenance: RouterProvenance, product: {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly field: string;
|
|
29
|
+
} | undefined): NormalizedHardware;
|