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
+30 -6
View File
@@ -1,16 +1,20 @@
const IDENTIFIER = /^[a-z][a-z0-9._:-]{1,159}$/;
const FIELD = /^[a-z][a-z0-9_.-]{0,127}$/;
const SEMVER = /^\d+\.\d+\.\d+$/;
const SECRET_LIKE = /(?:token|secret|password|authorization|access[_-]?token|refresh[_-]?token|api[_-]?key|imei|phone|decrypt|address|raw[_-]?params?)/i;
const SECRET_MATERIAL = /(?:token|secret|password|authorization|access[_-]?token|refresh[_-]?token|api[_-]?key|decrypt|raw[_-]?params?)/i;
const RESTRICTED_IDENTIFIER = /(?:imei|phone|telephone|address)/i;
const PROFILE_KEYS = new Set(["id", "version", "title", "semanticTypes", "defaultTabId", "tabs"]);
const TAB_KEYS = new Set(["id", "label", "emptyMessage", "sections"]);
const SECTION_KEYS = new Set(["id", "label", "fields"]);
const FIELD_KEYS = new Set(["id", "aspectId", "source", "field", "label", "format", "unit", "allowedReadingIds"]);
const FIELD_KEYS = new Set([
"id", "aspectId", "source", "field", "label", "format", "unit", "allowedReadingIds", "dataClass",
]);
const SOURCES = new Set(["fact", "attribute", "geometry", "context"]);
const FORMATS = new Set([
"text", "number", "timestamp", "boolean", "coordinate",
"signal_state", "movement_state", "telemetry_readings",
]);
const DATA_CLASSES = new Set(["operational", "restricted"]);
const FACT_FIELDS = new Set(["sourceId", "semanticType", "observedAt", "receivedAt", "presentationStatus"]);
const GEOMETRY_FIELDS = new Set(["latitude", "longitude"]);
const CONTEXT_FIELDS = new Set(["dataProductId", "bindingId"]);
@@ -75,11 +79,22 @@ function normalizeField(value) {
if (!SOURCES.has(source)) fail("invalid_map_subject_detail_profile_field_source");
if (!FORMATS.has(format)) fail("invalid_map_subject_detail_profile_field_format");
const field = sourceField(source, value.field);
if (source === "attribute" && SECRET_LIKE.test(field)) fail("map_subject_detail_profile_restricted_field");
const dataClass = value.dataClass === undefined
? "operational"
: text(value.dataClass, 32, "invalid_map_subject_detail_profile_field_data_class");
if (!DATA_CLASSES.has(dataClass)) fail("invalid_map_subject_detail_profile_field_data_class");
if (source === "attribute" && SECRET_MATERIAL.test(field)) {
fail("map_subject_detail_profile_secret_field");
}
if (source === "attribute" && RESTRICTED_IDENTIFIER.test(field) && dataClass !== "restricted") {
fail("map_subject_detail_profile_restricted_field");
}
const allowedReadingIds = value.allowedReadingIds === undefined
? []
: fields(value.allowedReadingIds, 0, 256, "invalid_map_subject_detail_profile_reading_ids");
if (allowedReadingIds.some((item) => SECRET_LIKE.test(item))) fail("map_subject_detail_profile_restricted_reading_id");
if (allowedReadingIds.some((item) => SECRET_MATERIAL.test(item) || RESTRICTED_IDENTIFIER.test(item))) {
fail("map_subject_detail_profile_restricted_reading_id");
}
if (format === "telemetry_readings" && (source !== "attribute" || field !== "sensor_readings")) {
fail("map_subject_detail_profile_readings_source_invalid");
}
@@ -93,8 +108,9 @@ function normalizeField(value) {
aspectId: identifier(value.aspectId ?? "primary", "invalid_map_subject_detail_profile_aspect_id"),
source,
field,
label: safeLabel(value.label, 100, "invalid_map_subject_detail_profile_field_label"),
label: fieldLabel(value.label, dataClass),
format,
dataClass,
...(unit ? { unit } : {}),
...(format === "telemetry_readings" ? { allowedReadingIds } : {}),
};
@@ -143,7 +159,15 @@ function fields(value, min, max, code) {
function safeLabel(value, max, code) {
const normalized = text(value, max, code);
if (SECRET_LIKE.test(normalized)) fail(code);
if (SECRET_MATERIAL.test(normalized)) fail(code);
return normalized;
}
function fieldLabel(value, dataClass) {
const normalized = safeLabel(value, 100, "invalid_map_subject_detail_profile_field_label");
if (RESTRICTED_IDENTIFIER.test(normalized) && dataClass !== "restricted") {
fail("map_subject_detail_profile_restricted_field");
}
return normalized;
}