feat(engine): register MCP profiles and provider transitions

This commit is contained in:
Codex
2026-07-25 15:39:23 +03:00
parent 951b884c4b
commit e9e03143cd
21 changed files with 3573 additions and 28 deletions
@@ -9,11 +9,12 @@ export const L2_EXECUTION_PLAN_SCHEMA_VERSION = "nodedc.l2-execution-plan/v1";
export const L2_GRAPH_BLUEPRINT_SCHEMA_VERSION = "nodedc.l2-graph-blueprint/v1";
export const L2_MATERIALIZATION_RECEIPT_SCHEMA_VERSION = "nodedc.l2-materialization-receipt/v1";
export const L2_MAPPING_RUNTIME_SCHEMA_VERSION = "nodedc.semantic-mapping-runtime/v1";
export const L2_EXECUTION_PLAN_COMPILER_VERSION = "1.4.0";
export const L2_EXECUTION_PLAN_COMPILER_VERSION = "1.5.0";
export const L2_EXECUTION_PLAN_SUPPORTED_COMPILER_VERSIONS = Object.freeze([
"1.1.0",
"1.2.0",
"1.3.0",
"1.4.0",
L2_EXECUTION_PLAN_COMPILER_VERSION,
]);
@@ -259,6 +259,14 @@ export function validateProviderPackage(value) {
if (!selectedCapabilityIds.includes(mapping.sourceCapabilityId)) {
errors.push(`collectionProfile.${profile.id}.mapping_source_capability_must_be_selected`);
}
for (const derivation of Object.values(mapping.derivations || {})) {
if (
derivation?.kind === "bounded_response_lookup"
&& !selectedCapabilityIds.includes(derivation.parameters?.responseCapabilityId)
) {
errors.push(`collectionProfile.${profile.id}.lookup_response_capability_must_be_selected`);
}
}
if (mapping.fieldPolicyId !== profile.fieldPolicyId) errors.push(`collectionProfile.${profile.id}.mapping_field_policy_mismatch`);
if (mapping.target?.dataProductId !== profile.dataProductId) errors.push(`collectionProfile.${profile.id}.mapping_data_product_mismatch`);
}
@@ -283,6 +291,16 @@ export function validateProviderPackage(value) {
}
for (const mapping of mappingContracts) {
assertReference(mapping.sourceCapabilityId, capabilityIds, `mappingContract.${mapping.id}.sourceCapabilityId`, errors);
for (const [derivationId, derivation] of Object.entries(mapping.derivations || {})) {
if (derivation?.kind === "bounded_response_lookup") {
assertReference(
derivation.parameters?.responseCapabilityId,
capabilityIds,
`mappingContract.${mapping.id}.derivations.${derivationId}.parameters.responseCapabilityId`,
errors,
);
}
}
assertReference(mapping.fieldPolicyId, fieldPolicyIds, `mappingContract.${mapping.id}.fieldPolicyId`, errors);
assertReference(mapping.target?.dataProductId, dataProductIds, `mappingContract.${mapping.id}.target.dataProductId`, errors);
const product = dataProducts.find((item) => item.id === mapping.target?.dataProductId);
@@ -1049,6 +1067,7 @@ function validateDerivation(value, path, errors) {
"ordered_rules",
"flag_set",
"bounded_readings",
"bounded_response_lookup",
"bounded_string_list",
"bounded_named_values",
]).has(value.kind)) errors.push(`${path}.kind_invalid`);
@@ -1075,6 +1094,89 @@ function validateDerivation(value, path, errors) {
if (value.kind === "bounded_named_values") {
validateBoundedNamedValuesDerivation(value, path, errors);
}
if (value.kind === "bounded_response_lookup") {
validateBoundedResponseLookupDerivation(value, path, errors);
}
}
function validateBoundedResponseLookupDerivation(value, path, errors) {
const parameters = isPlainObject(value.parameters) ? value.parameters : {};
const allowed = new Set([
"responseCapabilityId",
"responseCollectionPath",
"responseKeyPath",
"sourceArrayPath",
"sourceKeyPath",
"valuePath",
"resultMode",
"maxResponseItems",
"maxSourceKeys",
"maxItems",
"maxItemLength",
]);
if (Object.keys(parameters).some((key) => !allowed.has(key))) {
errors.push(`${path}.parameters_shape_invalid`);
}
if (!IDENTIFIER.test(String(parameters.responseCapabilityId || ""))) {
errors.push(`${path}.parameters.responseCapabilityId_invalid`);
}
for (const key of [
"responseCollectionPath",
"responseKeyPath",
"sourceKeyPath",
"valuePath",
]) {
if (!isCanonicalSourcePath(parameters[key])) {
errors.push(`${path}.parameters.${key}_must_be_canonical_dot_path`);
}
}
if (SECRET_FIELD_NAME.test(String(parameters.valuePath || ""))) {
errors.push(`${path}.parameters.valuePath_secret_field_not_allowed`);
}
if (
parameters.sourceArrayPath !== undefined
&& !isCanonicalSourcePath(parameters.sourceArrayPath)
) {
errors.push(`${path}.parameters.sourceArrayPath_must_be_canonical_dot_path`);
}
if (!new Set(["first", "list"]).has(parameters.resultMode)) {
errors.push(`${path}.parameters.resultMode_invalid`);
}
if (
!Number.isInteger(parameters.maxResponseItems)
|| parameters.maxResponseItems < 1
|| parameters.maxResponseItems > 5000
) {
errors.push(`${path}.parameters.maxResponseItems_invalid`);
}
if (
!Number.isInteger(parameters.maxSourceKeys)
|| parameters.maxSourceKeys < 1
|| parameters.maxSourceKeys > 256
) {
errors.push(`${path}.parameters.maxSourceKeys_invalid`);
}
if (
!Number.isInteger(parameters.maxItems)
|| parameters.maxItems < 1
|| parameters.maxItems > 256
) {
errors.push(`${path}.parameters.maxItems_invalid`);
}
if (
!Number.isInteger(parameters.maxItemLength)
|| parameters.maxItemLength < 1
|| parameters.maxItemLength > 512
) {
errors.push(`${path}.parameters.maxItemLength_invalid`);
}
if (parameters.resultMode === "list") {
if (!Array.isArray(value.default) || value.default.length !== 0) {
errors.push(`${path}.default_must_be_empty_array_for_list`);
}
} else if (value.default !== "") {
errors.push(`${path}.default_must_be_empty_string_for_first`);
}
}
function validateBoundedStringListDerivation(value, path, errors) {