@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,161 @@
|
|
|
1
|
+
// Durable persistence for the operator's data-usage POLICY (cycle day + advisory
|
|
2
|
+
// threshold), the write-side counterpart of `store.ts`'s counter persistence.
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS IS LOCAL STATE AND NOT A MODEM WRITE. ModemManager has no data-usage
|
|
5
|
+
// API at all. Verified against a live MM 1.24.2 (`mmcli --help-all`, plus a D-Bus
|
|
6
|
+
// introspection of a real `…/ModemManager1/Modem/N`): the only `Setup`/threshold
|
|
7
|
+
// surface on the whole object is `Modem.Signal.Setup` /
|
|
8
|
+
// `Modem.Signal.SetupThresholds`, whose keys are `rssi-threshold` and
|
|
9
|
+
// `error-rate-threshold` — RADIO QUALITY, not bytes. The only byte counters MM
|
|
10
|
+
// offers are the per-BEARER read-only `Stats` (`rx-bytes`/`tx-bytes`), which
|
|
11
|
+
// reset with every connection and therefore cannot carry a monthly cycle.
|
|
12
|
+
//
|
|
13
|
+
// That is exactly why the sampler in this directory counts `/proc/net/dev`
|
|
14
|
+
// instead, and why `ports/README.md`'s ownership table records usage policy as
|
|
15
|
+
// LOCAL-CONTROLLER owned. So the write path is a local, versioned, fail-soft
|
|
16
|
+
// file — never a D-Bus mutation. (`Modem.Signal.Setup` is additionally forbidden
|
|
17
|
+
// outright by the shadow-mode mutation-freedom contract; nothing here goes near
|
|
18
|
+
// it.)
|
|
19
|
+
//
|
|
20
|
+
// The two hard guarantees are the SAME ones `store.ts` makes, and deliberately
|
|
21
|
+
// implemented the same way so the pair can be read side by side:
|
|
22
|
+
// - MODE 0600 via temp → chmod → atomic rename, regardless of umask.
|
|
23
|
+
// - FAIL-SOFT ON CORRUPTION: an unparseable/incompatible file logs METADATA
|
|
24
|
+
// ONLY (byte length + a classification reason, never the content) and is
|
|
25
|
+
// replaced by a fresh empty 0600 file rather than throwing.
|
|
26
|
+
//
|
|
27
|
+
// A policy row carries ONLY an opaque slot id and two numbers. By construction
|
|
28
|
+
// there is no subscriber or device identity here (no ICCID/IMSI/IMEI, no
|
|
29
|
+
// operator, no model) — the same no-PII property the counter store holds.
|
|
30
|
+
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
31
|
+
import { dirname } from 'node:path';
|
|
32
|
+
/** The current on-disk schema version. Bump when the persisted shape changes. */
|
|
33
|
+
export const USAGE_POLICY_SCHEMA_VERSION = 1;
|
|
34
|
+
function defaultLogger(event) {
|
|
35
|
+
console.warn(`[usage-policy] ${event.kind}: bytes=${event.bytes} reason=${event.reason}`);
|
|
36
|
+
}
|
|
37
|
+
function freshState(nowMs) {
|
|
38
|
+
return { schemaVersion: USAGE_POLICY_SCHEMA_VERSION, savedAtMs: nowMs, slots: [] };
|
|
39
|
+
}
|
|
40
|
+
/** A schema violation naming only the offending FIELD (never file content). */
|
|
41
|
+
class PolicySchemaError extends Error {
|
|
42
|
+
constructor(field) {
|
|
43
|
+
super(`schema-mismatch: ${field}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** True for a value that is a legal cycle day (integer 1–31). */
|
|
47
|
+
export function isValidCycleDay(value) {
|
|
48
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 31;
|
|
49
|
+
}
|
|
50
|
+
/** True for a value that is a legal advisory threshold (non-negative integer). */
|
|
51
|
+
export function isValidThresholdBytes(value) {
|
|
52
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
|
53
|
+
}
|
|
54
|
+
function validateSlot(raw) {
|
|
55
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
56
|
+
throw new PolicySchemaError('slot');
|
|
57
|
+
}
|
|
58
|
+
const slot = raw;
|
|
59
|
+
if (typeof slot.logicalSlotId !== 'string' || slot.logicalSlotId.length === 0) {
|
|
60
|
+
throw new PolicySchemaError('logicalSlotId');
|
|
61
|
+
}
|
|
62
|
+
if (slot.cycleDay !== undefined && !isValidCycleDay(slot.cycleDay)) {
|
|
63
|
+
throw new PolicySchemaError('cycleDay');
|
|
64
|
+
}
|
|
65
|
+
if (slot.thresholdBytes !== undefined && !isValidThresholdBytes(slot.thresholdBytes)) {
|
|
66
|
+
throw new PolicySchemaError('thresholdBytes');
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
logicalSlotId: slot.logicalSlotId,
|
|
70
|
+
...(slot.cycleDay !== undefined ? { cycleDay: slot.cycleDay } : {}),
|
|
71
|
+
...(slot.thresholdBytes !== undefined ? { thresholdBytes: slot.thresholdBytes } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Parse + validate the document. Throws `PolicySchemaError` (metadata-only). */
|
|
75
|
+
function validate(raw) {
|
|
76
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
77
|
+
throw new PolicySchemaError('document');
|
|
78
|
+
}
|
|
79
|
+
const doc = raw;
|
|
80
|
+
if (doc.schemaVersion !== USAGE_POLICY_SCHEMA_VERSION) {
|
|
81
|
+
throw new PolicySchemaError('schemaVersion');
|
|
82
|
+
}
|
|
83
|
+
if (typeof doc.savedAtMs !== 'number' || !Number.isFinite(doc.savedAtMs)) {
|
|
84
|
+
throw new PolicySchemaError('savedAtMs');
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(doc.slots)) {
|
|
87
|
+
throw new PolicySchemaError('slots');
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
|
|
91
|
+
savedAtMs: doc.savedAtMs,
|
|
92
|
+
slots: doc.slots.map(validateSlot),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** Classify a load failure into a metadata-only reason string (no raw content). */
|
|
96
|
+
function classifyFailure(error) {
|
|
97
|
+
if (error instanceof PolicySchemaError) {
|
|
98
|
+
return error.message;
|
|
99
|
+
}
|
|
100
|
+
if (error instanceof SyntaxError) {
|
|
101
|
+
const offset = /position (\d+)/.exec(error.message)?.[1];
|
|
102
|
+
return offset !== undefined ? `invalid-json at offset ${offset}` : 'invalid-json';
|
|
103
|
+
}
|
|
104
|
+
return 'unreadable';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Read one slot's policy out of a loaded document.
|
|
108
|
+
*
|
|
109
|
+
* This is the read half the composition root uses to build each slot's
|
|
110
|
+
* `UsageObservation.usage`, so the persisted file — not an in-memory guess — is
|
|
111
|
+
* what the sampler accounts against. An unknown slot answers `{}`, i.e. "no
|
|
112
|
+
* policy set", which is exactly what `defaultCellularPolicy` starts from.
|
|
113
|
+
*/
|
|
114
|
+
export function selectUsagePolicy(state, logicalSlotId) {
|
|
115
|
+
const slot = state.slots.find((entry) => entry.logicalSlotId === logicalSlotId);
|
|
116
|
+
if (slot === undefined) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...(slot.cycleDay !== undefined ? { cycleDay: slot.cycleDay } : {}),
|
|
121
|
+
...(slot.thresholdBytes !== undefined ? { thresholdBytes: slot.thresholdBytes } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function createUsagePolicyFileStore(options) {
|
|
125
|
+
const logger = options.logger ?? defaultLogger;
|
|
126
|
+
const { path } = options;
|
|
127
|
+
async function writeAtomic(state) {
|
|
128
|
+
await mkdir(dirname(path), { recursive: true });
|
|
129
|
+
const tmp = `${path}.tmp`;
|
|
130
|
+
await writeFile(tmp, JSON.stringify(state));
|
|
131
|
+
// chmod AFTER the write (not an open flag) so mode is 0600 regardless of umask.
|
|
132
|
+
await chmod(tmp, 0o600);
|
|
133
|
+
await rename(tmp, path);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
async load(nowMs) {
|
|
137
|
+
let text;
|
|
138
|
+
try {
|
|
139
|
+
text = await readFile(path, 'utf8');
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Absent (or unreadable) → start empty; the first save lays down a 0600 file.
|
|
143
|
+
return freshState(nowMs);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return validate(JSON.parse(text));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
logger({
|
|
150
|
+
kind: 'corrupt-policy',
|
|
151
|
+
bytes: Buffer.byteLength(text, 'utf8'),
|
|
152
|
+
reason: classifyFailure(error),
|
|
153
|
+
});
|
|
154
|
+
const fresh = freshState(nowMs);
|
|
155
|
+
await writeAtomic(fresh);
|
|
156
|
+
return fresh;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
save: writeAtomic,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { DesiredUsage } from '../../domain/index.js';
|
|
2
|
+
import { type UsagePolicyStore } from './policy-store.js';
|
|
3
|
+
/**
|
|
4
|
+
* The live-apply seam. `UsageSampler` implements it; a caller with no running
|
|
5
|
+
* sampler simply omits it and the write is persistence-only.
|
|
6
|
+
*/
|
|
7
|
+
export interface UsagePolicyTarget {
|
|
8
|
+
applyUsagePolicy(logicalSlotId: string, usage: DesiredUsage, atMs?: number): UsagePolicyApplication;
|
|
9
|
+
}
|
|
10
|
+
/** What a live apply did to the slot's accounting window. */
|
|
11
|
+
export interface UsagePolicyApplication {
|
|
12
|
+
/** The UTC start of the cycle the slot is now accruing into. */
|
|
13
|
+
readonly cycleStartMs: number;
|
|
14
|
+
/** True when the cycle ANCHOR moved, so the per-cycle total restarted at 0. */
|
|
15
|
+
readonly cycleReset: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface SetUsagePolicyDeps {
|
|
18
|
+
readonly store: UsagePolicyStore;
|
|
19
|
+
/** Optional live sampler to apply the change to immediately. */
|
|
20
|
+
readonly sampler?: UsagePolicyTarget;
|
|
21
|
+
/** Injectable clock (defaults to `Date.now`). */
|
|
22
|
+
readonly now?: () => number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The requested change.
|
|
26
|
+
*
|
|
27
|
+
* Tri-state per field, and the distinction is the whole point: `undefined`
|
|
28
|
+
* leaves the persisted value ALONE (so a caller changing only the threshold
|
|
29
|
+
* cannot silently drop a cycle day it never mentioned), while an explicit `null`
|
|
30
|
+
* CLEARS it. A caller that cannot express `null` can never unset a policy.
|
|
31
|
+
*/
|
|
32
|
+
export interface SetUsagePolicyRequest {
|
|
33
|
+
readonly logicalSlotId: string;
|
|
34
|
+
readonly cycleDay?: number | null;
|
|
35
|
+
readonly thresholdBytes?: number | null;
|
|
36
|
+
}
|
|
37
|
+
export type SetUsagePolicyRejection = 'invalid-slot-id' | 'invalid-cycle-day' | 'invalid-threshold-bytes';
|
|
38
|
+
export type SetUsagePolicyResult = {
|
|
39
|
+
readonly status: 'applied';
|
|
40
|
+
readonly logicalSlotId: string;
|
|
41
|
+
/** The policy now persisted for this slot (post-merge). */
|
|
42
|
+
readonly usage: DesiredUsage;
|
|
43
|
+
/** Present only when a live sampler was supplied. */
|
|
44
|
+
readonly applied?: UsagePolicyApplication;
|
|
45
|
+
} | {
|
|
46
|
+
readonly status: 'rejected';
|
|
47
|
+
readonly logicalSlotId: string;
|
|
48
|
+
readonly reason: SetUsagePolicyRejection;
|
|
49
|
+
} | {
|
|
50
|
+
readonly status: 'failed';
|
|
51
|
+
readonly logicalSlotId: string;
|
|
52
|
+
readonly reason: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Persist a slot's usage policy and, when a live sampler is supplied, apply it
|
|
56
|
+
* to that sampler in the same call.
|
|
57
|
+
*
|
|
58
|
+
* ORDER IS LOAD → VALIDATE → PERSIST → APPLY, and it is deliberate. The store is
|
|
59
|
+
* the source of truth (the composition root rebuilds every `UsageObservation`
|
|
60
|
+
* from it), so a live apply that landed while the write failed would leave the
|
|
61
|
+
* running process disagreeing with what a restart would restore.
|
|
62
|
+
*/
|
|
63
|
+
export declare function setUsagePolicy(deps: SetUsagePolicyDeps, request: SetUsagePolicyRequest): Promise<SetUsagePolicyResult>;
|
|
64
|
+
/** Read one slot's persisted policy. The read counterpart of `setUsagePolicy`. */
|
|
65
|
+
export declare function getUsagePolicy(deps: Pick<SetUsagePolicyDeps, 'store' | 'now'>, logicalSlotId: string): Promise<DesiredUsage>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// `setUsagePolicy` — the WRITE half of the data-usage surface.
|
|
2
|
+
//
|
|
3
|
+
// The read half already existed (`UsageSampler.snapshot()` reports `cycleBytes`,
|
|
4
|
+
// `thresholdBytes` and `thresholdExceeded`), but nothing could SET the two
|
|
5
|
+
// numbers those readings are computed against: `DesiredUsage` was a shape the
|
|
6
|
+
// planner echoed into a receipt, with no persistence and no apply path. This
|
|
7
|
+
// module closes that, mirroring the read side's file-store idiom exactly.
|
|
8
|
+
//
|
|
9
|
+
// It is a LOCAL write, not a modem write — see `policy-store.ts`'s header for the
|
|
10
|
+
// ModemManager API evidence. Nothing here touches D-Bus, `mmcli`, or any bearer.
|
|
11
|
+
//
|
|
12
|
+
// TYPED RESULTS, NEVER THROWS ON BAD INPUT. Following the `PowerHook` precedent
|
|
13
|
+
// (`power-contract.ts`: `applied` / `unsupported` / `failed`), an out-of-range
|
|
14
|
+
// day is a `rejected` result carrying a named reason rather than an exception —
|
|
15
|
+
// this is called from an RPC boundary where a throw becomes an opaque 500.
|
|
16
|
+
import { isValidCycleDay, isValidThresholdBytes, selectUsagePolicy, USAGE_POLICY_SCHEMA_VERSION, } from './policy-store.js';
|
|
17
|
+
function validateRequest(request) {
|
|
18
|
+
if (typeof request.logicalSlotId !== 'string' || request.logicalSlotId.length === 0) {
|
|
19
|
+
return 'invalid-slot-id';
|
|
20
|
+
}
|
|
21
|
+
if (request.cycleDay !== undefined &&
|
|
22
|
+
request.cycleDay !== null &&
|
|
23
|
+
!isValidCycleDay(request.cycleDay)) {
|
|
24
|
+
return 'invalid-cycle-day';
|
|
25
|
+
}
|
|
26
|
+
if (request.thresholdBytes !== undefined &&
|
|
27
|
+
request.thresholdBytes !== null &&
|
|
28
|
+
!isValidThresholdBytes(request.thresholdBytes)) {
|
|
29
|
+
return 'invalid-threshold-bytes';
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/** Fold the request onto the currently-persisted policy (tri-state merge). */
|
|
34
|
+
function mergePolicy(current, request) {
|
|
35
|
+
const cycleDay = request.cycleDay === undefined ? current.cycleDay : (request.cycleDay ?? undefined);
|
|
36
|
+
const thresholdBytes = request.thresholdBytes === undefined
|
|
37
|
+
? current.thresholdBytes
|
|
38
|
+
: (request.thresholdBytes ?? undefined);
|
|
39
|
+
return {
|
|
40
|
+
...(cycleDay !== undefined ? { cycleDay } : {}),
|
|
41
|
+
...(thresholdBytes !== undefined ? { thresholdBytes } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function toSlot(logicalSlotId, usage) {
|
|
45
|
+
return {
|
|
46
|
+
logicalSlotId,
|
|
47
|
+
...(usage.cycleDay !== undefined ? { cycleDay: usage.cycleDay } : {}),
|
|
48
|
+
...(usage.thresholdBytes !== undefined ? { thresholdBytes: usage.thresholdBytes } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Persist a slot's usage policy and, when a live sampler is supplied, apply it
|
|
53
|
+
* to that sampler in the same call.
|
|
54
|
+
*
|
|
55
|
+
* ORDER IS LOAD → VALIDATE → PERSIST → APPLY, and it is deliberate. The store is
|
|
56
|
+
* the source of truth (the composition root rebuilds every `UsageObservation`
|
|
57
|
+
* from it), so a live apply that landed while the write failed would leave the
|
|
58
|
+
* running process disagreeing with what a restart would restore.
|
|
59
|
+
*/
|
|
60
|
+
export async function setUsagePolicy(deps, request) {
|
|
61
|
+
const logicalSlotId = typeof request.logicalSlotId === 'string' ? request.logicalSlotId : '';
|
|
62
|
+
const rejection = validateRequest(request);
|
|
63
|
+
if (rejection !== undefined) {
|
|
64
|
+
return { status: 'rejected', logicalSlotId, reason: rejection };
|
|
65
|
+
}
|
|
66
|
+
const now = deps.now ?? Date.now;
|
|
67
|
+
const at = now();
|
|
68
|
+
let usage;
|
|
69
|
+
try {
|
|
70
|
+
const state = await deps.store.load(at);
|
|
71
|
+
usage = mergePolicy(selectUsagePolicy(state, logicalSlotId), request);
|
|
72
|
+
const others = state.slots.filter((slot) => slot.logicalSlotId !== logicalSlotId);
|
|
73
|
+
// An empty policy is REMOVED rather than stored as an empty row: "no policy"
|
|
74
|
+
// and "a policy that sets nothing" are the same fact, and keeping the row
|
|
75
|
+
// would grow the file by one entry per slot an operator ever cleared.
|
|
76
|
+
const slots = usage.cycleDay === undefined && usage.thresholdBytes === undefined
|
|
77
|
+
? others
|
|
78
|
+
: [...others, toSlot(logicalSlotId, usage)];
|
|
79
|
+
await deps.store.save({
|
|
80
|
+
schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
|
|
81
|
+
savedAtMs: at,
|
|
82
|
+
slots,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return {
|
|
87
|
+
status: 'failed',
|
|
88
|
+
logicalSlotId,
|
|
89
|
+
reason: error instanceof Error ? error.message : 'persist-failed',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (deps.sampler === undefined) {
|
|
93
|
+
return { status: 'applied', logicalSlotId, usage };
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const applied = deps.sampler.applyUsagePolicy(logicalSlotId, usage, at);
|
|
97
|
+
return { status: 'applied', logicalSlotId, usage, applied };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
status: 'failed',
|
|
102
|
+
logicalSlotId,
|
|
103
|
+
reason: error instanceof Error ? error.message : 'apply-failed',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Read one slot's persisted policy. The read counterpart of `setUsagePolicy`. */
|
|
108
|
+
export async function getUsagePolicy(deps, logicalSlotId) {
|
|
109
|
+
const now = deps.now ?? Date.now;
|
|
110
|
+
const state = await deps.store.load(now());
|
|
111
|
+
return selectUsagePolicy(state, logicalSlotId);
|
|
112
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** A source of cumulative rx+tx byte counters keyed by interface name. */
|
|
2
|
+
export interface CounterSource {
|
|
3
|
+
/** Read the current cumulative rx+tx byte total for every interface. */
|
|
4
|
+
read(): Promise<ReadonlyMap<string, number>>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Parse `/proc/net/dev` text into a map of interface name → cumulative rx+tx bytes.
|
|
8
|
+
* The two-line header is skipped; malformed or short rows are ignored rather than
|
|
9
|
+
* throwing, so a single odd line can never break sampling.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseProcNetDev(text: string): Map<string, number>;
|
|
12
|
+
/**
|
|
13
|
+
* The production counter source: reads and parses the real `/proc/net/dev`. The
|
|
14
|
+
* path is injectable so tests can point at a fixture, but the default is the live
|
|
15
|
+
* kernel file. A missing/unreadable file yields an empty map (fail-soft — a read
|
|
16
|
+
* error never crashes the sampler; that pass simply attributes no new bytes).
|
|
17
|
+
*/
|
|
18
|
+
export declare function procNetDevCounterSource(path?: string): CounterSource;
|
|
@@ -11,67 +11,59 @@
|
|
|
11
11
|
// accounting layer clamps). We parse the proc text ourselves rather than shelling
|
|
12
12
|
// out to `ip`/`ifconfig` because the raw file is the most reliable, consistent
|
|
13
13
|
// source and matches exactly what the plan names.
|
|
14
|
-
|
|
15
|
-
/** A source of cumulative rx+tx byte counters keyed by interface name. */
|
|
16
|
-
export interface CounterSource {
|
|
17
|
-
/** Read the current cumulative rx+tx byte total for every interface. */
|
|
18
|
-
read(): Promise<ReadonlyMap<string, number>>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
14
|
const HEADER_LINE_COUNT = 2;
|
|
22
15
|
const RX_BYTES_COLUMN = 0;
|
|
23
16
|
const TX_BYTES_COLUMN = 8;
|
|
24
17
|
const MIN_COLUMNS = TX_BYTES_COLUMN + 1;
|
|
25
|
-
|
|
26
18
|
/**
|
|
27
19
|
* Parse `/proc/net/dev` text into a map of interface name → cumulative rx+tx bytes.
|
|
28
20
|
* The two-line header is skipped; malformed or short rows are ignored rather than
|
|
29
21
|
* throwing, so a single odd line can never break sampling.
|
|
30
22
|
*/
|
|
31
|
-
export function parseProcNetDev(text
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
23
|
+
export function parseProcNetDev(text) {
|
|
24
|
+
const counters = new Map();
|
|
25
|
+
const lines = text.split('\n').slice(HEADER_LINE_COUNT);
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const colon = line.indexOf(':');
|
|
28
|
+
if (colon < 0) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const ifname = line.slice(0, colon).trim();
|
|
32
|
+
if (ifname.length === 0) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const columns = line
|
|
36
|
+
.slice(colon + 1)
|
|
37
|
+
.trim()
|
|
38
|
+
.split(/\s+/);
|
|
39
|
+
if (columns.length < MIN_COLUMNS) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const rx = Number(columns[RX_BYTES_COLUMN]);
|
|
43
|
+
const tx = Number(columns[TX_BYTES_COLUMN]);
|
|
44
|
+
if (!Number.isFinite(rx) || !Number.isFinite(tx) || rx < 0 || tx < 0) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
counters.set(ifname, rx + tx);
|
|
48
|
+
}
|
|
49
|
+
return counters;
|
|
58
50
|
}
|
|
59
|
-
|
|
60
51
|
/**
|
|
61
52
|
* The production counter source: reads and parses the real `/proc/net/dev`. The
|
|
62
53
|
* path is injectable so tests can point at a fixture, but the default is the live
|
|
63
54
|
* kernel file. A missing/unreadable file yields an empty map (fail-soft — a read
|
|
64
55
|
* error never crashes the sampler; that pass simply attributes no new bytes).
|
|
65
56
|
*/
|
|
66
|
-
export function procNetDevCounterSource(path = '/proc/net/dev')
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
57
|
+
export function procNetDevCounterSource(path = '/proc/net/dev') {
|
|
58
|
+
return {
|
|
59
|
+
async read() {
|
|
60
|
+
try {
|
|
61
|
+
const text = await Bun.file(path).text();
|
|
62
|
+
return parseProcNetDev(text);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return new Map();
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
77
69
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type DesiredUsage, type LogicalSlotId } from '../../domain/index.js';
|
|
2
|
+
import type { CounterSource } from './proc-net-dev.js';
|
|
3
|
+
import type { UsageStore } from './store.js';
|
|
4
|
+
/** One slot's observation for a sampling pass — identity + mapping + local policy. */
|
|
5
|
+
export interface UsageObservation {
|
|
6
|
+
readonly logicalSlotId: LogicalSlotId;
|
|
7
|
+
/** Bumped by A4.2 whenever the ifname-to-slot mapping changes (→ zero-delta rebase). */
|
|
8
|
+
readonly mappingGeneration: number;
|
|
9
|
+
readonly ifname: string;
|
|
10
|
+
/** A3.2 identity-ladder confidence; `low` pauses sampling for this slot. */
|
|
11
|
+
readonly confidence: 'high' | 'medium' | 'low';
|
|
12
|
+
/** Local-controller-owned usage policy (cycle day + advisory threshold). */
|
|
13
|
+
readonly usage: DesiredUsage;
|
|
14
|
+
}
|
|
15
|
+
/** A queryable per-slot usage figure — consumed by the A6.1 bench CLI `usage` command. */
|
|
16
|
+
export interface SlotUsageSnapshot {
|
|
17
|
+
readonly logicalSlotId: string;
|
|
18
|
+
readonly cycleBytes: number;
|
|
19
|
+
readonly cycleStartMs: number;
|
|
20
|
+
readonly paused: boolean;
|
|
21
|
+
/** The cycle day in force for this slot, when the operator set one. */
|
|
22
|
+
readonly cycleDay?: number;
|
|
23
|
+
readonly thresholdBytes?: number;
|
|
24
|
+
/** Advisory-only: `cycleBytes > thresholdBytes`. Never gates the connection. */
|
|
25
|
+
readonly thresholdExceeded: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** The sampler's current state, per slot, at a point in time. */
|
|
28
|
+
export interface UsageSnapshot {
|
|
29
|
+
readonly bootId: string;
|
|
30
|
+
readonly generatedAtMs: number;
|
|
31
|
+
readonly slots: readonly SlotUsageSnapshot[];
|
|
32
|
+
}
|
|
33
|
+
export interface UsageSamplerOptions {
|
|
34
|
+
/** The kernel boot id (see `readBootId`) — the session identity. */
|
|
35
|
+
readonly bootId: string;
|
|
36
|
+
readonly source: CounterSource;
|
|
37
|
+
readonly store: UsageStore;
|
|
38
|
+
/** Injectable clock (defaults to `Date.now`). */
|
|
39
|
+
readonly now?: () => number;
|
|
40
|
+
/** Minimum spacing between persists (default 60_000 ms = the ≤1-min loss bound). */
|
|
41
|
+
readonly persistIntervalMs?: number;
|
|
42
|
+
/** Cycle day used when a slot's policy omits `cycleDay` (default 1 = 1st of month). */
|
|
43
|
+
readonly defaultCycleDay?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare class UsageSampler {
|
|
46
|
+
#private;
|
|
47
|
+
private constructor();
|
|
48
|
+
/** Load persisted state (recreating a fresh file if absent/corrupt) then build the sampler. */
|
|
49
|
+
static create(options: UsageSamplerOptions): Promise<UsageSampler>;
|
|
50
|
+
/** Take one sampling pass over the current counters for the given observations. */
|
|
51
|
+
sample(observations: readonly UsageObservation[]): Promise<void>;
|
|
52
|
+
/** Current per-slot usage — the queryable snapshot the CLI and platform read. */
|
|
53
|
+
snapshot(): UsageSnapshot;
|
|
54
|
+
/**
|
|
55
|
+
* Apply an operator's usage policy to this slot immediately, without waiting
|
|
56
|
+
* for the next sampling pass.
|
|
57
|
+
*
|
|
58
|
+
* A CHANGED CYCLE ANCHOR RESTARTS THE WINDOW AT ZERO, and keeps the counter
|
|
59
|
+
* BASELINE. Those two halves are the honest answer to a question with no
|
|
60
|
+
* truthful one: bytes already accrued were measured under the OLD window, so
|
|
61
|
+
* carrying them into the new one over-reports it, and there is no record of
|
|
62
|
+
* how they were distributed within it. Starting fresh states plainly that the
|
|
63
|
+
* new window began now; keeping `lastObserved` means the next sample still
|
|
64
|
+
* attributes only genuinely new bytes, never a jump. A threshold-only change
|
|
65
|
+
* moves no anchor and therefore resets nothing.
|
|
66
|
+
*/
|
|
67
|
+
applyUsagePolicy(logicalSlotId: string, usage: DesiredUsage, atMs?: number): {
|
|
68
|
+
cycleStartMs: number;
|
|
69
|
+
cycleReset: boolean;
|
|
70
|
+
};
|
|
71
|
+
/** Flush unpersisted state immediately — the shutdown hook (bounds loss to ≤1 min). */
|
|
72
|
+
flush(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
/** Load persisted state and build a ready sampler. See `UsageSampler.create`. */
|
|
75
|
+
export declare function createUsageSampler(options: UsageSamplerOptions): Promise<UsageSampler>;
|