feat(foundry): support typed non-geometric subject aspects

This commit is contained in:
Codex
2026-07-23 09:43:51 +03:00
parent dc82ae4c07
commit 03aa9e3e7e
4 changed files with 376 additions and 7 deletions
+19 -6
View File
@@ -157,6 +157,7 @@ function validateConsumerContract(value) {
const semanticTypes = Array.isArray(value.semanticTypes) ? value.semanticTypes : [];
const fieldProjection = Array.isArray(value.fieldProjection) ? value.fieldProjection : [];
const geometryTypes = Array.isArray(value.geometryTypes) ? value.geometryTypes : [];
const supportedGeometryTypes = new Set(["Point", "LineString", "Polygon", "MultiPolygon"]);
const fieldTypes = value.fieldTypes && typeof value.fieldTypes === "object" && !Array.isArray(value.fieldTypes)
? value.fieldTypes
: null;
@@ -174,7 +175,9 @@ function validateConsumerContract(value) {
|| !fieldTypes
|| JSON.stringify(Object.keys(fieldTypes)) !== JSON.stringify(fieldProjection)
|| Object.values(fieldTypes).some((type) => !["string", "number", "boolean"].includes(type))
|| JSON.stringify(geometryTypes) !== JSON.stringify(["Polygon", "MultiPolygon"])
|| geometryTypes.length > supportedGeometryTypes.size
|| new Set(geometryTypes).size !== geometryTypes.length
|| geometryTypes.some((type) => !supportedGeometryTypes.has(type))
|| value.subjectIdentity !== "semantic-type+source-id"
|| value.snapshotMode !== "atomic-replace"
|| value.patchMode !== "atomic"
@@ -198,6 +201,8 @@ function assertConsumerContract(contract, product, binding) {
field,
product?.fieldContracts?.[field]?.type,
]));
const productHasGeometry = Array.isArray(product.fields) && product.fields.includes("geometry");
const contractHasGeometry = contract.geometryTypes.length > 0;
if (
product.ontologyRevision !== contract.ontologyRevision
|| product.deliveryMode !== contract.deliveryMode
@@ -207,6 +212,8 @@ function assertConsumerContract(contract, product, binding) {
|| !Array.isArray(product.fields)
|| contract.fieldProjection.some((field) => !product.fields.includes(field))
|| JSON.stringify(productFieldTypes) !== JSON.stringify(contract.fieldTypes)
|| productHasGeometry !== contractHasGeometry
|| (contractHasGeometry && product?.fieldContracts?.geometry?.type !== "geometry")
) throw consumerError("data_product_consumer_contract_mismatch", 409);
}
@@ -219,11 +226,17 @@ function assertEnvelopeProduct(envelope, product, errorCode) {
function assertFactContract(fact, policy) {
const contract = policy.consumerContract;
if (!contract) return;
if (
!contract.semanticTypes.includes(fact.semanticType)
|| !fact.geometry
|| !contract.geometryTypes.includes(fact.geometry.type)
) throw consumerError("data_product_consumer_fact_contract_invalid", 502);
if (!contract.semanticTypes.includes(fact.semanticType)) {
throw consumerError("data_product_consumer_fact_contract_invalid", 502);
}
const hasGeometry = fact.geometry !== undefined && fact.geometry !== null;
if (contract.geometryTypes.length === 0) {
if (hasGeometry) throw consumerError("data_product_consumer_fact_contract_invalid", 502);
return;
}
if (!hasGeometry || !contract.geometryTypes.includes(fact.geometry.type)) {
throw consumerError("data_product_consumer_fact_contract_invalid", 502);
}
}
function validatePolicy(policy, product) {