feat(device-core): add control resource ledger
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const schemaUrl = new URL(
|
||||
"../migrations/010_device_control_resources.sql",
|
||||
import.meta.url,
|
||||
);
|
||||
const commandsUrl = new URL(
|
||||
"../migrations/011_device_control_resource_commands.sql",
|
||||
import.meta.url,
|
||||
);
|
||||
const repositoryUrl = new URL("../src/postgres-repository.mjs", import.meta.url);
|
||||
const appUrl = new URL("../src/app.mjs", import.meta.url);
|
||||
|
||||
test("control resource schema separates bindings, revisions and current state", async () => {
|
||||
const sql = await readFile(schemaUrl, "utf8");
|
||||
|
||||
assert.match(sql, /create table if not exists device_resource_bindings/);
|
||||
assert.match(sql, /pending_external_approval/);
|
||||
assert.match(sql, /external_approval_digest/);
|
||||
assert.match(sql, /device_binding_source_scope_mismatch/);
|
||||
assert.match(sql, /create table if not exists device_configuration_revisions/);
|
||||
assert.match(sql, /create table if not exists device_configuration_state/);
|
||||
assert.match(sql, /device_configuration_revision_scope_mismatch/);
|
||||
assert.match(sql, /unique \(device_id, revision_number\)/);
|
||||
});
|
||||
|
||||
test("command ledger has honest ordered states without a transport API", async () => {
|
||||
const sql = await readFile(schemaUrl, "utf8");
|
||||
const app = await readFile(appUrl, "utf8");
|
||||
|
||||
assert.match(sql, /create table if not exists device_commands/);
|
||||
assert.match(sql, /create table if not exists device_command_events/);
|
||||
for (const state of [
|
||||
"draft",
|
||||
"planned",
|
||||
"awaiting_confirmation",
|
||||
"queued",
|
||||
"dispatched",
|
||||
"acknowledged",
|
||||
"verified",
|
||||
"failed",
|
||||
"expired",
|
||||
"unknown",
|
||||
]) {
|
||||
assert.match(sql, new RegExp(`'${state}'`));
|
||||
}
|
||||
assert.match(sql, /device_command_initial_event_invalid/);
|
||||
assert.match(sql, /device_command_event_sequence_invalid/);
|
||||
assert.match(sql, /device_command_event_transition_invalid/);
|
||||
assert.match(sql, /device_command_event_projection_mismatch/);
|
||||
assert.match(sql, /device_command_events_current_projection_guard/);
|
||||
assert.match(sql, /device_command_current_projection_mismatch/);
|
||||
assert.doesNotMatch(app, /device-commands:(?:plan|confirm|dispatch)/);
|
||||
});
|
||||
|
||||
test("configuration, command history and audit are append-only", async () => {
|
||||
const sql = await readFile(schemaUrl, "utf8");
|
||||
|
||||
for (const table of [
|
||||
"device_configuration_revisions",
|
||||
"device_command_events",
|
||||
"device_audit_events",
|
||||
]) {
|
||||
assert.match(
|
||||
sql,
|
||||
new RegExp(`${table}_immutable_guard[\\s\\S]*before update or delete or truncate`),
|
||||
);
|
||||
}
|
||||
assert.match(sql, /device_immutable_record_mutation_forbidden/);
|
||||
assert.match(sql, /device_transfer_active_resource_binding/);
|
||||
assert.match(sql, /device_transfer_applied_configuration/);
|
||||
assert.match(sql, /device_transfer_nonterminal_command/);
|
||||
});
|
||||
|
||||
test("control resource schema contains no seeded device or raw secret material", async () => {
|
||||
const sql = await readFile(schemaUrl, "utf8");
|
||||
|
||||
assert.doesNotMatch(sql, /insert\s+into/i);
|
||||
assert.doesNotMatch(sql, /dcctouch|arusnavi|gelios|\bb2\b|imei/i);
|
||||
assert.doesNotMatch(sql, /password\s+text|token\s+text|secret\s+text|raw_command|raw_packet/i);
|
||||
});
|
||||
|
||||
test("commands extend receipts only after their schema", async () => {
|
||||
const commands = await readFile(commandsUrl, "utf8");
|
||||
const repository = await readFile(repositoryUrl, "utf8");
|
||||
|
||||
for (const kind of [
|
||||
"device_binding.ensure",
|
||||
"device_binding.revoke",
|
||||
"device_configuration_revision.create",
|
||||
"device_configuration_desired.set",
|
||||
]) {
|
||||
assert.match(commands, new RegExp(`'${kind.replace(".", "\\.")}'`));
|
||||
}
|
||||
const schemaIndex = repository.indexOf("010_device_control_resources.sql");
|
||||
const commandsIndex = repository.indexOf(
|
||||
"011_device_control_resource_commands.sql",
|
||||
);
|
||||
assert.notEqual(schemaIndex, -1);
|
||||
assert.notEqual(commandsIndex, -1);
|
||||
assert.ok(schemaIndex < commandsIndex);
|
||||
});
|
||||
Reference in New Issue
Block a user