@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,76 @@
|
|
|
1
|
+
// Freshness evaluation — how a retained observation ages, and what it never becomes.
|
|
2
|
+
//
|
|
3
|
+
// Three rules carry the weight here:
|
|
4
|
+
//
|
|
5
|
+
// 1. **Staleness keeps the value.** An aged reading is the last thing the device
|
|
6
|
+
// actually said; discarding it leaves an operator with a blank field and no way
|
|
7
|
+
// to tell "we lost contact" from "the modem reports nothing".
|
|
8
|
+
// 2. **Unavailable is terminal on re-evaluation.** An envelope that carries no value
|
|
9
|
+
// cannot become stale, because there is nothing to age. Re-classifying it would
|
|
10
|
+
// have to invent a value to be stale about.
|
|
11
|
+
// 3. **Staleness is monotonic.** A stale envelope is returned UNCHANGED, so its
|
|
12
|
+
// `since` and `reason` record the FIRST cause. Freshness comes from a new read,
|
|
13
|
+
// never from re-evaluating an old one.
|
|
14
|
+
//
|
|
15
|
+
// This module has no clock: the caller supplies `at` on every evaluation, which is
|
|
16
|
+
// what makes the whole window testable without waiting. Same discipline as
|
|
17
|
+
// `location/fix-state.ts`.
|
|
18
|
+
import { epochMillis, isCurrentGeneration, } from '../domain/index.js';
|
|
19
|
+
import { viewEnvelope } from './envelope.js';
|
|
20
|
+
/** How long ago an envelope was observed, floored at zero for a clock that moved back. */
|
|
21
|
+
export function observationAgeMs(envelope, at) {
|
|
22
|
+
return Math.max(0, at - envelope.observedAt);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Re-classify an envelope against the current time, epoch, generation and source
|
|
26
|
+
* health.
|
|
27
|
+
*
|
|
28
|
+
* Trigger precedence, when several apply at once: superseded generation → superseded
|
|
29
|
+
* source epoch → degraded source → TTL expiry. The first three are positive statements
|
|
30
|
+
* that the reading has been overtaken by a newer reality; TTL expiry only says nobody
|
|
31
|
+
* has looked recently, and reporting it over a supersession would understate why.
|
|
32
|
+
*/
|
|
33
|
+
export function evaluateFreshness(envelope, evaluation) {
|
|
34
|
+
const view = viewEnvelope(envelope);
|
|
35
|
+
if (view.kind === 'unavailable' || view.freshness.state !== 'fresh') {
|
|
36
|
+
return envelope;
|
|
37
|
+
}
|
|
38
|
+
const reason = staleReason(envelope, evaluation);
|
|
39
|
+
if (reason === undefined) {
|
|
40
|
+
return envelope;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
...envelope,
|
|
44
|
+
freshness: { state: 'stale', since: staleSince(envelope, evaluation, reason), reason },
|
|
45
|
+
value: view.value,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function staleReason(envelope, evaluation) {
|
|
49
|
+
if (evaluation.currentGeneration !== undefined &&
|
|
50
|
+
!isCurrentGeneration(envelope.generation, evaluation.currentGeneration)) {
|
|
51
|
+
return 'source-epoch-superseded';
|
|
52
|
+
}
|
|
53
|
+
if (evaluation.currentSourceEpoch !== undefined &&
|
|
54
|
+
envelope.sourceEpoch !== evaluation.currentSourceEpoch) {
|
|
55
|
+
return 'source-epoch-superseded';
|
|
56
|
+
}
|
|
57
|
+
if (evaluation.sourceHealthy === false) {
|
|
58
|
+
return 'source-degraded';
|
|
59
|
+
}
|
|
60
|
+
return observationAgeMs(envelope, evaluation.at) > evaluation.window.ttlMs
|
|
61
|
+
? 'ttl-expired'
|
|
62
|
+
: undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* When the reading became stale.
|
|
66
|
+
*
|
|
67
|
+
* A TTL expiry has an exact moment — one window after it was observed — and reporting
|
|
68
|
+
* the evaluation time instead would make a reading that expired an hour ago look like
|
|
69
|
+
* it just went stale. A supersession or a degradation is only known at the moment it
|
|
70
|
+
* is evaluated, so those honestly report `at`.
|
|
71
|
+
*/
|
|
72
|
+
function staleSince(envelope, evaluation, reason) {
|
|
73
|
+
return reason === 'ttl-expired'
|
|
74
|
+
? epochMillis(envelope.observedAt + evaluation.window.ttlMs)
|
|
75
|
+
: evaluation.at;
|
|
76
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './envelope.js';
|
|
2
|
+
export * from './freshness.js';
|
|
3
|
+
export * from './metric.js';
|
|
4
|
+
export * from './model.js';
|
|
5
|
+
export * from './provenance.js';
|
|
6
|
+
export * from './raw.js';
|
|
7
|
+
export * from './reading.js';
|
|
8
|
+
export * from './sources/hilink.js';
|
|
9
|
+
export * from './sources/modemmanager.js';
|
|
10
|
+
export * from './sources/router-shared.js';
|
|
11
|
+
export * from './sources/ufi.js';
|
|
12
|
+
export * from './sources/zte.js';
|
|
13
|
+
export * from './state-separation.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The observation layer — normalization, provenance, freshness.
|
|
2
|
+
//
|
|
3
|
+
// It sits directly on top of the migrated pure logic (`domain/mm-enums.ts`,
|
|
4
|
+
// `domain/modem-presentation.ts`, `hardware/router-parsers.ts`) and turns a raw
|
|
5
|
+
// per-vendor payload into ONE `ObservationEnvelope<NormalizedModemObservation>` that
|
|
6
|
+
// carries where every value came from and why any value is missing. It opens no
|
|
7
|
+
// transport: a provider performs the read, this layer explains the result.
|
|
8
|
+
//
|
|
9
|
+
// Reachable through the package root, deliberately not through a new subpath — the
|
|
10
|
+
// public specifier set is a frozen contract and this is normalization, which the root
|
|
11
|
+
// entry already owns.
|
|
12
|
+
export * from './envelope.js';
|
|
13
|
+
export * from './freshness.js';
|
|
14
|
+
export * from './metric.js';
|
|
15
|
+
export * from './model.js';
|
|
16
|
+
export * from './provenance.js';
|
|
17
|
+
export * from './raw.js';
|
|
18
|
+
export * from './reading.js';
|
|
19
|
+
export * from './sources/hilink.js';
|
|
20
|
+
export * from './sources/modemmanager.js';
|
|
21
|
+
export * from './sources/router-shared.js';
|
|
22
|
+
export * from './sources/ufi.js';
|
|
23
|
+
export * from './sources/zte.js';
|
|
24
|
+
export * from './state-separation.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { RouterSignalMetric, RouterSignalUnknownReason } from '../hardware/router-parsers.js';
|
|
2
|
+
import type { MetricProvenance } from './provenance.js';
|
|
3
|
+
export declare const METRIC_UNKNOWN_REASONS: readonly [
|
|
4
|
+
/** A positive claim about the SOURCE: it cannot express this datum at all. */
|
|
5
|
+
'unsupported',
|
|
6
|
+
/** The source answered and simply did not include this field. */
|
|
7
|
+
'not-reported',
|
|
8
|
+
/** Nobody has read this yet. Says nothing about the source or the device. */
|
|
9
|
+
'not-observed',
|
|
10
|
+
/** The source answered with something this layer could not decode. */
|
|
11
|
+
'malformed',
|
|
12
|
+
/** The source refused the read; the session needs re-authentication. */
|
|
13
|
+
'auth-expired',
|
|
14
|
+
/** The source answered with an explicit refusal code for this field. */
|
|
15
|
+
'refused',
|
|
16
|
+
/** The source could not be reached for this read. */
|
|
17
|
+
'unreachable'];
|
|
18
|
+
export type MetricUnknownReason = (typeof METRIC_UNKNOWN_REASONS)[number];
|
|
19
|
+
/**
|
|
20
|
+
* The two classes an unknown reason falls into.
|
|
21
|
+
*
|
|
22
|
+
* `capability` is a durable statement about the source; `read` is a statement about
|
|
23
|
+
* one attempt and may differ on the next one. A consumer deciding whether to HIDE a
|
|
24
|
+
* control (capability) or show it as pending (read) must branch on this, never on the
|
|
25
|
+
* bare fact that a value is missing.
|
|
26
|
+
*/
|
|
27
|
+
export type MetricUnknownClass = 'capability' | 'read';
|
|
28
|
+
export declare function metricUnknownClass(reason: MetricUnknownReason): MetricUnknownClass;
|
|
29
|
+
/** Whether a reason is the positive "this source cannot report it" claim. */
|
|
30
|
+
export declare function isCapabilityUnknown(reason: MetricUnknownReason): reason is 'unsupported';
|
|
31
|
+
export type NormalizedMetric<T> = {
|
|
32
|
+
readonly state: 'known';
|
|
33
|
+
readonly value: T;
|
|
34
|
+
readonly provenance: MetricProvenance;
|
|
35
|
+
} | {
|
|
36
|
+
readonly state: 'unknown';
|
|
37
|
+
readonly reason: MetricUnknownReason;
|
|
38
|
+
readonly provenance: MetricProvenance;
|
|
39
|
+
};
|
|
40
|
+
export declare function knownMetric<T>(value: T, provenance: MetricProvenance): NormalizedMetric<T>;
|
|
41
|
+
export declare function unknownMetric<T>(reason: MetricUnknownReason, provenance: MetricProvenance): NormalizedMetric<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Lift an optional decode result into a metric.
|
|
44
|
+
*
|
|
45
|
+
* `reason` is supplied by the caller precisely so this helper cannot pick one: the
|
|
46
|
+
* difference between "the provider omitted the field" and "this provider has no such
|
|
47
|
+
* field" is knowledge the decoder does not have and the call site does.
|
|
48
|
+
*/
|
|
49
|
+
export declare function metricFromOptional<T>(value: T | undefined, reason: MetricUnknownReason, provenance: MetricProvenance): NormalizedMetric<T>;
|
|
50
|
+
/**
|
|
51
|
+
* The migrated router parsers' own unknown vocabulary, carried across UNCHANGED.
|
|
52
|
+
*
|
|
53
|
+
* Every member of `RouterSignalUnknownReason` is a member of `MetricUnknownReason`
|
|
54
|
+
* with the same meaning, so this is a widening and never a re-classification — in
|
|
55
|
+
* particular `not-reported` stays `not-reported` and does not become `unsupported`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function metricUnknownReasonFromRouter(reason: RouterSignalUnknownReason | 'refused'): MetricUnknownReason;
|
|
58
|
+
/** Wrap one migrated router signal metric with its provenance. */
|
|
59
|
+
export declare function metricFromRouterSignal(metric: RouterSignalMetric, provenance: MetricProvenance): NormalizedMetric<number>;
|
|
60
|
+
/** Transform a known value while preserving state, reason and provenance. */
|
|
61
|
+
export declare function mapMetric<T, U>(metric: NormalizedMetric<T>, transform: (value: T) => U): NormalizedMetric<U>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// A normalized metric: a value, or an explicit reason there is none — with the
|
|
2
|
+
// provenance of the exact reading that produced it.
|
|
3
|
+
//
|
|
4
|
+
// `unknown` is a FIRST-CLASS state here, and its reason is what keeps it from
|
|
5
|
+
// collapsing into `unsupported`. Those two answer different questions: `unsupported`
|
|
6
|
+
// is a positive claim about the SOURCE ("this provider cannot express this datum at
|
|
7
|
+
// all"), while every other reason is a claim about one READ ("the provider could have
|
|
8
|
+
// said, and did not / could not be asked / answered nonsense"). Folding the second
|
|
9
|
+
// class into the first is how a control disappears from an operator's screen because
|
|
10
|
+
// one poll came back empty.
|
|
11
|
+
export const METRIC_UNKNOWN_REASONS = [
|
|
12
|
+
/** A positive claim about the SOURCE: it cannot express this datum at all. */
|
|
13
|
+
'unsupported',
|
|
14
|
+
/** The source answered and simply did not include this field. */
|
|
15
|
+
'not-reported',
|
|
16
|
+
/** Nobody has read this yet. Says nothing about the source or the device. */
|
|
17
|
+
'not-observed',
|
|
18
|
+
/** The source answered with something this layer could not decode. */
|
|
19
|
+
'malformed',
|
|
20
|
+
/** The source refused the read; the session needs re-authentication. */
|
|
21
|
+
'auth-expired',
|
|
22
|
+
/** The source answered with an explicit refusal code for this field. */
|
|
23
|
+
'refused',
|
|
24
|
+
/** The source could not be reached for this read. */
|
|
25
|
+
'unreachable',
|
|
26
|
+
];
|
|
27
|
+
export function metricUnknownClass(reason) {
|
|
28
|
+
return reason === 'unsupported' ? 'capability' : 'read';
|
|
29
|
+
}
|
|
30
|
+
/** Whether a reason is the positive "this source cannot report it" claim. */
|
|
31
|
+
export function isCapabilityUnknown(reason) {
|
|
32
|
+
return metricUnknownClass(reason) === 'capability';
|
|
33
|
+
}
|
|
34
|
+
export function knownMetric(value, provenance) {
|
|
35
|
+
return { state: 'known', value, provenance };
|
|
36
|
+
}
|
|
37
|
+
export function unknownMetric(reason, provenance) {
|
|
38
|
+
return { state: 'unknown', reason, provenance };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Lift an optional decode result into a metric.
|
|
42
|
+
*
|
|
43
|
+
* `reason` is supplied by the caller precisely so this helper cannot pick one: the
|
|
44
|
+
* difference between "the provider omitted the field" and "this provider has no such
|
|
45
|
+
* field" is knowledge the decoder does not have and the call site does.
|
|
46
|
+
*/
|
|
47
|
+
export function metricFromOptional(value, reason, provenance) {
|
|
48
|
+
return value === undefined
|
|
49
|
+
? unknownMetric(reason, provenance)
|
|
50
|
+
: knownMetric(value, provenance);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The migrated router parsers' own unknown vocabulary, carried across UNCHANGED.
|
|
54
|
+
*
|
|
55
|
+
* Every member of `RouterSignalUnknownReason` is a member of `MetricUnknownReason`
|
|
56
|
+
* with the same meaning, so this is a widening and never a re-classification — in
|
|
57
|
+
* particular `not-reported` stays `not-reported` and does not become `unsupported`.
|
|
58
|
+
*/
|
|
59
|
+
export function metricUnknownReasonFromRouter(reason) {
|
|
60
|
+
return reason;
|
|
61
|
+
}
|
|
62
|
+
/** Wrap one migrated router signal metric with its provenance. */
|
|
63
|
+
export function metricFromRouterSignal(metric, provenance) {
|
|
64
|
+
return metric.state === 'known'
|
|
65
|
+
? knownMetric(metric.value, provenance)
|
|
66
|
+
: unknownMetric(metricUnknownReasonFromRouter(metric.reason), provenance);
|
|
67
|
+
}
|
|
68
|
+
/** Transform a known value while preserving state, reason and provenance. */
|
|
69
|
+
export function mapMetric(metric, transform) {
|
|
70
|
+
return metric.state === 'known'
|
|
71
|
+
? knownMetric(transform(metric.value), metric.provenance)
|
|
72
|
+
: unknownMetric(metric.reason, metric.provenance);
|
|
73
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { RadioAccessTechnology } from '../domain/index.js';
|
|
2
|
+
import type { SimPresenceEvidence } from '../hardware/router-parsers.js';
|
|
3
|
+
import type { NormalizedMetric } from './metric.js';
|
|
4
|
+
import type { ObservationDiagnostics, ObservationSourceKind } from './provenance.js';
|
|
5
|
+
export type NormalizedHardware = {
|
|
6
|
+
/** The operator-facing model label, sanitized by the migrated presentation rules. */
|
|
7
|
+
readonly label: NormalizedMetric<string>;
|
|
8
|
+
/**
|
|
9
|
+
* The label plus a short equipment-identifier tail, as CeraUI has always rendered
|
|
10
|
+
* it. It embeds the last five digits of an IMEI, so it is display copy and must
|
|
11
|
+
* not be used as a key or written to a log.
|
|
12
|
+
*/
|
|
13
|
+
readonly displayName: NormalizedMetric<string>;
|
|
14
|
+
};
|
|
15
|
+
export type NormalizedRadio = {
|
|
16
|
+
readonly modemState: NormalizedMetric<string>;
|
|
17
|
+
readonly registration: NormalizedMetric<string>;
|
|
18
|
+
readonly accessTechnologies: NormalizedMetric<readonly RadioAccessTechnology[]>;
|
|
19
|
+
readonly modeLabel: NormalizedMetric<string>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Signal metrics.
|
|
23
|
+
*
|
|
24
|
+
* `quality` and `bars`/`maxBars` are different measurements and both are kept:
|
|
25
|
+
* ModemManager reports a 0-100 percentage and no bar count, while the router admin
|
|
26
|
+
* APIs report a vendor bar scale and no percentage. Deriving one from the other would
|
|
27
|
+
* be inventing a reading, so each source reports what it has and answers
|
|
28
|
+
* `unsupported` — a positive claim — for what it structurally cannot.
|
|
29
|
+
*/
|
|
30
|
+
export type NormalizedSignal = {
|
|
31
|
+
readonly quality: NormalizedMetric<number>;
|
|
32
|
+
/**
|
|
33
|
+
* ModemManager's `SignalQuality` is a `(ub)` — a percentage AND a boolean saying
|
|
34
|
+
* whether it was measured recently or is the last cached reading. The boolean is a
|
|
35
|
+
* separate fact about the same measurement, so it gets its own metric rather than
|
|
36
|
+
* being folded into freshness: an envelope's staleness is about when WE read, this
|
|
37
|
+
* is about when the MODEM last measured. The router APIs have no such flag and say
|
|
38
|
+
* so with `unsupported`.
|
|
39
|
+
*/
|
|
40
|
+
readonly qualityRecent: NormalizedMetric<boolean>;
|
|
41
|
+
readonly bars: NormalizedMetric<number>;
|
|
42
|
+
readonly maxBars: NormalizedMetric<number>;
|
|
43
|
+
readonly dbm: NormalizedMetric<number>;
|
|
44
|
+
readonly rsrp: NormalizedMetric<number>;
|
|
45
|
+
readonly rsrq: NormalizedMetric<number>;
|
|
46
|
+
readonly snr: NormalizedMetric<number>;
|
|
47
|
+
readonly sinr: NormalizedMetric<number>;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* SIM presence as a metric value is BINARY on purpose.
|
|
51
|
+
*
|
|
52
|
+
* The migrated `deriveSimPresence` answers `present | absent | unknown`; the third
|
|
53
|
+
* member is not a presence, it is the absence of an answer, so it becomes the
|
|
54
|
+
* metric's `unknown` state with a reason instead of a third value. That is what stops
|
|
55
|
+
* "we could not tell" from being rendered beside "there is no SIM".
|
|
56
|
+
*/
|
|
57
|
+
export type SimPresenceValue = 'present' | 'absent';
|
|
58
|
+
export type NormalizedSim = {
|
|
59
|
+
readonly presence: NormalizedMetric<SimPresenceValue>;
|
|
60
|
+
/**
|
|
61
|
+
* WHICH FACT decided `presence`. Carried beside the metric rather than derived from
|
|
62
|
+
* it, because "absent" and "we could not tell" are read off the SAME empty fields
|
|
63
|
+
* and only the evidence separates them. `absent` is reachable through exactly one
|
|
64
|
+
* evidence kind (`state-failed-reason`), which is what makes "never inferred from a
|
|
65
|
+
* blank field" a property a test can assert rather than a convention.
|
|
66
|
+
*/
|
|
67
|
+
readonly presenceEvidence: SimPresenceEvidence;
|
|
68
|
+
readonly lockRequired: NormalizedMetric<string>;
|
|
69
|
+
readonly kind: NormalizedMetric<'physical' | 'esim'>;
|
|
70
|
+
readonly esimStatus: NormalizedMetric<'no-profiles' | 'with-profiles'>;
|
|
71
|
+
};
|
|
72
|
+
export type NormalizedModemObservation = {
|
|
73
|
+
readonly source: ObservationSourceKind;
|
|
74
|
+
readonly hardware: NormalizedHardware;
|
|
75
|
+
readonly radio: NormalizedRadio;
|
|
76
|
+
readonly signal: NormalizedSignal;
|
|
77
|
+
readonly sim: NormalizedSim;
|
|
78
|
+
/** Everything the provider said, verbatim, plus what was and was not claimed. */
|
|
79
|
+
readonly diagnostics: ObservationDiagnostics;
|
|
80
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// The normalized observation shape every source produces.
|
|
2
|
+
//
|
|
3
|
+
// It is deliberately NARROW. A field earns a slot here only when more than one source
|
|
4
|
+
// can express it and an operator surface acts on it; everything else stays verbatim in
|
|
5
|
+
// the diagnostics block rather than growing a per-vendor branch of the model. That is
|
|
6
|
+
// the whole trade this layer makes — one shape to render, nothing thrown away.
|
|
7
|
+
//
|
|
8
|
+
// Every leaf is a `NormalizedMetric`, so "the source cannot report this" and "the
|
|
9
|
+
// source did not report this on this read" are different values with different
|
|
10
|
+
// reasons rather than the same absent field.
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { EpochMillis, ObservationAuthority, SourceEpoch } from '../domain/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The provider families this layer normalizes.
|
|
4
|
+
*
|
|
5
|
+
* These are SOURCE SHAPES, not live transports. Todo 18 builds the normalization
|
|
6
|
+
* layer; the providers that will feed it (ModemManager D-Bus, the HiLink/goform/HIMI
|
|
7
|
+
* HTTP sessions) are separate work and open no connection from here.
|
|
8
|
+
*
|
|
9
|
+
* `networkmanager` is here because NM is a source of a DIFFERENT fact than the other
|
|
10
|
+
* four: it reports which connection is in force on which interface, never a radio
|
|
11
|
+
* reading. Giving it its own kind is what keeps a bearer observation attributable to
|
|
12
|
+
* NM rather than laundered through whichever provider happened to read the modem.
|
|
13
|
+
*/
|
|
14
|
+
export declare const OBSERVATION_SOURCE_KINDS: readonly ['modemmanager', 'huawei-hilink', 'zte-goform', 'ufi-himiapi', 'networkmanager'];
|
|
15
|
+
export type ObservationSourceKind = (typeof OBSERVATION_SOURCE_KINDS)[number];
|
|
16
|
+
/**
|
|
17
|
+
* A provider-native value, kept exactly as the provider expressed it.
|
|
18
|
+
*
|
|
19
|
+
* Arrays are part of the union because ModemManager genuinely returns them
|
|
20
|
+
* (`Modem.SimSlots` is an `ao`); flattening one to its first member here would be a
|
|
21
|
+
* silent drop of exactly the kind this module exists to prevent.
|
|
22
|
+
*/
|
|
23
|
+
export type RawFieldValue = string | number | boolean | null | readonly RawFieldValue[];
|
|
24
|
+
/** Provider-native fields keyed by their provider-native names. */
|
|
25
|
+
export type RawFieldRecord = Readonly<Record<string, RawFieldValue>>;
|
|
26
|
+
/**
|
|
27
|
+
* Per-metric provenance: which source, which reading of it, and when.
|
|
28
|
+
*
|
|
29
|
+
* It is carried on EVERY metric rather than only on the envelope because one
|
|
30
|
+
* normalized observation routinely folds several provider reads together (HiLink
|
|
31
|
+
* answers `device_signal` and `monitoring_status` separately; UFI answers three
|
|
32
|
+
* endpoints), so a single envelope-level `observedAt` would be a claim about a
|
|
33
|
+
* reading no individual metric came from.
|
|
34
|
+
*/
|
|
35
|
+
export type MetricProvenance = {
|
|
36
|
+
readonly source: ObservationSourceKind;
|
|
37
|
+
readonly sourceEpoch: SourceEpoch;
|
|
38
|
+
readonly observedAt: EpochMillis;
|
|
39
|
+
readonly authority: ObservationAuthority;
|
|
40
|
+
/** The provider-native field name(s) this value was normalized from. */
|
|
41
|
+
readonly rawFields: readonly string[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Structural complaints about a payload. A note names the FIELD or body it concerns
|
|
45
|
+
* and never carries its value: a body can hold an ICCID or a one-time code, and a
|
|
46
|
+
* diagnostic is the one place a reviewer reads verbatim.
|
|
47
|
+
*/
|
|
48
|
+
export declare const OBSERVATION_DIAGNOSTIC_CODES: readonly ['unparseable-body', 'empty-body', 'auth-expired', 'field-shape-unrecognized'];
|
|
49
|
+
export type ObservationDiagnosticCode = (typeof OBSERVATION_DIAGNOSTIC_CODES)[number];
|
|
50
|
+
export type ObservationDiagnosticNote = {
|
|
51
|
+
readonly code: ObservationDiagnosticCode;
|
|
52
|
+
/** The provider-native field or body name, never its content. */
|
|
53
|
+
readonly field: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The typed diagnostics block: the provider payload, verbatim, plus which fields the
|
|
57
|
+
* normalizer claimed and which it did not.
|
|
58
|
+
*
|
|
59
|
+
* `raw` is a REDACTION-CLASS boundary. A vendor payload routinely carries an ICCID or
|
|
60
|
+
* an IMSI (the UFI overview endpoint reports both), so anything that logs, serializes
|
|
61
|
+
* or files a diagnostics block must route it through {@link redactObservationDiagnostics}
|
|
62
|
+
* first. Retention and disclosure are different decisions; this layer only guarantees
|
|
63
|
+
* the first.
|
|
64
|
+
*/
|
|
65
|
+
export type ObservationDiagnostics = {
|
|
66
|
+
readonly source: ObservationSourceKind;
|
|
67
|
+
/** Every provider-native field, verbatim. Nothing is dropped during normalization. */
|
|
68
|
+
readonly raw: RawFieldRecord;
|
|
69
|
+
/** Fields a normalized metric claims. They remain present in `raw` as well. */
|
|
70
|
+
readonly consumed: readonly string[];
|
|
71
|
+
/** Fields in `raw` that no normalized field claims — retained, never discarded. */
|
|
72
|
+
readonly unmapped: readonly string[];
|
|
73
|
+
readonly notes: readonly ObservationDiagnosticNote[];
|
|
74
|
+
};
|
|
75
|
+
export type DiagnosticsInput = {
|
|
76
|
+
readonly source: ObservationSourceKind;
|
|
77
|
+
readonly raw: RawFieldRecord;
|
|
78
|
+
readonly consumed?: readonly string[];
|
|
79
|
+
readonly notes?: readonly ObservationDiagnosticNote[];
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Build a diagnostics block, deriving `unmapped` rather than accepting it.
|
|
83
|
+
*
|
|
84
|
+
* Deriving it is what makes the no-drop property structural: a normalizer cannot
|
|
85
|
+
* declare a field mapped without a metric that names it, and a field it never
|
|
86
|
+
* mentions lands in `unmapped` automatically instead of vanishing.
|
|
87
|
+
*/
|
|
88
|
+
export declare function createObservationDiagnostics(input: DiagnosticsInput): ObservationDiagnostics;
|
|
89
|
+
/**
|
|
90
|
+
* A copy of `diagnostics` with every sensitive raw field replaced by the shared
|
|
91
|
+
* redaction marker. Routed through the package's own key-based `redact`, so the
|
|
92
|
+
* classes it masks here are exactly the classes it masks everywhere else.
|
|
93
|
+
*/
|
|
94
|
+
export declare function redactObservationDiagnostics(diagnostics: ObservationDiagnostics): ObservationDiagnostics;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Provenance + diagnostics — where a normalized value came from, and everything the
|
|
2
|
+
// provider said that the normalized model has no field for.
|
|
3
|
+
//
|
|
4
|
+
// The rule this module exists to enforce: normalization NARROWS a vendor payload into
|
|
5
|
+
// one shape, it does not DISCARD it. Every provider-native field a normalizer reads —
|
|
6
|
+
// and every one it does not — is retained verbatim in `ObservationDiagnostics.raw`,
|
|
7
|
+
// and each normalized metric carries the provenance of the exact observation that
|
|
8
|
+
// produced it. A consumer can therefore always answer "which provider said this, and
|
|
9
|
+
// when" without the provider having to widen the normalized model.
|
|
10
|
+
import { redact } from '../redact.js';
|
|
11
|
+
/**
|
|
12
|
+
* The provider families this layer normalizes.
|
|
13
|
+
*
|
|
14
|
+
* These are SOURCE SHAPES, not live transports. Todo 18 builds the normalization
|
|
15
|
+
* layer; the providers that will feed it (ModemManager D-Bus, the HiLink/goform/HIMI
|
|
16
|
+
* HTTP sessions) are separate work and open no connection from here.
|
|
17
|
+
*
|
|
18
|
+
* `networkmanager` is here because NM is a source of a DIFFERENT fact than the other
|
|
19
|
+
* four: it reports which connection is in force on which interface, never a radio
|
|
20
|
+
* reading. Giving it its own kind is what keeps a bearer observation attributable to
|
|
21
|
+
* NM rather than laundered through whichever provider happened to read the modem.
|
|
22
|
+
*/
|
|
23
|
+
export const OBSERVATION_SOURCE_KINDS = [
|
|
24
|
+
'modemmanager',
|
|
25
|
+
'huawei-hilink',
|
|
26
|
+
'zte-goform',
|
|
27
|
+
'ufi-himiapi',
|
|
28
|
+
'networkmanager',
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Structural complaints about a payload. A note names the FIELD or body it concerns
|
|
32
|
+
* and never carries its value: a body can hold an ICCID or a one-time code, and a
|
|
33
|
+
* diagnostic is the one place a reviewer reads verbatim.
|
|
34
|
+
*/
|
|
35
|
+
export const OBSERVATION_DIAGNOSTIC_CODES = [
|
|
36
|
+
'unparseable-body',
|
|
37
|
+
'empty-body',
|
|
38
|
+
'auth-expired',
|
|
39
|
+
'field-shape-unrecognized',
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Build a diagnostics block, deriving `unmapped` rather than accepting it.
|
|
43
|
+
*
|
|
44
|
+
* Deriving it is what makes the no-drop property structural: a normalizer cannot
|
|
45
|
+
* declare a field mapped without a metric that names it, and a field it never
|
|
46
|
+
* mentions lands in `unmapped` automatically instead of vanishing.
|
|
47
|
+
*/
|
|
48
|
+
export function createObservationDiagnostics(input) {
|
|
49
|
+
const rawKeys = Object.keys(input.raw);
|
|
50
|
+
const consumed = [...new Set(input.consumed ?? [])].filter((key) => rawKeys.includes(key)).sort();
|
|
51
|
+
const consumedSet = new Set(consumed);
|
|
52
|
+
return {
|
|
53
|
+
source: input.source,
|
|
54
|
+
raw: input.raw,
|
|
55
|
+
consumed,
|
|
56
|
+
unmapped: rawKeys.filter((key) => !consumedSet.has(key)).sort(),
|
|
57
|
+
notes: input.notes ?? [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A copy of `diagnostics` with every sensitive raw field replaced by the shared
|
|
62
|
+
* redaction marker. Routed through the package's own key-based `redact`, so the
|
|
63
|
+
* classes it masks here are exactly the classes it masks everywhere else.
|
|
64
|
+
*/
|
|
65
|
+
export function redactObservationDiagnostics(diagnostics) {
|
|
66
|
+
return { ...diagnostics, raw: redact(diagnostics.raw) };
|
|
67
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RawFieldRecord, RawFieldValue } from './provenance.js';
|
|
2
|
+
export declare function rawKey(body: string, field: string): string;
|
|
3
|
+
/** Prefix a provider's flat record with the body it came from. */
|
|
4
|
+
export declare function prefixRawRecord(body: string, record: Readonly<Record<string, RawFieldValue>> | undefined): RawFieldRecord;
|
|
5
|
+
export declare function mergeRawRecords(...records: readonly RawFieldRecord[]): RawFieldRecord;
|
|
6
|
+
export declare function rawString(record: RawFieldRecord, key: string): string | undefined;
|
|
7
|
+
export declare function rawNumber(record: RawFieldRecord, key: string): number | undefined;
|
|
8
|
+
export declare function rawStringArray(record: RawFieldRecord, key: string): readonly string[] | undefined;
|
|
9
|
+
export declare function hasRawField(record: RawFieldRecord, key: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* One member of a D-Bus STRUCT retained verbatim.
|
|
12
|
+
*
|
|
13
|
+
* ModemManager's `SignalQuality` is a `(ub)` and its `CurrentModes` is a `(uu)`, so
|
|
14
|
+
* retaining them verbatim means the raw record holds an ARRAY where a naive read
|
|
15
|
+
* expects a scalar. A caller that flattened the struct before retention would keep the
|
|
16
|
+
* first member and silently drop the second — the recency flag, and the preferred mode
|
|
17
|
+
* — which is the drop this layer exists to prevent. A scalar answers at index 0, so a
|
|
18
|
+
* source that already flattened (mmcli, or a pre-existing fixture) still decodes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function rawStructMember(record: RawFieldRecord, key: string, index: number): RawFieldValue | undefined;
|
|
21
|
+
export declare function rawNumberAt(record: RawFieldRecord, key: string, index: number): number | undefined;
|
|
22
|
+
export declare function rawBooleanAt(record: RawFieldRecord, key: string, index: number): boolean | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Flatten every leaf element of an XML body into `<body>.<Tag>` entries.
|
|
25
|
+
*
|
|
26
|
+
* A repeated tag — HiLink's `<NetworkMode>` list repeats `<Index>` once per mode —
|
|
27
|
+
* would otherwise overwrite its predecessors, so the second and later occurrences are
|
|
28
|
+
* suffixed `#2`, `#3`, …. Losing a repeat is exactly the silent drop this layer
|
|
29
|
+
* exists to prevent, and the suffix keeps the original tag name legible.
|
|
30
|
+
*/
|
|
31
|
+
export declare function flattenXmlBody(body: string, name: string): RawFieldRecord;
|
|
32
|
+
/** Parse a JSON object body, or `undefined` when the bytes are not a JSON object. */
|
|
33
|
+
export declare function parseJsonObject(body: string): Readonly<Record<string, unknown>> | undefined;
|
|
34
|
+
/** Parse a JSON object body into a flat record, or `undefined` when it is not one. */
|
|
35
|
+
export declare function parseJsonRecord(body: string): Readonly<Record<string, RawFieldValue>> | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* A nested object has no flat representation, so its JSON text is kept.
|
|
38
|
+
*
|
|
39
|
+
* Serializing rather than dropping is the whole contract of this layer: a payload
|
|
40
|
+
* this record cannot model structurally is still a payload a diagnostician can read.
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizeRawValue(value: unknown): RawFieldValue;
|