feat(foundry): support restricted subject aspects

This commit is contained in:
Codex
2026-07-24 09:31:34 +03:00
parent 03aa9e3e7e
commit 49f0c449c1
13 changed files with 313 additions and 15 deletions
+27 -2
View File
@@ -348,6 +348,9 @@ function validateMapDataProductBinding(value) {
const joinToBindingId = value.joinToBindingId === undefined
? null
: requireNonEmptyString(value.joinToBindingId, "invalid_map_data_product_join_binding_id", 128);
const dataClass = value.dataClass === undefined
? "operational"
: requireNonEmptyString(value.dataClass, "invalid_map_data_product_data_class", 32);
if (!/^[A-Za-z0-9._:-]+$/.test(id)) throw applicationError("invalid_map_data_product_binding_id");
if (!/^[A-Za-z0-9._:-]+$/.test(dataProductId)) throw applicationError("invalid_map_data_product_id");
if (!/^[A-Za-z0-9-]+$/.test(slotId)) throw applicationError("invalid_map_data_product_slot");
@@ -367,6 +370,9 @@ function validateMapDataProductBinding(value) {
if (joinToBindingId && !/^[A-Za-z0-9._:-]+$/.test(joinToBindingId)) {
throw applicationError("invalid_map_data_product_join_binding_id");
}
if (!new Set(["operational", "restricted"]).has(dataClass)) {
throw applicationError("invalid_map_data_product_data_class");
}
if (Object.keys(value).some((key) => /(provider|tenant|connection|endpoint|url|credential|token|secret|payload)/i.test(key))) {
throw applicationError("map_data_product_binding_contains_transport");
}
@@ -383,6 +389,7 @@ function validateMapDataProductBinding(value) {
...(subjectDetailProfileId ? { subjectDetailProfileId } : {}),
aspectId,
...(joinToBindingId ? { joinToBindingId } : {}),
dataClass,
};
}
@@ -524,9 +531,27 @@ function validateMapPageLayout(value) {
}
}
for (const primary of dataProductBindings.filter((binding) => !binding.joinToBindingId)) {
const aspectIds = [primary, ...dataProductBindings.filter((binding) => binding.joinToBindingId === primary.id)]
.map((binding) => binding.aspectId);
const composition = [
primary,
...dataProductBindings.filter((binding) => binding.joinToBindingId === primary.id),
];
const aspectIds = composition.map((binding) => binding.aspectId);
if (new Set(aspectIds).size !== aspectIds.length) throw applicationError("duplicate_map_data_product_aspect_id");
const detailProfile = primary.subjectDetailProfileId
? subjectDetailProfilesById.get(primary.subjectDetailProfileId)
: null;
if (!detailProfile) continue;
const aspectsById = new Map(composition.map((binding) => [binding.aspectId, binding]));
const detailFields = detailProfile.tabs.flatMap(
(tab) => tab.sections.flatMap((section) => section.fields),
);
for (const field of detailFields) {
const aspect = aspectsById.get(field.aspectId);
if (!aspect) throw applicationError("map_subject_detail_profile_aspect_not_found");
if (field.dataClass !== aspect.dataClass) {
throw applicationError("map_subject_detail_profile_data_class_mismatch");
}
}
}
return {
schemaVersion: 1,