@ceralive/modem-control 0.2.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +76 -0
- package/dist/backend/transition-preconditions.js +64 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +196 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +26 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +47 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +39 -0
- package/dist/providers/modem-manager/provider.js +152 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +135 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +163 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +78 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +151 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +5 -0
- package/dist/usb-mode/index.js +15 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +84 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -221
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -27
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
// Idempotent re-apply — a minimal in-memory stack executes the planner's
|
|
2
|
-
// port-tagged ops; re-applying the same policy performs ZERO new side effects and
|
|
3
|
-
// returns the same receipts. NOT the A2.3 fake D-Bus harness — just enough to prove
|
|
4
|
-
// the pure planner converges to a fixpoint.
|
|
5
|
-
|
|
6
|
-
import { expect, test } from 'bun:test';
|
|
7
|
-
import {
|
|
8
|
-
type DesiredCellularPolicy,
|
|
9
|
-
defaultCellularPolicy,
|
|
10
|
-
imeiEquipmentId,
|
|
11
|
-
type PolicyBindingKey,
|
|
12
|
-
policyBindingKey,
|
|
13
|
-
runtimePath,
|
|
14
|
-
} from '../domain';
|
|
15
|
-
import { connectionId, deviceIfname } from './network-manager';
|
|
16
|
-
import type { PortTaggedOp } from './ops';
|
|
17
|
-
import type { Receipt } from './receipts';
|
|
18
|
-
import { type AppliedCellularState, type ModemCapabilities, planReconcile } from './reconcile';
|
|
19
|
-
|
|
20
|
-
const BOUND_TO: PolicyBindingKey = policyBindingKey({
|
|
21
|
-
equipmentId: imeiEquipmentId('490154203237518'),
|
|
22
|
-
runtimePath: runtimePath('/org/freedesktop/ModemManager1/Modem/0'),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const FIVE_G_DUAL_SLOT: ModemCapabilities = {
|
|
26
|
-
supportedRats: new Set(['5gnr', 'lte', 'umts', 'gsm']),
|
|
27
|
-
simSlotCount: 2,
|
|
28
|
-
supportsAutoApn: true,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const FOUR_G_ONLY: ModemCapabilities = {
|
|
32
|
-
supportedRats: new Set(['lte', 'umts', 'gsm']),
|
|
33
|
-
simSlotCount: 2,
|
|
34
|
-
supportsAutoApn: true,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
class InMemoryStack {
|
|
38
|
-
private state: AppliedCellularState;
|
|
39
|
-
readonly executed: PortTaggedOp[] = [];
|
|
40
|
-
|
|
41
|
-
constructor(
|
|
42
|
-
initial: AppliedCellularState,
|
|
43
|
-
private readonly capabilities: ModemCapabilities,
|
|
44
|
-
) {
|
|
45
|
-
this.state = initial;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private execute(op: PortTaggedOp): void {
|
|
49
|
-
this.executed.push(op);
|
|
50
|
-
if (op.port === 'mm') {
|
|
51
|
-
this.state =
|
|
52
|
-
op.op.kind === 'setRadioModes'
|
|
53
|
-
? { ...this.state, appliedRadioPreference: op.op.preference.preferenceOrdered }
|
|
54
|
-
: { ...this.state, activePrimarySlot: op.op.slotIndex };
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
switch (op.op.kind) {
|
|
58
|
-
case 'createGsmProfile':
|
|
59
|
-
this.state = {
|
|
60
|
-
...this.state,
|
|
61
|
-
hasProfile: true,
|
|
62
|
-
connectionId: connectionId('conn-generated'),
|
|
63
|
-
appliedApn: op.op.profile.autoConfig ? 'auto' : op.op.profile.apn,
|
|
64
|
-
appliedRoaming: !op.op.profile.homeOnly,
|
|
65
|
-
};
|
|
66
|
-
break;
|
|
67
|
-
case 'updateGsmProfile': {
|
|
68
|
-
const { patch } = op.op;
|
|
69
|
-
const nextApn = patch.autoConfig ? 'auto' : patch.apn;
|
|
70
|
-
this.state = {
|
|
71
|
-
...this.state,
|
|
72
|
-
...(nextApn !== undefined ? { appliedApn: nextApn } : {}),
|
|
73
|
-
...(patch.homeOnly !== undefined ? { appliedRoaming: !patch.homeOnly } : {}),
|
|
74
|
-
};
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case 'activate':
|
|
78
|
-
this.state = { ...this.state, nmActivation: 'activated' };
|
|
79
|
-
break;
|
|
80
|
-
case 'deactivate':
|
|
81
|
-
this.state = { ...this.state, nmActivation: 'disconnected' };
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
apply(desired: DesiredCellularPolicy): readonly Receipt[] {
|
|
87
|
-
for (let iteration = 0; iteration < 20; iteration += 1) {
|
|
88
|
-
const plan = planReconcile(this.state, desired, this.capabilities);
|
|
89
|
-
if (plan.ops.length === 0) {
|
|
90
|
-
return plan.receipts;
|
|
91
|
-
}
|
|
92
|
-
for (const op of plan.ops) {
|
|
93
|
-
this.execute(op);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
throw new Error('reconcile did not converge within the iteration budget');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function baseState(): AppliedCellularState {
|
|
101
|
-
return { nmActivation: 'disconnected', hasProfile: false, deviceIfname: deviceIfname('wwan0') };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
test('a fresh policy converges, then re-apply does nothing and returns the same receipts', () => {
|
|
105
|
-
const stack = new InMemoryStack(baseState(), FIVE_G_DUAL_SLOT);
|
|
106
|
-
const policy = defaultCellularPolicy(BOUND_TO);
|
|
107
|
-
|
|
108
|
-
const first = stack.apply(policy);
|
|
109
|
-
const sideEffectsAfterFirst = stack.executed.length;
|
|
110
|
-
expect(sideEffectsAfterFirst).toBeGreaterThan(0);
|
|
111
|
-
|
|
112
|
-
const second = stack.apply(policy);
|
|
113
|
-
expect(stack.executed.length).toBe(sideEffectsAfterFirst);
|
|
114
|
-
expect(second).toEqual(first);
|
|
115
|
-
|
|
116
|
-
for (const entry of second) {
|
|
117
|
-
expect(entry.status).toBe('applied');
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('each op is executed exactly once across convergence (no double side-effects)', () => {
|
|
122
|
-
const stack = new InMemoryStack(baseState(), FIVE_G_DUAL_SLOT);
|
|
123
|
-
const policy = defaultCellularPolicy(BOUND_TO);
|
|
124
|
-
stack.apply(policy);
|
|
125
|
-
|
|
126
|
-
const kinds = stack.executed.map((op) => op.op.kind).sort();
|
|
127
|
-
expect(kinds).toEqual(['activate', 'createGsmProfile', 'setRadioModes']);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('an unsupported dimension is never applied as an op, on the first apply or any re-apply', () => {
|
|
131
|
-
const stack = new InMemoryStack(baseState(), FOUR_G_ONLY);
|
|
132
|
-
const policy = defaultCellularPolicy(BOUND_TO);
|
|
133
|
-
|
|
134
|
-
const first = stack.apply(policy);
|
|
135
|
-
stack.apply(policy);
|
|
136
|
-
|
|
137
|
-
expect(stack.executed.some((op) => op.port === 'mm' && op.op.kind === 'setRadioModes')).toBe(
|
|
138
|
-
false,
|
|
139
|
-
);
|
|
140
|
-
expect(first.find((entry) => entry.dimension === 'radio')?.status).toBe('unsupported');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('deactivation is idempotent — disabling an already-inactive connection is a no-op', () => {
|
|
144
|
-
const stack = new InMemoryStack(baseState(), FIVE_G_DUAL_SLOT);
|
|
145
|
-
const policy: DesiredCellularPolicy = { ...defaultCellularPolicy(BOUND_TO), enabled: false };
|
|
146
|
-
|
|
147
|
-
stack.apply(policy);
|
|
148
|
-
const sideEffects = stack.executed.length;
|
|
149
|
-
stack.apply(policy);
|
|
150
|
-
expect(stack.executed.length).toBe(sideEffects);
|
|
151
|
-
expect(stack.executed.some((op) => op.port === 'nm' && op.op.kind === 'activate')).toBe(false);
|
|
152
|
-
});
|
package/src/ports/reconcile.ts
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
// The desired-state planner — pure. Given the currently-applied state, a desired
|
|
2
|
-
// policy, and the modem's capabilities, it produces the port-tagged ops needed to
|
|
3
|
-
// converge PLUS an honest receipt per policy dimension. It performs no I/O and no
|
|
4
|
-
// side effects: the same inputs always yield the same ops and receipts, which is
|
|
5
|
-
// what makes re-applying a policy idempotent (see reconcile.test.ts).
|
|
6
|
-
|
|
7
|
-
import type { DesiredCellularPolicy, NmActivation, RadioAccessTechnology } from '../domain';
|
|
8
|
-
import type { ConnectionId, DeviceIfname, GsmProfileInput } from './network-manager';
|
|
9
|
-
import { mmOp, nmOp, type PortTaggedOp } from './ops';
|
|
10
|
-
import { type Receipt, receipt } from './receipts';
|
|
11
|
-
|
|
12
|
-
/** What the modem / stack can actually do — the capability set the planner honors. */
|
|
13
|
-
export interface ModemCapabilities {
|
|
14
|
-
readonly supportedRats: ReadonlySet<RadioAccessTechnology>;
|
|
15
|
-
readonly simSlotCount: number;
|
|
16
|
-
readonly supportsAutoApn: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The currently-applied cellular configuration, as the planner sees it — the "is"
|
|
21
|
-
* state, distinct from the observational snapshot: it tracks what has actually been
|
|
22
|
-
* written (profile, radio preference, primary slot) plus the live NM activation.
|
|
23
|
-
*/
|
|
24
|
-
export interface AppliedCellularState {
|
|
25
|
-
readonly nmActivation: NmActivation;
|
|
26
|
-
readonly hasProfile: boolean;
|
|
27
|
-
readonly connectionId?: ConnectionId;
|
|
28
|
-
readonly deviceIfname?: DeviceIfname;
|
|
29
|
-
readonly appliedApn?: 'auto' | string;
|
|
30
|
-
readonly appliedRoaming?: boolean;
|
|
31
|
-
readonly appliedRadioPreference?: readonly RadioAccessTechnology[];
|
|
32
|
-
readonly activePrimarySlot?: number;
|
|
33
|
-
readonly activationFailureReason?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** A reconcile plan: the ops to run and one receipt per policy dimension. */
|
|
37
|
-
export interface Plan {
|
|
38
|
-
readonly ops: readonly PortTaggedOp[];
|
|
39
|
-
readonly receipts: readonly Receipt[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface DimensionResult {
|
|
43
|
-
readonly receipt: Receipt;
|
|
44
|
-
readonly op?: PortTaggedOp;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface ProfilePlan {
|
|
48
|
-
readonly connection: Receipt;
|
|
49
|
-
readonly roaming: Receipt;
|
|
50
|
-
readonly op?: PortTaggedOp;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Reconcile a desired policy against the applied state into ops + receipts. */
|
|
54
|
-
export function planReconcile(
|
|
55
|
-
current: AppliedCellularState,
|
|
56
|
-
desired: DesiredCellularPolicy,
|
|
57
|
-
capabilities: ModemCapabilities,
|
|
58
|
-
): Plan {
|
|
59
|
-
const profile = planNmProfile(current, desired, capabilities);
|
|
60
|
-
const results: readonly DimensionResult[] = [
|
|
61
|
-
planRadio(current, desired, capabilities),
|
|
62
|
-
planSimSlot(current, desired, capabilities),
|
|
63
|
-
planEnabled(current, desired),
|
|
64
|
-
{ receipt: planRecovery(desired) },
|
|
65
|
-
{ receipt: planUsage(desired) },
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
const ops: PortTaggedOp[] = [];
|
|
69
|
-
const receipts: Receipt[] = [profile.connection, profile.roaming];
|
|
70
|
-
if (profile.op !== undefined) {
|
|
71
|
-
ops.push(profile.op);
|
|
72
|
-
}
|
|
73
|
-
for (const result of results) {
|
|
74
|
-
receipts.push(result.receipt);
|
|
75
|
-
if (result.op !== undefined) {
|
|
76
|
-
ops.push(result.op);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return { ops, receipts };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function profileFromPolicy(desired: DesiredCellularPolicy): GsmProfileInput {
|
|
83
|
-
const auto = desired.connection.apn === 'auto';
|
|
84
|
-
const base: GsmProfileInput = {
|
|
85
|
-
connectionName: 'ceralive-cellular',
|
|
86
|
-
apn: auto ? '' : desired.connection.apn,
|
|
87
|
-
homeOnly: !desired.roaming,
|
|
88
|
-
autoConfig: auto,
|
|
89
|
-
};
|
|
90
|
-
// SENSITIVE creds only when explicitly provided AND not in auto-config mode.
|
|
91
|
-
if (!auto && desired.connection.auth !== undefined) {
|
|
92
|
-
const { username, password } = desired.connection.auth;
|
|
93
|
-
return {
|
|
94
|
-
...base,
|
|
95
|
-
...(username !== undefined ? { username } : {}),
|
|
96
|
-
...(password !== undefined ? { password } : {}),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
return base;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// NM owns APN / auth / roaming / autoconnect: one profile write converges all of
|
|
103
|
-
// them, so connection + roaming share a single op and each get their own receipt.
|
|
104
|
-
function planNmProfile(
|
|
105
|
-
current: AppliedCellularState,
|
|
106
|
-
desired: DesiredCellularPolicy,
|
|
107
|
-
capabilities: ModemCapabilities,
|
|
108
|
-
): ProfilePlan {
|
|
109
|
-
const auto = desired.connection.apn === 'auto';
|
|
110
|
-
if (auto && !capabilities.supportsAutoApn) {
|
|
111
|
-
return {
|
|
112
|
-
connection: receipt(
|
|
113
|
-
'connection',
|
|
114
|
-
'unsupported',
|
|
115
|
-
'Auto-APN is not available on this NetworkManager / modem',
|
|
116
|
-
),
|
|
117
|
-
roaming: receipt(
|
|
118
|
-
'roaming',
|
|
119
|
-
'unsupported',
|
|
120
|
-
'roaming cannot be applied without a connection profile',
|
|
121
|
-
),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const needsWrite =
|
|
125
|
-
!current.hasProfile ||
|
|
126
|
-
current.appliedApn !== desired.connection.apn ||
|
|
127
|
-
current.appliedRoaming !== desired.roaming;
|
|
128
|
-
if (!needsWrite) {
|
|
129
|
-
return {
|
|
130
|
-
connection: receipt(
|
|
131
|
-
'connection',
|
|
132
|
-
'applied',
|
|
133
|
-
`connection APN '${desired.connection.apn}' already configured`,
|
|
134
|
-
),
|
|
135
|
-
roaming: receipt(
|
|
136
|
-
'roaming',
|
|
137
|
-
'applied',
|
|
138
|
-
`roaming already ${desired.roaming ? 'enabled' : 'disabled'}`,
|
|
139
|
-
),
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
const op =
|
|
143
|
-
current.hasProfile && current.connectionId !== undefined
|
|
144
|
-
? nmOp({
|
|
145
|
-
kind: 'updateGsmProfile',
|
|
146
|
-
connectionId: current.connectionId,
|
|
147
|
-
patch: profileFromPolicy(desired),
|
|
148
|
-
})
|
|
149
|
-
: nmOp({ kind: 'createGsmProfile', profile: profileFromPolicy(desired) });
|
|
150
|
-
const verb = current.hasProfile ? 'updating' : 'creating';
|
|
151
|
-
return {
|
|
152
|
-
connection: receipt(
|
|
153
|
-
'connection',
|
|
154
|
-
'pending',
|
|
155
|
-
`${verb} connection profile for APN '${desired.connection.apn}'`,
|
|
156
|
-
),
|
|
157
|
-
roaming: receipt(
|
|
158
|
-
'roaming',
|
|
159
|
-
'pending',
|
|
160
|
-
`roaming ${desired.roaming ? 'enabled' : 'disabled'} via connection profile`,
|
|
161
|
-
),
|
|
162
|
-
op,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function radioMatches(
|
|
167
|
-
applied: readonly RadioAccessTechnology[] | undefined,
|
|
168
|
-
desired: readonly RadioAccessTechnology[],
|
|
169
|
-
): boolean {
|
|
170
|
-
if (applied === undefined || applied.length !== desired.length) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
return applied.every((rat, index) => rat === desired[index]);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function planRadio(
|
|
177
|
-
current: AppliedCellularState,
|
|
178
|
-
desired: DesiredCellularPolicy,
|
|
179
|
-
capabilities: ModemCapabilities,
|
|
180
|
-
): DimensionResult {
|
|
181
|
-
const preference = desired.radio.preferenceOrdered;
|
|
182
|
-
const top = preference[0];
|
|
183
|
-
if (top === undefined) {
|
|
184
|
-
return {
|
|
185
|
-
receipt: receipt(
|
|
186
|
-
'radio',
|
|
187
|
-
'failed',
|
|
188
|
-
'radio preference must list at least one access technology',
|
|
189
|
-
),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
if (!capabilities.supportedRats.has(top)) {
|
|
193
|
-
const supported = [...capabilities.supportedRats].join(', ') || 'none';
|
|
194
|
-
return {
|
|
195
|
-
receipt: receipt(
|
|
196
|
-
'radio',
|
|
197
|
-
'unsupported',
|
|
198
|
-
`preferred radio access technology '${top}' is not supported by this modem (supports ${supported})`,
|
|
199
|
-
),
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
if (radioMatches(current.appliedRadioPreference, preference)) {
|
|
203
|
-
return {
|
|
204
|
-
receipt: receipt('radio', 'applied', `radio preference already ${preference.join(' > ')}`),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
return {
|
|
208
|
-
receipt: receipt('radio', 'pending', `setting radio preference to ${preference.join(' > ')}`),
|
|
209
|
-
op: mmOp({ kind: 'setRadioModes', preference: desired.radio }),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function planSimSlot(
|
|
214
|
-
current: AppliedCellularState,
|
|
215
|
-
desired: DesiredCellularPolicy,
|
|
216
|
-
capabilities: ModemCapabilities,
|
|
217
|
-
): DimensionResult {
|
|
218
|
-
if (desired.simSlot === undefined) {
|
|
219
|
-
return { receipt: receipt('simSlot', 'applied', 'no primary SIM slot preference set') };
|
|
220
|
-
}
|
|
221
|
-
if (capabilities.simSlotCount <= 1) {
|
|
222
|
-
return {
|
|
223
|
-
receipt: receipt(
|
|
224
|
-
'simSlot',
|
|
225
|
-
'unsupported',
|
|
226
|
-
`primary SIM slot selection requires a multi-slot modem (this modem has ${capabilities.simSlotCount})`,
|
|
227
|
-
),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
if (desired.simSlot < 1 || desired.simSlot > capabilities.simSlotCount) {
|
|
231
|
-
return {
|
|
232
|
-
receipt: receipt(
|
|
233
|
-
'simSlot',
|
|
234
|
-
'failed',
|
|
235
|
-
`SIM slot ${desired.simSlot} is out of range (1..${capabilities.simSlotCount})`,
|
|
236
|
-
),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
if (current.activePrimarySlot === desired.simSlot) {
|
|
240
|
-
return {
|
|
241
|
-
receipt: receipt('simSlot', 'applied', `SIM slot ${desired.simSlot} already primary`),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
receipt: receipt('simSlot', 'pending', `switching primary SIM slot to ${desired.simSlot}`),
|
|
246
|
-
op: mmOp({ kind: 'setPrimarySimSlot', slotIndex: desired.simSlot }),
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function planEnabled(
|
|
251
|
-
current: AppliedCellularState,
|
|
252
|
-
desired: DesiredCellularPolicy,
|
|
253
|
-
): DimensionResult {
|
|
254
|
-
const nm = current.nmActivation;
|
|
255
|
-
const addressable = current.connectionId !== undefined && current.deviceIfname !== undefined;
|
|
256
|
-
if (desired.enabled) {
|
|
257
|
-
switch (nm) {
|
|
258
|
-
case 'activated':
|
|
259
|
-
return { receipt: receipt('enabled', 'applied', 'connection is active') };
|
|
260
|
-
case 'activating':
|
|
261
|
-
return { receipt: receipt('enabled', 'pending', 'connection activation in progress') };
|
|
262
|
-
case 'failed':
|
|
263
|
-
return {
|
|
264
|
-
receipt: receipt(
|
|
265
|
-
'enabled',
|
|
266
|
-
'failed',
|
|
267
|
-
`connection activation failed${current.activationFailureReason ? `: ${current.activationFailureReason}` : ''}`,
|
|
268
|
-
),
|
|
269
|
-
};
|
|
270
|
-
case 'unmanaged':
|
|
271
|
-
return {
|
|
272
|
-
receipt: receipt('enabled', 'unsupported', 'device is not managed by NetworkManager'),
|
|
273
|
-
};
|
|
274
|
-
default:
|
|
275
|
-
if (
|
|
276
|
-
!addressable ||
|
|
277
|
-
current.connectionId === undefined ||
|
|
278
|
-
current.deviceIfname === undefined
|
|
279
|
-
) {
|
|
280
|
-
return {
|
|
281
|
-
receipt: receipt(
|
|
282
|
-
'enabled',
|
|
283
|
-
'pending',
|
|
284
|
-
'awaiting connection profile and data interface before activation',
|
|
285
|
-
),
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
receipt: receipt('enabled', 'pending', 'activating connection'),
|
|
290
|
-
op: nmOp({
|
|
291
|
-
kind: 'activate',
|
|
292
|
-
connectionId: current.connectionId,
|
|
293
|
-
deviceIfname: current.deviceIfname,
|
|
294
|
-
}),
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (
|
|
299
|
-
(nm === 'activated' || nm === 'activating') &&
|
|
300
|
-
current.connectionId !== undefined &&
|
|
301
|
-
current.deviceIfname !== undefined
|
|
302
|
-
) {
|
|
303
|
-
return {
|
|
304
|
-
receipt: receipt('enabled', 'pending', 'deactivating connection'),
|
|
305
|
-
op: nmOp({
|
|
306
|
-
kind: 'deactivate',
|
|
307
|
-
connectionId: current.connectionId,
|
|
308
|
-
deviceIfname: current.deviceIfname,
|
|
309
|
-
}),
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
return { receipt: receipt('enabled', 'applied', 'connection is inactive') };
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Recovery + usage are LOCAL-CONTROLLER owned (README ownership table): they emit no
|
|
316
|
-
// MM / NM op, only a receipt recording that the local policy was accepted.
|
|
317
|
-
function planRecovery(desired: DesiredCellularPolicy): Receipt {
|
|
318
|
-
return receipt(
|
|
319
|
-
'recovery',
|
|
320
|
-
'applied',
|
|
321
|
-
desired.recovery.enabled ? 'recovery policy recorded (enabled)' : 'recovery disabled (default)',
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function planUsage(desired: DesiredCellularPolicy): Receipt {
|
|
326
|
-
const parts: string[] = [];
|
|
327
|
-
if (desired.usage.cycleDay !== undefined) {
|
|
328
|
-
parts.push(`cycle day ${desired.usage.cycleDay}`);
|
|
329
|
-
}
|
|
330
|
-
if (desired.usage.thresholdBytes !== undefined) {
|
|
331
|
-
parts.push(`threshold ${desired.usage.thresholdBytes} bytes`);
|
|
332
|
-
}
|
|
333
|
-
return receipt(
|
|
334
|
-
'usage',
|
|
335
|
-
'applied',
|
|
336
|
-
parts.length > 0 ? `usage policy recorded (${parts.join(', ')})` : 'no usage policy set',
|
|
337
|
-
);
|
|
338
|
-
}
|
package/src/ports/router.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// The router port — for devices MM cannot control (HiLink, router-ethernet class).
|
|
2
|
-
//
|
|
3
|
-
// This port is DELIBERATELY tiny: presence and advisory health ONLY. Health never
|
|
4
|
-
// drives activation or recovery — a degraded router stays in the routing set (it is
|
|
5
|
-
// never evicted on health alone). A router device is an Ethernet uplink we OBSERVE,
|
|
6
|
-
// not a modem we configure; there is no bearer / APN / radio verb here.
|
|
7
|
-
|
|
8
|
-
import type { EpochMillis } from '../domain';
|
|
9
|
-
import type { DeviceIfname } from './network-manager';
|
|
10
|
-
|
|
11
|
-
/** Whether a router-class device is present on an interface. */
|
|
12
|
-
export type RouterPresence = 'present' | 'absent';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Advisory health of a router-class uplink. Every field is informational — a
|
|
16
|
-
* degraded router is reported, never removed from the routing set on health alone.
|
|
17
|
-
*/
|
|
18
|
-
export interface RouterHealth {
|
|
19
|
-
readonly presence: RouterPresence;
|
|
20
|
-
readonly gatewayReachable: boolean;
|
|
21
|
-
readonly egressHealthy: boolean;
|
|
22
|
-
readonly observedAt: EpochMillis;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** The router port — presence and advisory health only. No mutation verbs. */
|
|
26
|
-
export interface RouterPort {
|
|
27
|
-
probePresence(ifname: DeviceIfname): Promise<RouterPresence>;
|
|
28
|
-
checkHealth(ifname: DeviceIfname): Promise<RouterHealth>;
|
|
29
|
-
}
|
package/src/redact.test.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
// Redaction classes — ICCID, IMSI, EID, PIN, PUK, and APN/connection passwords are
|
|
2
|
-
// stripped, including deeply nested occurrences and inside arrays; non-secret
|
|
3
|
-
// siblings survive and the input is never mutated.
|
|
4
|
-
|
|
5
|
-
import { expect, test } from 'bun:test';
|
|
6
|
-
import { REDACTED, redact } from './redact';
|
|
7
|
-
|
|
8
|
-
test('redacts every sensitive class at the top level, keeping non-secret siblings', () => {
|
|
9
|
-
const input = {
|
|
10
|
-
iccid: '8988303000000000000',
|
|
11
|
-
imsi: '310150123456789',
|
|
12
|
-
eid: '89049032000000000000000000000000',
|
|
13
|
-
pin: '1234',
|
|
14
|
-
puk: '12345678',
|
|
15
|
-
password: 's3cret',
|
|
16
|
-
apn: 'internet',
|
|
17
|
-
username: 'operator-user',
|
|
18
|
-
};
|
|
19
|
-
const out = redact(input) as Record<string, unknown>;
|
|
20
|
-
|
|
21
|
-
expect(out.iccid).toBe(REDACTED);
|
|
22
|
-
expect(out.imsi).toBe(REDACTED);
|
|
23
|
-
expect(out.eid).toBe(REDACTED);
|
|
24
|
-
expect(out.pin).toBe(REDACTED);
|
|
25
|
-
expect(out.puk).toBe(REDACTED);
|
|
26
|
-
expect(out.password).toBe(REDACTED);
|
|
27
|
-
expect(out.apn).toBe('internet');
|
|
28
|
-
expect(out.username).toBe('operator-user');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('redacts an APN password nested three levels deep', () => {
|
|
32
|
-
const policy = { connection: { auth: { username: 'u', password: 'hunter2' } } };
|
|
33
|
-
const out = redact(policy) as { connection: { auth: { username: string; password: string } } };
|
|
34
|
-
expect(out.connection.auth.password).toBe(REDACTED);
|
|
35
|
-
expect(out.connection.auth.username).toBe('u');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('redacts ICCID inside an array of SIM slots', () => {
|
|
39
|
-
const input = {
|
|
40
|
-
simSlots: [
|
|
41
|
-
{ index: 1, iccid: '8988000000000000001' },
|
|
42
|
-
{ index: 2, iccid: '8988000000000000002' },
|
|
43
|
-
],
|
|
44
|
-
};
|
|
45
|
-
const out = redact(input) as { simSlots: Array<{ index: number; iccid: string }> };
|
|
46
|
-
expect(out.simSlots[0]?.iccid).toBe(REDACTED);
|
|
47
|
-
expect(out.simSlots[1]?.iccid).toBe(REDACTED);
|
|
48
|
-
expect(out.simSlots[0]?.index).toBe(1);
|
|
49
|
-
expect(out.simSlots[1]?.index).toBe(2);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('redacts NM-style gsm.password but keeps the gsm.password-flags flag', () => {
|
|
53
|
-
const input = { 'gsm.password': 'secret', 'gsm.password-flags': '0', 'gsm.apn': 'internet' };
|
|
54
|
-
const out = redact(input) as Record<string, unknown>;
|
|
55
|
-
expect(out['gsm.password']).toBe(REDACTED);
|
|
56
|
-
expect(out['gsm.password-flags']).toBe('0');
|
|
57
|
-
expect(out['gsm.apn']).toBe('internet');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('redacts subscriptionId, newPin, and puk2 variants', () => {
|
|
61
|
-
const input = { subscriptionId: '8988303000000000000', newPin: '4321', puk2: '87654321' };
|
|
62
|
-
const out = redact(input) as Record<string, unknown>;
|
|
63
|
-
expect(out.subscriptionId).toBe(REDACTED);
|
|
64
|
-
expect(out.newPin).toBe(REDACTED);
|
|
65
|
-
expect(out.puk2).toBe(REDACTED);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('does not mutate the input', () => {
|
|
69
|
-
const input = { pin: '1234', nested: { iccid: '5678' } };
|
|
70
|
-
const before = JSON.stringify(input);
|
|
71
|
-
redact(input);
|
|
72
|
-
expect(JSON.stringify(input)).toBe(before);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('passes primitives and empty containers through unchanged', () => {
|
|
76
|
-
expect(redact('plain')).toBe('plain');
|
|
77
|
-
expect(redact(42)).toBe(42);
|
|
78
|
-
expect(redact(null)).toBe(null);
|
|
79
|
-
expect(redact(undefined)).toBe(undefined);
|
|
80
|
-
expect(redact({})).toEqual({});
|
|
81
|
-
expect(redact([])).toEqual([]);
|
|
82
|
-
});
|
package/src/redact.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
// Redaction — strip sensitive identifiers from any value before it is logged,
|
|
2
|
-
// serialized into a receipt, or written to a bundle.
|
|
3
|
-
//
|
|
4
|
-
// The sensitive CLASSES (draft §Oracle #1, round-5 auth semantics): ICCID, IMSI,
|
|
5
|
-
// EID, SIM PIN, SIM PUK, and APN / connection passwords. Redaction is KEY-BASED and
|
|
6
|
-
// RECURSIVE: it walks nested objects and arrays and replaces the value under any
|
|
7
|
-
// sensitive key with a fixed marker, no matter how deep — e.g. a password at
|
|
8
|
-
// `policy.connection.auth.password`, or an `iccid` inside an array of SIM slots.
|
|
9
|
-
|
|
10
|
-
/** The marker substituted for every redacted value. */
|
|
11
|
-
export const REDACTED = '[redacted]';
|
|
12
|
-
|
|
13
|
-
// Leaf key names carrying a sensitive value, matched case-insensitively. The match
|
|
14
|
-
// is EXACT (or exact on the last dotted segment), so NM-style keys like
|
|
15
|
-
// `gsm.password` are caught while non-secret siblings like `gsm.password-flags`
|
|
16
|
-
// (a "0"/"4" flag, not a secret) are not.
|
|
17
|
-
const SENSITIVE_KEYS: ReadonlySet<string> = new Set<string>([
|
|
18
|
-
'iccid',
|
|
19
|
-
'imsi',
|
|
20
|
-
'eid',
|
|
21
|
-
'pin',
|
|
22
|
-
'pin2',
|
|
23
|
-
'newpin',
|
|
24
|
-
'puk',
|
|
25
|
-
'puk2',
|
|
26
|
-
'password',
|
|
27
|
-
'passwd',
|
|
28
|
-
'subscriptionid',
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
function isSensitiveKey(key: string): boolean {
|
|
32
|
-
const lower = key.toLowerCase();
|
|
33
|
-
if (SENSITIVE_KEYS.has(lower)) {
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
const dot = lower.lastIndexOf('.');
|
|
37
|
-
return dot >= 0 && SENSITIVE_KEYS.has(lower.slice(dot + 1));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
41
|
-
if (typeof value !== 'object' || value === null) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
const proto = Object.getPrototypeOf(value) as unknown;
|
|
45
|
-
return proto === Object.prototype || proto === null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function redactValue(value: unknown, underSensitiveKey: boolean): unknown {
|
|
49
|
-
if (underSensitiveKey) {
|
|
50
|
-
return REDACTED;
|
|
51
|
-
}
|
|
52
|
-
if (Array.isArray(value)) {
|
|
53
|
-
return value.map((item) => redactValue(item, false));
|
|
54
|
-
}
|
|
55
|
-
if (isPlainObject(value)) {
|
|
56
|
-
const out: Record<string, unknown> = {};
|
|
57
|
-
for (const [key, child] of Object.entries(value)) {
|
|
58
|
-
out[key] = redactValue(child, isSensitiveKey(key));
|
|
59
|
-
}
|
|
60
|
-
return out;
|
|
61
|
-
}
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Return a deep copy of `value` with every sensitive field redacted. Plain objects
|
|
67
|
-
* and arrays are walked recursively; all other values (primitives, and opaque
|
|
68
|
-
* objects like `Date` / `Set` / `Map`) are returned unchanged. The input is never
|
|
69
|
-
* mutated.
|
|
70
|
-
*/
|
|
71
|
-
export function redact(value: unknown): unknown {
|
|
72
|
-
return redactValue(value, false);
|
|
73
|
-
}
|