feat(device-plane): add universal adapter acceptance boundary

This commit is contained in:
Codex
2026-08-11 19:11:48 +03:00
parent 227c7c26c1
commit 393741f1bd
26 changed files with 1887 additions and 127 deletions
@@ -1,7 +1,10 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createCoreDiscoveryClient } from "../src/core-client.mjs";
import {
createCoreDiscoveryClient,
createCoreGatewayClient,
} from "../src/core-client.mjs";
const gatewayToken = "test-only-gateway-token-with-32-bytes";
@@ -41,14 +44,14 @@ test("posts a discovery through the authenticated internal Core boundary", async
assert.equal(discovery.lifecycleState, "quarantine");
});
test("fails closed when Core does not return a quarantine view", async () => {
test("fails closed when Core does not return an accepted discovery view", async () => {
const observe = createCoreDiscoveryClient({
coreUrl: "http://device-control-core:18120",
gatewayToken,
fetchImpl: async () => new Response(JSON.stringify({
ok: true,
discovery: {
lifecycleState: "claimed",
lifecycleState: "observed",
commandTransport: "disabled",
},
}), { status: 200 }),
@@ -58,3 +61,58 @@ test("fails closed when Core does not return a quarantine view", async () => {
/device_gateway_core_ingest_contract_invalid/,
);
});
test("accepts a package only through the explicit Core acceptance contract", async () => {
let captured;
const client = createCoreGatewayClient({
coreUrl: "http://device-control-core:18120",
gatewayToken,
fetchImpl: async (url, options) => {
captured = { url, options };
const message = JSON.parse(options.body);
return new Response(JSON.stringify({
ok: true,
acceptance: {
schemaVersion: "nodedc.device-adapter-acceptance.v1",
acceptanceRef: "acceptance:test-001",
idempotencyKey: message.idempotencyKey,
status: "accepted",
replayed: false,
acceptedAt: "2026-08-11T12:00:00.000Z",
},
}), { status: 201 });
},
});
const message = { idempotencyKey: `sha256:${"a".repeat(64)}` };
const acceptance = await client.acceptMessage(message);
assert.equal(
captured.url,
"http://device-control-core:18120/internal/v1/gateway/messages:accept",
);
assert.equal(acceptance.status, "accepted");
assert.equal(acceptance.idempotencyKey, message.idempotencyKey);
});
test("rejects a mismatched or non-durable Core package response", async () => {
const client = createCoreGatewayClient({
coreUrl: "http://device-control-core:18120",
gatewayToken,
fetchImpl: async () => new Response(JSON.stringify({
ok: true,
acceptance: {
schemaVersion: "nodedc.device-adapter-acceptance.v1",
acceptanceRef: "acceptance:test-001",
idempotencyKey: `sha256:${"b".repeat(64)}`,
status: "accepted",
replayed: false,
acceptedAt: "2026-08-11T12:00:00.000Z",
},
}), { status: 201 }),
});
await assert.rejects(
() => client.acceptMessage({
idempotencyKey: `sha256:${"a".repeat(64)}`,
}),
/device_gateway_core_acceptance_mismatch/,
);
});
@@ -2,10 +2,13 @@ import assert from "node:assert/strict";
import { connect } from "node:net";
import test from "node:test";
import {
DEVICE_ADAPTER_CATALOG,
} from "../../../packages/device-adapter-catalog/src/index.mjs";
import {
createControlCoreApp,
} from "../../device-control-core/src/app.mjs";
import { createCoreDiscoveryClient } from "../src/core-client.mjs";
import { createCoreGatewayClient } from "../src/core-client.mjs";
import { createDeviceGatewayRuntime } from "../src/runtime.mjs";
const gatewayToken = "test-only-gateway-token-with-32-bytes";
@@ -21,6 +24,7 @@ const specificationPackage = Buffer.from(
test("B2 HEADER2 becomes a persisted masked quarantine discovery before ACK", async () => {
let stored;
let storedMessage;
const core = createControlCoreApp({
discoveryIngestEnabled: true,
gatewayToken,
@@ -37,11 +41,22 @@ test("B2 HEADER2 becomes a persisted masked quarantine discovery before ACK", as
},
};
},
acceptAdapterMessage: async (value) => {
storedMessage = value;
return {
schemaVersion: "nodedc.device-adapter-acceptance.v1",
acceptanceRef: "acceptance:integration-001",
idempotencyKey: value.safeView.idempotencyKey,
status: "accepted",
replayed: false,
acceptedAt: "2026-08-11T12:00:00.000Z",
};
},
},
});
await listen(core);
const coreAddress = core.address();
const observe = createCoreDiscoveryClient({
const observe = createCoreGatewayClient({
coreUrl: `http://127.0.0.1:${coreAddress.port}`,
gatewayToken,
});
@@ -51,8 +66,13 @@ test("B2 HEADER2 becomes a persisted masked quarantine discovery before ACK", as
tcpPort: 0,
listenEnabled: true,
publicIngressEnabled: true,
coreChannelAuthenticated: true,
adapterRegistry: DEVICE_ADAPTER_CATALOG.registry,
protocolProfileRef: DEVICE_ADAPTER_CATALOG.defaultProfileRef,
edgeRef: "edge:integration-001",
now: () => new Date(0x52db95de * 1000),
onDiscovery: observe,
onDiscovery: observe.observeDiscovery,
onMessage: observe.acceptMessage,
});
const addresses = await gateway.start();
try {
@@ -73,6 +93,12 @@ test("B2 HEADER2 becomes a persisted masked quarantine discovery before ACK", as
JSON.stringify(stored).includes("865209039777769"),
false,
);
assert.equal(storedMessage.safeView.messageType, "telemetry.package");
assert.equal(storedMessage.safeView.identifier.masked, "***********7769");
assert.equal(
JSON.stringify(storedMessage).includes("865209039777769"),
false,
);
} finally {
await gateway.stop();
await close(core);
@@ -2,6 +2,9 @@ import assert from "node:assert/strict";
import { connect } from "node:net";
import test from "node:test";
import {
DEVICE_ADAPTER_CATALOG,
} from "../../../packages/device-adapter-catalog/src/index.mjs";
import { createDeviceGatewayRuntime } from "../src/runtime.mjs";
const specificationHeader = Buffer.from(
@@ -34,7 +37,7 @@ test("baseline health exposes no public ingress and no command transport", async
}
});
test("discovery-only ingress persists HEADER2 before acknowledging packages", async () => {
test("telemetry ingress persists HEADER2 before acknowledging packages", async () => {
const captured = [];
const runtime = createDeviceGatewayRuntime({
healthPort: 0,
@@ -42,8 +45,14 @@ test("discovery-only ingress persists HEADER2 before acknowledging packages", as
tcpPort: 0,
listenEnabled: true,
publicIngressEnabled: true,
coreChannelAuthenticated: true,
now: () => new Date(0x52db95de * 1000),
onDiscovery: async (value) => captured.push(value),
...gatewayAdapterOptions(),
onDiscovery: async (value) => {
captured.push(value);
return { lifecycleState: "quarantine" };
},
onMessage: async (message) => acceptanceFor(message),
});
const addresses = await runtime.start();
const client = await connectAndCollect(addresses.tcpAddress.port);
@@ -70,17 +79,18 @@ test("discovery-only ingress persists HEADER2 before acknowledging packages", as
"7B00017D",
);
assert.equal(runtime.status().totalDiscoveries, 1);
assert.equal(runtime.status().totalMessagesAccepted, 1);
assert.equal(runtime.status().totalPackagesAcknowledged, 1);
assert.equal(runtime.status().commandTransport, "disabled");
assert.equal(runtime.status().publicIngress, "discovery-only");
assert.equal(runtime.status().publicIngress, "telemetry-ingest");
const response = await fetch(
`http://127.0.0.1:${addresses.healthAddress.port}/healthz`,
);
const body = await response.json();
assert.equal(body.framing, "verified-read-only");
assert.equal(body.tcpListener, "discovery-only");
assert.equal(body.publicIngress, "discovery-only");
assert.equal(body.tcpListener, "telemetry-ingest");
assert.equal(body.publicIngress, "telemetry-ingest");
assert.equal(body.commandTransport, "disabled");
} finally {
client.socket.destroy();
@@ -94,7 +104,12 @@ test("does not acknowledge malformed or unverified initial bytes", async () => {
healthPort: 0,
tcpPort: 0,
listenEnabled: true,
onDiscovery: async (value) => captured.push(value),
...gatewayAdapterOptions(),
onDiscovery: async (value) => {
captured.push(value);
return { lifecycleState: "quarantine" };
},
onMessage: async (message) => acceptanceFor(message),
});
const addresses = await runtime.start();
try {
@@ -110,14 +125,137 @@ test("does not acknowledge malformed or unverified initial bytes", async () => {
}
});
test("public ingress requires an authenticated discovery sink", () => {
test("does not acknowledge a header when Core rejects discovery", async () => {
const runtime = createDeviceGatewayRuntime({
healthPort: 0,
tcpPort: 0,
listenEnabled: true,
...gatewayAdapterOptions(),
onDiscovery: async () => {
throw new Error("core_unavailable");
},
onMessage: async (message) => acceptanceFor(message),
});
const addresses = await runtime.start();
try {
const received = await sendAndCollect(
addresses.tcpAddress.port,
specificationHeader,
);
assert.equal(received.length, 0);
assert.equal(runtime.status().totalDiscoveries, 0);
assert.equal(runtime.status().totalRejected, 1);
} finally {
await runtime.stop();
}
});
test("does not acknowledge a package when durable Core acceptance fails", async () => {
const runtime = createDeviceGatewayRuntime({
healthPort: 0,
tcpPort: 0,
listenEnabled: true,
now: () => new Date(0x52db95de * 1000),
...gatewayAdapterOptions(),
onDiscovery: async () => ({ lifecycleState: "quarantine" }),
onMessage: async () => {
throw new Error("core_commit_failed");
},
});
const addresses = await runtime.start();
try {
const received = await sendAndCollect(
addresses.tcpAddress.port,
Buffer.concat([specificationHeader, specificationPackage]),
);
assert.equal(
received.toString("hex").toUpperCase(),
"7B0400A0DE95DB527D",
);
assert.equal(runtime.status().totalMessagesAccepted, 0);
assert.equal(runtime.status().totalPackagesAcknowledged, 0);
assert.equal(runtime.status().totalRejected, 1);
} finally {
await runtime.stop();
}
});
test("acknowledges an idempotent Core replay as accepted delivery", async () => {
const runtime = createDeviceGatewayRuntime({
healthPort: 0,
tcpPort: 0,
listenEnabled: true,
now: () => new Date(0x52db95de * 1000),
...gatewayAdapterOptions(),
onDiscovery: async () => ({ lifecycleState: "quarantine" }),
onMessage: async (message) => acceptanceFor(message, true),
});
const addresses = await runtime.start();
try {
const received = await sendAndCollect(
addresses.tcpAddress.port,
Buffer.concat([specificationHeader, specificationPackage]),
);
assert.equal(
received.toString("hex").toUpperCase(),
"7B0400A0DE95DB527D7B00017D",
);
assert.equal(runtime.status().totalMessagesAccepted, 1);
assert.equal(runtime.status().totalPackagesAcknowledged, 1);
} finally {
await runtime.stop();
}
});
test("closes an oversized tracker buffer and releases its aggregate budget", async () => {
const runtime = createDeviceGatewayRuntime({
healthPort: 0,
tcpPort: 0,
listenEnabled: true,
maxBufferedBytes: 1024,
maxAggregateBufferedBytes: 1024,
...gatewayAdapterOptions(),
onDiscovery: async () => ({ lifecycleState: "quarantine" }),
onMessage: async (message) => acceptanceFor(message),
});
const addresses = await runtime.start();
try {
const received = await sendAndCollect(
addresses.tcpAddress.port,
Buffer.alloc(1025, 0x01),
);
assert.equal(received.length, 0);
assert.equal(runtime.status().totalRejected, 1);
assert.equal(runtime.status().totalBufferedBytes, 0);
assert.equal(runtime.status().activeSessions, 0);
} finally {
await runtime.stop();
}
});
test("public ingress requires both discovery and durable message acceptance sinks", () => {
assert.throws(
() => createDeviceGatewayRuntime({
listenEnabled: true,
publicIngressEnabled: true,
coreChannelAuthenticated: true,
tcpHost: "0.0.0.0",
}),
/device_gateway_core_acceptance_sink_required/,
);
});
test("public ingress cannot start on the legacy bearer HTTP Core client", () => {
assert.throws(
() => createDeviceGatewayRuntime({
listenEnabled: true,
publicIngressEnabled: true,
tcpHost: "0.0.0.0",
...gatewayAdapterOptions(),
onDiscovery: async () => ({ lifecycleState: "quarantine" }),
onMessage: async (message) => acceptanceFor(message),
}),
/device_gateway_discovery_sink_required/,
/device_gateway_authenticated_core_channel_required/,
);
});
@@ -126,6 +264,9 @@ test("baseline rejects non-loopback binding", () => {
() => createDeviceGatewayRuntime({
listenEnabled: true,
tcpHost: "0.0.0.0",
...gatewayAdapterOptions(),
onDiscovery: async () => ({ lifecycleState: "quarantine" }),
onMessage: async (message) => acceptanceFor(message),
}),
/device_gateway_baseline_loopback_only/,
);
@@ -192,3 +333,22 @@ function sendAndCollect(port, payload) {
socket.on("error", reject);
});
}
function gatewayAdapterOptions() {
return {
adapterRegistry: DEVICE_ADAPTER_CATALOG.registry,
protocolProfileRef: DEVICE_ADAPTER_CATALOG.defaultProfileRef,
edgeRef: "edge:test-001",
};
}
function acceptanceFor(message, replayed = false) {
return {
schemaVersion: "nodedc.device-adapter-acceptance.v1",
acceptanceRef: "acceptance:test-001",
idempotencyKey: message.idempotencyKey,
status: "accepted",
replayed,
acceptedAt: "2026-08-11T12:00:00.000Z",
};
}