From b48b6dbfc96d8312e9777ff28d687b327129ae36 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 5 Aug 2026 15:20:23 +0300 Subject: [PATCH] feat(gelios): expand safe B2 telemetry registry --- .../providers/gelios/index.mjs | 4 + .../providers/gelios/v14/index.mjs | 7 ++ .../providers/gelios/v14/package.mjs | 22 +++++ .../gelios/v14/telemetry-field-registry.mjs | 97 +++++++++++++++++++ .../test/l2-execution-plan.test.mjs | 23 +++++ .../test/provider-package.test.mjs | 5 + .../test/telemetry-field-registry.test.mjs | 54 +++++++++++ 7 files changed, 212 insertions(+) create mode 100644 packages/external-provider-contract/providers/gelios/v14/index.mjs create mode 100644 packages/external-provider-contract/providers/gelios/v14/package.mjs create mode 100644 packages/external-provider-contract/providers/gelios/v14/telemetry-field-registry.mjs diff --git a/packages/external-provider-contract/providers/gelios/index.mjs b/packages/external-provider-contract/providers/gelios/index.mjs index fcb5f5a..149ba55 100644 --- a/packages/external-provider-contract/providers/gelios/index.mjs +++ b/packages/external-provider-contract/providers/gelios/index.mjs @@ -8,3 +8,7 @@ export { geliosProviderPackageV10 } from "./v10/index.mjs"; export { geliosProviderPackageV11 } from "./v11/index.mjs"; export { geliosProviderPackageV12 } from "./v12/index.mjs"; export { geliosProviderPackageV13 } from "./v13/index.mjs"; +export { + geliosProviderPackageV14, + geliosTelemetryFieldRegistryV6, +} from "./v14/index.mjs"; diff --git a/packages/external-provider-contract/providers/gelios/v14/index.mjs b/packages/external-provider-contract/providers/gelios/v14/index.mjs new file mode 100644 index 0000000..7782224 --- /dev/null +++ b/packages/external-provider-contract/providers/gelios/v14/index.mjs @@ -0,0 +1,7 @@ +export { + GELIOS_PROVIDER_PACKAGE_ID, + GELIOS_PROVIDER_PACKAGE_VERSION, + geliosProviderPackageV14, +} from "./package.mjs"; +export { geliosTelemetryFieldRegistryV6 } from "./telemetry-field-registry.mjs"; +export { geliosCapabilityCatalogV3 } from "../capability-catalog-v3.mjs"; diff --git a/packages/external-provider-contract/providers/gelios/v14/package.mjs b/packages/external-provider-contract/providers/gelios/v14/package.mjs new file mode 100644 index 0000000..3001018 --- /dev/null +++ b/packages/external-provider-contract/providers/gelios/v14/package.mjs @@ -0,0 +1,22 @@ +import { geliosProviderPackageV13 } from "../v13/package.mjs"; + +export const GELIOS_PROVIDER_PACKAGE_ID = "gelios.provider.v14"; +export const GELIOS_PROVIDER_PACKAGE_VERSION = "14.0.0"; + +const value = structuredClone(geliosProviderPackageV13); +value.id = GELIOS_PROVIDER_PACKAGE_ID; +value.version = GELIOS_PROVIDER_PACKAGE_VERSION; +value.manifest = { + ...value.manifest, + id: "gelios.provider.manifest.v14", + version: GELIOS_PROVIDER_PACKAGE_VERSION, +}; + +export const geliosProviderPackageV14 = deepFreeze(value); + +function deepFreeze(input) { + if (!input || typeof input !== "object" || Object.isFrozen(input)) return input; + Object.freeze(input); + for (const child of Object.values(input)) deepFreeze(child); + return input; +} diff --git a/packages/external-provider-contract/providers/gelios/v14/telemetry-field-registry.mjs b/packages/external-provider-contract/providers/gelios/v14/telemetry-field-registry.mjs new file mode 100644 index 0000000..be1ae56 --- /dev/null +++ b/packages/external-provider-contract/providers/gelios/v14/telemetry-field-registry.mjs @@ -0,0 +1,97 @@ +import { geliosTelemetryFieldRegistryV5 } from "../v12/telemetry-field-registry.mjs"; +import { + GELIOS_PROVIDER_PACKAGE_ID, + GELIOS_PROVIDER_PACKAGE_VERSION, +} from "./package.mjs"; + +const EXPOSED_SURFACES = Object.freeze([ + "audit", + "data_product", + "analytics", + "foundry", +]); + +const value = structuredClone(geliosTelemetryFieldRegistryV5); +value.id = "gelios.units.current.telemetry.v3"; +value.version = "3.0.0"; +value.providerPackage = { + id: GELIOS_PROVIDER_PACKAGE_ID, + version: GELIOS_PROVIDER_PACKAGE_VERSION, +}; +value.sourceNamespace = "gelios.units.telemetry_parameter"; + +const existingKeys = new Set(value.entries.map((entry) => entry.sourceKey)); +for (const sourceKey of inputKeys()) { + if (existingKeys.has(sourceKey)) continue; + value.entries.push(providerConfiguredNumber(sourceKey)); + existingKeys.add(sourceKey); +} + +for (const [sourceKey, readingId, label] of [ + ["GSM_level", "sensor.param.gsm_level", "Уровень GSM"], + ["call_btn", "sensor.param.call_btn", "Состояние кнопки вызова"], + ["gps_mod", "sensor.param.gps_mod", "Состояние GPS-модуля"], + ["gsm", "sensor.param.gsm", "Сигнал GSM"], + ["gsm_st", "sensor.param.gsm_st", "Состояние GSM"], + ["gyro", "sensor.param.gyro", "Состояние гироскопа"], + ["ign_virt", "sensor.param.ign_virt", "Виртуальное зажигание"], + ["nav_st", "sensor.param.nav_st", "Состояние навигации"], + ["pwr_ext", "sensor.param.pwr_ext", "Внешнее питание"], + ["pwr_int", "sensor.param.pwr_int", "Внутреннее питание"], + ["sim1_st", "sensor.param.sim1_st", "Состояние SIM 1"], + ["sim2_st", "sensor.param.sim2_st", "Состояние SIM 2"], + ["v_in", "sensor.param.v_in", "Вход питания"], +]) { + if (existingKeys.has(sourceKey)) continue; + value.entries.push(registryNumber(sourceKey, readingId, label)); + existingKeys.add(sourceKey); +} + +export const geliosTelemetryFieldRegistryV6 = deepFreeze(value); + +function inputKeys() { + return [ + ...Array.from({ length: 16 }, (_, index) => `in_${index}`), + ...Array.from({ length: 16 }, (_, index) => `in${index}`), + ]; +} + +function providerConfiguredNumber(sourceKey) { + return { + id: `gelios.sensor-parameter.${sourceKey.replaceAll("_", "-underscore-")}`, + sourceKey, + status: "approved", + sensitivity: "operational", + valueType: "number", + semanticReading: { + id: `sensor.param.${sourceKey}`, + labelSource: "provider_configured", + unitSource: "provider_configured", + }, + allowedSurfaces: [...EXPOSED_SURFACES], + }; +} + +function registryNumber(sourceKey, readingId, label) { + return { + id: `gelios.telemetry-parameter.${sourceKey.toLowerCase()}`, + sourceKey, + status: "approved", + sensitivity: "operational", + valueType: "number", + semanticReading: { + id: readingId, + labelSource: "registry", + label, + unitSource: "none", + }, + allowedSurfaces: [...EXPOSED_SURFACES], + }; +} + +function deepFreeze(input) { + if (!input || typeof input !== "object" || Object.isFrozen(input)) return input; + Object.freeze(input); + for (const child of Object.values(input)) deepFreeze(child); + return input; +} diff --git a/packages/external-provider-contract/test/l2-execution-plan.test.mjs b/packages/external-provider-contract/test/l2-execution-plan.test.mjs index 16565ad..371570d 100644 --- a/packages/external-provider-contract/test/l2-execution-plan.test.mjs +++ b/packages/external-provider-contract/test/l2-execution-plan.test.mjs @@ -17,6 +17,10 @@ import { geliosProviderPackageV11, geliosTelemetryFieldRegistryV4, } from "../providers/gelios/v11/index.mjs"; +import { + geliosProviderPackageV14, + geliosTelemetryFieldRegistryV6, +} from "../providers/gelios/v14/index.mjs"; const positionsConnection = instantiateL2Connection(geliosProviderPackageV11, { tenantId: "tenant-compiler-fixture", @@ -122,6 +126,25 @@ assert.equal(Object.isFrozen(positionsPlan), true); assert.equal(Object.isFrozen(positionsPlan.steps), true); assert.equal(Object.isFrozen(positionsPlan.graphBlueprint), true); +const operationalConnection = instantiateL2Connection(geliosProviderPackageV14, { + tenantId: "tenant-compiler-fixture", + connectionId: "gelios-operational-telemetry-fixture", + collectionProfileId: "gelios.positions.current.realtime.v7", + providerCredentialRef: "ndc-credref:provider-compiler-fixture-0001", +}); +const operationalPlan = compileL2ExecutionPlan( + geliosProviderPackageV14, + operationalConnection, + { telemetryFieldRegistry: geliosTelemetryFieldRegistryV6 }, +); +assert.deepEqual(validateL2ExecutionPlan(operationalPlan), { ok: true, errors: [] }); +assert.equal( + operationalPlan.steps + .find((step) => step.kind === "semantic_mapping") + .config.telemetryProjection.entries.length, + 45, +); + const clonedPlan = compileL2ExecutionPlan( structuredClone(geliosProviderPackageV11), structuredClone(positionsConnection), diff --git a/packages/external-provider-contract/test/provider-package.test.mjs b/packages/external-provider-contract/test/provider-package.test.mjs index 058e32c..e53e8dc 100644 --- a/packages/external-provider-contract/test/provider-package.test.mjs +++ b/packages/external-provider-contract/test/provider-package.test.mjs @@ -26,6 +26,7 @@ import { geliosProviderPackageV10 } from "../providers/gelios/v10/index.mjs"; import { geliosProviderPackageV11 } from "../providers/gelios/v11/index.mjs"; import { geliosProviderPackageV12 } from "../providers/gelios/v12/index.mjs"; import { geliosProviderPackageV13 } from "../providers/gelios/v13/index.mjs"; +import { geliosProviderPackageV14 } from "../providers/gelios/v14/index.mjs"; import { normalizeDataProductDefinition } from "../../../services/external-data-plane/src/data-product-policy.mjs"; const expectedFields = [ @@ -57,6 +58,7 @@ assert.deepEqual(validateProviderPackage(geliosProviderPackageV10), { ok: true, assert.deepEqual(validateProviderPackage(geliosProviderPackageV11), { ok: true, errors: [] }); assert.deepEqual(validateProviderPackage(geliosProviderPackageV12), { ok: true, errors: [] }); assert.deepEqual(validateProviderPackage(geliosProviderPackageV13), { ok: true, errors: [] }); +assert.deepEqual(validateProviderPackage(geliosProviderPackageV14), { ok: true, errors: [] }); assert.equal(geliosProviderPackageV9.id, "gelios.provider.v9"); assert.equal(geliosProviderPackageV9.version, "9.0.0"); assert.equal(geliosProviderPackageV10.id, "gelios.provider.v10"); @@ -67,6 +69,8 @@ assert.equal(geliosProviderPackageV12.id, "gelios.provider.v12"); assert.equal(geliosProviderPackageV12.version, "12.0.1"); assert.equal(geliosProviderPackageV13.id, "gelios.provider.v13"); assert.equal(geliosProviderPackageV13.version, "13.0.0"); +assert.equal(geliosProviderPackageV14.id, "gelios.provider.v14"); +assert.equal(geliosProviderPackageV14.version, "14.0.0"); assert.equal(geliosProviderPackageV8.id, "gelios.provider.v8"); const profileProduct = geliosProviderPackageV8.dataProducts.find((product) => product.id === "fleet.units.profile.current.v1"); @@ -181,6 +185,7 @@ assert.equal( "first", ); assert.equal(Object.isFrozen(geliosProviderPackageV13), true); +assert.equal(Object.isFrozen(geliosProviderPackageV14), true); const identityMapping = geliosProviderPackageV11.mappingContracts.find( (mapping) => mapping.id === "gelios.units.to.fleet.units.identity.current.v1", ); diff --git a/packages/external-provider-contract/test/telemetry-field-registry.test.mjs b/packages/external-provider-contract/test/telemetry-field-registry.test.mjs index 32c8c38..e114a13 100644 --- a/packages/external-provider-contract/test/telemetry-field-registry.test.mjs +++ b/packages/external-provider-contract/test/telemetry-field-registry.test.mjs @@ -11,6 +11,9 @@ import { import { geliosTelemetryFieldRegistryV2, } from "../providers/gelios/v9/index.mjs"; +import { + geliosTelemetryFieldRegistryV6, +} from "../providers/gelios/v14/index.mjs"; assert.deepEqual(validateTelemetryFieldRegistry(geliosTelemetryFieldRegistryV1), { ok: true, @@ -65,6 +68,57 @@ assert.notEqual( digestTelemetryFieldRegistry(geliosTelemetryFieldRegistryV1), ); +assert.deepEqual(validateTelemetryFieldRegistry(geliosTelemetryFieldRegistryV6), { + ok: true, + errors: [], +}); +const operationalProjection = compileTelemetryFieldProjection( + geliosTelemetryFieldRegistryV6, + { + providerPackageId: "gelios.provider.v14", + providerPackageVersion: "14.0.0", + dataProductId: "fleet.positions.current.v5", + surface: "data_product", + }, +); +assert.equal(operationalProjection.entries.length, 45); +assert.deepEqual( + operationalProjection.entries + .filter((entry) => ["pwr_ext", "gsm_st", "nav_st"].includes(entry.sourceKey)) + .map((entry) => ({ + sourceKey: entry.sourceKey, + readingId: entry.reading.id, + labelSource: entry.reading.labelSource, + unitSource: entry.reading.unitSource, + })), + [ + { + sourceKey: "gsm_st", + readingId: "sensor.param.gsm_st", + labelSource: "registry", + unitSource: "none", + }, + { + sourceKey: "nav_st", + readingId: "sensor.param.nav_st", + labelSource: "registry", + unitSource: "none", + }, + { + sourceKey: "pwr_ext", + readingId: "sensor.param.pwr_ext", + labelSource: "registry", + unitSource: "none", + }, + ], +); +for (const forbiddenSourceKey of ["CID", "LAC", "MCC", "MNC", "imei", "commands"]) { + assert.equal( + operationalProjection.entries.some((entry) => entry.sourceKey === forbiddenSourceKey), + false, + ); +} + const observedExposed = structuredClone(geliosTelemetryFieldRegistryV1); observedExposed.entries.find((entry) => entry.sourceKey === "in_0").allowedSurfaces.push("foundry"); assert.equal(