411 lines
18 KiB
JavaScript
411 lines
18 KiB
JavaScript
import { EXTERNAL_PROVIDER_CONTRACT_VERSION } from "./contract-version.mjs";
|
|
|
|
export { EXTERNAL_PROVIDER_CONTRACT_VERSION } from "./contract-version.mjs";
|
|
export { validateIntakeBatch } from "./intake-batch.mjs";
|
|
export {
|
|
DATA_PRODUCT_GEOMETRY_TYPES,
|
|
DATA_PRODUCT_MAX_GEOMETRY_BYTES,
|
|
DATA_PRODUCT_MAX_GEOMETRY_VERTICES,
|
|
isBoundedGeoJsonGeometry,
|
|
validateGeoJsonGeometry,
|
|
} from "./geometry.mjs";
|
|
export {
|
|
TELEMETRY_READINGS_MAX_BYTES,
|
|
TELEMETRY_READINGS_MAX_ITEMS,
|
|
isBoundedTelemetryReadings,
|
|
} from "./telemetry-readings.mjs";
|
|
export {
|
|
TELEMETRY_FIELD_PROJECTION_SCHEMA_VERSION,
|
|
TELEMETRY_FIELD_REGISTRY_SCHEMA_VERSION,
|
|
compileTelemetryFieldProjection,
|
|
digestTelemetryFieldRegistry,
|
|
validateTelemetryFieldRegistry,
|
|
} from "./telemetry-field-registry.mjs";
|
|
export {
|
|
L2_EXECUTION_PLAN_COMPILER_VERSION,
|
|
L2_EXECUTION_PLAN_SCHEMA_VERSION,
|
|
L2_GRAPH_BLUEPRINT_SCHEMA_VERSION,
|
|
L2_MAPPING_RUNTIME_SCHEMA_VERSION,
|
|
L2_MATERIALIZATION_RECEIPT_SCHEMA_VERSION,
|
|
attestL2ExecutionPlanMaterialization,
|
|
compileL2ExecutionPlan,
|
|
compileL2GraphBlueprint,
|
|
validateL2ExecutionPlan,
|
|
} from "./l2-execution-plan.mjs";
|
|
export {
|
|
ZONE_SOURCE_ADAPTERS,
|
|
ZONE_SOURCE_GENERATION_SCHEMA_VERSION,
|
|
normalizeZoneSourceGeneration,
|
|
} from "./zone-source.mjs";
|
|
export const FOUNDRY_BINDING_UPSERT_SCHEMA_VERSION = "nodedc.foundry.binding-upsert/v1";
|
|
|
|
import {
|
|
SECRET_LIKE_KEY,
|
|
SECRET_LIKE_VALUE,
|
|
} from "./sensitive-field-policy.mjs";
|
|
|
|
export {
|
|
L2_CONNECTION_INSTANCE_SCHEMA_VERSION,
|
|
L2_TEMPLATE_SCHEMA_VERSION,
|
|
PROVIDER_PACKAGE_SCHEMA_VERSION,
|
|
SEMANTIC_MAPPING_SCHEMA_VERSION,
|
|
instantiateL2Connection,
|
|
validateProviderPackage,
|
|
} from "./provider-package.mjs";
|
|
|
|
export {
|
|
PROVIDER_CAPABILITY_CATALOG_SCHEMA_VERSION,
|
|
validateProviderCapabilityCatalog,
|
|
} from "./provider-capability-catalog.mjs";
|
|
|
|
export {
|
|
DATA_PRODUCT_HISTORY_SCHEMA_VERSION,
|
|
DATA_PRODUCT_PATCH_SCHEMA_VERSION,
|
|
DATA_PRODUCT_PUBLISH_SCHEMA_VERSION,
|
|
DATA_PRODUCT_SNAPSHOT_SCHEMA_VERSION,
|
|
validateDataProductHistory,
|
|
validateDataProductPatch,
|
|
validateDataProductPublish,
|
|
validateDataProductSnapshot,
|
|
} from "./data-product.mjs";
|
|
|
|
export {
|
|
ENGINE_PRIVATE_EXTENSION_APPLY_RECEIPT_SCHEMA_VERSION,
|
|
ENGINE_PRIVATE_EXTENSION_APPLY_REQUEST_SCHEMA_VERSION,
|
|
ENGINE_PRIVATE_EXTENSION_CREDENTIAL_SCHEMAS,
|
|
ENGINE_PRIVATE_EXTENSION_CREDENTIAL_TYPES,
|
|
ENGINE_PRIVATE_EXTENSION_INACTIVE_BASELINE,
|
|
ENGINE_PRIVATE_EXTENSION_MANAGE_CAPABILITY,
|
|
ENGINE_PRIVATE_EXTENSION_NODE_TYPES,
|
|
ENGINE_PRIVATE_EXTENSION_OPERATION_SCHEMA_VERSION,
|
|
ENGINE_PRIVATE_EXTENSION_PACKAGE_NAME,
|
|
ENGINE_PRIVATE_EXTENSION_PLAN_REQUEST_SCHEMA_VERSION,
|
|
ENGINE_PRIVATE_EXTENSION_PLAN_SCHEMA_VERSION,
|
|
ENGINE_PRIVATE_EXTENSION_READ_CAPABILITY,
|
|
ENGINE_PRIVATE_EXTENSION_STATUS_SCHEMA_VERSION,
|
|
authorizeEnginePrivateExtensionOperation,
|
|
computeEnginePrivateExtensionPlanHash,
|
|
validateEnginePrivateExtensionApplyReceipt,
|
|
validateEnginePrivateExtensionApplyRequest,
|
|
validateEnginePrivateExtensionOperation,
|
|
validateEnginePrivateExtensionPlan,
|
|
validateEnginePrivateExtensionPlanRequest,
|
|
validateEnginePrivateExtensionStatus,
|
|
} from "./engine-private-extension.mjs";
|
|
|
|
const COLLECTION_MODES = new Set(["realtime", "manual", "weekly", "history"]);
|
|
const DELIVERY_MODES = new Set(["snapshot", "snapshot+patch", "query"]);
|
|
const CAPABILITY_CLASSIFICATIONS = new Set(["read", "metadata", "write", "destructive", "unknown"]);
|
|
const IDENTIFIER = /^[a-z][a-z0-9._:-]{2,127}$/;
|
|
const FOUNDRY_APPLICATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
const FOUNDRY_PAGE_ID = /^[a-z0-9][a-z0-9-]{0,79}$/;
|
|
const FOUNDRY_SLOT_ID = /^[A-Za-z0-9][A-Za-z0-9-]{0,79}$/;
|
|
const CONTRACT_VERSION = /^\d+\.\d+\.\d+(?:[-+][a-z0-9.-]+)?$/i;
|
|
|
|
const PROVIDER_MANIFEST_KEYS = new Set(["schemaVersion", "id", "providerId", "version", "ontology", "l2Template", "capabilities", "dataProductIds"]);
|
|
const CONNECTION_PROFILE_KEYS = new Set(["schemaVersion", "id", "providerId", "tenantId", "credentialRef", "scope"]);
|
|
const COLLECTION_PROFILE_KEYS = new Set(["schemaVersion", "id", "connectionId", "dataProductId", "mode", "schedule", "capabilityIds"]);
|
|
const DATA_PRODUCT_KEYS = new Set(["schemaVersion", "id", "version", "delivery", "semanticTypes", "fields", "access"]);
|
|
const FOUNDRY_BINDING_KEYS = new Set(["schemaVersion", "id", "dataProductId", "applicationId", "pageId", "templateId", "slotId", "semanticType"]);
|
|
const FOUNDRY_BINDING_UPSERT_KEYS = new Set(["schemaVersion", "applicationId", "pageId", "idempotencyKey", "binding"]);
|
|
const FOUNDRY_BINDING_UPSERT_BINDING_KEYS = new Set(["id", "dataProductId", "slotId", "semanticTypes", "fieldProjection"]);
|
|
|
|
/**
|
|
* Provider-neutral, versioned description of an L2 connector template.
|
|
*
|
|
* This is a declarative package artifact, not a tenant connection or an
|
|
* executable adapter. It may catalogue write capabilities, but it never
|
|
* grants or transports them. Runtime API requests, secrets and connection
|
|
* scope remain outside this manifest.
|
|
*/
|
|
export function validateProviderManifest(value) {
|
|
const errors = baseErrors(value, "providerManifest");
|
|
rejectUnknownKeys(value, PROVIDER_MANIFEST_KEYS, "providerManifest", errors);
|
|
requiredIdentifier(value?.id, "id", errors);
|
|
requiredIdentifier(value?.providerId, "providerId", errors);
|
|
requiredString(value?.version, "version", errors);
|
|
if (value?.version && !CONTRACT_VERSION.test(value.version)) {
|
|
errors.push("version_must_be_semver");
|
|
}
|
|
|
|
if (!isPlainObject(value?.ontology)) {
|
|
errors.push("ontology_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.ontology, new Set(["packageId", "revision"]), "ontology", errors);
|
|
}
|
|
requiredIdentifier(value?.ontology?.packageId, "ontology.packageId", errors);
|
|
requiredIdentifier(value?.ontology?.revision, "ontology.revision", errors);
|
|
if (!isPlainObject(value?.l2Template)) {
|
|
errors.push("l2Template_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.l2Template, new Set(["id", "version"]), "l2Template", errors);
|
|
}
|
|
requiredIdentifier(value?.l2Template?.id, "l2Template.id", errors);
|
|
requiredString(value?.l2Template?.version, "l2Template.version", errors);
|
|
if (value?.l2Template?.version && !CONTRACT_VERSION.test(value.l2Template.version)) {
|
|
errors.push("l2Template.version_must_be_semver");
|
|
}
|
|
|
|
if (!Array.isArray(value?.capabilities) || value.capabilities.length === 0) {
|
|
errors.push("capabilities_must_be_nonempty_array");
|
|
} else {
|
|
value.capabilities.forEach((capability, index) => {
|
|
if (!isPlainObject(capability)) {
|
|
errors.push(`capabilities[${index}]_must_be_object`);
|
|
return;
|
|
}
|
|
rejectUnknownKeys(capability, new Set(["id", "classification"]), `capabilities[${index}]`, errors);
|
|
requiredIdentifier(capability?.id, `capabilities[${index}].id`, errors);
|
|
if (!CAPABILITY_CLASSIFICATIONS.has(capability?.classification)) {
|
|
errors.push(`capabilities[${index}].classification_invalid`);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!Array.isArray(value?.dataProductIds) || value.dataProductIds.length === 0) {
|
|
errors.push("dataProductIds_must_be_nonempty_array");
|
|
} else {
|
|
value.dataProductIds.forEach((dataProductId, index) => {
|
|
requiredIdentifier(dataProductId, `dataProductIds[${index}]`, errors);
|
|
});
|
|
}
|
|
|
|
if (value?.tenantId !== undefined || value?.connectionId !== undefined || value?.credentialRef !== undefined) {
|
|
errors.push("manifest_must_not_contain_connection_runtime_state");
|
|
}
|
|
if (value?.endpoint !== undefined || value?.url !== undefined || value?.host !== undefined) {
|
|
errors.push("manifest_must_not_contain_provider_transport");
|
|
}
|
|
if (containsSecretLikeMaterial(value)) errors.push("manifest_must_not_contain_secret_material");
|
|
|
|
return result(errors);
|
|
}
|
|
|
|
export function validateConnectionProfile(value) {
|
|
const errors = baseErrors(value, "connection");
|
|
rejectUnknownKeys(value, CONNECTION_PROFILE_KEYS, "connection", errors);
|
|
requiredIdentifier(value?.id, "id", errors);
|
|
requiredIdentifier(value?.providerId, "providerId", errors);
|
|
requiredIdentifier(value?.tenantId, "tenantId", errors);
|
|
if (!isPlainObject(value?.credentialRef)) {
|
|
errors.push("credentialRef_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.credentialRef, new Set(["owner", "reference"]), "credentialRef", errors);
|
|
}
|
|
requiredString(value?.credentialRef?.owner, "credentialRef.owner", errors);
|
|
requiredString(value?.credentialRef?.reference, "credentialRef.reference", errors);
|
|
|
|
if (value?.credentialRef?.owner && value.credentialRef.owner !== "ndc_l2_credentials") {
|
|
errors.push("credentialRef.owner_must_be_ndc_l2_credentials");
|
|
}
|
|
if (containsSecretLikeMaterial(value)) errors.push("profile_must_not_contain_secret_material");
|
|
if (value?.scope !== undefined) {
|
|
if (!isPlainObject(value.scope)) {
|
|
errors.push("scope_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.scope, new Set(["capabilityIds", "fieldPolicyId", "retentionPolicyId", "collectionProfileIds"]), "scope", errors);
|
|
validateOptionalIdentifierArray(value.scope.capabilityIds, "scope.capabilityIds", errors);
|
|
validateOptionalIdentifierArray(value.scope.collectionProfileIds, "scope.collectionProfileIds", errors);
|
|
if (value.scope.fieldPolicyId !== undefined) requiredIdentifier(value.scope.fieldPolicyId, "scope.fieldPolicyId", errors);
|
|
if (value.scope.retentionPolicyId !== undefined) requiredIdentifier(value.scope.retentionPolicyId, "scope.retentionPolicyId", errors);
|
|
}
|
|
}
|
|
|
|
return result(errors);
|
|
}
|
|
|
|
export function validateCollectionProfile(value) {
|
|
const errors = baseErrors(value, "collectionProfile");
|
|
rejectUnknownKeys(value, COLLECTION_PROFILE_KEYS, "collectionProfile", errors);
|
|
requiredIdentifier(value?.id, "id", errors);
|
|
requiredIdentifier(value?.connectionId, "connectionId", errors);
|
|
requiredIdentifier(value?.dataProductId, "dataProductId", errors);
|
|
|
|
if (!COLLECTION_MODES.has(value?.mode)) errors.push("mode_must_be_realtime_manual_weekly_or_history");
|
|
if (!Array.isArray(value?.capabilityIds) || value.capabilityIds.length === 0) {
|
|
errors.push("capabilityIds_must_be_nonempty_array");
|
|
} else {
|
|
value.capabilityIds.forEach((capabilityId, index) => requiredIdentifier(capabilityId, `capabilityIds[${index}]`, errors));
|
|
}
|
|
|
|
const intervalMs = value?.schedule?.intervalMs;
|
|
if (value?.schedule !== undefined) {
|
|
if (!isPlainObject(value.schedule)) {
|
|
errors.push("schedule_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.schedule, new Set(["intervalMs"]), "schedule", errors);
|
|
}
|
|
}
|
|
if (value?.mode === "realtime") {
|
|
if (!Number.isInteger(intervalMs) || intervalMs < 1000) errors.push("realtime_schedule_intervalMs_must_be_integer_gte_1000");
|
|
} else if (value?.mode === "manual") {
|
|
if (intervalMs !== undefined) errors.push("manual_profile_must_not_define_intervalMs");
|
|
}
|
|
|
|
if (containsSecretLikeMaterial(value)) errors.push("collectionProfile_must_not_contain_secret_material");
|
|
|
|
return result(errors);
|
|
}
|
|
|
|
export function validateDataProduct(value) {
|
|
const errors = baseErrors(value, "dataProduct");
|
|
rejectUnknownKeys(value, DATA_PRODUCT_KEYS, "dataProduct", errors);
|
|
requiredIdentifier(value?.id, "id", errors);
|
|
requiredString(value?.version, "version", errors);
|
|
|
|
if (!isPlainObject(value?.delivery)) {
|
|
errors.push("delivery_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.delivery, new Set(["mode"]), "delivery", errors);
|
|
}
|
|
if (!DELIVERY_MODES.has(value?.delivery?.mode)) errors.push("delivery.mode_must_be_snapshot_snapshot+patch_or_query");
|
|
if (!Array.isArray(value?.semanticTypes) || value.semanticTypes.length === 0) {
|
|
errors.push("semanticTypes_must_be_nonempty_array");
|
|
} else {
|
|
value.semanticTypes.forEach((semanticType, index) => requiredIdentifier(semanticType, `semanticTypes[${index}]`, errors));
|
|
}
|
|
if (!Array.isArray(value?.fields) || value.fields.length === 0) {
|
|
errors.push("fields_must_be_nonempty_array");
|
|
} else {
|
|
value.fields.forEach((field, index) => requiredIdentifier(field, `fields[${index}]`, errors));
|
|
}
|
|
if (!isPlainObject(value?.access)) {
|
|
errors.push("access_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.access, new Set(["audience"]), "access", errors);
|
|
}
|
|
if (value?.access?.audience !== "internal") errors.push("access.audience_must_be_internal");
|
|
if (containsSecretLikeMaterial(value)) errors.push("dataProduct_must_not_contain_secret_material");
|
|
|
|
return result(errors);
|
|
}
|
|
|
|
export function validateFoundryBinding(value) {
|
|
const errors = baseErrors(value, "foundryBinding");
|
|
rejectUnknownKeys(value, FOUNDRY_BINDING_KEYS, "foundryBinding", errors);
|
|
requiredIdentifier(value?.id, "id", errors);
|
|
requiredIdentifier(value?.dataProductId, "dataProductId", errors);
|
|
if (typeof value?.applicationId !== "string" || !FOUNDRY_APPLICATION_ID.test(value.applicationId)) {
|
|
errors.push("applicationId_invalid");
|
|
}
|
|
if (typeof value?.pageId !== "string" || !FOUNDRY_PAGE_ID.test(value.pageId)) errors.push("pageId_invalid");
|
|
if (value?.templateId !== undefined) requiredIdentifier(value.templateId, "templateId", errors);
|
|
if (typeof value?.slotId !== "string" || !FOUNDRY_SLOT_ID.test(value.slotId)) errors.push("slotId_invalid");
|
|
requiredIdentifier(value?.semanticType, "semanticType", errors);
|
|
|
|
if (containsSecretLikeMaterial(value)) errors.push("binding_must_not_contain_secret_material");
|
|
if (value?.providerId !== undefined || value?.credentialRef !== undefined || value?.endpoint !== undefined) {
|
|
errors.push("binding_must_reference_data_product_not_provider_transport");
|
|
}
|
|
|
|
return result(errors);
|
|
}
|
|
|
|
/**
|
|
* Replay-safe control-plane command emitted by `NDC Foundry Binding`.
|
|
*
|
|
* This is deliberately separate from the declarative Foundry binding artifact
|
|
* above: the command carries an idempotency key and can express a safe
|
|
* semantic/field projection, while authorization is materialized exclusively
|
|
* from the opaque workload grant at the receiving service.
|
|
*/
|
|
export function validateFoundryBindingUpsert(value) {
|
|
const errors = [];
|
|
if (!isPlainObject(value)) return result(["foundryBindingUpsert_must_be_object"]);
|
|
if (value.schemaVersion !== FOUNDRY_BINDING_UPSERT_SCHEMA_VERSION) errors.push("schemaVersion_mismatch");
|
|
rejectUnknownKeys(value, FOUNDRY_BINDING_UPSERT_KEYS, "foundryBindingUpsert", errors);
|
|
if (typeof value.applicationId !== "string" || !FOUNDRY_APPLICATION_ID.test(value.applicationId)) {
|
|
errors.push("applicationId_invalid");
|
|
}
|
|
if (typeof value.pageId !== "string" || !FOUNDRY_PAGE_ID.test(value.pageId)) errors.push("pageId_invalid");
|
|
requiredIdentifier(value.idempotencyKey, "idempotencyKey", errors);
|
|
|
|
if (!isPlainObject(value.binding)) {
|
|
errors.push("binding_must_be_object");
|
|
} else {
|
|
rejectUnknownKeys(value.binding, FOUNDRY_BINDING_UPSERT_BINDING_KEYS, "binding", errors);
|
|
requiredIdentifier(value.binding.id, "binding.id", errors);
|
|
requiredIdentifier(value.binding.dataProductId, "binding.dataProductId", errors);
|
|
if (typeof value.binding.slotId !== "string" || !FOUNDRY_SLOT_ID.test(value.binding.slotId)) {
|
|
errors.push("binding.slotId_invalid");
|
|
}
|
|
validateRequiredUniqueIdentifierArray(value.binding.semanticTypes, "binding.semanticTypes", errors);
|
|
validateUniqueIdentifierArray(value.binding.fieldProjection, "binding.fieldProjection", errors);
|
|
}
|
|
|
|
if (containsSecretLikeMaterial(value)) errors.push("binding_must_not_contain_secret_material");
|
|
return result(errors);
|
|
}
|
|
|
|
export function assertValid(validator, value) {
|
|
const validation = validator(value);
|
|
if (!validation.ok) throw new Error(`external_provider_contract_invalid:${validation.errors.join(",")}`);
|
|
return value;
|
|
}
|
|
|
|
function baseErrors(value, label) {
|
|
const errors = [];
|
|
if (!isPlainObject(value)) return [`${label}_must_be_object`];
|
|
if (value.schemaVersion !== EXTERNAL_PROVIDER_CONTRACT_VERSION) {
|
|
errors.push("schemaVersion_mismatch");
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
function result(errors) {
|
|
const uniqueErrors = [...new Set(errors)];
|
|
return Object.freeze({ ok: uniqueErrors.length === 0, errors: Object.freeze(uniqueErrors) });
|
|
}
|
|
|
|
function requiredString(value, path, errors) {
|
|
if (typeof value !== "string" || !value.trim()) errors.push(`${path}_required`);
|
|
}
|
|
|
|
function requiredIdentifier(value, path, errors) {
|
|
if (typeof value !== "string" || !IDENTIFIER.test(value)) errors.push(`${path}_invalid`);
|
|
}
|
|
|
|
function validateOptionalIdentifierArray(value, path, errors) {
|
|
if (value === undefined) return;
|
|
if (!Array.isArray(value)) {
|
|
errors.push(`${path}_must_be_array`);
|
|
return;
|
|
}
|
|
value.forEach((item, index) => requiredIdentifier(item, `${path}[${index}]`, errors));
|
|
}
|
|
|
|
function validateRequiredUniqueIdentifierArray(value, path, errors) {
|
|
if (!Array.isArray(value) || value.length === 0) {
|
|
errors.push(`${path}_must_be_nonempty_array`);
|
|
return;
|
|
}
|
|
validateUniqueIdentifierArray(value, path, errors);
|
|
}
|
|
|
|
function validateUniqueIdentifierArray(value, path, errors) {
|
|
if (!Array.isArray(value)) {
|
|
errors.push(`${path}_must_be_array`);
|
|
return;
|
|
}
|
|
value.forEach((item, index) => requiredIdentifier(item, `${path}[${index}]`, errors));
|
|
if (new Set(value).size !== value.length) errors.push(`${path}_must_not_contain_duplicates`);
|
|
}
|
|
|
|
function isPlainObject(value) {
|
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function rejectUnknownKeys(value, allowedKeys, path, errors) {
|
|
if (!isPlainObject(value)) return;
|
|
for (const key of Object.keys(value)) {
|
|
if (!allowedKeys.has(key)) errors.push(`${path}.${key}_not_allowed`);
|
|
}
|
|
}
|
|
|
|
function containsSecretLikeMaterial(value) {
|
|
if (typeof value === "string") return SECRET_LIKE_VALUE.test(value);
|
|
if (Array.isArray(value)) return value.some(containsSecretLikeMaterial);
|
|
if (!isPlainObject(value)) return false;
|
|
return Object.entries(value).some(([key, child]) => SECRET_LIKE_KEY.test(key) || containsSecretLikeMaterial(child));
|
|
}
|