@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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Brand, EpochMillis } from '../domain/index.js';
|
|
2
|
+
import type { Receipt } from './receipts.js';
|
|
3
|
+
/** A NetworkManager connection-profile UUID. */
|
|
4
|
+
export type ConnectionId = Brand<string, 'ConnectionId'>;
|
|
5
|
+
/** A kernel network-device interface name (e.g. `wwan0`). */
|
|
6
|
+
export type DeviceIfname = Brand<string, 'DeviceIfname'>;
|
|
7
|
+
/** Construct a `ConnectionId` from a non-empty NM connection UUID. */
|
|
8
|
+
export declare function connectionId(value: string): ConnectionId;
|
|
9
|
+
/** Construct a `DeviceIfname` from a non-empty interface name. */
|
|
10
|
+
export declare function deviceIfname(value: string): DeviceIfname;
|
|
11
|
+
/**
|
|
12
|
+
* A GSM connection profile — NM's `gsm.*` setting group. `password` is SENSITIVE
|
|
13
|
+
* and MUST be redacted in every log / output (see `../redact`). The full nine-field
|
|
14
|
+
* nmcli write parity lands with the concrete adapter (A4.1); this is the port
|
|
15
|
+
* contract that adapter fulfils.
|
|
16
|
+
*/
|
|
17
|
+
export interface GsmProfileInput {
|
|
18
|
+
readonly connectionName: string;
|
|
19
|
+
/** Concrete APN, or the empty string when `autoConfig` drives it (A4.1). */
|
|
20
|
+
readonly apn: string;
|
|
21
|
+
readonly username?: string;
|
|
22
|
+
/** SENSITIVE — redact everywhere. */
|
|
23
|
+
readonly password?: string;
|
|
24
|
+
/** `true` ⇒ `gsm.home-only` (roaming disabled). */
|
|
25
|
+
readonly homeOnly: boolean;
|
|
26
|
+
/** `true` ⇒ `gsm.auto-config yes` (Auto-APN); mutually exclusive with creds. */
|
|
27
|
+
readonly autoConfig: boolean;
|
|
28
|
+
/** Manual operator selection (`gsm.network-id`); empty unless roaming-pinned. */
|
|
29
|
+
readonly networkId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A persisted GSM profile as read back from NM, keyed by its connection id. */
|
|
32
|
+
export interface GsmProfile extends GsmProfileInput {
|
|
33
|
+
readonly connectionId: ConnectionId;
|
|
34
|
+
}
|
|
35
|
+
/** A partial update to an existing GSM profile. */
|
|
36
|
+
export type GsmProfilePatch = Partial<GsmProfileInput>;
|
|
37
|
+
/**
|
|
38
|
+
* A quiesce lease — a held guarantee that the connection on THIS exact device stays
|
|
39
|
+
* deactivated while a disruptive MM operation runs, then is reactivated on release.
|
|
40
|
+
* Bound to BOTH `connectionId` and `deviceIfname` so it can never quiesce the wrong
|
|
41
|
+
* device (the two-device isolation invariant, A4.1).
|
|
42
|
+
*/
|
|
43
|
+
export interface QuiesceLease {
|
|
44
|
+
readonly connectionId: ConnectionId;
|
|
45
|
+
readonly deviceIfname: DeviceIfname;
|
|
46
|
+
readonly acquiredAt: EpochMillis;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The NetworkManager port. Activation and deactivation take BOTH the connection id
|
|
50
|
+
* AND the device interface name — never an id alone. This encodes, at the type
|
|
51
|
+
* level, that we address a connection on an EXACT device (`nmcli connection up
|
|
52
|
+
* <uuid> ifname <dev>` / verify-then-`device disconnect <ifname>`); the id-only
|
|
53
|
+
* `nmcli connection down` is structurally impossible to express here (A4.1).
|
|
54
|
+
*/
|
|
55
|
+
export interface NetworkManagerPort {
|
|
56
|
+
createGsmProfile(profile: GsmProfileInput): Promise<GsmProfile>;
|
|
57
|
+
readGsmProfile(id: ConnectionId): Promise<GsmProfile | undefined>;
|
|
58
|
+
updateGsmProfile(id: ConnectionId, patch: GsmProfilePatch): Promise<GsmProfile>;
|
|
59
|
+
deleteGsmProfile(id: ConnectionId): Promise<void>;
|
|
60
|
+
/** Activate `id` on the EXACT device `ifname`. */
|
|
61
|
+
activate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt>;
|
|
62
|
+
/** Deactivate `id` on the EXACT device `ifname`. */
|
|
63
|
+
deactivate(id: ConnectionId, ifname: DeviceIfname): Promise<Receipt>;
|
|
64
|
+
/** Take a quiesce lease over `id` on the EXACT device `ifname`. */
|
|
65
|
+
acquireQuiesceLease(id: ConnectionId, ifname: DeviceIfname): Promise<QuiesceLease>;
|
|
66
|
+
/** Release a quiesce lease, reactivating the connection it held down. */
|
|
67
|
+
releaseQuiesceLease(lease: QuiesceLease): Promise<void>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The NetworkManager port — NM is the SOLE owner of bearers, APN, auth, roaming,
|
|
2
|
+
// autoconnect, and activation (see README ownership table). This port is the ONLY
|
|
3
|
+
// way the controller touches any of those resources; the ModemManager port has no
|
|
4
|
+
// bearer / connection verbs at all.
|
|
5
|
+
import { nonEmptyString } from '../domain/index.js';
|
|
6
|
+
/** Construct a `ConnectionId` from a non-empty NM connection UUID. */
|
|
7
|
+
export function connectionId(value) {
|
|
8
|
+
return nonEmptyString(value, 'connectionId');
|
|
9
|
+
}
|
|
10
|
+
/** Construct a `DeviceIfname` from a non-empty interface name. */
|
|
11
|
+
export function deviceIfname(value) {
|
|
12
|
+
return nonEmptyString(value, 'deviceIfname');
|
|
13
|
+
}
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// `ModemObservationPort` is the NARROW port: it observes modems and reports them as
|
|
4
|
-
// a stream of discriminated list results. It carries NO mutation methods at all.
|
|
5
|
-
// The full `ModemManagerPort` (mutations) EXTENDS this, so a shadow / read-only
|
|
6
|
-
// consumer can depend on observation alone and never gain a mutating verb.
|
|
7
|
-
|
|
8
|
-
import type { CellularSnapshot } from '../domain';
|
|
9
|
-
|
|
1
|
+
import type { CellularSnapshot } from '../domain/index.js';
|
|
10
2
|
/** Cancels a subscription created by `observe`. Calling it twice is a no-op. */
|
|
11
3
|
export type Unsubscribe = () => void;
|
|
12
|
-
|
|
13
4
|
/** Why an observation could not produce an authoritative list this cycle. */
|
|
14
5
|
export type ObservationFailureReason = 'not-started' | 'source-unavailable' | 'bus-error';
|
|
15
|
-
|
|
16
6
|
/**
|
|
17
7
|
* The result of listing modems — DISCRIMINATED on `ok`, and it NEVER throws away
|
|
18
8
|
* rows. Even the failure arm carries `rows`: the last-known modems are RETAINED so
|
|
@@ -20,18 +10,17 @@ export type ObservationFailureReason = 'not-started' | 'source-unavailable' | 'b
|
|
|
20
10
|
* expressed by an `ok: true` snapshot that OMITS a modem (A3.1 epoch authority) —
|
|
21
11
|
* the false-removal class is dead by construction.
|
|
22
12
|
*/
|
|
23
|
-
export type ObservationList<T = CellularSnapshot> =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
13
|
+
export type ObservationList<T = CellularSnapshot> = {
|
|
14
|
+
readonly ok: true;
|
|
15
|
+
readonly rows: readonly T[];
|
|
16
|
+
} | {
|
|
17
|
+
readonly ok: false;
|
|
18
|
+
readonly reason: ObservationFailureReason;
|
|
19
|
+
/** Retained rows from the last authoritative snapshot — never dropped. */
|
|
20
|
+
readonly rows: readonly T[];
|
|
21
|
+
};
|
|
32
22
|
/** A subscriber to the ongoing observation stream. */
|
|
33
23
|
export type ObservationListener<T = CellularSnapshot> = (list: ObservationList<T>) => void;
|
|
34
|
-
|
|
35
24
|
/**
|
|
36
25
|
* The read-only observation port. `start()` connects, subscribes, and resolves the
|
|
37
26
|
* first AUTHORITATIVE list; `observe()` streams every subsequent list (each snapshot
|
|
@@ -39,10 +28,10 @@ export type ObservationListener<T = CellularSnapshot> = (list: ObservationList<T
|
|
|
39
28
|
* here can change modem state — that narrowness is the whole point of the port.
|
|
40
29
|
*/
|
|
41
30
|
export interface ModemObservationPort {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
/** Connect, subscribe, and resolve the first authoritative observation list. */
|
|
32
|
+
start(): Promise<ObservationList>;
|
|
33
|
+
/** Subscribe to the ongoing stream of observation lists. Returns an unsubscribe. */
|
|
34
|
+
observe(listener: ObservationListener): Unsubscribe;
|
|
35
|
+
/** Tear down the subscription and release the source. Idempotent. */
|
|
36
|
+
stop(): Promise<void>;
|
|
48
37
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The observation contract — the read side shared by every ModemManager backend.
|
|
2
|
+
//
|
|
3
|
+
// `ModemObservationPort` is the NARROW port: it observes modems and reports them as
|
|
4
|
+
// a stream of discriminated list results. It carries NO mutation methods at all.
|
|
5
|
+
// The full `ModemManagerPort` (mutations) EXTENDS this, so a shadow / read-only
|
|
6
|
+
// consumer can depend on observation alone and never gain a mutating verb.
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DesiredRadio } from '../domain/index.js';
|
|
2
|
+
import type { ConnectionId, DeviceIfname, GsmProfileInput, GsmProfilePatch } from './network-manager.js';
|
|
3
|
+
/** Radio + SIM ops — owned SOLELY by the ModemManager port. */
|
|
4
|
+
export type MmOp = {
|
|
5
|
+
readonly kind: 'setRadioModes';
|
|
6
|
+
readonly preference: DesiredRadio;
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'setPrimarySimSlot';
|
|
9
|
+
readonly slotIndex: number;
|
|
10
|
+
};
|
|
11
|
+
/** Connection / APN / activation ops — owned SOLELY by the NetworkManager port. */
|
|
12
|
+
export type NmOp = {
|
|
13
|
+
readonly kind: 'createGsmProfile';
|
|
14
|
+
readonly profile: GsmProfileInput;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'updateGsmProfile';
|
|
17
|
+
readonly connectionId: ConnectionId;
|
|
18
|
+
readonly patch: GsmProfilePatch;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: 'activate';
|
|
21
|
+
readonly connectionId: ConnectionId;
|
|
22
|
+
readonly deviceIfname: DeviceIfname;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'deactivate';
|
|
25
|
+
readonly connectionId: ConnectionId;
|
|
26
|
+
readonly deviceIfname: DeviceIfname;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A planned op tagged with its owning port. The two arms are disjoint on BOTH the
|
|
30
|
+
* `port` discriminant AND the op `kind` space, so the planner cannot emit — and a
|
|
31
|
+
* reviewer cannot write — a radio op under the NM port, or a connection op under
|
|
32
|
+
* the MM port.
|
|
33
|
+
*/
|
|
34
|
+
export type PortTaggedOp = {
|
|
35
|
+
readonly port: 'mm';
|
|
36
|
+
readonly op: MmOp;
|
|
37
|
+
} | {
|
|
38
|
+
readonly port: 'nm';
|
|
39
|
+
readonly op: NmOp;
|
|
40
|
+
};
|
|
41
|
+
/** Tag an MM op for the ModemManager port. Only an `MmOp` is accepted. */
|
|
42
|
+
export declare function mmOp(op: MmOp): PortTaggedOp;
|
|
43
|
+
/** Tag an NM op for the NetworkManager port. Only an `NmOp` is accepted. */
|
|
44
|
+
export declare function nmOp(op: NmOp): PortTaggedOp;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Port-tagged reconcile ops — a planned mutation TAGGED with the port that owns it.
|
|
2
|
+
//
|
|
3
|
+
// The op kinds are DISJOINT by construction: a radio / SIM op (`MmOp`) shares no
|
|
4
|
+
// `kind` with a connection / APN op (`NmOp`). Tagging is therefore type-checked —
|
|
5
|
+
// `{ port: 'nm', op: { kind: 'setRadioModes', … } }` does NOT compile, because a
|
|
6
|
+
// radio op is not assignable to the NM arm. That mis-tag is a COMPILE-TIME error
|
|
7
|
+
// (see `ops.type-test.ts` for the `@ts-expect-error` proofs). This is the ownership
|
|
8
|
+
// matrix (README) enforced by the type system, not merely by convention.
|
|
9
|
+
/** Tag an MM op for the ModemManager port. Only an `MmOp` is accepted. */
|
|
10
|
+
export function mmOp(op) {
|
|
11
|
+
return { port: 'mm', op };
|
|
12
|
+
}
|
|
13
|
+
/** Tag an NM op for the NetworkManager port. Only an `NmOp` is accepted. */
|
|
14
|
+
export function nmOp(op) {
|
|
15
|
+
return { port: 'nm', op };
|
|
16
|
+
}
|
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
// Reconcile receipts — the HONEST outcome of trying to apply one policy dimension.
|
|
2
|
-
//
|
|
3
|
-
// Every attempt to converge a dimension yields exactly one receipt with a status
|
|
4
|
-
// AND a reason. Nothing is ever silently dropped: "prefer 5G" on a 4G-only modem is
|
|
5
|
-
// reported as `unsupported` with a reason, never quietly downgraded to "5G off".
|
|
6
|
-
|
|
7
1
|
/** The policy dimension a receipt is about. */
|
|
8
|
-
export type PolicyDimension =
|
|
9
|
-
| 'enabled'
|
|
10
|
-
| 'connection'
|
|
11
|
-
| 'roaming'
|
|
12
|
-
| 'radio'
|
|
13
|
-
| 'simSlot'
|
|
14
|
-
| 'recovery'
|
|
15
|
-
| 'usage';
|
|
16
|
-
|
|
2
|
+
export type PolicyDimension = 'enabled' | 'connection' | 'roaming' | 'radio' | 'band' | 'simSlot' | 'recovery' | 'usage';
|
|
17
3
|
/**
|
|
18
4
|
* Receipt status taxonomy:
|
|
19
5
|
* - applied — the desired state is in effect (converged, or already was).
|
|
@@ -22,22 +8,14 @@ export type PolicyDimension =
|
|
|
22
8
|
* - failed — an op was attempted and errored.
|
|
23
9
|
*/
|
|
24
10
|
export type ReceiptStatus = 'applied' | 'pending' | 'unsupported' | 'failed';
|
|
25
|
-
|
|
26
11
|
/**
|
|
27
12
|
* One reconcile receipt. `reason` is ALWAYS populated — a receipt with no reason
|
|
28
13
|
* would be a silent outcome, which the contract forbids.
|
|
29
14
|
*/
|
|
30
15
|
export interface Receipt {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
readonly dimension: PolicyDimension;
|
|
17
|
+
readonly status: ReceiptStatus;
|
|
18
|
+
readonly reason: string;
|
|
34
19
|
}
|
|
35
|
-
|
|
36
20
|
/** Build a receipt. Kept as a helper so `reason` can never be forgotten. */
|
|
37
|
-
export function receipt(
|
|
38
|
-
dimension: PolicyDimension,
|
|
39
|
-
status: ReceiptStatus,
|
|
40
|
-
reason: string,
|
|
41
|
-
): Receipt {
|
|
42
|
-
return { dimension, status, reason };
|
|
43
|
-
}
|
|
21
|
+
export declare function receipt(dimension: PolicyDimension, status: ReceiptStatus, reason: string): Receipt;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Reconcile receipts — the HONEST outcome of trying to apply one policy dimension.
|
|
2
|
+
//
|
|
3
|
+
// Every attempt to converge a dimension yields exactly one receipt with a status
|
|
4
|
+
// AND a reason. Nothing is ever silently dropped: "prefer 5G" on a 4G-only modem is
|
|
5
|
+
// reported as `unsupported` with a reason, never quietly downgraded to "5G off".
|
|
6
|
+
/** Build a receipt. Kept as a helper so `reason` can never be forgotten. */
|
|
7
|
+
export function receipt(dimension, status, reason) {
|
|
8
|
+
return { dimension, status, reason };
|
|
9
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DesiredCellularPolicy, NmActivation, RadioAccessTechnology } from '../domain/index.js';
|
|
2
|
+
import type { ConnectionId, DeviceIfname } from './network-manager.js';
|
|
3
|
+
import { type PortTaggedOp } from './ops.js';
|
|
4
|
+
import { type Receipt } from './receipts.js';
|
|
5
|
+
/** What the modem / stack can actually do — the capability set the planner honors. */
|
|
6
|
+
export interface ModemCapabilities {
|
|
7
|
+
readonly supportedRats: ReadonlySet<RadioAccessTechnology>;
|
|
8
|
+
readonly simSlotCount: number;
|
|
9
|
+
readonly supportsAutoApn: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The currently-applied cellular configuration, as the planner sees it — the "is"
|
|
13
|
+
* state, distinct from the observational snapshot: it tracks what has actually been
|
|
14
|
+
* written (profile, radio preference, primary slot) plus the live NM activation.
|
|
15
|
+
*/
|
|
16
|
+
export interface AppliedCellularState {
|
|
17
|
+
readonly nmActivation: NmActivation;
|
|
18
|
+
readonly hasProfile: boolean;
|
|
19
|
+
readonly connectionId?: ConnectionId;
|
|
20
|
+
readonly deviceIfname?: DeviceIfname;
|
|
21
|
+
readonly appliedApn?: 'auto' | string;
|
|
22
|
+
readonly appliedRoaming?: boolean;
|
|
23
|
+
readonly appliedRadioPreference?: readonly RadioAccessTechnology[];
|
|
24
|
+
readonly activePrimarySlot?: number;
|
|
25
|
+
readonly activationFailureReason?: string;
|
|
26
|
+
}
|
|
27
|
+
/** A reconcile plan: the ops to run and one receipt per policy dimension. */
|
|
28
|
+
export interface Plan {
|
|
29
|
+
readonly ops: readonly PortTaggedOp[];
|
|
30
|
+
readonly receipts: readonly Receipt[];
|
|
31
|
+
}
|
|
32
|
+
/** Reconcile a desired policy against the applied state into ops + receipts. */
|
|
33
|
+
export declare function planReconcile(current: AppliedCellularState, desired: DesiredCellularPolicy, capabilities: ModemCapabilities): Plan;
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
import { mmOp, nmOp } from './ops.js';
|
|
7
|
+
import { receipt } from './receipts.js';
|
|
8
|
+
/** Reconcile a desired policy against the applied state into ops + receipts. */
|
|
9
|
+
export function planReconcile(current, desired, capabilities) {
|
|
10
|
+
const profile = planNmProfile(current, desired, capabilities);
|
|
11
|
+
const results = [
|
|
12
|
+
planRadio(current, desired, capabilities),
|
|
13
|
+
planSimSlot(current, desired, capabilities),
|
|
14
|
+
planEnabled(current, desired),
|
|
15
|
+
{ receipt: planRecovery(desired) },
|
|
16
|
+
{ receipt: planUsage(desired) },
|
|
17
|
+
];
|
|
18
|
+
const ops = [];
|
|
19
|
+
const receipts = [profile.connection, profile.roaming];
|
|
20
|
+
if (profile.op !== undefined) {
|
|
21
|
+
ops.push(profile.op);
|
|
22
|
+
}
|
|
23
|
+
for (const result of results) {
|
|
24
|
+
receipts.push(result.receipt);
|
|
25
|
+
if (result.op !== undefined) {
|
|
26
|
+
ops.push(result.op);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { ops, receipts };
|
|
30
|
+
}
|
|
31
|
+
function profileFromPolicy(desired) {
|
|
32
|
+
const auto = desired.connection.apn === 'auto';
|
|
33
|
+
const base = {
|
|
34
|
+
connectionName: 'ceralive-cellular',
|
|
35
|
+
apn: auto ? '' : desired.connection.apn,
|
|
36
|
+
homeOnly: !desired.roaming,
|
|
37
|
+
autoConfig: auto,
|
|
38
|
+
};
|
|
39
|
+
// SENSITIVE creds only when explicitly provided AND not in auto-config mode.
|
|
40
|
+
if (!auto && desired.connection.auth !== undefined) {
|
|
41
|
+
const { username, password } = desired.connection.auth;
|
|
42
|
+
return {
|
|
43
|
+
...base,
|
|
44
|
+
...(username !== undefined ? { username } : {}),
|
|
45
|
+
...(password !== undefined ? { password } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return base;
|
|
49
|
+
}
|
|
50
|
+
// NM owns APN / auth / roaming / autoconnect: one profile write converges all of
|
|
51
|
+
// them, so connection + roaming share a single op and each get their own receipt.
|
|
52
|
+
function planNmProfile(current, desired, capabilities) {
|
|
53
|
+
const auto = desired.connection.apn === 'auto';
|
|
54
|
+
if (auto && !capabilities.supportsAutoApn) {
|
|
55
|
+
return {
|
|
56
|
+
connection: receipt('connection', 'unsupported', 'Auto-APN is not available on this NetworkManager / modem'),
|
|
57
|
+
roaming: receipt('roaming', 'unsupported', 'roaming cannot be applied without a connection profile'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const needsWrite = !current.hasProfile ||
|
|
61
|
+
current.appliedApn !== desired.connection.apn ||
|
|
62
|
+
current.appliedRoaming !== desired.roaming;
|
|
63
|
+
if (!needsWrite) {
|
|
64
|
+
return {
|
|
65
|
+
connection: receipt('connection', 'applied', `connection APN '${desired.connection.apn}' already configured`),
|
|
66
|
+
roaming: receipt('roaming', 'applied', `roaming already ${desired.roaming ? 'enabled' : 'disabled'}`),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const op = current.hasProfile && current.connectionId !== undefined
|
|
70
|
+
? nmOp({
|
|
71
|
+
kind: 'updateGsmProfile',
|
|
72
|
+
connectionId: current.connectionId,
|
|
73
|
+
patch: profileFromPolicy(desired),
|
|
74
|
+
})
|
|
75
|
+
: nmOp({ kind: 'createGsmProfile', profile: profileFromPolicy(desired) });
|
|
76
|
+
const verb = current.hasProfile ? 'updating' : 'creating';
|
|
77
|
+
return {
|
|
78
|
+
connection: receipt('connection', 'pending', `${verb} connection profile for APN '${desired.connection.apn}'`),
|
|
79
|
+
roaming: receipt('roaming', 'pending', `roaming ${desired.roaming ? 'enabled' : 'disabled'} via connection profile`),
|
|
80
|
+
op,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function radioMatches(applied, desired) {
|
|
84
|
+
if (applied === undefined || applied.length !== desired.length) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return applied.every((rat, index) => rat === desired[index]);
|
|
88
|
+
}
|
|
89
|
+
function planRadio(current, desired, capabilities) {
|
|
90
|
+
const preference = desired.radio.preferenceOrdered;
|
|
91
|
+
const top = preference[0];
|
|
92
|
+
if (top === undefined) {
|
|
93
|
+
return {
|
|
94
|
+
receipt: receipt('radio', 'failed', 'radio preference must list at least one access technology'),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (!capabilities.supportedRats.has(top)) {
|
|
98
|
+
const supported = [...capabilities.supportedRats].join(', ') || 'none';
|
|
99
|
+
return {
|
|
100
|
+
receipt: receipt('radio', 'unsupported', `preferred radio access technology '${top}' is not supported by this modem (supports ${supported})`),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (radioMatches(current.appliedRadioPreference, preference)) {
|
|
104
|
+
return {
|
|
105
|
+
receipt: receipt('radio', 'applied', `radio preference already ${preference.join(' > ')}`),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
receipt: receipt('radio', 'pending', `setting radio preference to ${preference.join(' > ')}`),
|
|
110
|
+
op: mmOp({ kind: 'setRadioModes', preference: desired.radio }),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function planSimSlot(current, desired, capabilities) {
|
|
114
|
+
if (desired.simSlot === undefined) {
|
|
115
|
+
return { receipt: receipt('simSlot', 'applied', 'no primary SIM slot preference set') };
|
|
116
|
+
}
|
|
117
|
+
if (capabilities.simSlotCount <= 1) {
|
|
118
|
+
return {
|
|
119
|
+
receipt: receipt('simSlot', 'unsupported', `primary SIM slot selection requires a multi-slot modem (this modem has ${capabilities.simSlotCount})`),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (desired.simSlot < 1 || desired.simSlot > capabilities.simSlotCount) {
|
|
123
|
+
return {
|
|
124
|
+
receipt: receipt('simSlot', 'failed', `SIM slot ${desired.simSlot} is out of range (1..${capabilities.simSlotCount})`),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (current.activePrimarySlot === desired.simSlot) {
|
|
128
|
+
return {
|
|
129
|
+
receipt: receipt('simSlot', 'applied', `SIM slot ${desired.simSlot} already primary`),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
receipt: receipt('simSlot', 'pending', `switching primary SIM slot to ${desired.simSlot}`),
|
|
134
|
+
op: mmOp({ kind: 'setPrimarySimSlot', slotIndex: desired.simSlot }),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function planEnabled(current, desired) {
|
|
138
|
+
const nm = current.nmActivation;
|
|
139
|
+
const addressable = current.connectionId !== undefined && current.deviceIfname !== undefined;
|
|
140
|
+
if (desired.enabled) {
|
|
141
|
+
switch (nm) {
|
|
142
|
+
case 'activated':
|
|
143
|
+
return { receipt: receipt('enabled', 'applied', 'connection is active') };
|
|
144
|
+
case 'activating':
|
|
145
|
+
return { receipt: receipt('enabled', 'pending', 'connection activation in progress') };
|
|
146
|
+
case 'failed':
|
|
147
|
+
return {
|
|
148
|
+
receipt: receipt('enabled', 'failed', `connection activation failed${current.activationFailureReason ? `: ${current.activationFailureReason}` : ''}`),
|
|
149
|
+
};
|
|
150
|
+
case 'unmanaged':
|
|
151
|
+
return {
|
|
152
|
+
receipt: receipt('enabled', 'unsupported', 'device is not managed by NetworkManager'),
|
|
153
|
+
};
|
|
154
|
+
default:
|
|
155
|
+
if (!addressable ||
|
|
156
|
+
current.connectionId === undefined ||
|
|
157
|
+
current.deviceIfname === undefined) {
|
|
158
|
+
return {
|
|
159
|
+
receipt: receipt('enabled', 'pending', 'awaiting connection profile and data interface before activation'),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
receipt: receipt('enabled', 'pending', 'activating connection'),
|
|
164
|
+
op: nmOp({
|
|
165
|
+
kind: 'activate',
|
|
166
|
+
connectionId: current.connectionId,
|
|
167
|
+
deviceIfname: current.deviceIfname,
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if ((nm === 'activated' || nm === 'activating') &&
|
|
173
|
+
current.connectionId !== undefined &&
|
|
174
|
+
current.deviceIfname !== undefined) {
|
|
175
|
+
return {
|
|
176
|
+
receipt: receipt('enabled', 'pending', 'deactivating connection'),
|
|
177
|
+
op: nmOp({
|
|
178
|
+
kind: 'deactivate',
|
|
179
|
+
connectionId: current.connectionId,
|
|
180
|
+
deviceIfname: current.deviceIfname,
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { receipt: receipt('enabled', 'applied', 'connection is inactive') };
|
|
185
|
+
}
|
|
186
|
+
// Recovery + usage are LOCAL-CONTROLLER owned (README ownership table): they emit no
|
|
187
|
+
// MM / NM op, only a receipt recording that the local policy was accepted.
|
|
188
|
+
function planRecovery(desired) {
|
|
189
|
+
return receipt('recovery', 'applied', desired.recovery.enabled ? 'recovery policy recorded (enabled)' : 'recovery disabled (default)');
|
|
190
|
+
}
|
|
191
|
+
function planUsage(desired) {
|
|
192
|
+
const parts = [];
|
|
193
|
+
if (desired.usage.cycleDay !== undefined) {
|
|
194
|
+
parts.push(`cycle day ${desired.usage.cycleDay}`);
|
|
195
|
+
}
|
|
196
|
+
if (desired.usage.thresholdBytes !== undefined) {
|
|
197
|
+
parts.push(`threshold ${desired.usage.thresholdBytes} bytes`);
|
|
198
|
+
}
|
|
199
|
+
return receipt('usage', 'applied', parts.length > 0 ? `usage policy recorded (${parts.join(', ')})` : 'no usage policy set');
|
|
200
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const DEFAULT_MODEM_CONTROL_LOCK_PATH = "/run/ceralive/modem-control.lock";
|
|
2
|
+
export type ExclusiveResource = 'file-store' | 'router-session' | 'usb-hub';
|
|
3
|
+
export type ResourceOwnershipRequest = {
|
|
4
|
+
readonly resource: ExclusiveResource;
|
|
5
|
+
};
|
|
6
|
+
export type ResourceOwnershipHolder = {
|
|
7
|
+
readonly pid: number;
|
|
8
|
+
readonly startedAtEpochMs: number;
|
|
9
|
+
};
|
|
10
|
+
export type ResourceOwnershipLoss = {
|
|
11
|
+
readonly reason: 'holder-exited';
|
|
12
|
+
};
|
|
13
|
+
export interface ResourceOwnershipLease {
|
|
14
|
+
readonly holder: ResourceOwnershipHolder;
|
|
15
|
+
readonly lost: Promise<ResourceOwnershipLoss>;
|
|
16
|
+
release(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export type ResourceOwnershipResult = {
|
|
19
|
+
readonly status: 'acquired';
|
|
20
|
+
readonly lease: ResourceOwnershipLease;
|
|
21
|
+
} | {
|
|
22
|
+
readonly status: 'refused';
|
|
23
|
+
readonly reason: 'already-owned';
|
|
24
|
+
readonly holder?: ResourceOwnershipHolder;
|
|
25
|
+
};
|
|
26
|
+
/** Acquire-or-refuse ownership; implementations must never queue a contender. */
|
|
27
|
+
export interface ResourceOwnershipPort {
|
|
28
|
+
acquire(request: ResourceOwnershipRequest): Promise<ResourceOwnershipResult>;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_MODEM_CONTROL_LOCK_PATH = '/run/ceralive/modem-control.lock';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EpochMillis } from '../domain/index.js';
|
|
2
|
+
import type { DeviceIfname } from './network-manager.js';
|
|
3
|
+
/** Whether a router-class device is present on an interface. */
|
|
4
|
+
export type RouterPresence = 'present' | 'absent';
|
|
5
|
+
/**
|
|
6
|
+
* Advisory health of a router-class uplink. Every field is informational — a
|
|
7
|
+
* degraded router is reported, never removed from the routing set on health alone.
|
|
8
|
+
*/
|
|
9
|
+
export interface RouterHealth {
|
|
10
|
+
readonly presence: RouterPresence;
|
|
11
|
+
readonly gatewayReachable: boolean;
|
|
12
|
+
readonly egressHealthy: boolean;
|
|
13
|
+
readonly observedAt: EpochMillis;
|
|
14
|
+
}
|
|
15
|
+
/** The router port — presence and advisory health only. No mutation verbs. */
|
|
16
|
+
export interface RouterPort {
|
|
17
|
+
probePresence(ifname: DeviceIfname): Promise<RouterPresence>;
|
|
18
|
+
checkHealth(ifname: DeviceIfname): Promise<RouterHealth>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Unsubscribe } from './observation.js';
|
|
2
|
+
/** A message's lifecycle state, verbatim from ModemManager's own vocabulary. */
|
|
3
|
+
export type SmsState = 'unknown' | 'stored' | 'receiving' | 'received' | 'sending' | 'sent';
|
|
4
|
+
/** One stored message, normalized. `text` is `''` for a data-only (WAP/PDU) message. */
|
|
5
|
+
export interface SmsMessage {
|
|
6
|
+
/** The trailing `/SMS/<n>` object index, stringified. */
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly from?: string;
|
|
9
|
+
/** The service-centre timestamp, verbatim as the device reported it. */
|
|
10
|
+
readonly timestamp?: string;
|
|
11
|
+
readonly text: string;
|
|
12
|
+
readonly state: SmsState;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Why an inbox read produced no list. These are the four operator-actionable
|
|
16
|
+
* facts, and they are deliberately distinct from one another — a modem with no
|
|
17
|
+
* Messaging interface, a radio that has not come up, a selector nothing answers
|
|
18
|
+
* to, and drift in whatever the source printed are four different next steps.
|
|
19
|
+
*/
|
|
20
|
+
export type SmsReadRefusal = 'unsupported' | 'not_enabled' | 'unknown_modem' | 'read_failed';
|
|
21
|
+
/**
|
|
22
|
+
* The result of an inbox read. A REFUSAL IS NEVER AN EMPTY LIST: `{ok: true,
|
|
23
|
+
* messages: []}` means this modem has an inbox and it is empty.
|
|
24
|
+
*/
|
|
25
|
+
export type SmsInboxResult = {
|
|
26
|
+
readonly ok: true;
|
|
27
|
+
readonly messages: readonly SmsMessage[];
|
|
28
|
+
} | {
|
|
29
|
+
readonly ok: false;
|
|
30
|
+
readonly reason: SmsReadRefusal;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* One inbox change. `resynced` carries a full authoritative list and is what a
|
|
34
|
+
* source restart produces — a consumer replaces its rows with it rather than
|
|
35
|
+
* merging, so a restart can never duplicate a row it already holds.
|
|
36
|
+
*/
|
|
37
|
+
export type SmsInboxEvent = {
|
|
38
|
+
readonly kind: 'added';
|
|
39
|
+
readonly message: SmsMessage;
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: 'deleted';
|
|
42
|
+
readonly id: string;
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: 'resynced';
|
|
45
|
+
readonly messages: readonly SmsMessage[];
|
|
46
|
+
};
|
|
47
|
+
export type SmsInboxListener = (event: SmsInboxEvent) => void;
|
|
48
|
+
/**
|
|
49
|
+
* The read-only SMS port. `list()` resolves the current inbox once; `observe()`
|
|
50
|
+
* streams every subsequent change; `stop()` releases the source.
|
|
51
|
+
*
|
|
52
|
+
* It is deliberately NOT an extension of `ModemManagerPort`: a consumer that
|
|
53
|
+
* only reads an inbox must not acquire radio, SIM, or inhibit verbs along the
|
|
54
|
+
* way — the same narrowing argument `ModemObservationPort` makes for the
|
|
55
|
+
* shadow reader.
|
|
56
|
+
*/
|
|
57
|
+
export interface SmsObservationPort {
|
|
58
|
+
/** Read the modem's stored inbox, newest-first and capped. */
|
|
59
|
+
list(): Promise<SmsInboxResult>;
|
|
60
|
+
/** Subscribe to Added/Deleted/resync events. Returns an unsubscribe. */
|
|
61
|
+
observe(listener: SmsInboxListener): Unsubscribe;
|
|
62
|
+
/** Release the source. Idempotent. */
|
|
63
|
+
stop(): Promise<void>;
|
|
64
|
+
}
|