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
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import test from "node:test";
import {
DEVICE_ADAPTER_MESSAGE_SCHEMA,
DEVICE_DISCOVERY_SIGNAL_SCHEMA,
} from "../../../packages/device-protocol-contract/src/index.mjs";
import { createControlCoreApp } from "../src/app.mjs";
@@ -362,6 +363,9 @@ test("authenticated ingest stores only digest and returns a masked view", async
},
};
},
acceptAdapterMessage: async () => {
throw new Error("must_not_accept_message");
},
},
});
try {
@@ -400,6 +404,54 @@ test("authenticated ingest stores only digest and returns a masked view", async
}
});
test("gateway message endpoint returns acceptance only after repository commit", async () => {
let stored;
const runtime = await startTestServer({
discoveryIngestEnabled: true,
gatewayToken,
identifierPepper,
repository: {
health: async () => "ready",
upsertQuarantineDiscovery: async () => {
throw new Error("must_not_observe_discovery");
},
acceptAdapterMessage: async (value) => {
stored = value;
return {
schemaVersion: "nodedc.device-adapter-acceptance.v1",
acceptanceRef: "acceptance:test-001",
idempotencyKey: value.safeView.idempotencyKey,
status: "accepted",
replayed: false,
acceptedAt: "2026-08-11T12:00:00.000Z",
};
},
},
});
try {
const response = await fetch(
`${runtime.baseUrl}/internal/v1/gateway/messages:accept`,
{
method: "POST",
headers: {
Authorization: `Bearer ${gatewayToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(fakeAdapterMessage()),
},
);
assert.equal(response.status, 201);
const body = await response.json();
assert.equal(body.acceptance.status, "accepted");
assert.match(stored.identifierDigest, /^hmac-sha256:[a-f0-9]{64}$/);
assert.match(stored.requestDigest, /^sha256:[a-f0-9]{64}$/);
assert.equal(stored.safeView.identifier.masked, "***********0001");
assert.equal(JSON.stringify(stored).includes(fakeImei), false);
} finally {
await runtime.close();
}
});
function fakeSignal() {
return {
schemaVersion: DEVICE_DISCOVERY_SIGNAL_SCHEMA,
@@ -417,6 +469,29 @@ function fakeSignal() {
};
}
function fakeAdapterMessage() {
return {
schemaVersion: DEVICE_ADAPTER_MESSAGE_SCHEMA,
edgeRef: "edge:test-001",
adapterRef: "arusnavi-b2",
protocolProfileRef: "arusnavi.b2.internal.v1",
protocol: "INTERNAL",
sessionRef: "session:test-001",
messageRef: "package:1:test",
messageType: "telemetry.package",
sequence: 1,
observedAt: "2026-08-11T12:00:00.000Z",
idempotencyKey: `sha256:${"a".repeat(64)}`,
identifier: { kind: "imei", value: fakeImei },
payloadSchemaRef: "arusnavi.internal.package-metadata.v1",
payload: {
packageNumber: 1,
packetCount: 1,
packageDigest: `sha256:${"b".repeat(64)}`,
},
};
}
function managementHeaders({ includeIdempotency = true } = {}) {
return {
Authorization: `Bearer ${managementToken}`,
@@ -0,0 +1,39 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
const migrationUrl = new URL(
"../migrations/012_device_gateway_message_receipts.sql",
import.meta.url,
);
const repositoryUrl = new URL("../src/postgres-repository.mjs", import.meta.url);
test("gateway receipts persist only typed bounded Core acceptance evidence", async () => {
const sql = await readFile(migrationUrl, "utf8");
assert.match(sql, /create table if not exists device_gateway_message_receipts/);
assert.match(sql, /unique \(idempotency_key\)/);
assert.match(sql, /unique \(edge_ref, session_ref, message_ref\)/);
assert.match(sql, /identifier_digest text not null/);
assert.match(sql, /identifier_masked text not null/);
assert.match(sql, /payload_schema_ref text not null/);
assert.match(sql, /payload jsonb not null/);
assert.match(sql, /device_gateway_message_receipts_immutable_guard/);
assert.doesNotMatch(sql, /raw_packet|raw_identifier|password|token|secret/i);
assert.doesNotMatch(sql, /insert\s+into|arusnavi|gelios|\bb2\b|imei/i);
});
test("gateway receipt migration follows the generic control resource schema", async () => {
const repository = await readFile(repositoryUrl, "utf8");
const controlResourceIndex = repository.indexOf(
"011_device_control_resource_commands.sql",
);
const gatewayReceiptIndex = repository.indexOf(
"012_device_gateway_message_receipts.sql",
);
assert.notEqual(controlResourceIndex, -1);
assert.notEqual(gatewayReceiptIndex, -1);
assert.ok(controlResourceIndex < gatewayReceiptIndex);
assert.doesNotMatch(repository, /arusnavi-b2-adapter/);
});
@@ -0,0 +1,165 @@
import assert from "node:assert/strict";
import test from "node:test";
import { acceptGatewayMessage } from "../src/gateway-message-repository.mjs";
const acceptedAt = new Date("2026-08-11T12:00:00.000Z");
const idempotencyKey = `sha256:${"a".repeat(64)}`;
const requestDigest = `sha256:${"b".repeat(64)}`;
test("commits a gateway receipt before returning Core acceptance", async () => {
const client = scriptedClient([
step("begin"),
step("insert into device_gateway_message_receipts", {
rows: [{
id: "11111111-1111-4111-8111-111111111111",
idempotency_key: idempotencyKey,
accepted_at: acceptedAt,
}],
}),
step("commit"),
]);
const result = await acceptGatewayMessage(messageInput(client));
assert.equal(result.status, "accepted");
assert.equal(result.replayed, false);
assert.equal(result.idempotencyKey, idempotencyKey);
assert.equal(result.acceptedAt, acceptedAt.toISOString());
assert.equal(client.remaining(), 0);
assert.equal(client.released, true);
});
test("replays one durable receipt for the same normalized request", async () => {
const client = scriptedClient([
step("begin"),
step("insert into device_gateway_message_receipts", { rows: [] }),
step("from device_gateway_message_receipts", {
rows: [{
id: "11111111-1111-4111-8111-111111111111",
idempotency_key: idempotencyKey,
request_digest: requestDigest,
accepted_at: acceptedAt,
}],
}),
step("commit"),
]);
const result = await acceptGatewayMessage(messageInput(client));
assert.equal(result.status, "accepted");
assert.equal(result.replayed, true);
assert.equal(client.remaining(), 0);
assert.equal(client.released, true);
});
test("rejects idempotency reuse with different content", async () => {
const client = scriptedClient([
step("begin"),
step("insert into device_gateway_message_receipts", { rows: [] }),
step("from device_gateway_message_receipts", {
rows: [{
id: "11111111-1111-4111-8111-111111111111",
idempotency_key: idempotencyKey,
request_digest: `sha256:${"c".repeat(64)}`,
accepted_at: acceptedAt,
}],
}),
step("rollback"),
]);
await assert.rejects(
acceptGatewayMessage(messageInput(client)),
/device_gateway_idempotency_conflict/,
);
assert.equal(client.remaining(), 0);
assert.equal(client.released, true);
});
test("fails closed when a route does not match its Edge contract", async () => {
const routeId = "22222222-2222-4222-8222-222222222222";
const client = scriptedClient([
step("begin"),
step("from device_routes r", {
rows: [{
id: routeId,
project_id: "33333333-3333-4333-8333-333333333333",
edge_id: "44444444-4444-4444-8444-444444444444",
model_profile_ref: "generic.model.protocol.v1",
protocol: "GENERIC_TCP",
lifecycle_state: "active",
edge_lifecycle_state: "active",
profile_lifecycle_state: "active",
adapter_ref: "generic-adapter",
adapter_lifecycle_state: "active",
adapter_version_lifecycle_state: "active",
}],
}),
step("rollback"),
]);
const input = messageInput(client);
input.safeView.routeRef = `route:${routeId}`;
input.safeView.edgeRef = "edge:55555555-5555-4555-8555-555555555555";
await assert.rejects(
acceptGatewayMessage(input),
/device_gateway_route_contract_mismatch/,
);
assert.equal(client.remaining(), 0);
assert.equal(client.released, true);
});
function messageInput(client) {
return {
pool: {
connect: async () => client,
},
identifierDigest: `hmac-sha256:${"d".repeat(64)}`,
requestDigest,
safeView: {
edgeRef: "edge:test-001",
adapterRef: "generic-adapter",
protocolProfileRef: "generic.model.protocol.v1",
protocol: "GENERIC_TCP",
sessionRef: "session:test-001",
messageRef: "message:test-001",
messageType: "telemetry.sample",
sequence: 1,
idempotencyKey,
identifier: {
kind: "serial",
masked: "********0001",
},
payloadSchemaRef: "generic.telemetry.v1",
payload: { value: 1 },
observedAt: "2026-08-11T12:00:00.000Z",
},
};
}
function step(includes, result = { rows: [] }) {
return { includes, result };
}
function scriptedClient(steps) {
const queue = [...steps];
return {
released: false,
async query(sql) {
const next = queue.shift();
assert.ok(next, `Unexpected query: ${sql}`);
assert.match(String(sql), new RegExp(escapeRegExp(next.includes), "i"));
return next.result;
},
release() {
this.released = true;
},
remaining() {
return queue.length;
},
};
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}