feat(edp): provision Foundry reader grants

This commit is contained in:
Codex
2026-07-19 15:03:48 +03:00
parent 847a08da93
commit def9a24e0d
17 changed files with 891 additions and 7 deletions
@@ -11,6 +11,13 @@ const MANAGED_REQUEST_KEYS = new Set([
"generation",
"capabilityDigest",
]);
const MANAGED_CONSUMER_REQUEST_KEYS = new Set([
"allowedDataProductIds",
"expiresAt",
"generation",
"capabilityDigest",
]);
const MANAGED_CONSUMER_PLAN_KEYS = new Set(["allowedDataProductIds"]);
const SOURCE_KEYS = new Set(["tenantId", "connectionId", "providerId"]);
const SHA256_DIGEST = /^[a-f0-9]{64}$/;
@@ -82,6 +89,38 @@ export function normalizeManagedReaderBindingRequest(value) {
});
}
export function normalizeManagedConsumerReaderPlanRequest(value) {
if (!isPlainObject(value) || containsSecretLikeKey(value) || !hasOnlyKeys(value, MANAGED_CONSUMER_PLAN_KEYS)) {
throw readerError("managed_consumer_reader_plan_request_invalid");
}
const allowedDataProductIds = uniqueIdentifiers(value.allowedDataProductIds);
if (!allowedDataProductIds.length) throw readerError("managed_consumer_reader_scope_invalid");
return Object.freeze({ allowedDataProductIds: Object.freeze([...allowedDataProductIds].sort()) });
}
export function normalizeManagedConsumerReaderBindingRequest(value) {
if (!isPlainObject(value) || containsSecretLikeKey(value) || !hasOnlyKeys(value, MANAGED_CONSUMER_REQUEST_KEYS)) {
throw readerError("managed_consumer_reader_binding_request_invalid");
}
const allowedDataProductIds = uniqueIdentifiers(value.allowedDataProductIds);
if (!allowedDataProductIds.length) throw readerError("managed_consumer_reader_scope_invalid");
if (value.expiresAt !== null) throw readerError("managed_consumer_reader_must_be_durable");
const generation = Number(value.generation);
if (!Number.isSafeInteger(generation) || generation < 1 || generation > 2_147_483_647) {
throw readerError("managed_consumer_reader_generation_invalid");
}
const capabilityDigest = String(value.capabilityDigest || "").toLowerCase();
if (!SHA256_DIGEST.test(capabilityDigest)) {
throw readerError("managed_consumer_reader_capability_digest_invalid");
}
return Object.freeze({
allowedDataProductIds: Object.freeze([...allowedDataProductIds].sort()),
expiresAt: null,
generation,
capabilityDigest,
});
}
export function readerBindingRequestHash(policy) {
const canonical = JSON.stringify({
tenantId: policy.tenantId,
@@ -95,6 +134,15 @@ export function readerBindingRequestHash(policy) {
return createHash("sha256").update(canonical, "utf8").digest("hex");
}
export function consumerReaderBindingRequestHash(policy) {
return createHash("sha256").update(JSON.stringify({
allowedDataProductIds: [...policy.allowedDataProductIds].sort(),
expiresAt: null,
generation: policy.generation,
capabilityDigest: policy.capabilityDigest,
}), "utf8").digest("hex");
}
export function assertReaderProduct(binding, dataProductId, now = new Date()) {
const expired = binding?.expiresAt !== null && new Date(binding?.expiresAt) <= now;
if (!isPlainObject(binding) || binding.active !== true || expired) {
@@ -124,6 +172,21 @@ export function safeReaderBinding(binding) {
};
}
export function safeManagedConsumerReaderBinding(binding) {
return {
id: binding.id,
bindingKey: binding.bindingKey,
generation: Number(binding.generation),
allowedDataProductIds: uniqueIdentifiers(binding.allowedDataProductIds),
active: binding.active === true,
expiresAt: binding.expiresAt === null ? null : 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,
sourceScope: "resolved-server-side",
};
}
function readerError(code, status = 400) {
return Object.assign(new Error(code), { status, code });
}