@ceralive/modem-control 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -0
- package/dist/backend/at-lease.d.ts +59 -0
- package/dist/backend/at-lease.js +118 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +101 -0
- package/dist/backend/transition-preconditions.js +132 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +216 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +27 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +61 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operation-ids.d.ts +2 -0
- package/dist/operation-ids.js +26 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +10 -0
- package/dist/providers/modem-manager/generic-operations.js +209 -0
- package/dist/providers/modem-manager/index.d.ts +4 -0
- package/dist/providers/modem-manager/index.js +4 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +41 -0
- package/dist/providers/modem-manager/provider.js +155 -0
- package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
- package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +137 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +171 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +56 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +23 -0
- package/dist/providers/zte-goform/session.js +197 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +88 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +138 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +6 -0
- package/dist/usb-mode/index.js +16 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/runtime-capability.d.ts +59 -0
- package/dist/usb-mode/runtime-capability.js +157 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +106 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -236
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/uhubctl-power-hook.test.ts +0 -274
- package/src/backend/uhubctl-power-hook.ts +0 -377
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -60
- package/src/backend/usage/policy-store.test.ts +0 -164
- package/src/backend/usage/policy-store.ts +0 -216
- package/src/backend/usage/policy-write.test.ts +0 -198
- package/src/backend/usage/policy-write.ts +0 -207
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -327
- package/src/backend/usage/sampler.ts +0 -282
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -58
- package/src/usb-mode/ingestion.test.ts +0 -268
- package/src/usb-mode/ingestion.ts +0 -297
- package/src/usb-mode/promotion-review.ts +0 -117
- package/src/usb-mode/usb-devices-parse.ts +0 -196
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// The `mmcli -K` SMS grammar.
|
|
2
|
+
//
|
|
3
|
+
// WHY A CLI GRAMMAR LIVES BESIDE A D-BUS PORT: `mmcli` is a client of the SAME
|
|
4
|
+
// ModemManager daemon the D-Bus adapter talks to, and CeraUI has been reading
|
|
5
|
+
// its inbox through it on real hardware since Phase A. Owning the grammar here
|
|
6
|
+
// is what makes the port's output provable byte-for-byte against that reader on
|
|
7
|
+
// the same captured output (`mmcli-parse.parity.test.ts`) — a claim no amount of
|
|
8
|
+
// D-Bus-only code could support — and it is what lets a consumer move its
|
|
9
|
+
// parsing onto this package without also having to move its transport in the
|
|
10
|
+
// same change.
|
|
11
|
+
//
|
|
12
|
+
// CONTENT-FREE BY CONSTRUCTION. Unlike an ordinary parser, nothing here ever
|
|
13
|
+
// puts a parsed LINE into an error, a log, or a receipt. A message body is
|
|
14
|
+
// routinely a one-time code and a sender identifies the subscriber, so a
|
|
15
|
+
// malformed record reports the KEY NAMES it found and nothing else — enough to
|
|
16
|
+
// diagnose CLI drift, and carrying no message content. This module also never
|
|
17
|
+
// logs at all, which is why it does not reuse a generic key/value splitter: the
|
|
18
|
+
// generic one prints the offending line verbatim on any line it cannot split,
|
|
19
|
+
// which would put message text into a log the first time mmcli reframed a body.
|
|
20
|
+
import { normalizeSmsState, smsPathIndex } from './normalize.js';
|
|
21
|
+
const fail = (reason, detail) => ({
|
|
22
|
+
ok: false,
|
|
23
|
+
reason,
|
|
24
|
+
detail,
|
|
25
|
+
});
|
|
26
|
+
const SMS_LIST_KEY = 'modem.messaging.sms';
|
|
27
|
+
const LIST_LENGTH_SUFFIX = /\.length$/;
|
|
28
|
+
const LIST_VALUE_SUFFIX = /\.value\[\d+]$/;
|
|
29
|
+
const SMS_PATH_ANYWHERE_RE = /\/org\/freedesktop\/ModemManager1\/SMS\/(\d+)/;
|
|
30
|
+
/**
|
|
31
|
+
* The escapes `g_strescape()` emits, minus the octal form. mmcli's `-K` writer
|
|
32
|
+
* runs EVERY value it prints through `g_strescape (value, NULL)`
|
|
33
|
+
* (`cli/mmcli-output.c`), so this table plus the 3-digit octal form is the
|
|
34
|
+
* complete grammar — anything else can be left untouched instead of guessed at.
|
|
35
|
+
*/
|
|
36
|
+
const SIMPLE_ESCAPES = {
|
|
37
|
+
b: 0x08,
|
|
38
|
+
f: 0x0c,
|
|
39
|
+
n: 0x0a,
|
|
40
|
+
r: 0x0d,
|
|
41
|
+
t: 0x09,
|
|
42
|
+
v: 0x0b,
|
|
43
|
+
'"': 0x22,
|
|
44
|
+
'\\': 0x5c,
|
|
45
|
+
};
|
|
46
|
+
/** Octal triplet first: `\302` is a byte, `\\302` is a backslash then "302". */
|
|
47
|
+
const ESCAPE_RE = /\\(?:([0-7]{3})|([bfnrtv"\\]))/g;
|
|
48
|
+
/**
|
|
49
|
+
* Undo `g_strescape()` on one `-K` value.
|
|
50
|
+
*
|
|
51
|
+
* mmcli does not print non-ASCII text — it prints the LITERAL ASCII characters
|
|
52
|
+
* of the octal escape. A Spanish message arrives on stdout as the eight
|
|
53
|
+
* characters `\`,`3`,`0`,`2`,`\`,`2`,`4`,`1` where the wire carried the two
|
|
54
|
+
* UTF-8 bytes `0xC2 0xA1` (confirmed on the bench board with `od -c`). The
|
|
55
|
+
* escapes are therefore per-BYTE, not per-character, and the only correct
|
|
56
|
+
* decode is to rebuild the byte sequence and read it back as UTF-8 — decoding
|
|
57
|
+
* each escape with `String.fromCharCode` instead turns `¡` into `¡`.
|
|
58
|
+
*
|
|
59
|
+
* Total and silent: it never throws and never logs, because the value it holds
|
|
60
|
+
* is message content. An escape outside the grammar is copied VERBATIM rather
|
|
61
|
+
* than dropped — mmcli cannot emit one, so meeting one means the value was
|
|
62
|
+
* never escaped and copying it is the only lossless answer.
|
|
63
|
+
*/
|
|
64
|
+
export function unescapeMmcliValue(value) {
|
|
65
|
+
if (!value.includes('\\')) {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
const encoder = new TextEncoder();
|
|
69
|
+
const bytes = [];
|
|
70
|
+
const pushText = (text) => {
|
|
71
|
+
for (const byte of encoder.encode(text)) {
|
|
72
|
+
bytes.push(byte);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
let cursor = 0;
|
|
76
|
+
ESCAPE_RE.lastIndex = 0;
|
|
77
|
+
let match = ESCAPE_RE.exec(value);
|
|
78
|
+
while (match !== null) {
|
|
79
|
+
if (match.index > cursor) {
|
|
80
|
+
pushText(value.slice(cursor, match.index));
|
|
81
|
+
}
|
|
82
|
+
const octal = match[1];
|
|
83
|
+
const simple = match[2];
|
|
84
|
+
if (octal !== undefined) {
|
|
85
|
+
bytes.push(Number.parseInt(octal, 8));
|
|
86
|
+
}
|
|
87
|
+
else if (simple !== undefined) {
|
|
88
|
+
const byte = SIMPLE_ESCAPES[simple];
|
|
89
|
+
if (byte !== undefined) {
|
|
90
|
+
bytes.push(byte);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
cursor = match.index + match[0].length;
|
|
94
|
+
match = ESCAPE_RE.exec(value);
|
|
95
|
+
}
|
|
96
|
+
if (cursor < value.length) {
|
|
97
|
+
pushText(value.slice(cursor));
|
|
98
|
+
}
|
|
99
|
+
// A byte run that is not valid UTF-8 becomes U+FFFD — an honest
|
|
100
|
+
// "undecodable here" mark. Failing the whole read would lose every other
|
|
101
|
+
// field over one bad byte.
|
|
102
|
+
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
103
|
+
}
|
|
104
|
+
/** Collect the `modem.messaging.sms` entries, honouring mmcli's array form. */
|
|
105
|
+
function collectListEntries(raw) {
|
|
106
|
+
let scalar;
|
|
107
|
+
const array = [];
|
|
108
|
+
let sawArray = false;
|
|
109
|
+
for (const line of raw.split('\n')) {
|
|
110
|
+
const separator = line.indexOf(':');
|
|
111
|
+
if (separator <= 0) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const key = line.slice(0, separator).trim();
|
|
115
|
+
// Unescaped AFTER the split, never before: a decoded byte must never be
|
|
116
|
+
// able to forge the `:` the line was split on.
|
|
117
|
+
const value = unescapeMmcliValue(line.slice(separator + 1).trim());
|
|
118
|
+
// mmcli renders an absent value as `--`; it carries no entry.
|
|
119
|
+
if (value === '--') {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (key.replace(LIST_LENGTH_SUFFIX, '') === SMS_LIST_KEY && LIST_LENGTH_SUFFIX.test(key)) {
|
|
123
|
+
sawArray = true;
|
|
124
|
+
}
|
|
125
|
+
else if (key.replace(LIST_VALUE_SUFFIX, '') === SMS_LIST_KEY && LIST_VALUE_SUFFIX.test(key)) {
|
|
126
|
+
sawArray = true;
|
|
127
|
+
array.push(value);
|
|
128
|
+
}
|
|
129
|
+
else if (key === SMS_LIST_KEY) {
|
|
130
|
+
scalar = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (sawArray) {
|
|
134
|
+
return array;
|
|
135
|
+
}
|
|
136
|
+
return scalar === undefined ? undefined : [scalar];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Extract SMS object paths from `mmcli -K -m <id> --messaging-list-sms`.
|
|
140
|
+
*
|
|
141
|
+
* An inbox with no messages prints as `modem.messaging.sms: --`, whose `--`
|
|
142
|
+
* carries no entry — so an ABSENT key is a legitimate EMPTY INBOX, exactly as it
|
|
143
|
+
* is for `--3gpp-scan`, not drift. What IS drift, and what fails loud here, is
|
|
144
|
+
* output that never mentions the key at all (a renamed field, an error body
|
|
145
|
+
* reaching this parser): answering "no messages" to a read that never ran would
|
|
146
|
+
* be the worst possible lie about an inbox.
|
|
147
|
+
*/
|
|
148
|
+
export function parseSmsListOutput(raw) {
|
|
149
|
+
const entries = collectListEntries(raw);
|
|
150
|
+
if (entries === undefined) {
|
|
151
|
+
if (!raw.includes(SMS_LIST_KEY)) {
|
|
152
|
+
return fail('no modem.messaging.sms key in the mmcli output', 'no-key');
|
|
153
|
+
}
|
|
154
|
+
return { ok: true, value: [] };
|
|
155
|
+
}
|
|
156
|
+
const paths = entries.filter((entry) => SMS_PATH_ANYWHERE_RE.test(entry));
|
|
157
|
+
if (entries.length > 0 && paths.length === 0) {
|
|
158
|
+
return fail('no SMS paths matched the ModemManager path grammar', 'no-path-match');
|
|
159
|
+
}
|
|
160
|
+
return { ok: true, value: paths };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Parse one `mmcli -K -s <path>` record.
|
|
164
|
+
*
|
|
165
|
+
* A malformed record reports the KEY NAMES it found and nothing else — see the
|
|
166
|
+
* content-free note at the top of this file.
|
|
167
|
+
*/
|
|
168
|
+
export function parseSmsRecordOutput(raw) {
|
|
169
|
+
const fields = new Map();
|
|
170
|
+
for (const line of raw.split('\n')) {
|
|
171
|
+
const separator = line.indexOf(':');
|
|
172
|
+
if (separator <= 0) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const key = line.slice(0, separator).trim();
|
|
176
|
+
const value = line.slice(separator + 1).trim();
|
|
177
|
+
if (!key.startsWith('sms.') || value === '' || value === '--') {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
fields.set(key, unescapeMmcliValue(value));
|
|
181
|
+
}
|
|
182
|
+
const keyNames = [...fields.keys()].join(', ');
|
|
183
|
+
const dbusPath = fields.get('sms.dbus-path');
|
|
184
|
+
if (dbusPath === undefined) {
|
|
185
|
+
return fail('no sms.dbus-path key in the mmcli output', keyNames);
|
|
186
|
+
}
|
|
187
|
+
const index = smsPathIndex(dbusPath);
|
|
188
|
+
if (Number.isNaN(index)) {
|
|
189
|
+
return fail('sms.dbus-path did not match the ModemManager path grammar', keyNames);
|
|
190
|
+
}
|
|
191
|
+
const from = fields.get('sms.content.number');
|
|
192
|
+
const timestamp = fields.get('sms.properties.timestamp');
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
value: {
|
|
196
|
+
id: String(index),
|
|
197
|
+
...(from !== undefined ? { from } : {}),
|
|
198
|
+
...(timestamp !== undefined ? { timestamp } : {}),
|
|
199
|
+
// A data-only (WAP/PDU) message has no text at all; `''` says so honestly.
|
|
200
|
+
text: fields.get('sms.content.text') ?? '',
|
|
201
|
+
state: normalizeSmsState(fields.get('sms.properties.state')),
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Classify a source failure into a refusal the operator can act on.
|
|
207
|
+
*
|
|
208
|
+
* The three recognised strings are ModemManager 1.24's own, confirmed on the
|
|
209
|
+
* bench board: a modem with no Messaging interface, a radio that has not come up
|
|
210
|
+
* yet, and a selector nothing answers to. Anything else stays `read_failed`
|
|
211
|
+
* rather than being guessed at.
|
|
212
|
+
*/
|
|
213
|
+
export function classifySmsFailure(description) {
|
|
214
|
+
if (/no messaging capabilities/i.test(description)) {
|
|
215
|
+
return 'unsupported';
|
|
216
|
+
}
|
|
217
|
+
if (/not enabled yet/i.test(description)) {
|
|
218
|
+
return 'not_enabled';
|
|
219
|
+
}
|
|
220
|
+
if (/couldn't find modem|cannot find modem/i.test(description)) {
|
|
221
|
+
return 'unknown_modem';
|
|
222
|
+
}
|
|
223
|
+
return 'read_failed';
|
|
224
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SmsMessage, SmsState } from '../ports/sms.js';
|
|
2
|
+
/**
|
|
3
|
+
* The read cap. The list is reduced to the highest-indexed paths BEFORE any
|
|
4
|
+
* per-message read, so a modem holding several hundred stored messages still
|
|
5
|
+
* costs at most this many reads.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SMS_INBOX_CAP = 50;
|
|
8
|
+
/**
|
|
9
|
+
* The SMS object-path grammar.
|
|
10
|
+
*
|
|
11
|
+
* It is a SEPARATE regex from the modem-path one on purpose: a ModemManager
|
|
12
|
+
* modem path is anchored on `/Modem/`, so reusing it here would refuse every
|
|
13
|
+
* real message (`/org/freedesktop/ModemManager1/SMS/36`) on the device.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SMS_PATH_RE: RegExp;
|
|
16
|
+
/** The trailing `/SMS/<n>` index, or `Number.NaN` when the path carries none. */
|
|
17
|
+
export declare function smsPathIndex(path: string): number;
|
|
18
|
+
/**
|
|
19
|
+
* Fold whatever the source called the state onto the known vocabulary. An
|
|
20
|
+
* unrecognised value becomes `unknown` rather than being passed through — the
|
|
21
|
+
* state is rendered to an operator, and a raw token nobody can act on is worse
|
|
22
|
+
* than an honest "we do not know".
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeSmsState(raw: string | undefined): SmsState;
|
|
25
|
+
export declare function smsTimestampEpoch(timestamp: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Newest first, then capped.
|
|
28
|
+
*
|
|
29
|
+
* Sorted on the CARRIER timestamp because the object index is only a proxy for
|
|
30
|
+
* arrival order — ModemManager reuses freed indices, so a re-enumerated inbox
|
|
31
|
+
* can hand back a low index for the newest message. A message with no (or an
|
|
32
|
+
* unparseable) timestamp sorts LAST rather than first: promoting an undated
|
|
33
|
+
* message to the top of a "newest first" list would be a claim the device
|
|
34
|
+
* cannot support. Ties fall back to the index, descending.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sortAndCapSms(messages: readonly SmsMessage[], cap?: number): SmsMessage[];
|
|
37
|
+
/**
|
|
38
|
+
* Reduce a candidate path list to the paths worth reading: the highest-indexed
|
|
39
|
+
* {@link SMS_INBOX_CAP}, each of which must match the path grammar before it can
|
|
40
|
+
* reach a source as a selector.
|
|
41
|
+
*/
|
|
42
|
+
export declare function selectReadablePaths(paths: readonly string[], cap?: number): string[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Device-agnostic SMS normalization — the rules that hold whichever source the
|
|
2
|
+
// inbox was read through (ModemManager D-Bus or the `mmcli` client of the same
|
|
3
|
+
// daemon).
|
|
4
|
+
//
|
|
5
|
+
// Every rule here is a PORT of a behaviour proven on the bench board through
|
|
6
|
+
// CeraUI's `modules/modems/mmcli-sms.ts`, and each one exists because getting it
|
|
7
|
+
// wrong is silent rather than loud. They are kept in one pure module so the
|
|
8
|
+
// D-Bus adapter and the mmcli grammar cannot drift into two different answers
|
|
9
|
+
// about the same inbox.
|
|
10
|
+
/**
|
|
11
|
+
* The read cap. The list is reduced to the highest-indexed paths BEFORE any
|
|
12
|
+
* per-message read, so a modem holding several hundred stored messages still
|
|
13
|
+
* costs at most this many reads.
|
|
14
|
+
*/
|
|
15
|
+
export const SMS_INBOX_CAP = 50;
|
|
16
|
+
/**
|
|
17
|
+
* The SMS object-path grammar.
|
|
18
|
+
*
|
|
19
|
+
* It is a SEPARATE regex from the modem-path one on purpose: a ModemManager
|
|
20
|
+
* modem path is anchored on `/Modem/`, so reusing it here would refuse every
|
|
21
|
+
* real message (`/org/freedesktop/ModemManager1/SMS/36`) on the device.
|
|
22
|
+
*/
|
|
23
|
+
export const SMS_PATH_RE = /^(?:\/org\/freedesktop\/ModemManager1\/SMS\/\d+|\d+)$/;
|
|
24
|
+
const SMS_PATH_INDEX_RE = /\/org\/freedesktop\/ModemManager1\/SMS\/(\d+)/;
|
|
25
|
+
const KNOWN_SMS_STATES = new Set([
|
|
26
|
+
'unknown',
|
|
27
|
+
'stored',
|
|
28
|
+
'receiving',
|
|
29
|
+
'received',
|
|
30
|
+
'sending',
|
|
31
|
+
'sent',
|
|
32
|
+
]);
|
|
33
|
+
/** The trailing `/SMS/<n>` index, or `Number.NaN` when the path carries none. */
|
|
34
|
+
export function smsPathIndex(path) {
|
|
35
|
+
const match = path.match(SMS_PATH_INDEX_RE);
|
|
36
|
+
return match?.[1] === undefined ? Number.NaN : Number.parseInt(match[1], 10);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Fold whatever the source called the state onto the known vocabulary. An
|
|
40
|
+
* unrecognised value becomes `unknown` rather than being passed through — the
|
|
41
|
+
* state is rendered to an operator, and a raw token nobody can act on is worse
|
|
42
|
+
* than an honest "we do not know".
|
|
43
|
+
*/
|
|
44
|
+
export function normalizeSmsState(raw) {
|
|
45
|
+
return raw !== undefined && KNOWN_SMS_STATES.has(raw) ? raw : 'unknown';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* ModemManager reports the service-centre timestamp with an HOURS-ONLY UTC
|
|
49
|
+
* offset — `2025-08-21T17:20:16-05`, captured verbatim from the bench board.
|
|
50
|
+
* That is not valid ISO 8601 and `Date.parse` answers NaN for it. Left
|
|
51
|
+
* unhandled, EVERY message scores as undated and "newest first" silently
|
|
52
|
+
* degrades to object-index order, which is the one ordering this module must
|
|
53
|
+
* not trust. The offset is widened to `-05:00`; the anchor requires a full
|
|
54
|
+
* `T??:??:??` time in front of it, so a bare `YYYY-MM-DD` is never mangled.
|
|
55
|
+
*/
|
|
56
|
+
const HOURS_ONLY_OFFSET_RE = /(T\d{2}:\d{2}:\d{2})([+-]\d{2})$/;
|
|
57
|
+
export function smsTimestampEpoch(timestamp) {
|
|
58
|
+
const parsedTime = Date.parse(timestamp.replace(HOURS_ONLY_OFFSET_RE, '$1$2:00'));
|
|
59
|
+
return Number.isNaN(parsedTime) ? Number.NEGATIVE_INFINITY : parsedTime;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Newest first, then capped.
|
|
63
|
+
*
|
|
64
|
+
* Sorted on the CARRIER timestamp because the object index is only a proxy for
|
|
65
|
+
* arrival order — ModemManager reuses freed indices, so a re-enumerated inbox
|
|
66
|
+
* can hand back a low index for the newest message. A message with no (or an
|
|
67
|
+
* unparseable) timestamp sorts LAST rather than first: promoting an undated
|
|
68
|
+
* message to the top of a "newest first" list would be a claim the device
|
|
69
|
+
* cannot support. Ties fall back to the index, descending.
|
|
70
|
+
*/
|
|
71
|
+
export function sortAndCapSms(messages, cap = SMS_INBOX_CAP) {
|
|
72
|
+
const epoch = (message) => message.timestamp === undefined
|
|
73
|
+
? Number.NEGATIVE_INFINITY
|
|
74
|
+
: smsTimestampEpoch(message.timestamp);
|
|
75
|
+
return [...messages]
|
|
76
|
+
.sort((a, b) => {
|
|
77
|
+
const delta = epoch(b) - epoch(a);
|
|
78
|
+
if (delta !== 0 && !Number.isNaN(delta)) {
|
|
79
|
+
return delta;
|
|
80
|
+
}
|
|
81
|
+
return Number(b.id) - Number(a.id);
|
|
82
|
+
})
|
|
83
|
+
.slice(0, cap);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Reduce a candidate path list to the paths worth reading: the highest-indexed
|
|
87
|
+
* {@link SMS_INBOX_CAP}, each of which must match the path grammar before it can
|
|
88
|
+
* reach a source as a selector.
|
|
89
|
+
*/
|
|
90
|
+
export function selectReadablePaths(paths, cap = SMS_INBOX_CAP) {
|
|
91
|
+
return [...paths]
|
|
92
|
+
.sort((a, b) => smsPathIndex(b) - smsPathIndex(a))
|
|
93
|
+
.slice(0, cap)
|
|
94
|
+
.filter((path) => SMS_PATH_RE.test(path));
|
|
95
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type DeviceGeneration, type EpochMillis, type ObservationAuthority, type ObservationEnvelope, type OperationCompletion, type OperationConfidence, type OperationDescriptor, type OperationResult, type PhysicalModemId, type SourceEpoch, type StableKey } from '../domain/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* A canonical, valid `PhysicalModemId` for contract tests.
|
|
4
|
+
*
|
|
5
|
+
* It is built through the real constructor, so it can never be a value the domain
|
|
6
|
+
* refuses — an MM object path, an interface name, an IP address, an IMEI, or a
|
|
7
|
+
* subscriber identifier.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FAKE_PHYSICAL_MODEM_ID: PhysicalModemId;
|
|
10
|
+
/** The actor/storage key for {@link FAKE_PHYSICAL_MODEM_ID}. */
|
|
11
|
+
export declare const FAKE_STABLE_KEY: StableKey;
|
|
12
|
+
/** The generation every fake defaults to, so a consumer's fences have a stable anchor. */
|
|
13
|
+
export declare const FAKE_GENERATION: DeviceGeneration;
|
|
14
|
+
export type FakeObservationOptions = {
|
|
15
|
+
readonly stableKey?: StableKey;
|
|
16
|
+
readonly generation?: DeviceGeneration;
|
|
17
|
+
readonly source?: string;
|
|
18
|
+
readonly sourceEpoch?: SourceEpoch;
|
|
19
|
+
readonly observedAt?: EpochMillis;
|
|
20
|
+
readonly authority?: ObservationAuthority;
|
|
21
|
+
};
|
|
22
|
+
/** A fresh observation carrying `value`. */
|
|
23
|
+
export declare function fakeFreshObservation<T>(value: T, options?: FakeObservationOptions): ObservationEnvelope<T>;
|
|
24
|
+
/** A stale observation that RETAINS `value` — staleness never discards what was read. */
|
|
25
|
+
export declare function fakeStaleObservation<T>(value: T, reason?: 'source-epoch-superseded' | 'ttl-expired' | 'source-degraded', options?: FakeObservationOptions): ObservationEnvelope<T>;
|
|
26
|
+
/**
|
|
27
|
+
* An unavailable observation. It carries `value: null` by construction — there is no
|
|
28
|
+
* overload that lets a consumer's fake invent a value for data nobody could read.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fakeUnavailableObservation<T>(reason?: 'source-unavailable' | 'device-absent' | 'provider-unavailable', options?: FakeObservationOptions): ObservationEnvelope<T>;
|
|
31
|
+
export type FakeDescriptorOptions = {
|
|
32
|
+
readonly id?: string;
|
|
33
|
+
readonly provider?: string;
|
|
34
|
+
readonly authority?: 'provider' | 'controller' | 'hardware';
|
|
35
|
+
readonly confidence?: OperationConfidence;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A supported, auto-retryable idempotent READ descriptor.
|
|
39
|
+
*
|
|
40
|
+
* Routed through `defineOperationDescriptor`, so a fake that drifted into an
|
|
41
|
+
* unsupported/retryable combination throws instead of silently modelling a
|
|
42
|
+
* descriptor the domain would refuse.
|
|
43
|
+
*/
|
|
44
|
+
export declare function fakeReadDescriptor<O>(options?: FakeDescriptorOptions): OperationDescriptor<never, O>;
|
|
45
|
+
/** A supported WRITE descriptor. Writes are never auto-retryable, so `retryClass` is `never`. */
|
|
46
|
+
export declare function fakeWriteDescriptor<I, O>(options?: FakeDescriptorOptions): OperationDescriptor<I, O>;
|
|
47
|
+
export type FakeOperationResultOptions = {
|
|
48
|
+
readonly operation?: 'read' | 'write';
|
|
49
|
+
readonly completionGeneration?: DeviceGeneration;
|
|
50
|
+
readonly currentGeneration?: DeviceGeneration;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Build an `OperationResult` by running the REAL classifier over a completion.
|
|
54
|
+
*
|
|
55
|
+
* A hand-written result literal is how a consumer's fake comes to disagree with the
|
|
56
|
+
* package about which completions require reconciliation; this cannot.
|
|
57
|
+
*/
|
|
58
|
+
export declare function fakeOperationResult<O>(completion: OperationCompletion<O>, options?: FakeOperationResultOptions): OperationResult<O>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { classifyOperationCompletion, defineOperationDescriptor, deviceGeneration, epochMillis, physicalModemId, sourceEpoch, stableKeyFromPhysicalModemId, } from '../domain/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* A canonical, valid `PhysicalModemId` for contract tests.
|
|
4
|
+
*
|
|
5
|
+
* It is built through the real constructor, so it can never be a value the domain
|
|
6
|
+
* refuses — an MM object path, an interface name, an IP address, an IMEI, or a
|
|
7
|
+
* subscriber identifier.
|
|
8
|
+
*/
|
|
9
|
+
export const FAKE_PHYSICAL_MODEM_ID = physicalModemId('serial:ceralive-contract-fake');
|
|
10
|
+
/** The actor/storage key for {@link FAKE_PHYSICAL_MODEM_ID}. */
|
|
11
|
+
export const FAKE_STABLE_KEY = stableKeyFromPhysicalModemId(FAKE_PHYSICAL_MODEM_ID);
|
|
12
|
+
/** The generation every fake defaults to, so a consumer's fences have a stable anchor. */
|
|
13
|
+
export const FAKE_GENERATION = deviceGeneration(1);
|
|
14
|
+
function observationBase(options) {
|
|
15
|
+
return {
|
|
16
|
+
stableKey: options.stableKey ?? FAKE_STABLE_KEY,
|
|
17
|
+
generation: options.generation ?? FAKE_GENERATION,
|
|
18
|
+
source: options.source ?? 'contract-fake',
|
|
19
|
+
sourceEpoch: options.sourceEpoch ?? sourceEpoch(1),
|
|
20
|
+
observedAt: options.observedAt ?? epochMillis(1),
|
|
21
|
+
authority: options.authority ?? 'authoritative',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** A fresh observation carrying `value`. */
|
|
25
|
+
export function fakeFreshObservation(value, options = {}) {
|
|
26
|
+
return { ...observationBase(options), freshness: { state: 'fresh' }, value };
|
|
27
|
+
}
|
|
28
|
+
/** A stale observation that RETAINS `value` — staleness never discards what was read. */
|
|
29
|
+
export function fakeStaleObservation(value, reason = 'ttl-expired', options = {}) {
|
|
30
|
+
const base = observationBase(options);
|
|
31
|
+
return {
|
|
32
|
+
...base,
|
|
33
|
+
freshness: { state: 'stale', since: base.observedAt, reason },
|
|
34
|
+
value,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An unavailable observation. It carries `value: null` by construction — there is no
|
|
39
|
+
* overload that lets a consumer's fake invent a value for data nobody could read.
|
|
40
|
+
*/
|
|
41
|
+
export function fakeUnavailableObservation(reason = 'source-unavailable', options = {}) {
|
|
42
|
+
const base = observationBase(options);
|
|
43
|
+
return {
|
|
44
|
+
...base,
|
|
45
|
+
freshness: { state: 'unavailable', since: base.observedAt, reason },
|
|
46
|
+
value: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function baseDescriptor(options, support, mutationImpact, retryClass) {
|
|
50
|
+
return defineOperationDescriptor({
|
|
51
|
+
id: options.id ?? 'contract-fake-operation',
|
|
52
|
+
support: {
|
|
53
|
+
read: support.read ? { supported: true } : { supported: false, reason: 'contract-fake' },
|
|
54
|
+
write: support.write ? { supported: true } : { supported: false, reason: 'contract-fake' },
|
|
55
|
+
},
|
|
56
|
+
authority: options.authority ?? 'provider',
|
|
57
|
+
provider: options.provider ?? 'contract-fake-provider',
|
|
58
|
+
constraints: { kind: 'unconstrained' },
|
|
59
|
+
livePreconditions: [],
|
|
60
|
+
availability: { state: 'available' },
|
|
61
|
+
mutationImpact,
|
|
62
|
+
retryClass,
|
|
63
|
+
readback: { required: false },
|
|
64
|
+
rollback: { required: false },
|
|
65
|
+
journal: { required: false },
|
|
66
|
+
admission: { required: false },
|
|
67
|
+
evidence: { profiles: [], firmware: [] },
|
|
68
|
+
confidence: options.confidence ?? 'medium',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A supported, auto-retryable idempotent READ descriptor.
|
|
73
|
+
*
|
|
74
|
+
* Routed through `defineOperationDescriptor`, so a fake that drifted into an
|
|
75
|
+
* unsupported/retryable combination throws instead of silently modelling a
|
|
76
|
+
* descriptor the domain would refuse.
|
|
77
|
+
*/
|
|
78
|
+
export function fakeReadDescriptor(options = {}) {
|
|
79
|
+
return baseDescriptor(options, { read: true, write: false }, 'read', 'idempotent-read');
|
|
80
|
+
}
|
|
81
|
+
/** A supported WRITE descriptor. Writes are never auto-retryable, so `retryClass` is `never`. */
|
|
82
|
+
export function fakeWriteDescriptor(options = {}) {
|
|
83
|
+
return baseDescriptor(options, { read: true, write: true }, 'write', 'never');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build an `OperationResult` by running the REAL classifier over a completion.
|
|
87
|
+
*
|
|
88
|
+
* A hand-written result literal is how a consumer's fake comes to disagree with the
|
|
89
|
+
* package about which completions require reconciliation; this cannot.
|
|
90
|
+
*/
|
|
91
|
+
export function fakeOperationResult(completion, options = {}) {
|
|
92
|
+
return classifyOperationCompletion({
|
|
93
|
+
operation: options.operation ?? 'write',
|
|
94
|
+
completionGeneration: options.completionGeneration ?? FAKE_GENERATION,
|
|
95
|
+
currentGeneration: options.currentGeneration ?? FAKE_GENERATION,
|
|
96
|
+
completion,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// `@ceralive/modem-control/testing` — the PUBLIC contract-fakes surface.
|
|
2
|
+
//
|
|
3
|
+
// A consumer testing against this package needs valid instances of the frozen v1.1
|
|
4
|
+
// domain and provider contracts (`../domain`, `../providers`). Hand-rolling them is
|
|
5
|
+
// how a consumer's fixtures come to disagree with the package: a hand-written
|
|
6
|
+
// `OperationResult` literal quietly stops matching what `classifyOperationCompletion`
|
|
7
|
+
// actually returns, and a hand-written envelope invents a value for an unavailable
|
|
8
|
+
// read. Every fake here is built through the package's own constructors and
|
|
9
|
+
// classifiers, so it cannot express a shape the domain refuses.
|
|
10
|
+
//
|
|
11
|
+
// This is NOT `control/test-support/`. That directory holds this repo's own heavy
|
|
12
|
+
// internals — an MM-faithful fake D-Bus service on a private session bus and a
|
|
13
|
+
// stateful `nmcli` harness. It lives outside `src`, is not published, and is not a
|
|
14
|
+
// reusable public surface. This entry is pure data and functions: no bus, no daemon,
|
|
15
|
+
// no process, no filesystem.
|
|
16
|
+
export * from './domain-fakes.js';
|
|
17
|
+
export * from './provider-fakes.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DeviceGeneration } from '../domain/index.js';
|
|
2
|
+
import type { FingerprintResult, PassiveFact, ProviderDefinition, ProviderMatchRequest, ProviderOperationsSurface, ProviderTransport, UnauthenticatedProbe } from '../providers/index.js';
|
|
3
|
+
/** The model string every provider fake matches on unless one is named. */
|
|
4
|
+
export declare const FAKE_PROVIDER_MODEL = "contract-fake-model";
|
|
5
|
+
export type FakeMatchRequestOptions = {
|
|
6
|
+
readonly generation?: DeviceGeneration;
|
|
7
|
+
readonly transport?: ProviderTransport;
|
|
8
|
+
readonly model?: string;
|
|
9
|
+
readonly composition?: string;
|
|
10
|
+
readonly firmware?: string;
|
|
11
|
+
readonly passiveFacts?: readonly PassiveFact[];
|
|
12
|
+
};
|
|
13
|
+
/** A well-formed `ProviderMatchRequest` whose passive facts name exactly one model. */
|
|
14
|
+
export declare function fakeProviderMatchRequest(options?: FakeMatchRequestOptions): ProviderMatchRequest;
|
|
15
|
+
export type FakeProviderOptions<TObservation> = {
|
|
16
|
+
readonly id?: string;
|
|
17
|
+
readonly profile?: string;
|
|
18
|
+
readonly profileVersion?: string;
|
|
19
|
+
readonly model?: string;
|
|
20
|
+
readonly eligibleTransports?: readonly ProviderTransport[];
|
|
21
|
+
readonly access?: ProviderOperationsSurface['access'];
|
|
22
|
+
readonly observation?: TObservation;
|
|
23
|
+
readonly probes?: readonly UnauthenticatedProbe[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A minimal but COMPLETE `ProviderDefinition` — one strong required passive matcher on
|
|
27
|
+
* the model fact, no authenticated profile, no capability readers.
|
|
28
|
+
*
|
|
29
|
+
* It registers nothing and reaches no device: `observe` answers a fresh envelope built
|
|
30
|
+
* from the supplied observation, and `operations` returns the access surface asked for.
|
|
31
|
+
* This is the shape a consumer writes its own registry/matcher tests against without
|
|
32
|
+
* inventing a parallel provider contract.
|
|
33
|
+
*/
|
|
34
|
+
export declare function fakeProviderDefinition<TObservation>(options: FakeProviderOptions<TObservation> & {
|
|
35
|
+
readonly observation: TObservation;
|
|
36
|
+
}): ProviderDefinition<TObservation, ProviderOperationsSurface>;
|
|
37
|
+
export type FakeProbeOptions = {
|
|
38
|
+
readonly id?: string;
|
|
39
|
+
readonly signal?: FingerprintResult['signal'];
|
|
40
|
+
readonly strength?: FingerprintResult['strength'];
|
|
41
|
+
readonly profiles?: readonly string[];
|
|
42
|
+
readonly detail?: string;
|
|
43
|
+
};
|
|
44
|
+
/** A harmless unauthenticated probe that answers a fixed fingerprint. */
|
|
45
|
+
export declare function fakeUnauthenticatedProbe(options?: FakeProbeOptions): UnauthenticatedProbe;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { FAKE_GENERATION, FAKE_PHYSICAL_MODEM_ID, fakeFreshObservation } from './domain-fakes.js';
|
|
2
|
+
/** The model string every provider fake matches on unless one is named. */
|
|
3
|
+
export const FAKE_PROVIDER_MODEL = 'contract-fake-model';
|
|
4
|
+
/** A well-formed `ProviderMatchRequest` whose passive facts name exactly one model. */
|
|
5
|
+
export function fakeProviderMatchRequest(options = {}) {
|
|
6
|
+
const model = options.model ?? FAKE_PROVIDER_MODEL;
|
|
7
|
+
return {
|
|
8
|
+
physicalModemId: FAKE_PHYSICAL_MODEM_ID,
|
|
9
|
+
generation: options.generation ?? FAKE_GENERATION,
|
|
10
|
+
transport: options.transport ?? 'network',
|
|
11
|
+
passiveFacts: options.passiveFacts ?? [{ kind: 'model', value: model }],
|
|
12
|
+
composition: options.composition ?? 'ethernet-router',
|
|
13
|
+
firmware: options.firmware ?? '1.0.0',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A minimal but COMPLETE `ProviderDefinition` — one strong required passive matcher on
|
|
18
|
+
* the model fact, no authenticated profile, no capability readers.
|
|
19
|
+
*
|
|
20
|
+
* It registers nothing and reaches no device: `observe` answers a fresh envelope built
|
|
21
|
+
* from the supplied observation, and `operations` returns the access surface asked for.
|
|
22
|
+
* This is the shape a consumer writes its own registry/matcher tests against without
|
|
23
|
+
* inventing a parallel provider contract.
|
|
24
|
+
*/
|
|
25
|
+
export function fakeProviderDefinition(options) {
|
|
26
|
+
const id = options.id ?? 'contract-fake-provider';
|
|
27
|
+
const profile = options.profile ?? 'contract-fake-profile';
|
|
28
|
+
const model = options.model ?? FAKE_PROVIDER_MODEL;
|
|
29
|
+
const access = options.access ?? 'read-only';
|
|
30
|
+
const observation = options.observation;
|
|
31
|
+
return {
|
|
32
|
+
id,
|
|
33
|
+
profileVersion: options.profileVersion ?? '1.0.0',
|
|
34
|
+
eligibleTransports: options.eligibleTransports ?? ['network'],
|
|
35
|
+
passiveMatchers: [
|
|
36
|
+
{
|
|
37
|
+
id: `${id}-model`,
|
|
38
|
+
fact: 'model',
|
|
39
|
+
expected: [model],
|
|
40
|
+
profiles: [profile],
|
|
41
|
+
strength: 'strong',
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
unauthenticatedProbes: options.probes ?? [],
|
|
46
|
+
capabilityReaders: [],
|
|
47
|
+
observe: (_context) => Promise.resolve([fakeFreshObservation(observation, { source: id })]),
|
|
48
|
+
operations: (_profile) => ({ access }),
|
|
49
|
+
contractFixtures: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** A harmless unauthenticated probe that answers a fixed fingerprint. */
|
|
53
|
+
export function fakeUnauthenticatedProbe(options = {}) {
|
|
54
|
+
const result = {
|
|
55
|
+
signal: options.signal ?? 'match',
|
|
56
|
+
strength: options.strength ?? 'moderate',
|
|
57
|
+
profiles: options.profiles ?? ['contract-fake-profile'],
|
|
58
|
+
detail: options.detail ?? 'contract-fake probe',
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
id: options.id ?? 'contract-fake-probe',
|
|
62
|
+
run: (_context) => Promise.resolve(result),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RawBus } from './dbus-native.js';
|
|
2
|
+
import type { MethodCall, MethodReply } from './types.js';
|
|
3
|
+
export declare const DEFAULT_CALL_TIMEOUT_MS = 30000;
|
|
4
|
+
export declare class CallDispatcher {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(callTimeoutMs: number);
|
|
7
|
+
call(bus: RawBus | null, connected: boolean, call: MethodCall): Promise<MethodReply>;
|
|
8
|
+
rejectAll(cause: unknown): void;
|
|
9
|
+
}
|