@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
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const PACKAGE_NAME = "@ceralive/modem-control";
|
|
2
|
+
export * from './backend/index.js';
|
|
3
|
+
export * from './band/index.js';
|
|
4
|
+
export * from './capability/index.js';
|
|
5
|
+
export * from './domain/index.js';
|
|
6
|
+
export * from './fcc/index.js';
|
|
7
|
+
export * from './hardware/router-parsers.js';
|
|
8
|
+
export * from './journal/index.js';
|
|
9
|
+
export * from './location/index.js';
|
|
10
|
+
export * from './observations/index.js';
|
|
11
|
+
export * from './operations/index.js';
|
|
12
|
+
export * from './ports/index.js';
|
|
13
|
+
export * from './providers/index.js';
|
|
14
|
+
export * from './radio/index.js';
|
|
15
|
+
export * from './redact.js';
|
|
16
|
+
export * from './safety/index.js';
|
|
17
|
+
export * from './sms/index.js';
|
|
18
|
+
export * from './usb-mode/index.js';
|
|
19
|
+
export * from './ussd/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @ceralive/modem-control — package entry point.
|
|
2
|
+
//
|
|
3
|
+
// Phase A: the domain model (identity + orthogonal state + revisions) under
|
|
4
|
+
// `./domain`, the MM / NM / Router port contracts + desired-state planner under
|
|
5
|
+
// `./ports`, the redaction module (`./redact`), and the epoch-scoped ModemManager
|
|
6
|
+
// D-Bus observer under `./backend`. The NetworkManager adapter, USB composition-mode
|
|
7
|
+
// model, and data-usage sampler land in later waves.
|
|
8
|
+
export const PACKAGE_NAME = '@ceralive/modem-control';
|
|
9
|
+
export * from './backend/index.js';
|
|
10
|
+
export * from './band/index.js';
|
|
11
|
+
export * from './capability/index.js';
|
|
12
|
+
export * from './domain/index.js';
|
|
13
|
+
export * from './fcc/index.js';
|
|
14
|
+
export * from './hardware/router-parsers.js';
|
|
15
|
+
export * from './journal/index.js';
|
|
16
|
+
export * from './location/index.js';
|
|
17
|
+
export * from './observations/index.js';
|
|
18
|
+
export * from './operations/index.js';
|
|
19
|
+
export * from './ports/index.js';
|
|
20
|
+
export * from './providers/index.js';
|
|
21
|
+
export * from './radio/index.js';
|
|
22
|
+
export * from './redact.js';
|
|
23
|
+
export * from './safety/index.js';
|
|
24
|
+
export * from './sms/index.js';
|
|
25
|
+
export * from './usb-mode/index.js';
|
|
26
|
+
export * from './ussd/index.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type JournalEntry } from './entry.js';
|
|
2
|
+
/** Why one record could not be read. `field` names the offending key, never its value. */
|
|
3
|
+
export interface JournalDecodeFailure {
|
|
4
|
+
readonly code: 'empty' | 'invalid-json' | 'not-an-object' | 'unsupported-schema-version' | 'schema-mismatch' | 'unreadable';
|
|
5
|
+
readonly field?: string;
|
|
6
|
+
/** Byte offset for a JSON syntax error, when the runtime reported one. */
|
|
7
|
+
readonly offset?: number;
|
|
8
|
+
}
|
|
9
|
+
export type JournalDecodeResult<T> = {
|
|
10
|
+
readonly ok: true;
|
|
11
|
+
readonly value: T;
|
|
12
|
+
} | {
|
|
13
|
+
readonly ok: false;
|
|
14
|
+
readonly failure: JournalDecodeFailure;
|
|
15
|
+
};
|
|
16
|
+
declare function record(raw: unknown, field: string): Record<string, unknown>;
|
|
17
|
+
declare function requiredString(source: Record<string, unknown>, field: string): string;
|
|
18
|
+
declare function optionalString(source: Record<string, unknown>, field: string): string | undefined;
|
|
19
|
+
declare function requiredNonNegativeInteger(source: Record<string, unknown>, field: string): number;
|
|
20
|
+
declare function member<T extends string>(source: Record<string, unknown>, field: string, allowed: readonly T[]): T;
|
|
21
|
+
/**
|
|
22
|
+
* Serialize one entry to a single line WITHOUT its terminator.
|
|
23
|
+
*
|
|
24
|
+
* Keys are emitted in a fixed order because the round-trip tests compare bytes,
|
|
25
|
+
* and byte comparison is the only assertion that catches a field a reader silently
|
|
26
|
+
* dropped (`JSON.parse` + a permissive validator will happily lose one and still
|
|
27
|
+
* report success — the same trap the srtla telemetry byte-parity suite exists for).
|
|
28
|
+
*/
|
|
29
|
+
export declare function encodeJournalEntry(entry: JournalEntry): string;
|
|
30
|
+
/** Decode one line. An empty/whitespace-only line is reported, never silently dropped. */
|
|
31
|
+
export declare function decodeJournalEntry(line: string): JournalDecodeResult<JournalEntry>;
|
|
32
|
+
/** Decode an arbitrary JSON document with a caller-supplied validator. */
|
|
33
|
+
export declare function decodeJournalDocument<T>(text: string, validate: (raw: unknown) => T): JournalDecodeResult<T>;
|
|
34
|
+
/** The validator helpers the legacy reader reuses, so both shapes fail the same way. */
|
|
35
|
+
export declare const journalSchema: {
|
|
36
|
+
readonly record: typeof record;
|
|
37
|
+
readonly requiredString: typeof requiredString;
|
|
38
|
+
readonly optionalString: typeof optionalString;
|
|
39
|
+
readonly requiredNonNegativeInteger: typeof requiredNonNegativeInteger;
|
|
40
|
+
readonly member: typeof member;
|
|
41
|
+
readonly schemaError: (field: string) => Error;
|
|
42
|
+
readonly schemaVersionError: () => Error;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Line codec for the journal: exactly one entry per line, JSON, no wrapper.
|
|
2
|
+
//
|
|
3
|
+
// WHY DECODING RETURNS A RESULT INSTEAD OF THROWING. A journal is read at exactly
|
|
4
|
+
// one moment — recovery after an unclean restart — and that is the moment a throw
|
|
5
|
+
// is most expensive: it aborts the read at the first damaged byte and takes every
|
|
6
|
+
// still-valid entry after it with it. The whole point of this module is that a
|
|
7
|
+
// damaged record is DATA, reported alongside the records that survived, so nothing
|
|
8
|
+
// earlier or later is discarded to make one bad line disappear.
|
|
9
|
+
//
|
|
10
|
+
// FAILURES NAME A FIELD, NEVER CONTENT. Same rule the two policy stores follow: a
|
|
11
|
+
// classification carries the offending field name and a byte count, never the bytes
|
|
12
|
+
// themselves, because a journal line can hold provider identifiers and refusal
|
|
13
|
+
// reasons and a log line is the wrong place to reproduce them.
|
|
14
|
+
import { MODEM_CONTROL_JOURNAL_SCHEMA_VERSION, } from './entry.js';
|
|
15
|
+
class SchemaError extends Error {
|
|
16
|
+
field;
|
|
17
|
+
constructor(field) {
|
|
18
|
+
super(`schema-mismatch: ${field}`);
|
|
19
|
+
this.field = field;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
class SchemaVersionError extends Error {
|
|
23
|
+
constructor() {
|
|
24
|
+
super('unsupported-schema-version');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function record(raw, field) {
|
|
28
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
29
|
+
throw new SchemaError(field);
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
32
|
+
function requiredString(source, field) {
|
|
33
|
+
const value = source[field];
|
|
34
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
35
|
+
throw new SchemaError(field);
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
function optionalString(source, field) {
|
|
39
|
+
const value = source[field];
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
if (typeof value !== 'string')
|
|
43
|
+
throw new SchemaError(field);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function requiredNonNegativeInteger(source, field) {
|
|
47
|
+
const value = source[field];
|
|
48
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
|
|
49
|
+
throw new SchemaError(field);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function requiredStringArray(source, field) {
|
|
54
|
+
const value = source[field];
|
|
55
|
+
if (!Array.isArray(value) || value.some((member) => typeof member !== 'string')) {
|
|
56
|
+
throw new SchemaError(field);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
function member(source, field, allowed) {
|
|
61
|
+
const value = source[field];
|
|
62
|
+
if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
63
|
+
throw new SchemaError(field);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
const AUTHORITIES = ['provider', 'controller', 'hardware'];
|
|
68
|
+
const IMPACTS = ['read', 'write', 'session', 'disruptive', 'recovery'];
|
|
69
|
+
const CONFIDENCES = ['high', 'medium', 'low', 'unknown'];
|
|
70
|
+
const UNKNOWN_OUTCOME_REASONS = [
|
|
71
|
+
'stale-generation',
|
|
72
|
+
'write-reply-timed-out',
|
|
73
|
+
'write-reply-dropped',
|
|
74
|
+
];
|
|
75
|
+
function parseDescriptor(raw) {
|
|
76
|
+
const source = record(raw, 'descriptor');
|
|
77
|
+
return {
|
|
78
|
+
descriptorId: requiredString(source, 'descriptorId'),
|
|
79
|
+
provider: requiredString(source, 'provider'),
|
|
80
|
+
authority: member(source, 'authority', AUTHORITIES),
|
|
81
|
+
mutationImpact: member(source, 'mutationImpact', IMPACTS),
|
|
82
|
+
profiles: requiredStringArray(source, 'profiles'),
|
|
83
|
+
firmware: requiredStringArray(source, 'firmware'),
|
|
84
|
+
confidence: member(source, 'confidence', CONFIDENCES),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function parseOutcome(raw) {
|
|
88
|
+
const source = record(raw, 'outcome');
|
|
89
|
+
switch (member(source, 'status', ['applied', 'refused', 'failed', 'unknown-outcome'])) {
|
|
90
|
+
case 'applied':
|
|
91
|
+
return { status: 'applied' };
|
|
92
|
+
case 'refused':
|
|
93
|
+
return { status: 'refused', reason: requiredString(source, 'reason') };
|
|
94
|
+
case 'failed':
|
|
95
|
+
return { status: 'failed', reason: requiredString(source, 'reason') };
|
|
96
|
+
case 'unknown-outcome':
|
|
97
|
+
return {
|
|
98
|
+
status: 'unknown-outcome',
|
|
99
|
+
reason: member(source, 'reason', UNKNOWN_OUTCOME_REASONS),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function parseEntry(raw) {
|
|
104
|
+
const source = record(raw, 'entry');
|
|
105
|
+
if (source.schemaVersion !== MODEM_CONTROL_JOURNAL_SCHEMA_VERSION)
|
|
106
|
+
throw new SchemaVersionError();
|
|
107
|
+
const base = {
|
|
108
|
+
schemaVersion: MODEM_CONTROL_JOURNAL_SCHEMA_VERSION,
|
|
109
|
+
operationId: requiredString(source, 'operationId'),
|
|
110
|
+
physicalModemId: requiredString(source, 'physicalModemId'),
|
|
111
|
+
generation: requiredNonNegativeInteger(source, 'generation'),
|
|
112
|
+
recordedAtMs: requiredNonNegativeInteger(source, 'recordedAtMs'),
|
|
113
|
+
descriptor: parseDescriptor(source.descriptor),
|
|
114
|
+
};
|
|
115
|
+
if (member(source, 'phase', ['started', 'completed']) === 'started') {
|
|
116
|
+
return { ...base, phase: 'started' };
|
|
117
|
+
}
|
|
118
|
+
return { ...base, phase: 'completed', outcome: parseOutcome(source.outcome) };
|
|
119
|
+
}
|
|
120
|
+
/** Classify a decode failure into a metadata-only result (never raw content). */
|
|
121
|
+
function classify(error) {
|
|
122
|
+
if (error instanceof SchemaVersionError)
|
|
123
|
+
return { code: 'unsupported-schema-version' };
|
|
124
|
+
if (error instanceof SchemaError)
|
|
125
|
+
return { code: 'schema-mismatch', field: error.field };
|
|
126
|
+
if (error instanceof SyntaxError) {
|
|
127
|
+
const offset = /position (\d+)/.exec(error.message)?.[1];
|
|
128
|
+
return offset === undefined
|
|
129
|
+
? { code: 'invalid-json' }
|
|
130
|
+
: { code: 'invalid-json', offset: Number(offset) };
|
|
131
|
+
}
|
|
132
|
+
return { code: 'unreadable' };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Serialize one entry to a single line WITHOUT its terminator.
|
|
136
|
+
*
|
|
137
|
+
* Keys are emitted in a fixed order because the round-trip tests compare bytes,
|
|
138
|
+
* and byte comparison is the only assertion that catches a field a reader silently
|
|
139
|
+
* dropped (`JSON.parse` + a permissive validator will happily lose one and still
|
|
140
|
+
* report success — the same trap the srtla telemetry byte-parity suite exists for).
|
|
141
|
+
*/
|
|
142
|
+
export function encodeJournalEntry(entry) {
|
|
143
|
+
const descriptor = {
|
|
144
|
+
descriptorId: entry.descriptor.descriptorId,
|
|
145
|
+
provider: entry.descriptor.provider,
|
|
146
|
+
authority: entry.descriptor.authority,
|
|
147
|
+
mutationImpact: entry.descriptor.mutationImpact,
|
|
148
|
+
profiles: entry.descriptor.profiles,
|
|
149
|
+
firmware: entry.descriptor.firmware,
|
|
150
|
+
confidence: entry.descriptor.confidence,
|
|
151
|
+
};
|
|
152
|
+
const base = {
|
|
153
|
+
schemaVersion: entry.schemaVersion,
|
|
154
|
+
phase: entry.phase,
|
|
155
|
+
operationId: entry.operationId,
|
|
156
|
+
physicalModemId: entry.physicalModemId,
|
|
157
|
+
generation: entry.generation,
|
|
158
|
+
recordedAtMs: entry.recordedAtMs,
|
|
159
|
+
descriptor,
|
|
160
|
+
};
|
|
161
|
+
return JSON.stringify(entry.phase === 'started' ? base : { ...base, outcome: entry.outcome });
|
|
162
|
+
}
|
|
163
|
+
/** Decode one line. An empty/whitespace-only line is reported, never silently dropped. */
|
|
164
|
+
export function decodeJournalEntry(line) {
|
|
165
|
+
if (line.trim().length === 0)
|
|
166
|
+
return { ok: false, failure: { code: 'empty' } };
|
|
167
|
+
try {
|
|
168
|
+
return { ok: true, value: parseEntry(JSON.parse(line)) };
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return { ok: false, failure: classify(error) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Decode an arbitrary JSON document with a caller-supplied validator. */
|
|
175
|
+
export function decodeJournalDocument(text, validate) {
|
|
176
|
+
if (text.trim().length === 0)
|
|
177
|
+
return { ok: false, failure: { code: 'empty' } };
|
|
178
|
+
try {
|
|
179
|
+
return { ok: true, value: validate(JSON.parse(text)) };
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
return { ok: false, failure: classify(error) };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** The validator helpers the legacy reader reuses, so both shapes fail the same way. */
|
|
186
|
+
export const journalSchema = {
|
|
187
|
+
record,
|
|
188
|
+
requiredString,
|
|
189
|
+
optionalString,
|
|
190
|
+
requiredNonNegativeInteger,
|
|
191
|
+
member,
|
|
192
|
+
schemaError(field) {
|
|
193
|
+
return new SchemaError(field);
|
|
194
|
+
},
|
|
195
|
+
schemaVersionError() {
|
|
196
|
+
return new SchemaVersionError();
|
|
197
|
+
},
|
|
198
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { OperationJournalEvent, OperationJournalHook } from '../operations/contracts.js';
|
|
2
|
+
import { type JournalRecovery } from './recovery.js';
|
|
3
|
+
import type { JournalStore } from './store.js';
|
|
4
|
+
export interface JournalEngineOptions {
|
|
5
|
+
/** The store — and therefore the path — is supplied by the composition root. */
|
|
6
|
+
readonly store: JournalStore;
|
|
7
|
+
readonly now?: () => number;
|
|
8
|
+
}
|
|
9
|
+
export declare class JournalEngine {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(options: JournalEngineOptions);
|
|
12
|
+
/** Where this engine journals to; useful in a recovery log line. */
|
|
13
|
+
get path(): string;
|
|
14
|
+
/**
|
|
15
|
+
* The `OperationJournalHook` implementation.
|
|
16
|
+
*
|
|
17
|
+
* Generic per CALL rather than per instance, so one engine journals every
|
|
18
|
+
* descriptor in the process. A generic method satisfies the non-generic
|
|
19
|
+
* `OperationJournalHook<I, O>` member by instantiation, which is what lets an
|
|
20
|
+
* `OperationExecution` take the engine itself as its `journal`.
|
|
21
|
+
*/
|
|
22
|
+
record<I, O>(event: OperationJournalEvent<I, O>): Promise<void>;
|
|
23
|
+
/** An explicitly typed hook, for a caller that prefers a narrow object. */
|
|
24
|
+
hook<I, O>(): OperationJournalHook<I, O>;
|
|
25
|
+
/** Read the journal back and reconstruct what was in flight. */
|
|
26
|
+
recover(): Promise<JournalRecovery>;
|
|
27
|
+
}
|
|
28
|
+
export declare function createJournalEngine(options: JournalEngineOptions): JournalEngine;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// The journal engine: the operation engine's journal hook, plus replay.
|
|
2
|
+
//
|
|
3
|
+
// `OperationExecution.journal` (todo 20's `OperationJournalHook`) is the seam the
|
|
4
|
+
// operation engine already calls at `started` and at `completed`, and a descriptor
|
|
5
|
+
// that declares `journal: { required: true }` is REFUSED outright when no hook is
|
|
6
|
+
// supplied. This class is the durable implementation of that seam: it flattens the
|
|
7
|
+
// event onto a journal entry and appends it, and it reads the same file back.
|
|
8
|
+
//
|
|
9
|
+
// THE ENGINE HOLDS NO PATH. It holds a `JournalStore`, and the store was handed a
|
|
10
|
+
// path by whoever composed it. That is the same injection shape todo 19 used for
|
|
11
|
+
// the ownership lock (`FlockResourceOwnershipOptions.lockPath` is REQUIRED and the
|
|
12
|
+
// adapter substitutes nothing) and the same reason: an embedding process owns its
|
|
13
|
+
// filesystem layout, and a library that guesses one is a library that writes to the
|
|
14
|
+
// wrong disk on a device it has never seen.
|
|
15
|
+
//
|
|
16
|
+
// THE CLOCK IS INJECTED TOO. `now` defaults to `Date.now`, but a test that wants
|
|
17
|
+
// deterministic timestamps supplies its own — the observation layer's rule that
|
|
18
|
+
// this package never stamps data with a time it did not come from applies here as
|
|
19
|
+
// well, and a replay assertion over timestamps must not be a race.
|
|
20
|
+
import { journalDescriptorEvidence, journalOutcome } from './entry.js';
|
|
21
|
+
import { reconstructJournalRecovery } from './recovery.js';
|
|
22
|
+
export class JournalEngine {
|
|
23
|
+
#store;
|
|
24
|
+
#now;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.#store = options.store;
|
|
27
|
+
this.#now = options.now ?? (() => Date.now());
|
|
28
|
+
}
|
|
29
|
+
/** Where this engine journals to; useful in a recovery log line. */
|
|
30
|
+
get path() {
|
|
31
|
+
return this.#store.path;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The `OperationJournalHook` implementation.
|
|
35
|
+
*
|
|
36
|
+
* Generic per CALL rather than per instance, so one engine journals every
|
|
37
|
+
* descriptor in the process. A generic method satisfies the non-generic
|
|
38
|
+
* `OperationJournalHook<I, O>` member by instantiation, which is what lets an
|
|
39
|
+
* `OperationExecution` take the engine itself as its `journal`.
|
|
40
|
+
*/
|
|
41
|
+
record(event) {
|
|
42
|
+
return this.#store.append(entryFor(event, this.#now()));
|
|
43
|
+
}
|
|
44
|
+
/** An explicitly typed hook, for a caller that prefers a narrow object. */
|
|
45
|
+
hook() {
|
|
46
|
+
return { record: (event) => this.record(event) };
|
|
47
|
+
}
|
|
48
|
+
/** Read the journal back and reconstruct what was in flight. */
|
|
49
|
+
async recover() {
|
|
50
|
+
return reconstructJournalRecovery(await this.#store.read());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function entryFor(event, recordedAtMs) {
|
|
54
|
+
const base = {
|
|
55
|
+
schemaVersion: 1,
|
|
56
|
+
operationId: event.operationId,
|
|
57
|
+
physicalModemId: event.physicalModemId,
|
|
58
|
+
generation: event.generation,
|
|
59
|
+
recordedAtMs,
|
|
60
|
+
descriptor: journalDescriptorEvidence(event.descriptor),
|
|
61
|
+
};
|
|
62
|
+
return event.phase === 'started'
|
|
63
|
+
? { ...base, phase: 'started' }
|
|
64
|
+
: { ...base, phase: 'completed', outcome: journalOutcome(event.result) };
|
|
65
|
+
}
|
|
66
|
+
export function createJournalEngine(options) {
|
|
67
|
+
return new JournalEngine(options);
|
|
68
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { MutationImpact, OperationConfidence, OperationDescriptor, OperationResult } from '../domain/index.js';
|
|
2
|
+
/** The current on-disk schema version for one journal line. */
|
|
3
|
+
export declare const MODEM_CONTROL_JOURNAL_SCHEMA_VERSION = 1;
|
|
4
|
+
/** The two phases the operation engine emits, mirrored one-to-one on disk. */
|
|
5
|
+
export type JournalPhase = 'started' | 'completed';
|
|
6
|
+
/**
|
|
7
|
+
* How an operation ended, projected from `OperationResult` WITHOUT its value.
|
|
8
|
+
*
|
|
9
|
+
* `unknown-outcome` keeps the frozen domain reason union verbatim rather than
|
|
10
|
+
* widening to `string`, because those three reasons are the entire vocabulary a
|
|
11
|
+
* recovery pass branches on and a fourth spelling would silently read as an
|
|
12
|
+
* ordinary failure.
|
|
13
|
+
*/
|
|
14
|
+
export type JournalOutcome = {
|
|
15
|
+
readonly status: 'applied';
|
|
16
|
+
} | {
|
|
17
|
+
readonly status: 'refused';
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
} | {
|
|
20
|
+
readonly status: 'failed';
|
|
21
|
+
readonly reason: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly status: 'unknown-outcome';
|
|
24
|
+
readonly reason: 'stale-generation' | 'write-reply-timed-out' | 'write-reply-dropped';
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The descriptor facts a recovery pass needs, flattened out of the descriptor.
|
|
28
|
+
*
|
|
29
|
+
* The descriptor itself is not persisted: it carries FUNCTIONS (`readback.matches`,
|
|
30
|
+
* the constraint predicates) that no serialization round-trips, so storing it would
|
|
31
|
+
* produce a document that reads back as a different object than it was written from.
|
|
32
|
+
* These are the fields that answer "what was being changed, by whom, on what
|
|
33
|
+
* evidence" — everything a human or a reconciler needs to judge a stranded write.
|
|
34
|
+
*/
|
|
35
|
+
export interface JournalDescriptorEvidence {
|
|
36
|
+
readonly descriptorId: string;
|
|
37
|
+
readonly provider: string;
|
|
38
|
+
readonly authority: 'provider' | 'controller' | 'hardware';
|
|
39
|
+
readonly mutationImpact: MutationImpact;
|
|
40
|
+
readonly profiles: readonly string[];
|
|
41
|
+
readonly firmware: readonly string[];
|
|
42
|
+
readonly confidence: OperationConfidence;
|
|
43
|
+
}
|
|
44
|
+
interface JournalEntryBase {
|
|
45
|
+
readonly schemaVersion: typeof MODEM_CONTROL_JOURNAL_SCHEMA_VERSION;
|
|
46
|
+
readonly operationId: string;
|
|
47
|
+
/** The serialized `PhysicalModemId`. Stored as text; branding is a compile-time fact. */
|
|
48
|
+
readonly physicalModemId: string;
|
|
49
|
+
/** The serialized `DeviceGeneration` the operation was fenced to. */
|
|
50
|
+
readonly generation: number;
|
|
51
|
+
readonly recordedAtMs: number;
|
|
52
|
+
readonly descriptor: JournalDescriptorEvidence;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One journal line.
|
|
56
|
+
*
|
|
57
|
+
* The two members differ in SHAPE, not just in a label: a `started` entry has no
|
|
58
|
+
* `outcome` KEY at all. That is the same rule `observations/reading.ts` follows —
|
|
59
|
+
* a consumer cannot read an outcome off a phase that has none, so "in flight" can
|
|
60
|
+
* never be mistaken for "ended with an unset outcome".
|
|
61
|
+
*/
|
|
62
|
+
export type JournalEntry = (JournalEntryBase & {
|
|
63
|
+
readonly phase: 'started';
|
|
64
|
+
}) | (JournalEntryBase & {
|
|
65
|
+
readonly phase: 'completed';
|
|
66
|
+
readonly outcome: JournalOutcome;
|
|
67
|
+
});
|
|
68
|
+
/** Flatten a descriptor down to the serializable evidence the journal keeps. */
|
|
69
|
+
export declare function journalDescriptorEvidence<I, O>(descriptor: OperationDescriptor<I, O>): JournalDescriptorEvidence;
|
|
70
|
+
/** Project a result onto its journalable outcome, dropping the value by design. */
|
|
71
|
+
export declare function journalOutcome<O>(result: OperationResult<O>): JournalOutcome;
|
|
72
|
+
/** True when an outcome leaves the device in a state nobody has read back. */
|
|
73
|
+
export declare function outcomeRequiresReconciliation(outcome: JournalOutcome): boolean;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// The transaction journal's ENTRY vocabulary.
|
|
2
|
+
//
|
|
3
|
+
// The journal exists to answer ONE question after an unclean restart: which
|
|
4
|
+
// mutations were in flight, and which of them ended in an outcome nobody can
|
|
5
|
+
// read off the device. `operations/operation-engine.ts` already closes a
|
|
6
|
+
// per-modem gate when a write classifies `unknown-outcome`, but that gate lives
|
|
7
|
+
// in a `Set` on the engine instance — a process death takes it with it. Writing
|
|
8
|
+
// the same two facts down is what makes the gate survive the process.
|
|
9
|
+
//
|
|
10
|
+
// WHY AN EVENT LOG AND NOT A LATEST-STATE SNAPSHOT. A started event and its
|
|
11
|
+
// completion are two facts separated by exactly the window a crash lands in, so
|
|
12
|
+
// the shape has to be able to hold the first without the second. A document that
|
|
13
|
+
// only ever carries "the current state" cannot distinguish "we never dispatched"
|
|
14
|
+
// from "we dispatched and the reply never came" unless it spends a state name on
|
|
15
|
+
// each — which is how CeraUI's own mutation journal does it (see
|
|
16
|
+
// `legacy-ceraui.ts`, which reads that shape). Both are legitimate; this one is
|
|
17
|
+
// append-only because appending is the only write that cannot lose a prior fact.
|
|
18
|
+
//
|
|
19
|
+
// WHAT IS DELIBERATELY NOT RECORDED: the operation's INPUT and the operation's
|
|
20
|
+
// RETURNED VALUE. An input is routinely a PIN, a PUK, or a USSD command carrying
|
|
21
|
+
// a voucher code, and a returned value is routinely a message body or a location
|
|
22
|
+
// fix — all of them classes `../redact.ts` masks everywhere else. The journal
|
|
23
|
+
// records THAT an operation ran and HOW it ended, never WHAT was sent or read.
|
|
24
|
+
// A caller that needs a rollback payload owns persisting it beside the journal
|
|
25
|
+
// under its own redaction decision.
|
|
26
|
+
/** The current on-disk schema version for one journal line. */
|
|
27
|
+
export const MODEM_CONTROL_JOURNAL_SCHEMA_VERSION = 1;
|
|
28
|
+
/** Flatten a descriptor down to the serializable evidence the journal keeps. */
|
|
29
|
+
export function journalDescriptorEvidence(descriptor) {
|
|
30
|
+
return {
|
|
31
|
+
descriptorId: descriptor.id,
|
|
32
|
+
provider: descriptor.provider,
|
|
33
|
+
authority: descriptor.authority,
|
|
34
|
+
mutationImpact: descriptor.mutationImpact,
|
|
35
|
+
profiles: [...descriptor.evidence.profiles],
|
|
36
|
+
firmware: [...descriptor.evidence.firmware],
|
|
37
|
+
confidence: descriptor.confidence,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Project a result onto its journalable outcome, dropping the value by design. */
|
|
41
|
+
export function journalOutcome(result) {
|
|
42
|
+
switch (result.status) {
|
|
43
|
+
case 'applied':
|
|
44
|
+
return { status: 'applied' };
|
|
45
|
+
case 'refused':
|
|
46
|
+
return { status: 'refused', reason: result.reason };
|
|
47
|
+
case 'failed':
|
|
48
|
+
return { status: 'failed', reason: result.reason };
|
|
49
|
+
case 'unknown-outcome':
|
|
50
|
+
return { status: 'unknown-outcome', reason: result.reason };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** True when an outcome leaves the device in a state nobody has read back. */
|
|
54
|
+
export function outcomeRequiresReconciliation(outcome) {
|
|
55
|
+
return outcome.status === 'unknown-outcome';
|
|
56
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type JournalDecodeResult } from './codec.js';
|
|
2
|
+
import type { JournalDescriptorEvidence } from './entry.js';
|
|
3
|
+
import { type JournalOperationRecord, type JournalRecovery } from './recovery.js';
|
|
4
|
+
/** The `version` literal CeraUI's schema pins. */
|
|
5
|
+
export declare const LEGACY_CERAUI_JOURNAL_VERSION = 1;
|
|
6
|
+
/** CeraUI's cap on retained history entries per slot. */
|
|
7
|
+
export declare const LEGACY_CERAUI_HISTORY_CAP = 32;
|
|
8
|
+
export declare const LEGACY_CERAUI_MUTATION_STATES: readonly ["armed", "executing", "completed", "failed", "acknowledged", "device-absent-quarantine", "decommissioned", "recommission-pending"];
|
|
9
|
+
export type LegacyCeraUiMutationState = (typeof LEGACY_CERAUI_MUTATION_STATES)[number];
|
|
10
|
+
export declare const LEGACY_CERAUI_ACK_MODES: readonly ["verified-rollback", "force-rebaseline"];
|
|
11
|
+
export type LegacyCeraUiAckMode = (typeof LEGACY_CERAUI_ACK_MODES)[number];
|
|
12
|
+
export interface LegacyCeraUiHistoryEntry {
|
|
13
|
+
readonly state: LegacyCeraUiMutationState;
|
|
14
|
+
readonly at: number;
|
|
15
|
+
readonly detail?: string;
|
|
16
|
+
}
|
|
17
|
+
/** One CeraUI slot document, decoded verbatim. `preState` is kept opaque. */
|
|
18
|
+
export interface LegacyCeraUiMutationEntry {
|
|
19
|
+
readonly version: typeof LEGACY_CERAUI_JOURNAL_VERSION;
|
|
20
|
+
readonly stableKey: string;
|
|
21
|
+
readonly kind: string;
|
|
22
|
+
readonly state: LegacyCeraUiMutationState;
|
|
23
|
+
readonly attemptId: string;
|
|
24
|
+
readonly startedAt: number;
|
|
25
|
+
readonly updatedAt: number;
|
|
26
|
+
/** The rollback target. Opaque by design — its shape is the mutation kind's. */
|
|
27
|
+
readonly preState: Readonly<Record<string, unknown>>;
|
|
28
|
+
readonly detail?: string;
|
|
29
|
+
readonly acknowledgedMode?: LegacyCeraUiAckMode;
|
|
30
|
+
readonly history: readonly LegacyCeraUiHistoryEntry[];
|
|
31
|
+
}
|
|
32
|
+
export interface LegacyCeraUiJournalRead {
|
|
33
|
+
/** The same recovery model `reconstructJournalRecovery` produces. */
|
|
34
|
+
readonly recovery: JournalRecovery;
|
|
35
|
+
/** The decoded slot documents, verbatim, so `preState` survives the read. */
|
|
36
|
+
readonly entries: readonly LegacyCeraUiMutationEntry[];
|
|
37
|
+
}
|
|
38
|
+
export interface LegacyCeraUiJournalOptions {
|
|
39
|
+
/** REQUIRED. The embedding process owns where CeraUI put its journal. */
|
|
40
|
+
readonly dir: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The slot filename CeraUI derives from a stable key.
|
|
44
|
+
*
|
|
45
|
+
* RULE-D MIRROR of CeraUI's own helper. It is a plain lowercase-hex SHA-256 of the
|
|
46
|
+
* key's UTF-8 bytes; the key itself never appears in the filename in plaintext.
|
|
47
|
+
*/
|
|
48
|
+
export declare function legacyMutationSlotName(stableKey: string): string;
|
|
49
|
+
/** Validate one slot document. Throws the codec's metadata-only schema errors. */
|
|
50
|
+
export declare function validateLegacyCeraUiEntry(raw: unknown): LegacyCeraUiMutationEntry;
|
|
51
|
+
/** Decode one slot document's text. Never throws; returns a typed failure. */
|
|
52
|
+
export declare function decodeLegacyCeraUiEntry(text: string): JournalDecodeResult<LegacyCeraUiMutationEntry>;
|
|
53
|
+
/**
|
|
54
|
+
* The descriptor evidence a legacy entry can honestly supply.
|
|
55
|
+
*
|
|
56
|
+
* CeraUI's journal predates `OperationDescriptor`, so there is no descriptor to
|
|
57
|
+
* flatten. Every field below is either a fact the file actually carries (the
|
|
58
|
+
* mutation kind) or an explicit statement that the file carries nothing:
|
|
59
|
+
* `confidence: 'unknown'` rather than a borrowed default, and empty evidence
|
|
60
|
+
* arrays rather than invented profiles. `mutationImpact: 'write'` is a fact, not a
|
|
61
|
+
* guess — CeraUI's file is a MUTATION journal and records nothing else.
|
|
62
|
+
*/
|
|
63
|
+
export declare function legacyDescriptorEvidence(kind: string): JournalDescriptorEvidence;
|
|
64
|
+
/** Project one decoded slot onto the shared recovery record model. */
|
|
65
|
+
export declare function legacyOperationRecord(entry: LegacyCeraUiMutationEntry): JournalOperationRecord;
|
|
66
|
+
/**
|
|
67
|
+
* Read a whole CeraUI journal directory.
|
|
68
|
+
*
|
|
69
|
+
* An unreadable or non-conforming slot is reported as damage and LEFT IN PLACE;
|
|
70
|
+
* every readable slot still comes back. That is the same non-truncating contract
|
|
71
|
+
* the native store makes, applied to a directory instead of a file.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readLegacyCeraUiJournal(options: LegacyCeraUiJournalOptions): Promise<LegacyCeraUiJournalRead>;
|