feat(device-manager): add control and audit workspaces

This commit is contained in:
Codex
2026-08-13 11:37:36 +03:00
parent 4116f5ba95
commit 1c5246afe8
10 changed files with 1769 additions and 24 deletions
+316 -12
View File
@@ -4,7 +4,24 @@ const commandRoutes = new Map([
["owner-scopes:ensure", "/internal/v1/management/owner-scopes:ensure"],
["projects:ensure", "/internal/v1/management/projects:ensure"],
["collections:ensure", "/internal/v1/management/collections:ensure"],
["project-grants:upsert", "/internal/v1/management/project-grants:upsert"],
["adapter-packages:ensure", "/internal/v1/management/adapter-packages:ensure"],
["adapter-versions:register", "/internal/v1/management/adapter-versions:register"],
["model-profiles:register", "/internal/v1/management/model-profiles:register"],
["edges:ensure", "/internal/v1/management/edges:ensure"],
["routes:ensure", "/internal/v1/management/routes:ensure"],
["enrollment-intents:ensure", "/internal/v1/management/enrollment-intents:ensure"],
["devices:claim", "/internal/v1/management/devices:claim"],
["device-bindings:ensure", "/internal/v1/management/device-bindings:ensure"],
["device-bindings:revoke", "/internal/v1/management/device-bindings:revoke"],
[
"device-configuration-revisions:create",
"/internal/v1/management/device-configuration-revisions:create",
],
[
"device-configurations:set-desired",
"/internal/v1/management/device-configurations:set-desired",
],
]);
export function createDeviceCoreClient({ baseUrl, token, fetchImpl = fetch } = {}) {
@@ -67,6 +84,36 @@ export function createLocalPreviewDeviceCore() {
const ownerScopes = new Map();
const projects = new Map();
const collections = new Map();
const adapterPackages = new Map();
const adapterVersions = new Map();
const modelProfiles = new Map();
const edges = new Map();
const routes = new Map();
const bindings = new Map();
const grants = new Map();
const configurationRevisions = new Map();
const configurationStates = new Map();
const auditEvents = [];
function now() {
return new Date().toISOString();
}
function projectValues(store, projectRef) {
return [...store.values()].filter((value) => value.projectRef === projectRef);
}
function audit(actor, projectRef, eventType, refs = {}) {
auditEvents.unshift({
auditEventRef: `audit-event:${randomUUID()}`,
eventType,
actorRef: actor.userRef,
deviceRef: refs.deviceRef ?? null,
discoveryRef: refs.discoveryRef ?? null,
projectRef,
occurredAt: now(),
});
}
function projectSummary(project) {
const projectCollections = [...collections.values()]
@@ -77,23 +124,44 @@ export function createLocalPreviewDeviceCore() {
};
}
function workspace(projectRef) {
const project = projects.get(projectRef);
if (!project) throw serviceError("device_project_not_found", 404);
return {
project: projectSummary(project),
devices: [],
discoveries: [],
enrollments: [],
collections: projectValues(collections, projectRef)
.map(({ projectRef: _projectRef, ...collection }) => collection),
adapterPackages: [...adapterPackages.values()],
adapterVersions: [...adapterVersions.values()],
modelProfiles: [...modelProfiles.values()],
edges: [...edges.values()],
routes: projectValues(routes, projectRef),
sessions: [],
bindings: projectValues(bindings, projectRef),
configurationRevisions: projectValues(configurationRevisions, projectRef),
configurationStates: projectValues(configurationStates, projectRef),
commands: [],
auditEvents: auditEvents.filter((event) => event.projectRef === projectRef),
grants: projectValues(grants, projectRef),
policies: {
commandTransport: "disabled",
commandPlanningApi: "disabled",
identifierProjection: "masked-only",
auditPayloadProjection: "metadata-only",
},
};
}
return {
configured: true,
async listProjects() {
return [...projects.values()].map(projectSummary);
},
async getWorkspace(_actor, projectRef) {
const project = projects.get(projectRef);
if (!project) throw serviceError("device_project_not_found", 404);
return {
project: projectSummary(project),
devices: [],
discoveries: [],
enrollments: [],
collections: [...collections.values()]
.filter((collection) => collection.projectRef === projectRef)
.map(({ projectRef: _projectRef, ...collection }) => collection),
};
return workspace(projectRef);
},
async execute(command, actor, input) {
if (command === "owner-scopes:ensure") {
@@ -130,6 +198,20 @@ export function createLocalPreviewDeviceCore() {
updatedAt: new Date().toISOString(),
};
projects.set(projectRef, project);
if (!existing) {
const grantRef = `grant:${randomUUID()}`;
grants.set(grantRef, {
grantRef,
projectRef,
principalKind: "user",
principalRef: actor.userRef,
projectRole: "owner",
capabilityAllow: [],
capabilityDeny: [],
lifecycleState: "active",
});
audit(actor, projectRef, "project.created");
}
return { replayed: false, result: { created: !existing, project } };
}
if (command === "collections:ensure") {
@@ -152,19 +234,241 @@ export function createLocalPreviewDeviceCore() {
updatedAt: new Date().toISOString(),
};
collections.set(collectionRef, collection);
audit(actor, projectRef, createdEvent(existing, "collection"));
return { replayed: false, result: { created: !existing, collection } };
}
if (command === "project-grants:upsert") {
if (!projects.has(input.projectRef)) {
throw serviceError("device_project_not_found", 404);
}
const existing = [...grants.values()].find((grant) =>
grant.projectRef === input.projectRef
&& grant.principalKind === input.principalKind
&& grant.principalRef === input.principalRef
);
const grantRef = existing?.grantRef || `grant:${randomUUID()}`;
const grant = {
grantRef,
projectRef: input.projectRef,
principalKind: input.principalKind,
principalRef: input.principalRef,
projectRole: input.projectRole,
capabilityAllow: input.capabilityAllow ?? [],
capabilityDeny: input.capabilityDeny ?? [],
lifecycleState: input.lifecycleState ?? "active",
};
grants.set(grantRef, grant);
audit(actor, input.projectRef, createdEvent(existing, "project_grant"));
return { replayed: false, result: { created: !existing, grant } };
}
if (command === "adapter-packages:ensure") {
requirePlatformOwner(actor);
const existing = [...adapterPackages.values()].find(
(entry) => entry.packageKey === input.packageKey,
);
const adapterPackageRef = existing?.adapterPackageRef
|| `adapter-package:${randomUUID()}`;
const adapterPackage = {
adapterPackageRef,
packageKey: input.packageKey,
displayName: input.displayName,
publisherRef: input.publisherRef,
lifecycleState: input.lifecycleState ?? "active",
createdAt: existing?.createdAt || now(),
updatedAt: now(),
};
adapterPackages.set(adapterPackageRef, adapterPackage);
return { replayed: false, result: { created: !existing, adapterPackage } };
}
if (command === "adapter-versions:register") {
requirePlatformOwner(actor);
if (!adapterPackages.has(input.adapterPackageRef)) {
throw serviceError("device_adapter_package_not_found", 404);
}
const existing = [...adapterVersions.values()].find((entry) =>
entry.adapterPackageRef === input.adapterPackageRef
&& entry.version === input.version
);
const adapterVersionRef = existing?.adapterVersionRef
|| `adapter-version:${randomUUID()}`;
const adapterVersion = {
adapterVersionRef,
adapterPackageRef: input.adapterPackageRef,
version: input.version,
runtimePackageRef: input.runtimePackageRef,
contentDigest: input.contentDigest,
contractVersion: input.contractVersion,
capabilities: input.capabilities ?? [],
lifecycleState: input.lifecycleState ?? "draft",
createdAt: existing?.createdAt || now(),
updatedAt: now(),
};
adapterVersions.set(adapterVersionRef, adapterVersion);
return { replayed: false, result: { created: !existing, adapterVersion } };
}
if (command === "model-profiles:register") {
requirePlatformOwner(actor);
if (!adapterVersions.has(input.adapterVersionRef)) {
throw serviceError("device_adapter_version_not_found", 404);
}
const existing = modelProfiles.get(input.profileRef);
const modelProfile = {
modelProfileRef: input.profileRef,
adapterVersionRef: input.adapterVersionRef,
schemaVersion: input.schemaVersion,
vendor: input.vendor,
model: input.model,
deviceType: input.deviceType,
protocol: input.protocol,
schemaArtifactRef: input.schemaArtifactRef,
profileDigest: input.profileDigest,
capabilities: input.capabilities ?? [],
lifecycleState: input.lifecycleState ?? "draft",
createdAt: existing?.createdAt || now(),
updatedAt: now(),
};
modelProfiles.set(input.profileRef, modelProfile);
return { replayed: false, result: { created: !existing, modelProfile } };
}
if (command === "edges:ensure") {
requirePlatformOwner(actor);
const existing = [...edges.values()].find(
(entry) => entry.edgeKey === input.edgeKey,
);
const edgeRef = existing?.edgeRef || `edge:${randomUUID()}`;
const edge = {
edgeRef,
edgeKey: input.edgeKey,
displayName: input.displayName,
deploymentRef: input.deploymentRef ?? null,
lifecycleState: input.lifecycleState ?? "provisioning",
createdAt: existing?.createdAt || now(),
updatedAt: now(),
};
edges.set(edgeRef, edge);
return { replayed: false, result: { created: !existing, edge } };
}
if (command === "routes:ensure") {
if (!projects.has(input.projectRef)) {
throw serviceError("device_project_not_found", 404);
}
const edge = edges.get(input.edgeRef);
const profile = modelProfiles.get(input.modelProfileRef);
if (!edge) throw serviceError("device_edge_not_found", 404);
if (!profile) throw serviceError("device_model_profile_not_found", 404);
const existing = projectValues(routes, input.projectRef).find(
(entry) => entry.routeKey === input.routeKey,
);
const routeRef = existing?.routeRef || `route:${randomUUID()}`;
const route = {
routeRef,
projectRef: input.projectRef,
routeKey: input.routeKey,
displayName: input.displayName,
edgeRef: input.edgeRef,
edgeName: edge.displayName,
modelProfileRef: input.modelProfileRef,
profileName: `${profile.vendor} ${profile.model}`,
listenerRef: input.listenerRef,
protocol: input.protocol,
direction: input.direction ?? "telemetry",
lifecycleState: input.lifecycleState ?? "draft",
sessionCount: 0,
activeSessionCount: 0,
createdAt: existing?.createdAt || now(),
updatedAt: now(),
};
routes.set(routeRef, route);
audit(actor, input.projectRef, createdEvent(existing, "route"));
return { replayed: false, result: { created: !existing, route } };
}
if (command === "device-bindings:ensure") {
if (!projects.has(input.projectRef)) {
throw serviceError("device_project_not_found", 404);
}
if (input.source?.kind !== "collection" || !collections.has(input.source.ref)) {
throw serviceError("device_binding_source_not_found", 404);
}
const existing = projectValues(bindings, input.projectRef).find(
(entry) => entry.bindingKey === input.bindingKey,
);
const bindingRef = existing?.bindingRef || `binding:${randomUUID()}`;
const source = collections.get(input.source.ref);
const binding = {
bindingRef,
projectRef: input.projectRef,
bindingKey: input.bindingKey,
displayName: input.displayName,
source: {
kind: input.source.kind,
ref: input.source.ref,
displayName: source.name,
},
target: { kind: input.targetKind, ref: input.targetRef },
capabilities: input.capabilities,
lifecycleState: "pending_external_approval",
sourceApprovedAt: now(),
createdAt: existing?.createdAt || now(),
updatedAt: now(),
};
bindings.set(bindingRef, binding);
audit(actor, input.projectRef, createdEvent(existing, "device_binding"));
return { replayed: false, result: { created: !existing, binding } };
}
if (command === "device-bindings:revoke") {
const binding = bindings.get(input.bindingRef);
if (!binding || binding.projectRef !== input.projectRef) {
throw serviceError("device_binding_not_found", 404);
}
const revoked = { ...binding, lifecycleState: "revoked", updatedAt: now() };
bindings.set(binding.bindingRef, revoked);
audit(actor, input.projectRef, "device_binding.revoked");
return { replayed: false, result: { revoked: true, binding: revoked } };
}
if (command === "device-configuration-revisions:create") {
throw serviceError("device_not_found", 404);
}
if (command === "device-configurations:set-desired") {
throw serviceError("device_configuration_revision_not_found", 404);
}
if (command === "enrollment-intents:ensure") {
throw serviceError("device_enrollment_secure_input_required", 409);
}
if (command === "devices:claim") {
throw serviceError("device_discovery_not_found", 404);
}
throw serviceError("device_manager_command_invalid", 404);
},
snapshot() {
return { ownerScopes, projects, collections };
return {
ownerScopes,
projects,
collections,
adapterPackages,
adapterVersions,
modelProfiles,
edges,
routes,
bindings,
grants,
configurationRevisions,
configurationStates,
auditEvents,
};
},
};
}
function createdEvent(existing, resource) {
return `${resource}.${existing ? "updated" : "created"}`;
}
function requirePlatformOwner(actor) {
if (actor?.hubRole !== "owner") {
throw serviceError("device_platform_catalog_access_denied", 403);
}
}
const ownerCapabilities = Object.freeze([
"project.read",
"project.manage",