feat(device-core): add registry management commands

This commit is contained in:
Codex
2026-08-10 17:44:14 +03:00
parent 9fdaed1c95
commit 72db23c0e9
10 changed files with 1784 additions and 2 deletions
@@ -0,0 +1,43 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
const migrationUrl = new URL(
"../migrations/005_device_registry_commands.sql",
import.meta.url,
);
const repositoryUrl = new URL("../src/postgres-repository.mjs", import.meta.url);
test("registry command migration extends the durable receipt allowlist", async () => {
const sql = await readFile(migrationUrl, "utf8");
for (const kind of [
"adapter_package.ensure",
"adapter_version.register",
"model_profile.register",
"edge.ensure",
"route.ensure",
"enrollment_intent.ensure",
]) {
assert.match(sql, new RegExp(`'${kind.replace(".", "\\.")}'`));
}
assert.doesNotMatch(sql, /session\.(ensure|create|upsert)/);
});
test("registry command migration contains no environment or device data", async () => {
const sql = await readFile(migrationUrl, "utf8");
assert.doesNotMatch(sql, /insert\s+into/i);
assert.doesNotMatch(sql, /dcctouch|arusnavi|\bb2\b|imei|gelios/i);
assert.doesNotMatch(sql, /password|secret|credential|private_key/i);
});
test("repository applies registry commands after registry schema", async () => {
const source = await readFile(repositoryUrl, "utf8");
const schemaIndex = source.indexOf("004_device_registry_foundation.sql");
const commandsIndex = source.indexOf("005_device_registry_commands.sql");
assert.notEqual(schemaIndex, -1);
assert.notEqual(commandsIndex, -1);
assert.ok(schemaIndex < commandsIndex);
});
@@ -0,0 +1,102 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createControlCoreApp } from "../src/app.mjs";
const managementToken = "test-only-management-token-with-32-bytes";
test("management API forwards a normalized generic Edge registration", async () => {
let executed;
const runtime = await startServer({
managementApiEnabled: true,
managementToken,
repository: {
health: async () => "ready",
executeManagementCommand: async (input) => {
executed = input;
return { replayed: false, result: { created: true } };
},
},
});
try {
const response = await fetch(
`${runtime.baseUrl}/internal/v1/management/edges:ensure`,
{
method: "POST",
headers: managementHeaders(),
body: JSON.stringify({
edgeKey: "generic-edge",
displayName: "Generic Edge",
deploymentRef: "deployment:device-edge/pilot",
}),
},
);
assert.equal(response.status, 200);
assert.equal(executed.commandKind, "edge.ensure");
assert.deepEqual(executed.command, {
edgeKey: "generic-edge",
displayName: "Generic Edge",
deploymentRef: "deployment:device-edge/pilot",
lifecycleState: "provisioning",
});
assert.equal(executed.actor.hubRole, "owner");
} finally {
await runtime.close();
}
});
test("management API exposes no user-owned session mutation", async () => {
let executions = 0;
const runtime = await startServer({
managementApiEnabled: true,
managementToken,
repository: {
health: async () => "ready",
executeManagementCommand: async () => {
executions += 1;
return { replayed: false, result: {} };
},
},
});
try {
const response = await fetch(
`${runtime.baseUrl}/internal/v1/management/sessions:ensure`,
{
method: "POST",
headers: managementHeaders(),
body: "{}",
},
);
assert.equal(response.status, 404);
assert.equal(executions, 0);
} finally {
await runtime.close();
}
});
async function startServer(options) {
const server = createControlCoreApp(options);
await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address();
return {
baseUrl: `http://127.0.0.1:${address.port}`,
close: () => new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
}),
};
}
function managementHeaders() {
return {
Authorization: `Bearer ${managementToken}`,
"Content-Type": "application/json",
"Idempotency-Key": "phase23-edge-0001",
"X-NODEDC-User-Ref": "user:platform-owner",
"X-NODEDC-Hub-Role": "owner",
};
}
@@ -0,0 +1,153 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
assertPlatformCatalogAuthority,
DEVICE_INFRASTRUCTURE_COMMAND_KINDS,
normalizeInfrastructureManagementCommand,
} from "../src/infrastructure-management.mjs";
import {
ALL_DEVICE_MANAGEMENT_COMMAND_KINDS,
normalizeDeviceManagementCommand,
} from "../src/management-command.mjs";
import { normalizeManagementActor } from "../src/project-management.mjs";
const projectRef = "project:11111111-1111-4111-8111-111111111111";
const packageRef = "adapter-package:22222222-2222-4222-8222-222222222222";
const versionRef = "adapter-version:33333333-3333-4333-8333-333333333333";
const edgeRef = "edge:44444444-4444-4444-8444-444444444444";
const routeRef = "route:55555555-5555-4555-8555-555555555555";
const digest = `sha256:${"a".repeat(64)}`;
const identifierDigest = `hmac-sha256:${"b".repeat(64)}`;
test("aggregates project and infrastructure commands without a session mutation", () => {
for (const kind of DEVICE_INFRASTRUCTURE_COMMAND_KINDS) {
assert.equal(ALL_DEVICE_MANAGEMENT_COMMAND_KINDS.includes(kind), true);
}
assert.equal(ALL_DEVICE_MANAGEMENT_COMMAND_KINDS.includes("project.ensure"), true);
assert.equal(ALL_DEVICE_MANAGEMENT_COMMAND_KINDS.includes("session.ensure"), false);
assert.throws(
() => normalizeDeviceManagementCommand("session.ensure", {}),
/device_management_command_kind_invalid/,
);
});
test("normalizes immutable adapter version metadata and sorted capabilities", () => {
const command = normalizeInfrastructureManagementCommand(
"adapter_version.register",
{
adapterPackageRef: packageRef,
version: "1.2.3",
runtimePackageRef: "artifact:device-adapters/generic-1.2.3",
contentDigest: digest,
contractVersion: "nodedc.device-adapter.v1",
capabilities: ["telemetry.observe", "command.typed", "telemetry.observe"],
lifecycleState: "active",
},
);
assert.equal(command.adapterPackageId, packageRef.slice("adapter-package:".length));
assert.deepEqual(command.capabilities, ["command.typed", "telemetry.observe"]);
assert.equal(command.contentDigest, digest);
});
test("normalizes a generic model profile as artifact metadata, not executable payload", () => {
const command = normalizeInfrastructureManagementCommand(
"model_profile.register",
{
adapterVersionRef: versionRef,
profileRef: "vendor.model.protocol.v1",
schemaVersion: "nodedc.device-model-profile.v1",
vendor: "Example Vendor",
model: "Model One",
deviceType: "tracker",
protocol: "GENERIC_TCP",
schemaArtifactRef: "artifact:model-profiles/vendor-model-v1",
profileDigest: digest,
capabilities: ["telemetry.observe"],
},
);
assert.equal(command.adapterVersionId, versionRef.slice("adapter-version:".length));
assert.equal(command.protocol, "GENERIC_TCP");
assert.equal(command.lifecycleState, "draft");
assert.equal("profile" in command, false);
assert.equal("source" in command, false);
});
test("route and enrollment commands resolve only scoped references", () => {
const route = normalizeInfrastructureManagementCommand("route.ensure", {
projectRef,
routeKey: "primary-ingress",
displayName: "Primary ingress",
edgeRef,
modelProfileRef: "vendor.model.protocol.v1",
listenerRef: "listener:generic-tcp-primary",
protocol: "GENERIC_TCP",
direction: "bidirectional",
});
const enrollment = normalizeInfrastructureManagementCommand(
"enrollment_intent.ensure",
{
projectRef,
enrollmentKey: "pilot-device",
routeRef,
modelProfileRef: "vendor.model.protocol.v1",
displayName: "Pilot device",
identifierKind: "serial",
identifierDigest,
identifierMasked: "********0001",
expiresAt: "2026-09-01T00:00:00.000Z",
},
);
assert.equal(route.projectId, projectRef.slice("project:".length));
assert.equal(route.edgeId, edgeRef.slice("edge:".length));
assert.equal(enrollment.routeId, routeRef.slice("route:".length));
assert.equal(enrollment.identifierDigest, identifierDigest);
});
test("enrollment contract rejects raw identifiers and credential-shaped fields", () => {
const base = {
projectRef,
enrollmentKey: "pilot-device",
routeRef,
modelProfileRef: "vendor.model.protocol.v1",
displayName: "Pilot device",
identifierKind: "imei",
identifierDigest,
identifierMasked: "***********0001",
};
assert.throws(
() => normalizeInfrastructureManagementCommand(
"enrollment_intent.ensure",
{ ...base, identifierMasked: "000000000000001" },
),
/safe_projection_contains_unmasked_imei/,
);
assert.throws(
() => normalizeInfrastructureManagementCommand(
"enrollment_intent.ensure",
{ ...base, credential: "forbidden" },
),
/device_management_command_field_unexpected:credential/,
);
});
test("shared catalog and Edge authority requires the Hub owner ceiling", () => {
assert.doesNotThrow(() => assertPlatformCatalogAuthority(actor("owner")));
assert.throws(
() => assertPlatformCatalogAuthority(actor("admin")),
/device_platform_catalog_access_denied/,
);
});
function actor(hubRole) {
return normalizeManagementActor({
userRef: "user:platform-admin",
hubRole,
groupRefs: [],
ownerScopes: [],
});
}
@@ -0,0 +1,249 @@
import assert from "node:assert/strict";
import test from "node:test";
import { assertSafeProjection } from "../../../packages/device-protocol-contract/src/index.mjs";
import { normalizeDeviceManagementCommand } from "../src/management-command.mjs";
import { PostgresDeviceRepository } from "../src/postgres-repository.mjs";
import { normalizeManagementActor } from "../src/project-management.mjs";
const now = new Date("2026-08-10T00:00:00.000Z");
const projectId = "11111111-1111-4111-8111-111111111111";
const edgeId = "22222222-2222-4222-8222-222222222222";
const routeId = "33333333-3333-4333-8333-333333333333";
test("commits an owner-authorized generic Edge registration", async () => {
const actor = managementActor("owner");
const command = normalizeDeviceManagementCommand("edge.ensure", {
edgeKey: "generic-edge",
displayName: "Generic Edge",
deploymentRef: "deployment:device-edge/pilot",
});
const client = scriptedClient([
step("begin"),
step("insert into device_management_command_receipts", {
rows: [{ id: "receipt-edge" }],
}),
step("insert into device_edges", {
rows: [{
id: edgeId,
edge_key: command.edgeKey,
display_name: command.displayName,
deployment_ref: command.deploymentRef,
lifecycle_state: command.lifecycleState,
created_at: now,
updated_at: now,
created: true,
}],
}),
step("insert into device_audit_events"),
step("update device_management_command_receipts"),
step("commit"),
]);
const repository = repositoryWithClient(client);
const result = await repository.executeManagementCommand(commandInput({
actor,
commandKind: "edge.ensure",
command,
digestCharacter: "a",
}));
assert.equal(result.replayed, false);
assert.equal(result.result.edge.edgeRef, `edge:${edgeId}`);
assert.equal(result.result.edge.lifecycleState, "provisioning");
assert.equal(client.remaining(), 0);
assert.equal(client.released, true);
});
test("denies project route mutation without an explicit project grant", async () => {
const actor = managementActor("owner");
const command = normalizeDeviceManagementCommand("route.ensure", {
projectRef: `project:${projectId}`,
routeKey: "generic-ingress",
displayName: "Generic ingress",
edgeRef: `edge:${edgeId}`,
modelProfileRef: "vendor.model.protocol.v1",
listenerRef: "listener:generic-tcp-primary",
protocol: "GENERIC_TCP",
});
const client = scriptedClient([
step("begin"),
step("insert into device_management_command_receipts", {
rows: [{ id: "receipt-route" }],
}),
step("from device_projects p", {
rows: [{
id: projectId,
lifecycle_state: "active",
owner_lifecycle_state: "active",
}],
}),
step("from device_project_grants", { rows: [] }),
step("rollback"),
]);
const repository = repositoryWithClient(client);
await assert.rejects(
repository.executeManagementCommand(commandInput({
actor,
commandKind: "route.ensure",
command,
digestCharacter: "b",
})),
/device_project_capability_denied/,
);
assert.equal(client.remaining(), 0);
assert.equal(client.released, true);
});
test("stores enrollment digest but returns and audits only its masked projection", async () => {
const actor = managementActor("member");
const command = normalizeDeviceManagementCommand(
"enrollment_intent.ensure",
{
projectRef: `project:${projectId}`,
enrollmentKey: "pilot-device",
routeRef: `route:${routeId}`,
modelProfileRef: "vendor.model.protocol.v1",
displayName: "Pilot device",
identifierKind: "serial",
identifierDigest: `hmac-sha256:${"c".repeat(64)}`,
identifierMasked: "********0001",
expiresAt: "2026-09-01T00:00:00.000Z",
},
);
const client = scriptedClient([
step("begin"),
step("insert into device_management_command_receipts", {
rows: [{ id: "receipt-enrollment" }],
}),
step("from device_projects p", {
rows: [{
id: projectId,
lifecycle_state: "active",
owner_lifecycle_state: "active",
}],
}),
step("from device_project_grants", {
rows: [{
id: "44444444-4444-4444-8444-444444444444",
principal_kind: "user",
principal_ref: actor.userRef,
project_role: "engineer",
capability_allow: [],
capability_deny: [],
lifecycle_state: "active",
}],
}),
step("from device_routes", {
rows: [{
id: routeId,
project_id: projectId,
route_key: "generic-ingress",
display_name: "Generic ingress",
edge_id: edgeId,
model_profile_ref: command.modelProfileRef,
listener_ref: "listener:generic-tcp-primary",
protocol: "GENERIC_TCP",
direction: "telemetry",
lifecycle_state: "active",
created_at: now,
updated_at: now,
}],
}),
step("insert into device_enrollment_intents", {
rows: [{
id: "55555555-5555-4555-8555-555555555555",
project_id: projectId,
enrollment_key: command.enrollmentKey,
route_id: routeId,
model_profile_ref: command.modelProfileRef,
display_name: command.displayName,
expected_identifier_kind: command.identifierKind,
expected_identifier_masked: command.identifierMasked,
lifecycle_state: "pending",
expires_at: new Date(command.expiresAt),
claimed_device_id: null,
created_at: now,
updated_at: now,
created: true,
}],
}),
step("insert into device_audit_events"),
step("update device_management_command_receipts"),
step("commit"),
]);
const repository = repositoryWithClient(client);
const result = await repository.executeManagementCommand(commandInput({
actor,
commandKind: "enrollment_intent.ensure",
command,
digestCharacter: "d",
}));
assertSafeProjection(result.result.enrollmentIntent);
assert.equal(
result.result.enrollmentIntent.identifier.masked,
command.identifierMasked,
);
assert.equal(JSON.stringify(result.result).includes(command.identifierDigest), false);
assert.equal(client.remaining(), 0);
assert.equal(client.released, true);
});
function managementActor(hubRole) {
return normalizeManagementActor({
userRef: "user:platform-owner",
hubRole,
groupRefs: [],
ownerScopes: [],
});
}
function commandInput({ actor, commandKind, command, digestCharacter }) {
return {
idempotencyKey: `phase23-${commandKind.replaceAll(".", "-")}-0001`,
commandKind,
requestDigest: `sha256:${digestCharacter.repeat(64)}`,
actor,
command,
};
}
function repositoryWithClient(client) {
return new PostgresDeviceRepository({
pool: {
query: async () => ({ rows: [] }),
connect: async () => client,
end: async () => undefined,
},
});
}
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, "\\$&");
}