@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
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// The adversarial accounting matrix — pure, deterministic proofs that per-slot usage
|
|
2
|
-
// is attributed correctly across remaps, swaps, reuse, resets, pauses, and rollovers.
|
|
3
|
-
|
|
4
|
-
import { describe, expect, test } from 'bun:test';
|
|
5
|
-
import { type IdentityConfidence, logicalSlotId } from '../../domain';
|
|
6
|
-
import { applySample, type BaselineKey, type SlotAccount } from './accounting';
|
|
7
|
-
|
|
8
|
-
const SLOT_A = logicalSlotId('slot-a');
|
|
9
|
-
const SLOT_B = logicalSlotId('slot-b');
|
|
10
|
-
const SLOT_C = logicalSlotId('slot-c');
|
|
11
|
-
const BOOT = 'boot-uuid-1';
|
|
12
|
-
|
|
13
|
-
interface Step {
|
|
14
|
-
readonly slot: string;
|
|
15
|
-
readonly gen: number;
|
|
16
|
-
readonly ifname: string;
|
|
17
|
-
readonly current: number;
|
|
18
|
-
readonly confidence?: IdentityConfidence;
|
|
19
|
-
readonly cycleStartMs?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function key(slot: string, gen: number, ifname: string): BaselineKey {
|
|
23
|
-
return { logicalSlotId: slot, mappingGeneration: gen, ifname, bootId: BOOT };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function step(prior: SlotAccount | undefined, s: Step): SlotAccount {
|
|
27
|
-
return applySample(prior, {
|
|
28
|
-
key: key(s.slot, s.gen, s.ifname),
|
|
29
|
-
current: s.current,
|
|
30
|
-
confidence: s.confidence ?? 'high',
|
|
31
|
-
cycleStartMs: s.cycleStartMs ?? 1000,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe('applySample — two modems tracked independently', () => {
|
|
36
|
-
test('separate slots never cross-contaminate', () => {
|
|
37
|
-
let a = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
|
|
38
|
-
let b = step(undefined, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 5000 });
|
|
39
|
-
a = step(a, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 300 });
|
|
40
|
-
b = step(b, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 5001 });
|
|
41
|
-
expect(a.cycleBytes).toBe(200);
|
|
42
|
-
expect(b.cycleBytes).toBe(1);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('applySample — A/B ifname swap follows the slot, not the interface', () => {
|
|
47
|
-
test('each slot keeps its own total across a swap and re-baselines zero-delta', () => {
|
|
48
|
-
// Prime: A on wwan0, B on wwan1.
|
|
49
|
-
let a = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
|
|
50
|
-
let b = step(undefined, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 1000 });
|
|
51
|
-
a = step(a, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 200 });
|
|
52
|
-
b = step(b, { slot: SLOT_B, gen: 0, ifname: 'wwan1', current: 1100 });
|
|
53
|
-
expect(a.cycleBytes).toBe(100);
|
|
54
|
-
expect(b.cycleBytes).toBe(100);
|
|
55
|
-
|
|
56
|
-
// Swap: A now maps to wwan1, B to wwan0 (mapping generation bumps for both).
|
|
57
|
-
// First post-swap sample must be zero-delta despite the large counter change.
|
|
58
|
-
a = step(a, { slot: SLOT_A, gen: 1, ifname: 'wwan1', current: 1100 });
|
|
59
|
-
b = step(b, { slot: SLOT_B, gen: 1, ifname: 'wwan0', current: 200 });
|
|
60
|
-
expect(a.cycleBytes).toBe(100);
|
|
61
|
-
expect(b.cycleBytes).toBe(100);
|
|
62
|
-
|
|
63
|
-
// Subsequent growth attributes to the correct slot on its new interface.
|
|
64
|
-
a = step(a, { slot: SLOT_A, gen: 1, ifname: 'wwan1', current: 1150 });
|
|
65
|
-
b = step(b, { slot: SLOT_B, gen: 1, ifname: 'wwan0', current: 260 });
|
|
66
|
-
expect(a.cycleBytes).toBe(150);
|
|
67
|
-
expect(b.cycleBytes).toBe(160);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('applySample — old ifname reused by a different slot (no bleed)', () => {
|
|
72
|
-
test('a new slot inheriting an old interface starts from zero', () => {
|
|
73
|
-
let a = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
|
|
74
|
-
a = step(a, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 500 });
|
|
75
|
-
expect(a.cycleBytes).toBe(400);
|
|
76
|
-
|
|
77
|
-
// Slot C now occupies wwan0. Its own account starts fresh (zero-delta baseline).
|
|
78
|
-
let c = step(undefined, { slot: SLOT_C, gen: 0, ifname: 'wwan0', current: 500 });
|
|
79
|
-
c = step(c, { slot: SLOT_C, gen: 0, ifname: 'wwan0', current: 700 });
|
|
80
|
-
expect(c.cycleBytes).toBe(200);
|
|
81
|
-
// A's total is untouched — no bleed either direction.
|
|
82
|
-
expect(a.cycleBytes).toBe(400);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('applySample — rename-first-sample is zero-delta', () => {
|
|
87
|
-
test('the first sample after a remap never reports a spurious jump', () => {
|
|
88
|
-
let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
|
|
89
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 200 });
|
|
90
|
-
expect(s.cycleBytes).toBe(100);
|
|
91
|
-
// Rename wwan0 → wwan5 with a huge counter: must NOT attribute 9M.
|
|
92
|
-
s = step(s, { slot: SLOT_A, gen: 1, ifname: 'wwan5', current: 9_000_000 });
|
|
93
|
-
expect(s.cycleBytes).toBe(100);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('applySample — reset-then-positive', () => {
|
|
98
|
-
test('a counter reset clamps to zero, then normal increases resume', () => {
|
|
99
|
-
let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 1000 });
|
|
100
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 1500 });
|
|
101
|
-
expect(s.cycleBytes).toBe(500);
|
|
102
|
-
// Interface recreated: counter drops to 50 (decrease) → clamp + rebase.
|
|
103
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 50 });
|
|
104
|
-
expect(s.cycleBytes).toBe(500);
|
|
105
|
-
// Growth from the rebased baseline is tracked again.
|
|
106
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 250 });
|
|
107
|
-
expect(s.cycleBytes).toBe(700);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('a decrease never produces a negative delta', () => {
|
|
111
|
-
let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 800 });
|
|
112
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 300 });
|
|
113
|
-
expect(s.cycleBytes).toBe(0);
|
|
114
|
-
expect(s.cycleBytes).toBeGreaterThanOrEqual(0);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('applySample — ambiguous identity pauses sampling', () => {
|
|
119
|
-
test('low confidence attributes nothing and re-baselines zero-delta on resume', () => {
|
|
120
|
-
let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
|
|
121
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 300 });
|
|
122
|
-
expect(s.cycleBytes).toBe(200);
|
|
123
|
-
// Identity turns ambiguous → paused; the counter keeps climbing unattributed.
|
|
124
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 9999, confidence: 'low' });
|
|
125
|
-
expect(s.paused).toBe(true);
|
|
126
|
-
expect(s.cycleBytes).toBe(200);
|
|
127
|
-
// Confidence returns: resume is zero-delta, so the paused-window bytes are NOT
|
|
128
|
-
// back-attributed; only growth after resume counts.
|
|
129
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 9999 });
|
|
130
|
-
expect(s.paused).toBe(false);
|
|
131
|
-
expect(s.cycleBytes).toBe(200);
|
|
132
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 10_099 });
|
|
133
|
-
expect(s.cycleBytes).toBe(300);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('applySample — cycle rollover resets the per-cycle total', () => {
|
|
138
|
-
test('crossing into a newer cycle zeroes cycleBytes but keeps the baseline', () => {
|
|
139
|
-
let s = step(undefined, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 100 });
|
|
140
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 600 });
|
|
141
|
-
expect(s.cycleBytes).toBe(500);
|
|
142
|
-
// New cycle boundary — cycleBytes resets, counter continuity preserved.
|
|
143
|
-
s = step(s, { slot: SLOT_A, gen: 0, ifname: 'wwan0', current: 650, cycleStartMs: 2000 });
|
|
144
|
-
expect(s.cycleStartMs).toBe(2000);
|
|
145
|
-
expect(s.cycleBytes).toBe(50);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// The pure usage-accounting reducer — the heart of the sampler.
|
|
2
|
-
//
|
|
3
|
-
// For each logical slot we keep a per-cycle byte total plus a BASELINE: the last
|
|
4
|
-
// observed cumulative counter value under a specific key. The key is the composite
|
|
5
|
-
// `{logicalSlotId, mappingGeneration, ifname, bootId}`. The rules, all encoded here
|
|
6
|
-
// with zero I/O so they can be exhaustively unit-tested:
|
|
7
|
-
//
|
|
8
|
-
// - REMAP (any key field changes — e.g. the ifname-to-slot mapping bumped
|
|
9
|
-
// `mappingGeneration`, or a reboot changed `bootId`): rebaseline with a
|
|
10
|
-
// ZERO-DELTA sample. We start counting fresh from the current cumulative value
|
|
11
|
-
// and attribute nothing for that first sample — never a spurious jump.
|
|
12
|
-
// - SAME-KEY DECREASE (the cumulative counter went DOWN — interface re-created /
|
|
13
|
-
// counter reset without a reported remap): CLAMP the negative delta to zero and
|
|
14
|
-
// REBASE the baseline to the new lower value. Never report negative usage.
|
|
15
|
-
// - LOW CONFIDENCE (ambiguous identity, A3.2 ladder): sampling is PAUSED. We drop
|
|
16
|
-
// the baseline and attribute nothing; on resume the next sample is zero-delta so
|
|
17
|
-
// bytes moved during the ambiguous window are never mis-attributed.
|
|
18
|
-
// - CYCLE ROLLOVER (`now` crossed into a new billing cycle): the per-cycle total
|
|
19
|
-
// resets to zero. The baseline is kept — the kernel counter is continuous across
|
|
20
|
-
// a billing boundary; only OUR accounting window resets.
|
|
21
|
-
|
|
22
|
-
import type { IdentityConfidence, LogicalSlotId } from '../../domain';
|
|
23
|
-
|
|
24
|
-
/** The composite baseline key. A change in ANY field is a remap (zero-delta rebase). */
|
|
25
|
-
export interface BaselineKey {
|
|
26
|
-
readonly logicalSlotId: string;
|
|
27
|
-
readonly mappingGeneration: number;
|
|
28
|
-
readonly ifname: string;
|
|
29
|
-
readonly bootId: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Per-slot accounting state. `key`/`lastObserved` are unset before the first sample. */
|
|
33
|
-
export interface SlotAccount {
|
|
34
|
-
/** Bytes attributed to this slot within the current cycle. */
|
|
35
|
-
readonly cycleBytes: number;
|
|
36
|
-
/** UTC start of the cycle `cycleBytes` is accruing into. */
|
|
37
|
-
readonly cycleStartMs: number;
|
|
38
|
-
/** True when the last observation was low-confidence and sampling is paused. */
|
|
39
|
-
readonly paused: boolean;
|
|
40
|
-
/** The key the baseline was captured under (undefined = no baseline yet). */
|
|
41
|
-
readonly key?: BaselineKey;
|
|
42
|
-
/** The cumulative counter value at the last accepted sample. */
|
|
43
|
-
readonly lastObserved?: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** One observation fed to the reducer for a single slot in one sampling pass. */
|
|
47
|
-
export interface SampleInput {
|
|
48
|
-
readonly key: BaselineKey;
|
|
49
|
-
/** The current cumulative rx+tx counter for the slot's interface. */
|
|
50
|
-
readonly current: number;
|
|
51
|
-
readonly confidence: IdentityConfidence;
|
|
52
|
-
/** The UTC start of the slot's active cycle (from `cycleStart`). */
|
|
53
|
-
readonly cycleStartMs: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** A fresh account for a slot first seen in `cycleStartMs`. */
|
|
57
|
-
export function initialAccount(cycleStartMs: number): SlotAccount {
|
|
58
|
-
return { cycleBytes: 0, cycleStartMs, paused: false };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function sameKey(a: BaselineKey, b: BaselineKey): boolean {
|
|
62
|
-
return (
|
|
63
|
-
a.logicalSlotId === b.logicalSlotId &&
|
|
64
|
-
a.mappingGeneration === b.mappingGeneration &&
|
|
65
|
-
a.ifname === b.ifname &&
|
|
66
|
-
a.bootId === b.bootId
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Apply a cycle rollover: if we crossed into a newer cycle, zero the per-cycle total. */
|
|
71
|
-
function rollCycle(account: SlotAccount, cycleStartMs: number): SlotAccount {
|
|
72
|
-
if (cycleStartMs > account.cycleStartMs) {
|
|
73
|
-
return { ...account, cycleBytes: 0, cycleStartMs };
|
|
74
|
-
}
|
|
75
|
-
return account;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Fold one observation into a slot's account, returning the NEXT account. Pure —
|
|
80
|
-
* no clock, no I/O; the caller supplies `current`, `confidence` and `cycleStartMs`.
|
|
81
|
-
*/
|
|
82
|
-
export function applySample(prior: SlotAccount | undefined, input: SampleInput): SlotAccount {
|
|
83
|
-
const base = rollCycle(prior ?? initialAccount(input.cycleStartMs), input.cycleStartMs);
|
|
84
|
-
|
|
85
|
-
// Ambiguous identity → pause: attribute nothing and drop the baseline so the
|
|
86
|
-
// next confident sample re-baselines zero-delta (no back-attribution).
|
|
87
|
-
if (input.confidence === 'low') {
|
|
88
|
-
return { cycleBytes: base.cycleBytes, cycleStartMs: base.cycleStartMs, paused: true };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Remap, first-ever sample, or resuming from a pause → zero-delta rebaseline.
|
|
92
|
-
if (base.paused || base.key === undefined || base.lastObserved === undefined) {
|
|
93
|
-
return { ...base, paused: false, key: input.key, lastObserved: input.current };
|
|
94
|
-
}
|
|
95
|
-
if (!sameKey(base.key, input.key)) {
|
|
96
|
-
return { ...base, paused: false, key: input.key, lastObserved: input.current };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Same key: a decrease is a counter reset → clamp the negative delta and rebase.
|
|
100
|
-
if (input.current < base.lastObserved) {
|
|
101
|
-
return { ...base, paused: false, key: input.key, lastObserved: input.current };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Normal case: attribute the non-negative delta and advance the baseline.
|
|
105
|
-
const delta = input.current - base.lastObserved;
|
|
106
|
-
return {
|
|
107
|
-
...base,
|
|
108
|
-
paused: false,
|
|
109
|
-
key: input.key,
|
|
110
|
-
lastObserved: input.current,
|
|
111
|
-
cycleBytes: base.cycleBytes + delta,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Convenience constructor for a `BaselineKey` from its parts. */
|
|
116
|
-
export function baselineKey(
|
|
117
|
-
logicalSlotId: LogicalSlotId,
|
|
118
|
-
mappingGeneration: number,
|
|
119
|
-
ifname: string,
|
|
120
|
-
bootId: string,
|
|
121
|
-
): BaselineKey {
|
|
122
|
-
return { logicalSlotId, mappingGeneration, ifname, bootId };
|
|
123
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// UTC billing-cycle math — month-length clamping (cycleDay 31 → Feb 28/29) and the
|
|
2
|
-
// before/after-boundary cycle-start selection.
|
|
3
|
-
|
|
4
|
-
import { describe, expect, test } from 'bun:test';
|
|
5
|
-
import { type EpochMillis, epochMillis } from '../../domain';
|
|
6
|
-
import { clampCycleDay, cycleStart, daysInMonth } from './billing-cycle';
|
|
7
|
-
|
|
8
|
-
const utc = (y: number, m: number, d: number, h = 0): EpochMillis =>
|
|
9
|
-
epochMillis(Date.UTC(y, m, d, h));
|
|
10
|
-
|
|
11
|
-
describe('daysInMonth', () => {
|
|
12
|
-
test('February is 28 days in a common year, 29 in a leap year', () => {
|
|
13
|
-
expect(daysInMonth(2023, 1)).toBe(28);
|
|
14
|
-
expect(daysInMonth(2024, 1)).toBe(29); // 2024 is a leap year
|
|
15
|
-
expect(daysInMonth(2100, 1)).toBe(28); // century non-leap
|
|
16
|
-
expect(daysInMonth(2000, 1)).toBe(29); // 400-divisible leap
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('30- and 31-day months', () => {
|
|
20
|
-
expect(daysInMonth(2024, 3)).toBe(30); // April
|
|
21
|
-
expect(daysInMonth(2024, 0)).toBe(31); // January
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('clampCycleDay', () => {
|
|
26
|
-
test('cycleDay 31 clamps to the last day of a short month', () => {
|
|
27
|
-
expect(clampCycleDay(31, 2023, 1)).toBe(28); // Feb non-leap
|
|
28
|
-
expect(clampCycleDay(31, 2024, 1)).toBe(29); // Feb leap
|
|
29
|
-
expect(clampCycleDay(31, 2024, 3)).toBe(30); // April
|
|
30
|
-
expect(clampCycleDay(31, 2024, 0)).toBe(31); // January — no clamp
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('a valid in-range day is unchanged; a floor of 1 is enforced', () => {
|
|
34
|
-
expect(clampCycleDay(15, 2024, 5)).toBe(15);
|
|
35
|
-
expect(clampCycleDay(0, 2024, 5)).toBe(1);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('cycleStart — month-length clamp with before/after selection', () => {
|
|
40
|
-
test('cycleDay 31 in February resolves to the clamped Feb boundary', () => {
|
|
41
|
-
// 2023-02-15 with cycleDay 31 → cycle started 2023-01-31 (Feb boundary Feb 28
|
|
42
|
-
// is still ahead of the 15th).
|
|
43
|
-
expect(cycleStart(epochMillis(utc(2023, 1, 15)), 31)).toBe(utc(2023, 0, 31));
|
|
44
|
-
// 2023-03-01 with cycleDay 31 → the active cycle began at clamped Feb 28.
|
|
45
|
-
expect(cycleStart(epochMillis(utc(2023, 2, 1)), 31)).toBe(utc(2023, 1, 28));
|
|
46
|
-
// Leap year: 2024-03-01 → Feb 29.
|
|
47
|
-
expect(cycleStart(epochMillis(utc(2024, 2, 1)), 31)).toBe(utc(2024, 1, 29));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('now exactly on the boundary starts a new cycle', () => {
|
|
51
|
-
expect(cycleStart(epochMillis(utc(2024, 5, 10)), 10)).toBe(utc(2024, 5, 10));
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('now before this month boundary rolls back to the previous month', () => {
|
|
55
|
-
// cycleDay 20, now is the 5th → cycle began on the 20th of the prior month.
|
|
56
|
-
expect(cycleStart(epochMillis(utc(2024, 6, 5)), 20)).toBe(utc(2024, 5, 20));
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('January rollback wraps to December of the prior year', () => {
|
|
60
|
-
expect(cycleStart(epochMillis(utc(2024, 0, 5)), 20)).toBe(utc(2023, 11, 20));
|
|
61
|
-
});
|
|
62
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// UTC billing-cycle computation with month-length clamping.
|
|
2
|
-
//
|
|
3
|
-
// A usage cycle resets on a configured day of the month (`cycleDay`, 1–31) in UTC.
|
|
4
|
-
// Months are not all the same length, so a `cycleDay` past the end of a short month
|
|
5
|
-
// must clamp to that month's LAST day rather than rolling into the next month or
|
|
6
|
-
// throwing: `cycleDay: 31` resolves to Feb 28 (or Feb 29 in a leap year), Apr 30,
|
|
7
|
-
// etc. All arithmetic is UTC — the device clock's local zone never shifts a cycle.
|
|
8
|
-
|
|
9
|
-
import type { EpochMillis } from '../../domain';
|
|
10
|
-
|
|
11
|
-
/** Days in a given UTC month. `month` is 0-based (0 = January). Handles leap Feb. */
|
|
12
|
-
export function daysInMonth(year: number, month: number): number {
|
|
13
|
-
// Day 0 of the next month is the last day of this month; UTC avoids DST drift.
|
|
14
|
-
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Clamp a requested cycle day to the last valid day of the given month. */
|
|
18
|
-
export function clampCycleDay(cycleDay: number, year: number, month: number): number {
|
|
19
|
-
const floor = Math.max(1, Math.trunc(cycleDay));
|
|
20
|
-
return Math.min(floor, daysInMonth(year, month));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** The UTC epoch-ms of the cycle boundary in a specific month (clamped). */
|
|
24
|
-
function boundaryFor(cycleDay: number, year: number, month: number): number {
|
|
25
|
-
return Date.UTC(year, month, clampCycleDay(cycleDay, year, month));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The UTC start (`EpochMillis`) of the cycle that `now` falls in for a given
|
|
30
|
-
* `cycleDay`. If `now` is before this month's (clamped) boundary the active cycle
|
|
31
|
-
* began on the previous month's boundary; otherwise it began on this month's.
|
|
32
|
-
*/
|
|
33
|
-
export function cycleStart(now: EpochMillis, cycleDay: number): EpochMillis {
|
|
34
|
-
const at = new Date(now);
|
|
35
|
-
const year = at.getUTCFullYear();
|
|
36
|
-
const month = at.getUTCMonth();
|
|
37
|
-
const thisBoundary = boundaryFor(cycleDay, year, month);
|
|
38
|
-
if (now >= thisBoundary) {
|
|
39
|
-
return thisBoundary as EpochMillis;
|
|
40
|
-
}
|
|
41
|
-
// Roll back one month (December wraps to the prior year), re-clamping there.
|
|
42
|
-
const prevMonth = month === 0 ? 11 : month - 1;
|
|
43
|
-
const prevYear = month === 0 ? year - 1 : year;
|
|
44
|
-
return boundaryFor(cycleDay, prevYear, prevMonth) as EpochMillis;
|
|
45
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// Data-usage sampler — internal all-interface sampler over `/proc/net/dev`
|
|
2
|
-
// CUMULATIVE counters (A4.3). Public surface consumed by the A6.1 bench CLI `usage`
|
|
3
|
-
// command (via `UsageSnapshot`) and the composition root that wires the sampler.
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
applySample,
|
|
7
|
-
type BaselineKey,
|
|
8
|
-
baselineKey,
|
|
9
|
-
initialAccount,
|
|
10
|
-
type SampleInput,
|
|
11
|
-
type SlotAccount,
|
|
12
|
-
} from './accounting';
|
|
13
|
-
export { clampCycleDay, cycleStart, daysInMonth } from './billing-cycle';
|
|
14
|
-
export { readBootId } from './boot-id';
|
|
15
|
-
export {
|
|
16
|
-
createUsagePolicyFileStore,
|
|
17
|
-
isValidCycleDay,
|
|
18
|
-
isValidThresholdBytes,
|
|
19
|
-
type PersistedUsagePolicy,
|
|
20
|
-
type PersistedUsagePolicySlot,
|
|
21
|
-
selectUsagePolicy,
|
|
22
|
-
USAGE_POLICY_SCHEMA_VERSION,
|
|
23
|
-
type UsagePolicyFileStoreOptions,
|
|
24
|
-
type UsagePolicyLogEvent,
|
|
25
|
-
type UsagePolicyLogger,
|
|
26
|
-
type UsagePolicyStore,
|
|
27
|
-
} from './policy-store';
|
|
28
|
-
export {
|
|
29
|
-
getUsagePolicy,
|
|
30
|
-
type SetUsagePolicyDeps,
|
|
31
|
-
type SetUsagePolicyRejection,
|
|
32
|
-
type SetUsagePolicyRequest,
|
|
33
|
-
type SetUsagePolicyResult,
|
|
34
|
-
setUsagePolicy,
|
|
35
|
-
type UsagePolicyApplication,
|
|
36
|
-
type UsagePolicyTarget,
|
|
37
|
-
} from './policy-write';
|
|
38
|
-
export {
|
|
39
|
-
type CounterSource,
|
|
40
|
-
parseProcNetDev,
|
|
41
|
-
procNetDevCounterSource,
|
|
42
|
-
} from './proc-net-dev';
|
|
43
|
-
export {
|
|
44
|
-
createUsageSampler,
|
|
45
|
-
type SlotUsageSnapshot,
|
|
46
|
-
type UsageObservation,
|
|
47
|
-
UsageSampler,
|
|
48
|
-
type UsageSamplerOptions,
|
|
49
|
-
type UsageSnapshot,
|
|
50
|
-
} from './sampler';
|
|
51
|
-
export {
|
|
52
|
-
createUsageFileStore,
|
|
53
|
-
type PersistedSlot,
|
|
54
|
-
type PersistedUsage,
|
|
55
|
-
USAGE_SCHEMA_VERSION,
|
|
56
|
-
type UsageFileStoreOptions,
|
|
57
|
-
type UsageLogEvent,
|
|
58
|
-
type UsageLogger,
|
|
59
|
-
type UsageStore,
|
|
60
|
-
} from './store';
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
// Usage-policy persistence contract — mode 0600 (real fs.stat), fail-soft
|
|
2
|
-
// corruption recovery with METADATA-ONLY logging, and the per-slot selector.
|
|
3
|
-
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
5
|
-
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import {
|
|
9
|
-
createUsagePolicyFileStore,
|
|
10
|
-
isValidCycleDay,
|
|
11
|
-
isValidThresholdBytes,
|
|
12
|
-
type PersistedUsagePolicy,
|
|
13
|
-
selectUsagePolicy,
|
|
14
|
-
USAGE_POLICY_SCHEMA_VERSION,
|
|
15
|
-
type UsagePolicyLogEvent,
|
|
16
|
-
} from './policy-store';
|
|
17
|
-
|
|
18
|
-
let dir: string;
|
|
19
|
-
let path: string;
|
|
20
|
-
|
|
21
|
-
beforeEach(async () => {
|
|
22
|
-
dir = await mkdtemp(join(tmpdir(), 'usage-policy-'));
|
|
23
|
-
path = join(dir, 'policy.json');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(async () => {
|
|
27
|
-
await rm(dir, { recursive: true, force: true });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const sample: PersistedUsagePolicy = {
|
|
31
|
-
schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
|
|
32
|
-
savedAtMs: 1_700_000_000_000,
|
|
33
|
-
slots: [
|
|
34
|
-
{ logicalSlotId: 'slot-a', cycleDay: 15, thresholdBytes: 5_000_000_000 },
|
|
35
|
-
{ logicalSlotId: 'slot-b', cycleDay: 1 },
|
|
36
|
-
{ logicalSlotId: 'slot-c', thresholdBytes: 0 },
|
|
37
|
-
],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
describe('UsagePolicyStore — versioned round-trip', () => {
|
|
41
|
-
test('save then load returns identical, schema-versioned state', async () => {
|
|
42
|
-
const store = createUsagePolicyFileStore({ path });
|
|
43
|
-
await store.save(sample);
|
|
44
|
-
expect(await store.load(1)).toEqual(sample);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('an absent file loads as a fresh empty document and writes nothing', async () => {
|
|
48
|
-
const store = createUsagePolicyFileStore({ path });
|
|
49
|
-
const loaded = await store.load(4242);
|
|
50
|
-
expect(loaded).toEqual({
|
|
51
|
-
schemaVersion: USAGE_POLICY_SCHEMA_VERSION,
|
|
52
|
-
savedAtMs: 4242,
|
|
53
|
-
slots: [],
|
|
54
|
-
});
|
|
55
|
-
expect(readFile(path, 'utf8')).rejects.toThrow();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('the written file is mode 0600 regardless of umask', async () => {
|
|
59
|
-
const store = createUsagePolicyFileStore({ path });
|
|
60
|
-
await store.save(sample);
|
|
61
|
-
expect((await stat(path)).mode & 0o777).toBe(0o600);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('UsagePolicyStore — fail-soft corruption', () => {
|
|
66
|
-
test('invalid JSON is replaced by a fresh 0600 file and logged as METADATA ONLY', async () => {
|
|
67
|
-
const events: UsagePolicyLogEvent[] = [];
|
|
68
|
-
await writeFile(path, '{"schemaVersion":1,"slots":');
|
|
69
|
-
const store = createUsagePolicyFileStore({ path, logger: (e) => events.push(e) });
|
|
70
|
-
|
|
71
|
-
const loaded = await store.load(7);
|
|
72
|
-
|
|
73
|
-
expect(loaded.slots).toEqual([]);
|
|
74
|
-
expect(events).toHaveLength(1);
|
|
75
|
-
expect(events[0]?.kind).toBe('corrupt-policy');
|
|
76
|
-
expect(events[0]?.reason).toContain('invalid-json');
|
|
77
|
-
expect((await stat(path)).mode & 0o777).toBe(0o600);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test.each([
|
|
81
|
-
[
|
|
82
|
-
'schemaVersion',
|
|
83
|
-
JSON.stringify({ ...sample, schemaVersion: 99 }),
|
|
84
|
-
'schema-mismatch: schemaVersion',
|
|
85
|
-
],
|
|
86
|
-
['savedAtMs', JSON.stringify({ ...sample, savedAtMs: 'soon' }), 'schema-mismatch: savedAtMs'],
|
|
87
|
-
['slots', JSON.stringify({ ...sample, slots: {} }), 'schema-mismatch: slots'],
|
|
88
|
-
[
|
|
89
|
-
'logicalSlotId',
|
|
90
|
-
JSON.stringify({ ...sample, slots: [{ cycleDay: 3 }] }),
|
|
91
|
-
'schema-mismatch: logicalSlotId',
|
|
92
|
-
],
|
|
93
|
-
[
|
|
94
|
-
'cycleDay',
|
|
95
|
-
JSON.stringify({ ...sample, slots: [{ logicalSlotId: 'a', cycleDay: 32 }] }),
|
|
96
|
-
'schema-mismatch: cycleDay',
|
|
97
|
-
],
|
|
98
|
-
[
|
|
99
|
-
'thresholdBytes',
|
|
100
|
-
JSON.stringify({ ...sample, slots: [{ logicalSlotId: 'a', thresholdBytes: -1 }] }),
|
|
101
|
-
'schema-mismatch: thresholdBytes',
|
|
102
|
-
],
|
|
103
|
-
])('a bad %s is rejected by name and never throws', async (_field, text, reason) => {
|
|
104
|
-
const events: UsagePolicyLogEvent[] = [];
|
|
105
|
-
await writeFile(path, text);
|
|
106
|
-
const store = createUsagePolicyFileStore({ path, logger: (e) => events.push(e) });
|
|
107
|
-
|
|
108
|
-
expect((await store.load(9)).slots).toEqual([]);
|
|
109
|
-
expect(events[0]?.reason).toBe(reason);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('the corruption log carries a byte count and NEVER the file content', async () => {
|
|
113
|
-
const secretish = JSON.stringify({ schemaVersion: 1, slots: 'iccid-8991101200003204514' });
|
|
114
|
-
const events: UsagePolicyLogEvent[] = [];
|
|
115
|
-
await writeFile(path, secretish);
|
|
116
|
-
const store = createUsagePolicyFileStore({ path, logger: (e) => events.push(e) });
|
|
117
|
-
|
|
118
|
-
await store.load(11);
|
|
119
|
-
|
|
120
|
-
expect(events[0]?.bytes).toBe(Buffer.byteLength(secretish, 'utf8'));
|
|
121
|
-
expect(JSON.stringify(events[0])).not.toContain('8991101200003204514');
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('selectUsagePolicy', () => {
|
|
126
|
-
test('returns the slot policy, omitting fields the slot never set', () => {
|
|
127
|
-
expect(selectUsagePolicy(sample, 'slot-a')).toEqual({
|
|
128
|
-
cycleDay: 15,
|
|
129
|
-
thresholdBytes: 5_000_000_000,
|
|
130
|
-
});
|
|
131
|
-
expect(selectUsagePolicy(sample, 'slot-b')).toEqual({ cycleDay: 1 });
|
|
132
|
-
expect(selectUsagePolicy(sample, 'slot-c')).toEqual({ thresholdBytes: 0 });
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('an unknown slot answers "no policy set", not a default', () => {
|
|
136
|
-
expect(selectUsagePolicy(sample, 'nope')).toEqual({});
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('validators', () => {
|
|
141
|
-
test.each([
|
|
142
|
-
[1, true],
|
|
143
|
-
[31, true],
|
|
144
|
-
[15, true],
|
|
145
|
-
[0, false],
|
|
146
|
-
[32, false],
|
|
147
|
-
[1.5, false],
|
|
148
|
-
[Number.NaN, false],
|
|
149
|
-
['3', false],
|
|
150
|
-
])('isValidCycleDay(%p) === %p', (value, expected) => {
|
|
151
|
-
expect(isValidCycleDay(value)).toBe(expected);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test.each([
|
|
155
|
-
[0, true],
|
|
156
|
-
[5_000_000_000, true],
|
|
157
|
-
[-1, false],
|
|
158
|
-
[1.5, false],
|
|
159
|
-
[Number.POSITIVE_INFINITY, false],
|
|
160
|
-
['5', false],
|
|
161
|
-
])('isValidThresholdBytes(%p) === %p', (value, expected) => {
|
|
162
|
-
expect(isValidThresholdBytes(value)).toBe(expected);
|
|
163
|
-
});
|
|
164
|
-
});
|