Files
NODEDC_PLATFORM/services/external-data-plane/src/server.mjs
T

1367 lines
60 KiB
JavaScript

import express from "express";
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";
import { Pool } from "pg";
import { validateDataProductPublish, validateIntakeBatch } from "@nodedc/external-provider-contract/data-plane";
import { readConfig } from "./config.mjs";
import {
loadDataProductDefinition,
assertPublishMatchesDefinition,
persistDataProductDefinition,
persistDataProductPublish,
pruneBatchReceipts,
pruneDataProductHistory,
prunePatchOutbox,
readDataProductHistory,
readDataProductSnapshot,
readPatchEvents,
} from "./data-product-delivery.mjs";
import { normalizeDataProductDefinition, safeDataProductDefinition } from "./data-product-policy.mjs";
import { reconcileDataProductDefinitions } from "./definitions.mjs";
import { assertBatchTimeBounds, rawRetentionExpiry } from "./intake-policy.mjs";
import {
createManagedProvisionerRequestVerifier,
ManagedProvisionerReplayCache,
} from "./managed-provisioner-auth.mjs";
import {
assertReaderProduct,
consumerReaderBindingRequestHash,
createReaderToken,
hashReaderToken,
normalizeManagedConsumerReaderBindingRequest,
normalizeManagedConsumerReaderPlanRequest,
normalizeManagedReaderBindingRequest,
normalizeReaderBindingRequest,
readerBindingRequestHash,
safeManagedConsumerReaderBinding,
safeReaderBinding,
} from "./reader-binding.mjs";
import { resolveManagedReaderScope, resolveManagedReaderSourceConnection } from "./reader-source-scope.mjs";
import { migrate } from "./schema.mjs";
import {
createWriterToken,
canMigrateManagedWriterBindingToDurable,
hashWriterToken,
materializeDataProductPublish,
materializeWriterBoundBatch,
normalizeManagedWriterBindingRequest,
normalizeWriterBindingRequest,
safeWriterBinding,
writerBindingRequestHash,
} from "./writer-binding.mjs";
const config = readConfig();
const managedProvisionerReplayCache = config.managedProvisionerApiEnabled
? new ManagedProvisionerReplayCache({ maxEntries: config.managedProvisionerReplayCacheMaxEntries })
: null;
const verifyManagedProvisionerRequest = config.managedProvisionerApiEnabled
? createManagedProvisionerRequestVerifier({
publicKey: config.managedProvisionerPublicKey,
serviceId: config.managedProvisionerServiceId,
keyId: config.managedProvisionerKeyId,
audience: config.managedProvisionerAudience,
maxSkewMs: config.managedProvisionerMaxSkewMs,
replayCache: managedProvisionerReplayCache,
})
: null;
const foundryProvisionerReplayCache = config.foundryProvisionerApiEnabled
? new ManagedProvisionerReplayCache({ maxEntries: config.foundryProvisionerReplayCacheMaxEntries })
: null;
const verifyFoundryProvisionerRequest = config.foundryProvisionerApiEnabled
? createManagedProvisionerRequestVerifier({
publicKey: config.foundryProvisionerPublicKey,
serviceId: config.foundryProvisionerServiceId,
keyId: config.foundryProvisionerKeyId,
audience: config.foundryProvisionerAudience,
maxSkewMs: config.foundryProvisionerMaxSkewMs,
replayCache: foundryProvisionerReplayCache,
})
: null;
const pool = new Pool({ connectionString: config.databaseUrl, max: config.databasePoolSize });
const app = express();
const httpServer = createServer(app);
let retentionSweepTimer = null;
let lastRetentionSweepAt = null;
const activeReaderStreams = new Map();
const activeStreamResponses = new Set();
let shuttingDown = false;
let shutdownPromise = null;
app.disable("x-powered-by");
app.use((req, _res, next) => {
req.rawBody = Buffer.alloc(0);
req.rawBodyCaptured = false;
next();
});
app.use(express.json({
limit: config.maxBatchBytes,
inflate: false,
verify(req, _res, buffer) {
req.rawBody = Buffer.from(buffer);
req.rawBodyCaptured = true;
},
}));
app.get("/healthz", asyncRoute(async (_req, res) => {
await pool.query("select 1");
res.json({
ok: true,
service: "nodedc-external-data-plane",
database: "ready",
internalApiConfigured: Boolean(config.internalAccessToken),
providerLogic: "absent",
commandTransport: "absent",
writerBindings: "supported",
managedWriterBindings: "digest+idempotent-generation+explicit-revoke",
readerBindings: "supported",
managedReaderBindings: "digest+idempotent-generation+explicit-revoke",
readerSourceScope: "writer-resolved+fail-closed-ambiguity",
dataProductDelivery: "snapshot+history+durable-patch",
legacyIntake: config.legacyIntakeEnabled ? "migration-only" : "disabled",
legacyCapabilityProvisioning: config.provisionerApiEnabled ? "enabled" : "disabled",
managedWriterBindingProvisioning: config.managedProvisionerApiEnabled ? "enabled" : "disabled",
managedWriterBindingLifetime: config.managedProvisionerApiEnabled ? "explicit-revoke" : "disabled",
managedReaderBindingProvisioning: config.managedProvisionerApiEnabled ? "enabled" : "disabled",
managedReaderBindingLifetime: config.managedProvisionerApiEnabled ? "explicit-revoke" : "disabled",
foundryReaderBindingProvisioning: config.foundryProvisionerApiEnabled ? "digest+server-resolved-source" : "disabled",
foundryReaderBindingLifetime: config.foundryProvisionerApiEnabled ? "explicit-revoke" : "disabled",
rawRetentionSweep: {
mode: "server-scheduled",
lastSweepAt: lastRetentionSweepAt,
},
});
}));
app.put("/internal/data-plane/v1/data-products/:dataProductId", requireProvisionerApi, asyncRoute(async (req, res) => {
const definition = normalizeDataProductDefinition({ ...req.body, id: req.params.dataProductId });
const saved = await persistDataProductDefinition(pool, definition);
res.json({ ok: true, dataProduct: safeDataProductDefinition(saved) });
}));
app.post("/internal/data-plane/v1/data-products/:dataProductId/publish", requireWriterBinding, asyncRoute(async (req, res) => {
if (hasScopeHeaders(req)) throw httpError(400, "data_product_publish_scope_headers_forbidden");
const validation = validateDataProductPublish(req.body, {
maxFacts: config.maxFactsPerPublish,
maxAttributesBytes: config.maxAttributesBytesPerFact,
});
if (!validation.ok) throw httpError(422, validation.errors[0] || "invalid_data_product_publish");
const dataProductId = requireIdentifier(req.params.dataProductId, "data_product_id_invalid");
const definition = await loadDataProductDefinition(pool, dataProductId);
if (!definition) throw httpError(404, "data_product_not_found");
const batch = materializeDataProductPublish(req.body, req.writerBinding, definition, dataProductId);
const canonicalValidation = validateIntakeBatch(batch);
if (!canonicalValidation.ok) throw httpError(422, "materialized_publish_invalid");
assertBatchTimeBounds(batch, { maxFutureSkewSeconds: config.maxFutureSkewSeconds });
const receipt = await persistDataProductPublish(pool, batch, definition, {
maxPatchOperations: config.maxPatchOperations,
maxPatchBytes: config.maxPatchBytes,
});
res.status(receipt.idempotent ? 200 : 201).json({ ok: true, ...receipt });
}));
app.get("/internal/data-plane/v1/writer/data-products", requireWriterBinding, asyncRoute(async (req, res) => {
const dataProducts = await listGrantedProducts(req.writerBinding);
res.json({ ok: true, dataProducts });
}));
app.get("/internal/data-plane/v1/reader/data-products", requireReaderBinding, asyncRoute(async (req, res) => {
const dataProducts = await listGrantedProducts(req.readerBinding);
res.json({ ok: true, dataProducts });
}));
app.post("/internal/data-plane/v1/intake", requireLegacyIntake, requireInternalApi, asyncRoute(async (req, res) => {
const validation = validateIntakeBatch(req.body);
if (!validation.ok) throw httpError(422, "invalid_intake_batch");
assertBatchTimeBounds(req.body, { maxFutureSkewSeconds: config.maxFutureSkewSeconds });
await assertLegacyBatchProduct(req.body);
const scope = requireScope(req);
if (scope.tenantId !== req.body.source.tenantId || scope.connectionId !== req.body.source.connectionId) {
throw httpError(403, "scope_mismatch");
}
const result = await persistBatch(req.body);
res.status(result.idempotent ? 200 : 201).json({ ok: true, ...result });
}));
app.post("/internal/data-plane/v1/intake/writer-bound", requireLegacyIntake, requireWriterBinding, asyncRoute(async (req, res) => {
const batch = materializeWriterBoundBatch(req.body, req.writerBinding, {
hasScopeHeaders: hasScopeHeaders(req),
});
const validation = validateIntakeBatch(batch);
if (!validation.ok) throw httpError(422, "invalid_intake_batch");
assertBatchTimeBounds(batch, { maxFutureSkewSeconds: config.maxFutureSkewSeconds });
await assertLegacyBatchProduct(batch);
const result = await persistBatch(batch);
res.status(result.idempotent ? 200 : 201).json({ ok: true, ...result });
}));
app.put("/internal/data-plane/v1/writer-bindings/by-key/:bindingKey", requireManagedProvisionerApi, asyncRoute(async (req, res) => {
const bindingKey = requireIdentifier(req.params.bindingKey, "managed_writer_binding_key_invalid");
const policy = normalizeManagedWriterBindingRequest(req.body);
await assertRegisteredProductIds(policy.allowedDataProductIds);
const requestHash = writerBindingRequestHash(policy);
const client = await pool.connect();
let response;
try {
await client.query("begin");
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
const existing = await client.query(
`select id, token_hash as "capabilityDigest", binding_key as "bindingKey", request_hash as "requestHash", generation,
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_writer_bindings
where binding_key = $1 and generation = $2
for update`,
[bindingKey, policy.generation],
);
if (existing.rowCount) {
const binding = existing.rows[0];
if (binding.requestHash !== requestHash) {
const legacyCanMigrate = canMigrateManagedWriterBindingToDurable(binding, policy);
if (!legacyCanMigrate) throw httpError(409, "managed_writer_binding_request_conflict");
const migrated = await client.query(
`update external_data_plane_writer_bindings
set request_hash = $3, expires_at = null, rotated_at = now()
where binding_key = $1 and generation = $2 and token_hash = $4
and active = true and expires_at > now()
returning id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingKey, policy.generation, requestHash, policy.capabilityDigest],
);
if (!migrated.rowCount) throw httpError(409, "managed_writer_binding_generation_inactive");
response = { status: 200, idempotent: false, migrated: true, binding: migrated.rows[0] };
} else {
if (binding.active !== true || binding.expiresAt !== null) {
throw httpError(409, "managed_writer_binding_generation_inactive");
}
response = { status: 200, idempotent: true, migrated: false, binding };
}
} else {
const inserted = await client.query(
`insert into external_data_plane_writer_bindings (
id, token_hash, binding_key, request_hash, generation,
tenant_id, connection_id, provider_id, allowed_data_product_ids, expires_at
) values ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10)
returning id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[
randomUUID(),
policy.capabilityDigest,
bindingKey,
requestHash,
policy.generation,
policy.tenantId,
policy.connectionId,
policy.providerId,
JSON.stringify(policy.allowedDataProductIds),
policy.expiresAt,
],
);
response = { status: 201, idempotent: false, binding: inserted.rows[0] };
}
await client.query("commit");
} catch (error) {
await client.query("rollback");
if (error?.code === "23505") {
throw httpError(409, "managed_writer_binding_capability_digest_conflict");
}
throw error;
} finally {
client.release();
}
res.set("Cache-Control", "no-store, max-age=0");
res.status(response.status).json({
ok: true,
idempotent: response.idempotent,
migrated: response.migrated === true,
writerBinding: safeWriterBinding(response.binding),
});
}));
app.post("/internal/data-plane/v1/writer-bindings/by-key/:bindingKey/generations/:generation/revoke", requireManagedProvisionerApi, asyncRoute(async (req, res) => {
const bindingKey = requireIdentifier(req.params.bindingKey, "managed_writer_binding_key_invalid");
const generation = requirePositiveInteger(req.params.generation, "managed_writer_binding_generation_invalid");
const client = await pool.connect();
let binding;
let idempotent;
try {
await client.query("begin");
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
const existing = await client.query(
`select id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_writer_bindings
where binding_key = $1 and generation = $2
for update`,
[bindingKey, generation],
);
if (!existing.rowCount) throw httpError(404, "managed_writer_binding_not_found");
if (existing.rows[0].active === true) {
const revoked = await client.query(
`update external_data_plane_writer_bindings
set active = false, revoked_at = now()
where binding_key = $1 and generation = $2 and active = true
returning id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingKey, generation],
);
binding = revoked.rows[0];
idempotent = false;
} else {
binding = existing.rows[0];
idempotent = true;
}
await client.query("commit");
} catch (error) {
await client.query("rollback");
throw error;
} finally {
client.release();
}
res.set("Cache-Control", "no-store, max-age=0");
res.json({ ok: true, idempotent, writerBinding: safeWriterBinding(binding) });
}));
app.post("/internal/data-plane/v1/writer-bindings", requireProvisionerApi, asyncRoute(async (req, res) => {
const policy = normalizeWriterBindingRequest(req.body, {
maxTtlDays: config.writerBindingMaxTtlDays,
});
await assertRegisteredProductIds(policy.allowedDataProductIds);
const token = createWriterToken();
const bindingId = randomUUID();
const result = await pool.query(
`insert into external_data_plane_writer_bindings (
id, token_hash, tenant_id, connection_id, provider_id,
allowed_data_product_ids, expires_at
) values ($1, $2, $3, $4, $5, $6::jsonb, $7)
returning id, tenant_id as "tenantId", connection_id as "connectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[
bindingId,
hashWriterToken(token),
policy.tenantId,
policy.connectionId,
policy.providerId,
JSON.stringify(policy.allowedDataProductIds),
policy.expiresAt,
],
);
// The token is intentionally returned exactly once to a trusted provisioner.
// It must be placed directly into an opaque Engine credential reference and
// must never be logged, stored in L2 graph data or shown to a consumer.
sendOneTimeCapability(res, 201, { ok: true, writerBinding: safeWriterBinding(result.rows[0]), token });
}));
app.post("/internal/data-plane/v1/writer-bindings/:bindingId/rotate", requireProvisionerApi, asyncRoute(async (req, res) => {
const bindingId = requireUuid(req.params.bindingId, "writer_binding_id_invalid");
const token = createWriterToken();
const result = await pool.query(
`update external_data_plane_writer_bindings
set token_hash = $2, rotated_at = now()
where id = $1 and binding_key is null and active = true and expires_at > now()
returning id, tenant_id as "tenantId", connection_id as "connectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingId, hashWriterToken(token)],
);
if (!result.rowCount) throw httpError(404, "writer_binding_not_found_or_inactive");
sendOneTimeCapability(res, 200, { ok: true, writerBinding: safeWriterBinding(result.rows[0]), token });
}));
app.post("/internal/data-plane/v1/writer-bindings/:bindingId/revoke", requireProvisionerApi, asyncRoute(async (req, res) => {
const bindingId = requireUuid(req.params.bindingId, "writer_binding_id_invalid");
const result = await pool.query(
`update external_data_plane_writer_bindings
set active = false, revoked_at = now()
where id = $1 and binding_key is null and active = true
returning id, tenant_id as "tenantId", connection_id as "connectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingId],
);
if (!result.rowCount) throw httpError(404, "writer_binding_not_found_or_inactive");
res.json({ ok: true, writerBinding: safeWriterBinding(result.rows[0]) });
}));
app.post("/internal/data-plane/v1/consumer-reader-bindings/plan", requireFoundryProvisionerApi, asyncRoute(async (req, res) => {
const policy = normalizeManagedConsumerReaderPlanRequest(req.body);
await assertRegisteredProductIds(policy.allowedDataProductIds);
await resolveManagedReaderScope(pool, policy.allowedDataProductIds);
const dataProducts = [];
for (const dataProductId of policy.allowedDataProductIds) {
const definition = await loadDataProductDefinition(pool, dataProductId);
if (!definition || definition.active === false) throw httpError(404, "data_product_not_found");
dataProducts.push(safeDataProductDefinition(definition));
}
res.set("Cache-Control", "no-store, max-age=0");
res.json({ ok: true, sourceScope: "resolved-server-side", dataProducts });
}));
app.put("/internal/data-plane/v1/consumer-reader-bindings/by-key/:bindingKey", requireFoundryProvisionerApi, asyncRoute(async (req, res) => {
const bindingKey = requireIdentifier(req.params.bindingKey, "managed_consumer_reader_binding_key_invalid");
const policy = normalizeManagedConsumerReaderBindingRequest(req.body);
await assertRegisteredProductIds(policy.allowedDataProductIds);
const requestHash = consumerReaderBindingRequestHash(policy);
const client = await pool.connect();
let response;
try {
await client.query("begin");
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
const existing = await client.query(
`select id, binding_key as "bindingKey", request_hash as "requestHash", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_reader_bindings
where binding_key = $1 and generation = $2
for update`,
[bindingKey, policy.generation],
);
if (existing.rowCount) {
const binding = existing.rows[0];
if (binding.requestHash !== requestHash) throw httpError(409, "managed_consumer_reader_binding_request_conflict");
if (binding.active !== true || binding.expiresAt !== null) {
throw httpError(409, "managed_consumer_reader_binding_generation_inactive");
}
response = { status: 200, idempotent: true, binding };
} else {
const source = await resolveManagedReaderScope(client, policy.allowedDataProductIds);
const inserted = await client.query(
`insert into external_data_plane_reader_bindings (
id, token_hash, binding_key, request_hash, generation,
tenant_id, connection_id, source_connection_id, provider_id,
allowed_data_product_ids, expires_at
) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11)
returning id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[
randomUUID(),
policy.capabilityDigest,
bindingKey,
requestHash,
policy.generation,
source.tenantId,
source.connectionId,
source.connectionId,
source.providerId,
JSON.stringify(policy.allowedDataProductIds),
policy.expiresAt,
],
);
response = { status: 201, idempotent: false, binding: inserted.rows[0] };
}
await client.query("commit");
} catch (error) {
await client.query("rollback");
if (error?.code === "23505") throw httpError(409, "managed_consumer_reader_capability_digest_conflict");
throw error;
} finally {
client.release();
}
res.set("Cache-Control", "no-store, max-age=0");
res.status(response.status).json({
ok: true,
idempotent: response.idempotent,
readerBinding: safeManagedConsumerReaderBinding(response.binding),
});
}));
app.post("/internal/data-plane/v1/consumer-reader-bindings/by-key/:bindingKey/generations/:generation/revoke", requireFoundryProvisionerApi, asyncRoute(async (req, res) => {
const bindingKey = requireIdentifier(req.params.bindingKey, "managed_consumer_reader_binding_key_invalid");
const generation = requirePositiveInteger(req.params.generation, "managed_consumer_reader_generation_invalid");
const client = await pool.connect();
let binding;
let idempotent;
try {
await client.query("begin");
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
const existing = await client.query(
`select id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_reader_bindings
where binding_key = $1 and generation = $2
for update`,
[bindingKey, generation],
);
if (!existing.rowCount) throw httpError(404, "managed_consumer_reader_binding_not_found");
if (existing.rows[0].active === true) {
const revoked = await client.query(
`update external_data_plane_reader_bindings
set active = false, revoked_at = now()
where binding_key = $1 and generation = $2 and active = true
returning id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingKey, generation],
);
binding = revoked.rows[0];
idempotent = false;
} else {
binding = existing.rows[0];
idempotent = true;
}
await client.query("commit");
} catch (error) {
await client.query("rollback");
throw error;
} finally {
client.release();
}
res.set("Cache-Control", "no-store, max-age=0");
res.json({ ok: true, idempotent, readerBinding: safeManagedConsumerReaderBinding(binding) });
}));
app.put("/internal/data-plane/v1/reader-bindings/by-key/:bindingKey", requireManagedProvisionerApi, asyncRoute(async (req, res) => {
const bindingKey = requireIdentifier(req.params.bindingKey, "managed_reader_binding_key_invalid");
const policy = normalizeManagedReaderBindingRequest(req.body);
await assertRegisteredProductIds(policy.allowedDataProductIds);
const requestHash = readerBindingRequestHash(policy);
const client = await pool.connect();
let response;
try {
await client.query("begin");
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
const existing = await client.query(
`select id, binding_key as "bindingKey", request_hash as "requestHash", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_reader_bindings
where binding_key = $1 and generation = $2
for update`,
[bindingKey, policy.generation],
);
if (existing.rowCount) {
let binding = existing.rows[0];
if (binding.requestHash !== requestHash) {
throw httpError(409, "managed_reader_binding_request_conflict");
}
if (binding.active !== true || binding.expiresAt !== null) {
throw httpError(409, "managed_reader_binding_generation_inactive");
}
if (!binding.sourceConnectionId) {
const sourceConnectionId = await resolveManagedReaderSourceConnection(client, policy);
const resolved = await client.query(
`update external_data_plane_reader_bindings
set source_connection_id = $3
where binding_key = $1 and generation = $2 and source_connection_id is null
returning id, binding_key as "bindingKey", request_hash as "requestHash", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingKey, policy.generation, sourceConnectionId],
);
binding = resolved.rows[0] || binding;
}
response = { status: 200, idempotent: true, binding };
} else {
const sourceConnectionId = await resolveManagedReaderSourceConnection(client, policy);
const inserted = await client.query(
`insert into external_data_plane_reader_bindings (
id, token_hash, binding_key, request_hash, generation,
tenant_id, connection_id, source_connection_id, provider_id,
allowed_data_product_ids, expires_at
) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11)
returning id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[
randomUUID(),
policy.capabilityDigest,
bindingKey,
requestHash,
policy.generation,
policy.tenantId,
policy.connectionId,
sourceConnectionId,
policy.providerId,
JSON.stringify(policy.allowedDataProductIds),
policy.expiresAt,
],
);
response = { status: 201, idempotent: false, binding: inserted.rows[0] };
}
await client.query("commit");
} catch (error) {
await client.query("rollback");
if (error?.code === "23505") {
throw httpError(409, "managed_reader_binding_capability_digest_conflict");
}
throw error;
} finally {
client.release();
}
res.set("Cache-Control", "no-store, max-age=0");
res.status(response.status).json({
ok: true,
idempotent: response.idempotent,
readerBinding: safeReaderBinding(response.binding),
});
}));
app.post("/internal/data-plane/v1/reader-bindings/by-key/:bindingKey/generations/:generation/revoke", requireManagedProvisionerApi, asyncRoute(async (req, res) => {
const bindingKey = requireIdentifier(req.params.bindingKey, "managed_reader_binding_key_invalid");
const generation = requirePositiveInteger(req.params.generation, "managed_reader_binding_generation_invalid");
const client = await pool.connect();
let binding;
let idempotent;
try {
await client.query("begin");
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
const existing = await client.query(
`select id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_reader_bindings
where binding_key = $1 and generation = $2
for update`,
[bindingKey, generation],
);
if (!existing.rowCount) throw httpError(404, "managed_reader_binding_not_found");
if (existing.rows[0].active === true) {
const revoked = await client.query(
`update external_data_plane_reader_bindings
set active = false, revoked_at = now()
where binding_key = $1 and generation = $2 and active = true
returning id, binding_key as "bindingKey", generation,
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId", provider_id as "providerId",
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingKey, generation],
);
binding = revoked.rows[0];
idempotent = false;
} else {
binding = existing.rows[0];
idempotent = true;
}
await client.query("commit");
} catch (error) {
await client.query("rollback");
throw error;
} finally {
client.release();
}
res.set("Cache-Control", "no-store, max-age=0");
res.json({ ok: true, idempotent, readerBinding: safeReaderBinding(binding) });
}));
app.post("/internal/data-plane/v1/reader-bindings", requireProvisionerApi, asyncRoute(async (req, res) => {
const policy = normalizeReaderBindingRequest(req.body, {
maxTtlDays: config.writerBindingMaxTtlDays,
});
await assertRegisteredProductIds(policy.allowedDataProductIds);
const token = createReaderToken();
const bindingId = randomUUID();
const result = await pool.query(
`insert into external_data_plane_reader_bindings (
id, token_hash, tenant_id, connection_id, source_connection_id, provider_id,
allowed_data_product_ids, expires_at
) values ($1, $2, $3, $4, $4, $5, $6::jsonb, $7)
returning id, tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[
bindingId,
hashReaderToken(token),
policy.tenantId,
policy.connectionId,
policy.providerId,
JSON.stringify(policy.allowedDataProductIds),
policy.expiresAt,
],
);
sendOneTimeCapability(res, 201, { ok: true, readerBinding: safeReaderBinding(result.rows[0]), token });
}));
app.post("/internal/data-plane/v1/reader-bindings/:bindingId/rotate", requireProvisionerApi, asyncRoute(async (req, res) => {
const bindingId = requireUuid(req.params.bindingId, "reader_binding_id_invalid");
const token = createReaderToken();
const result = await pool.query(
`update external_data_plane_reader_bindings
set token_hash = $2, rotated_at = now()
where id = $1 and binding_key is null and active = true and expires_at > now()
returning id, tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingId, hashReaderToken(token)],
);
if (!result.rowCount) throw httpError(404, "reader_binding_not_found_or_inactive");
sendOneTimeCapability(res, 200, { ok: true, readerBinding: safeReaderBinding(result.rows[0]), token });
}));
app.post("/internal/data-plane/v1/reader-bindings/:bindingId/revoke", requireProvisionerApi, asyncRoute(async (req, res) => {
const bindingId = requireUuid(req.params.bindingId, "reader_binding_id_invalid");
const result = await pool.query(
`update external_data_plane_reader_bindings
set active = false, revoked_at = now()
where id = $1 and binding_key is null and active = true
returning id, tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
[bindingId],
);
if (!result.rowCount) throw httpError(404, "reader_binding_not_found_or_inactive");
res.json({ ok: true, readerBinding: safeReaderBinding(result.rows[0]) });
}));
app.get("/internal/data-plane/v1/data-products/:dataProductId/snapshot", requireReaderBinding, asyncRoute(async (req, res) => {
if (hasScopeHeaders(req)) throw httpError(400, "data_product_read_scope_headers_forbidden");
const dataProductId = assertReaderProduct(req.readerBinding, req.params.dataProductId);
const definition = await loadDataProductDefinition(pool, dataProductId);
if (!definition) throw httpError(404, "data_product_not_found");
const limit = boundedLimit(req.query.limit, 5000, 1, 5000);
const snapshot = await readDataProductSnapshot(pool, req.readerBinding, definition, { limit });
res.json(snapshot);
}));
app.get("/internal/data-plane/v1/data-products/:dataProductId/history", requireReaderBinding, asyncRoute(async (req, res) => {
if (hasScopeHeaders(req)) throw httpError(400, "data_product_read_scope_headers_forbidden");
const dataProductId = assertReaderProduct(req.readerBinding, req.params.dataProductId);
const definition = await loadDataProductDefinition(pool, dataProductId);
if (!definition) throw httpError(404, "data_product_not_found");
const history = await readDataProductHistory(pool, req.readerBinding, definition, {
from: req.query.from,
to: req.query.to,
resolutionMs: req.query.resolutionMs,
sourceIds: parseSourceIds(req.query.sourceIds),
limit: req.query.limit,
cursor: req.query.cursor,
});
res.json(history);
}));
app.get("/internal/data-plane/v1/data-products/:dataProductId/stream", requireReaderBinding, asyncRoute(async (req, res) => {
if (shuttingDown) throw httpError(503, "service_shutting_down");
if (hasScopeHeaders(req)) throw httpError(400, "data_product_read_scope_headers_forbidden");
const dataProductId = assertReaderProduct(req.readerBinding, req.params.dataProductId);
const definition = await loadDataProductDefinition(pool, dataProductId);
if (!definition) throw httpError(404, "data_product_not_found");
if (definition.deliveryMode !== "snapshot+patch") throw httpError(409, "data_product_stream_not_supported");
const streamCount = activeReaderStreams.get(req.readerBinding.id) || 0;
if (streamCount >= config.maxReaderStreams) throw httpError(429, "reader_stream_limit_exceeded");
activeReaderStreams.set(req.readerBinding.id, streamCount + 1);
activeStreamResponses.add(res);
let poll = null;
let heartbeat = null;
let polling = false;
let heartbeatWriting = false;
let cleanedUp = false;
const cleanup = () => {
if (cleanedUp) return;
cleanedUp = true;
if (poll) clearInterval(poll);
if (heartbeat) clearInterval(heartbeat);
activeStreamResponses.delete(res);
const remaining = Math.max(0, (activeReaderStreams.get(req.readerBinding.id) || 1) - 1);
if (remaining) activeReaderStreams.set(req.readerBinding.id, remaining);
else activeReaderStreams.delete(req.readerBinding.id);
};
req.once("close", cleanup);
res.once("close", cleanup);
try {
let cursor = parseCursor(req.get("last-event-id") || req.query.after || "0");
const initialEvents = await readPatchEvents(pool, req.readerBinding, definition, cursor);
if (res.destroyed || cleanedUp) return;
res.status(200);
res.set({
"Cache-Control": "no-cache, no-transform",
"Content-Type": "text/event-stream",
Connection: "keep-alive",
"X-Accel-Buffering": "no",
});
res.flushHeaders();
await writeSseFrame(res, ": nodedc-data-product-stream\n\n");
await writeSseFrame(res, `event: nodedc.data-product.ready.v1\ndata: ${JSON.stringify({
schemaVersion: "nodedc.data-product.ready/v1",
dataProductId,
cursor: cursor.toString(),
emittedAt: new Date().toISOString(),
})}\n\n`);
for (const event of initialEvents) {
await writePatchEvent(res, event);
cursor = BigInt(event.cursor);
}
const pump = async () => {
if (polling || res.writableEnded || res.destroyed || shuttingDown) return;
polling = true;
try {
await assertReaderStreamAccess(req.readerBinding, dataProductId);
const events = await readPatchEvents(pool, req.readerBinding, definition, cursor);
for (const event of events) {
await writePatchEvent(res, event);
cursor = BigInt(event.cursor);
}
} catch (error) {
const code = safeErrorCode(error);
if (!res.writableEnded && !res.destroyed) {
await writeSseFrame(res, `event: error\ndata: ${JSON.stringify({ ok: false, error: code })}\n\n`).catch(() => {});
res.end();
}
} finally {
polling = false;
}
};
poll = setInterval(() => { void pump(); }, config.streamPollMs);
poll.unref();
heartbeat = setInterval(() => {
if (polling || heartbeatWriting || res.writableEnded || res.destroyed) return;
heartbeatWriting = true;
void writeSseFrame(res, `: heartbeat ${Date.now()}\n\n`)
.catch(() => { if (!res.writableEnded) res.end(); })
.finally(() => { heartbeatWriting = false; });
}, config.streamHeartbeatMs);
heartbeat.unref();
} catch (error) {
cleanup();
if (res.headersSent) {
if (!res.writableEnded) res.end();
return;
}
throw error;
}
}));
app.get("/internal/data-plane/v1/data-products/:dataProductId/current", requireLegacyIntake, requireInternalApi, asyncRoute(async (req, res) => {
const scope = requireScope(req);
const dataProductId = String(req.params.dataProductId || "");
if (!isIdentifier(dataProductId)) throw httpError(400, "data_product_id_invalid");
const limit = boundedLimit(req.query.limit, 200, 1, 1000);
const rows = await pool.query(
`select
provider_id as "providerId", data_product_id as "dataProductId",
source_id as "sourceId", semantic_type as "semanticType",
observed_at as "observedAt", received_at as "receivedAt", attributes,
case when geometry is null then null
else jsonb_build_object('type', 'Point', 'coordinates', jsonb_build_array(
ST_X(geometry::geometry), ST_Y(geometry::geometry)
)) end as geometry
from external_data_plane_current
where tenant_id = $1 and connection_id = $2 and data_product_id = $3
order by observed_at desc, source_id asc
limit $4`,
[scope.tenantId, scope.connectionId, dataProductId, limit],
);
res.json({
ok: true,
dataProductId,
tenantId: scope.tenantId,
connectionId: scope.connectionId,
facts: rows.rows,
});
}));
app.get("/internal/data-plane/v1/status", requireLegacyIntake, requireInternalApi, asyncRoute(async (req, res) => {
const scope = requireScope(req);
const result = await pool.query(
`select count(*)::integer as "currentFactCount", max(received_at) as "lastReceivedAt"
from external_data_plane_current where tenant_id = $1 and connection_id = $2`,
[scope.tenantId, scope.connectionId],
);
res.json({ ok: true, ...scope, ...result.rows[0], providerLogic: "absent", commandTransport: "absent" });
}));
app.use((error, _req, res, _next) => {
const status = Number(error?.status || 500);
const publicStatus = status >= 400 && status < 600 ? status : 500;
console.error(JSON.stringify({ event: "external_data_plane_error", error: safeErrorCode(error), status: publicStatus }));
res.status(publicStatus).json({ ok: false, error: publicStatus >= 500 ? "internal_error" : safeErrorCode(error) });
});
await migrate(pool);
const bundledDataProducts = await reconcileDataProductDefinitions(pool);
retentionSweepTimer = setInterval(() => {
void sweepRetention().catch((error) => {
console.error(JSON.stringify({ event: "external_data_plane_retention_sweep_failed", error: safeErrorCode(error) }));
});
}, config.retentionSweepMs);
retentionSweepTimer.unref();
httpServer.listen(config.port, "0.0.0.0", () => {
console.log(`NODE.DC External Data Plane listening on http://0.0.0.0:${config.port}`);
console.log(JSON.stringify({ event: "external_data_plane_definitions_ready", count: bundledDataProducts.length }));
setImmediate(() => {
void sweepRetention().catch((error) => {
console.error(JSON.stringify({ event: "external_data_plane_retention_sweep_failed", error: safeErrorCode(error) }));
});
});
});
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
async function persistBatch(batch) {
const client = await pool.connect();
try {
await client.query("begin");
const existing = await client.query(
`select id, fact_count as "factCount", inserted_fact_count as "insertedFactCount"
from external_data_plane_batches
where tenant_id = $1 and connection_id = $2 and provider_id = $3
and data_product_id = $4 and idempotency_key = $5
for update`,
[
batch.source.tenantId, batch.source.connectionId, batch.source.providerId,
batch.contract.dataProductId, batch.batch.idempotencyKey,
],
);
if (existing.rowCount) {
await client.query("commit");
return { batchId: existing.rows[0].id, idempotent: true, ...existing.rows[0] };
}
const batchId = randomUUID();
await client.query(
`insert into external_data_plane_batches (
id, tenant_id, connection_id, provider_id, data_product_id, contract_version,
ontology_revision, run_id, sequence, idempotency_key, received_at, fact_count
) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
[
batchId, batch.source.tenantId, batch.source.connectionId, batch.source.providerId,
batch.contract.dataProductId, batch.contract.version, batch.contract.ontologyRevision,
batch.batch.runId, batch.batch.sequence, batch.batch.idempotencyKey,
batch.batch.receivedAt, batch.facts.length,
],
);
if (batch.raw) await persistRawEnvelope(client, batchId, batch);
const records = batch.facts.map((fact) => ({
id: randomUUID(),
source_id: fact.sourceId,
semantic_type: fact.semanticType,
observed_at: fact.observedAt,
attributes: fact.attributes || {},
geometry: fact.geometry || null,
fingerprint: hash(fact),
}));
const inserted = await client.query(
`insert into external_data_plane_facts (
id, batch_id, tenant_id, connection_id, provider_id, data_product_id,
source_id, semantic_type, observed_at, received_at, attributes, geometry, fingerprint
)
select item.id::uuid, $1, $2, $3, $4, $5,
item.source_id, item.semantic_type, item.observed_at, $6,
coalesce(item.attributes, '{}'::jsonb),
case when item.geometry is null then null
else ST_SetSRID(ST_MakePoint(
(item.geometry->'coordinates'->>0)::double precision,
(item.geometry->'coordinates'->>1)::double precision
), 4326)::geography end,
item.fingerprint
from jsonb_to_recordset($7::jsonb) as item(
id text, source_id text, semantic_type text, observed_at timestamptz,
attributes jsonb, geometry jsonb, fingerprint text
)
on conflict do nothing
returning id`,
[
batchId, batch.source.tenantId, batch.source.connectionId, batch.source.providerId,
batch.contract.dataProductId, batch.batch.receivedAt, JSON.stringify(records),
],
);
await client.query(
`insert into external_data_plane_current (
tenant_id, connection_id, provider_id, data_product_id, source_id, semantic_type,
observed_at, received_at, attributes, geometry, fingerprint, updated_at
)
select tenant_id, connection_id, provider_id, data_product_id, source_id, semantic_type,
observed_at, received_at, attributes, geometry, fingerprint, now()
from external_data_plane_facts where batch_id = $1
on conflict (tenant_id, connection_id, provider_id, data_product_id, source_id, semantic_type)
do update set
observed_at = excluded.observed_at,
received_at = excluded.received_at,
attributes = excluded.attributes,
geometry = excluded.geometry,
fingerprint = excluded.fingerprint,
updated_at = now()
where excluded.observed_at >= external_data_plane_current.observed_at`,
[batchId],
);
await client.query(
"update external_data_plane_batches set inserted_fact_count = $2 where id = $1",
[batchId, inserted.rowCount],
);
await client.query("commit");
return { batchId, idempotent: false, factCount: batch.facts.length, insertedFactCount: inserted.rowCount };
} catch (error) {
await client.query("rollback");
throw error;
} finally {
client.release();
}
}
async function persistRawEnvelope(client, batchId, batch) {
const serialized = batch.raw.payload === undefined ? null : JSON.stringify(batch.raw.payload);
const expiresAt = rawRetentionExpiry({ rawRetentionDays: config.rawRetentionDays });
await client.query(
`insert into external_data_plane_raw_envelopes (
id, batch_id, payload_hash, content_type, payload, payload_ref, payload_bytes, received_at, expires_at
) values ($1, $2, $3, $4, $5::jsonb, $6, $7, $8, $9)`,
[
randomUUID(), batchId, batch.raw.hash || hash(batch.raw.payload), batch.raw.contentType, serialized, batch.raw.ref || null,
serialized ? Buffer.byteLength(serialized) : null, batch.batch.receivedAt, expiresAt,
],
);
}
function requireInternalApi(req, _res, next) {
if (!config.internalAccessToken) return next(httpError(503, "internal_api_not_configured"));
const value = bearerToken(req);
if (!value || !safeEqual(value, config.internalAccessToken)) return next(httpError(401, "unauthorized"));
return next();
}
function requireLegacyIntake(_req, _res, next) {
if (!config.legacyIntakeEnabled) return next(httpError(410, "legacy_intake_disabled"));
return next();
}
function requireProvisionerApi(req, _res, next) {
if (!config.provisionerApiEnabled) return next(httpError(503, "provisioner_api_disabled"));
return requireProvisionerCredential(req, next);
}
function requireManagedProvisionerApi(req, _res, next) {
if (!config.managedProvisionerApiEnabled) return next(httpError(503, "managed_provisioner_api_disabled"));
if (!verifyManagedProvisionerRequest) return next(httpError(503, "managed_provisioner_api_not_configured"));
try {
req.managedProvisionerIdentity = verifyManagedProvisionerRequest(req);
return next();
} catch (error) {
return next(error);
}
}
function requireFoundryProvisionerApi(req, _res, next) {
if (!config.foundryProvisionerApiEnabled) return next(httpError(503, "foundry_provisioner_api_disabled"));
if (!verifyFoundryProvisionerRequest) return next(httpError(503, "foundry_provisioner_api_not_configured"));
try {
req.managedProvisionerIdentity = verifyFoundryProvisionerRequest(req);
return next();
} catch (error) {
return next(error);
}
}
function requireProvisionerCredential(req, next) {
if (!config.provisionerAccessToken) return next(httpError(503, "provisioner_api_not_configured"));
const value = bearerToken(req);
if (!value || !safeEqual(value, config.provisionerAccessToken)) return next(httpError(401, "provisioner_unauthorized"));
return next();
}
function requireWriterBinding(req, _res, next) {
return resolveWriterBinding(req)
.then((binding) => {
req.writerBinding = binding;
next();
})
.catch(next);
}
function requireReaderBinding(req, _res, next) {
return resolveReaderBinding(req)
.then((binding) => {
req.readerBinding = binding;
next();
})
.catch(next);
}
async function resolveWriterBinding(req) {
const token = bearerToken(req);
if (!token) throw httpError(401, "writer_binding_unauthorized");
const result = await pool.query(
`select id, tenant_id as "tenantId", connection_id as "connectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_writer_bindings
where token_hash = $1 and active = true
and (expires_at is null or expires_at > now())`,
[hashWriterToken(token)],
);
if (!result.rowCount) throw httpError(401, "writer_binding_unauthorized");
return result.rows[0];
}
async function resolveReaderBinding(req) {
const token = bearerToken(req);
if (!token) throw httpError(401, "reader_binding_unauthorized");
const result = await pool.query(
`select id, binding_key as "bindingKey", token_hash as "tokenHash",
tenant_id as "tenantId", connection_id as "connectionId",
source_connection_id as "sourceConnectionId",
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
expires_at as "expiresAt", active, created_at as "createdAt",
rotated_at as "rotatedAt", revoked_at as "revokedAt"
from external_data_plane_reader_bindings
where token_hash = $1 and active = true
and (expires_at is null or expires_at > now())`,
[hashReaderToken(token)],
);
if (!result.rowCount) throw httpError(401, "reader_binding_unauthorized");
if (result.rows[0].bindingKey && !result.rows[0].sourceConnectionId) {
throw httpError(409, "reader_source_scope_unresolved");
}
return result.rows[0];
}
async function assertReaderStreamAccess(binding, dataProductId) {
const result = await pool.query(
`select binding.id
from external_data_plane_reader_bindings as binding
join external_data_plane_products as product
on product.id = $3 and product.active = true
where binding.id = $1 and binding.token_hash = $2
and binding.active = true
and (binding.expires_at is null or binding.expires_at > now())
and binding.allowed_data_product_ids ? $3`,
[binding.id, binding.tokenHash, dataProductId],
);
if (!result.rowCount) throw httpError(401, "reader_stream_access_revoked");
}
async function assertLegacyBatchProduct(batch) {
const definition = await loadDataProductDefinition(pool, batch.contract.dataProductId);
if (!definition) throw httpError(422, "legacy_data_product_not_registered");
if (definition.version !== batch.contract.version || definition.ontologyRevision !== batch.contract.ontologyRevision) {
throw httpError(422, "legacy_data_product_contract_mismatch");
}
assertPublishMatchesDefinition(batch, definition);
}
async function listGrantedProducts(binding) {
const allowed = Array.isArray(binding.allowedDataProductIds) ? binding.allowedDataProductIds : [];
if (!allowed.length) return [];
const result = await pool.query(
`select id, version, ontology_revision as "ontologyRevision",
delivery_mode as "deliveryMode", semantic_types as "semanticTypes",
fields, field_contracts as "fieldContracts", history_policy as "historyPolicy", active,
created_at as "createdAt", updated_at as "updatedAt"
from external_data_plane_products
where active = true and id = any($1::text[])
order by id asc`,
[allowed],
);
return result.rows.map(safeDataProductDefinition);
}
async function assertRegisteredProductIds(dataProductIds) {
const result = await pool.query(
"select id from external_data_plane_products where active = true and id = any($1::text[])",
[dataProductIds],
);
const registered = new Set(result.rows.map((row) => row.id));
if (dataProductIds.some((id) => !registered.has(id))) throw httpError(422, "binding_data_product_not_registered");
}
function requireScope(req) {
const tenantId = String(req.get("x-nodedc-tenant-id") || "");
const connectionId = String(req.get("x-nodedc-connection-id") || "");
if (!isIdentifier(tenantId) || !isIdentifier(connectionId)) throw httpError(400, "scope_headers_required");
return { tenantId, connectionId };
}
function hasScopeHeaders(req) {
return Object.hasOwn(req.headers, "x-nodedc-tenant-id") || Object.hasOwn(req.headers, "x-nodedc-connection-id");
}
function bearerToken(req) {
return String(req.get("authorization") || "").replace(/^Bearer\s+/i, "");
}
function requireUuid(value, code) {
const normalized = String(value || "");
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalized)) {
throw httpError(400, code);
}
return normalized;
}
function requireIdentifier(value, code) {
const normalized = String(value || "");
if (!isIdentifier(normalized)) throw httpError(400, code);
return normalized;
}
function requirePositiveInteger(value, code) {
const normalized = String(value ?? "");
if (!/^[1-9]\d*$/.test(normalized)) throw httpError(400, code);
const number = Number(normalized);
if (!Number.isSafeInteger(number) || number > 2_147_483_647) throw httpError(400, code);
return number;
}
function parseCursor(value) {
const normalized = String(value ?? "");
if (!/^(?:0|[1-9]\d*)$/.test(normalized)) throw httpError(400, "data_product_cursor_invalid");
const cursor = BigInt(normalized);
if (cursor > 9_223_372_036_854_775_807n) throw httpError(400, "data_product_cursor_invalid");
return cursor;
}
function parseSourceIds(value) {
if (value === undefined || value === null || value === "") return [];
const values = Array.isArray(value) ? value : String(value).split(",");
return values.map((item) => String(item || "").trim()).filter(Boolean);
}
function writePatchEvent(res, event) {
return writeSseFrame(res, `id: ${event.cursor}\nevent: nodedc.data-product.patch.v1\ndata: ${JSON.stringify(event)}\n\n`);
}
function writeSseFrame(res, frame) {
if (res.writableEnded || res.destroyed) return Promise.reject(httpError(499, "reader_stream_closed"));
if (res.write(frame)) return Promise.resolve();
return new Promise((resolve, reject) => {
const cleanup = () => {
res.off("drain", onDrain);
res.off("close", onClose);
res.off("error", onError);
};
const onDrain = () => { cleanup(); resolve(); };
const onClose = () => { cleanup(); reject(httpError(499, "reader_stream_closed")); };
const onError = (error) => { cleanup(); reject(error); };
res.once("drain", onDrain);
res.once("close", onClose);
res.once("error", onError);
});
}
function sendOneTimeCapability(res, status, payload) {
// Create/rotate is the only boundary where a capability exists in
// plaintext. A trusted provisioner must consume the response in memory and
// place it directly into its opaque destination; intermediaries must never
// cache or persist it.
res.set({
"Cache-Control": "no-store, max-age=0",
Pragma: "no-cache",
Expires: "0",
});
return res.status(status).json(payload);
}
function safeEqual(left, right) {
const leftBuffer = Buffer.from(left);
const rightBuffer = Buffer.from(right);
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
}
function boundedLimit(value, fallback, min, max) {
const candidate = Number.parseInt(String(value ?? ""), 10);
if (!Number.isInteger(candidate)) return fallback;
return Math.max(min, Math.min(max, candidate));
}
function hash(value) {
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
}
function isIdentifier(value) {
return /^[a-z][a-z0-9._:-]{2,127}$/i.test(value);
}
function asyncRoute(handler) {
return (req, res, next) => Promise.resolve(handler(req, res, next)).catch(next);
}
function httpError(status, code) {
return Object.assign(new Error(code), { status, code });
}
function safeErrorCode(error) {
return String(error?.code || error?.message || "internal_error").replace(/[^a-z0-9_.:-]/gi, "_").slice(0, 120);
}
async function sweepExpiredRawEnvelopes() {
const result = await pool.query(
`with expired as (
select id from external_data_plane_raw_envelopes
where expires_at < now()
order by expires_at asc
limit $1
)
delete from external_data_plane_raw_envelopes as target
using expired where target.id = expired.id`,
[config.retentionDeleteLimit],
);
return result.rowCount;
}
async function sweepRetention() {
const rawEnvelopeCount = await sweepExpiredRawEnvelopes();
const patchEventCount = await prunePatchOutbox(pool, {
retentionMs: config.patchRetentionMs,
limit: config.retentionDeleteLimit,
});
const historyFactCount = await pruneDataProductHistory(pool, { limit: config.retentionDeleteLimit });
const batchReceiptCount = await pruneBatchReceipts(pool, {
retentionMs: config.receiptRetentionMs,
limit: config.retentionDeleteLimit,
});
lastRetentionSweepAt = new Date().toISOString();
return { rawEnvelopeCount, patchEventCount, historyFactCount, batchReceiptCount };
}
async function shutdown() {
if (shutdownPromise) return shutdownPromise;
shutdownPromise = (async () => {
shuttingDown = true;
if (retentionSweepTimer) clearInterval(retentionSweepTimer);
for (const response of activeStreamResponses) {
if (!response.writableEnded) response.end();
}
const closed = new Promise((resolve) => httpServer.close(resolve));
httpServer.closeIdleConnections?.();
const forceClose = setTimeout(() => httpServer.closeAllConnections?.(), 250);
await Promise.race([closed, new Promise((resolve) => setTimeout(resolve, 2_000))]);
clearTimeout(forceClose);
httpServer.closeAllConnections?.();
await pool.end();
})();
return shutdownPromise;
}