feat(data-plane): add provider contracts and ontology delivery

This commit is contained in:
Codex
2026-07-16 02:23:34 +03:00
parent e527812826
commit 569b8762e6
84 changed files with 11170 additions and 70 deletions
@@ -0,0 +1,90 @@
import { createHash, randomBytes } from "node:crypto";
const IDENTIFIER = /^[a-z][a-z0-9._:-]{2,127}$/;
const TOKEN_PREFIX = "ndc_edprb_";
const SECRET_LIKE_KEY = /(token|secret|password|authorization|access[_-]?token|refresh[_-]?token|api[_-]?key)/i;
const REQUEST_KEYS = new Set(["source", "allowedDataProductIds", "expiresAt"]);
const SOURCE_KEYS = new Set(["tenantId", "connectionId", "providerId"]);
export function createReaderToken() {
return `${TOKEN_PREFIX}${randomBytes(32).toString("base64url")}`;
}
export function hashReaderToken(token) {
return createHash("sha256").update(String(token), "utf8").digest("hex");
}
export function normalizeReaderBindingRequest(value, { now = new Date(), maxTtlDays = 90 } = {}) {
if (!isPlainObject(value) || !isPlainObject(value.source)) throw readerError("reader_binding_request_invalid");
if (containsSecretLikeKey(value)) throw readerError("reader_binding_request_secret_material_forbidden");
if (!hasOnlyKeys(value, REQUEST_KEYS) || !hasOnlyKeys(value.source, SOURCE_KEYS)) {
throw readerError("reader_binding_request_fields_invalid");
}
const tenantId = identifier(value.source.tenantId);
const connectionId = identifier(value.source.connectionId);
const providerId = identifier(value.source.providerId);
const allowedDataProductIds = uniqueIdentifiers(value.allowedDataProductIds);
if (!tenantId || !connectionId || !providerId || !allowedDataProductIds.length) {
throw readerError("reader_binding_scope_invalid");
}
const expiresAt = new Date(String(value.expiresAt || ""));
const maxExpiresAt = new Date(now.getTime() + maxTtlDays * 24 * 60 * 60 * 1000);
if (Number.isNaN(expiresAt.getTime()) || expiresAt <= now || expiresAt > maxExpiresAt) {
throw readerError("reader_binding_expiry_invalid");
}
return Object.freeze({ tenantId, connectionId, providerId, allowedDataProductIds, expiresAt: expiresAt.toISOString() });
}
export function assertReaderProduct(binding, dataProductId, now = new Date()) {
if (!isPlainObject(binding) || binding.active !== true || new Date(binding.expiresAt) <= now) {
throw readerError("reader_binding_inactive", 401);
}
const normalized = identifier(dataProductId);
if (!normalized || !uniqueIdentifiers(binding.allowedDataProductIds).includes(normalized)) {
throw readerError("reader_binding_data_product_forbidden", 403);
}
return normalized;
}
export function safeReaderBinding(binding) {
return {
id: binding.id,
tenantId: binding.tenantId,
connectionId: binding.connectionId,
providerId: binding.providerId,
allowedDataProductIds: uniqueIdentifiers(binding.allowedDataProductIds),
active: binding.active === true,
expiresAt: new Date(binding.expiresAt).toISOString(),
createdAt: binding.createdAt ? new Date(binding.createdAt).toISOString() : undefined,
rotatedAt: binding.rotatedAt ? new Date(binding.rotatedAt).toISOString() : undefined,
revokedAt: binding.revokedAt ? new Date(binding.revokedAt).toISOString() : undefined,
};
}
function readerError(code, status = 400) {
return Object.assign(new Error(code), { status, code });
}
function identifier(value) {
const normalized = typeof value === "string" ? value.trim() : "";
return IDENTIFIER.test(normalized) ? normalized : "";
}
function uniqueIdentifiers(value) {
if (!Array.isArray(value)) return [];
return [...new Set(value.map(identifier).filter(Boolean))];
}
function containsSecretLikeKey(value) {
if (Array.isArray(value)) return value.some(containsSecretLikeKey);
if (!isPlainObject(value)) return false;
return Object.entries(value).some(([key, child]) => SECRET_LIKE_KEY.test(key) || containsSecretLikeKey(child));
}
function isPlainObject(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function hasOnlyKeys(value, allowed) {
return Object.keys(value).every((key) => allowed.has(key));
}