feat(manager): expose safe VPS edge health

This commit is contained in:
DCCONSTRUCTIONS
2026-08-22 13:08:04 +03:00
parent 07ffb22d38
commit 6ed4414a97
14 changed files with 464 additions and 42 deletions
+3
View File
@@ -287,6 +287,9 @@ export function createControlCoreApp({
commandTransport: typedCommandRuntime
? "typed-service-ping-v1"
: "disabled",
edgeChannelStatus: edgeChannelStatusProvider
? edgeChannelStatusProvider()
: null,
},
);
return writeJson(response, 200, { ok: true, workspace });
@@ -50,7 +50,7 @@ export async function getDeviceProjectWorkspace(
client,
actor,
projectId,
{ commandTransport = "disabled" } = {},
{ commandTransport = "disabled", edgeChannelStatus = null } = {},
) {
const project = await findProjectWithCapability(
client,
@@ -204,7 +204,8 @@ export async function getDeviceProjectWorkspace(
);
const edges = await client.query(
`select de.id, de.edge_key, de.display_name, de.deployment_ref,
de.lifecycle_state, de.created_at, de.updated_at
de.lifecycle_state, de.channel_lifecycle_state,
de.channel_generation_ref, de.created_at, de.updated_at
from device_edges de
where $2::boolean
or exists (
@@ -333,7 +334,10 @@ export async function getDeviceProjectWorkspace(
adapterPackages: adapterPackages.rows.map(adapterPackageView),
adapterVersions: adapterVersions.rows.map(adapterVersionView),
modelProfiles: modelProfiles.rows.map(modelProfileView),
edges: edges.rows.map(edgeView),
edges: edges.rows.map((row) => edgeView(
row,
edgeChannelRuntime(edgeChannelStatus, `edge:${row.id}`),
)),
routes: routes.rows.map(routeView),
sessions: sessions.rows.map(sessionView),
bindings: bindings.rows.map(bindingView),
@@ -496,18 +500,41 @@ function modelProfileView(row) {
};
}
function edgeView(row) {
function edgeView(row, runtime) {
const channelLifecycleState = row.channel_lifecycle_state ?? "disabled";
return {
edgeRef: `edge:${row.id}`,
edgeKey: row.edge_key,
displayName: row.display_name,
deploymentRef: row.deployment_ref ?? null,
lifecycleState: row.lifecycle_state,
channel: {
lifecycleState: channelLifecycleState,
generationRef: row.channel_generation_ref ?? null,
runtimeState: channelLifecycleState === "active"
? runtime?.channel ?? "unobserved"
: channelLifecycleState,
lastErrorCode: runtime?.lastErrorCode ?? null,
},
createdAt: toIso(row.created_at),
updatedAt: toIso(row.updated_at),
};
}
function edgeChannelRuntime(status, edgeRef) {
if (!status || !Array.isArray(status.edges)) return null;
const value = status.edges.find((item) => item?.edgeRegistrationId === edgeRef);
if (!value) return null;
return {
channel: ["accepted", "connecting", "absent"].includes(value.channel)
? value.channel
: "unobserved",
lastErrorCode: typeof value.lastErrorCode === "string"
? value.lastErrorCode.slice(0, 128)
: null,
};
}
function routeView(row) {
return {
routeRef: `route:${row.id}`,
+12 -2
View File
@@ -347,14 +347,23 @@ test("project query is service-authenticated and forwards only the trusted actor
test("project workspace query accepts only a canonical project path", async () => {
const projectId = "11111111-1111-4111-8111-111111111111";
let queried;
const edgeChannels = {
enabled: true,
edges: [{
edgeRegistrationId: "edge:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
channel: "accepted",
lastErrorCode: null,
}],
};
const runtime = await startTestServer({
managementApiEnabled: true,
managementToken,
edgeChannelStatusProvider: () => edgeChannels,
repository: {
health: async () => "ready",
executeManagementCommand: async () => ({ replayed: false, result: {} }),
getProjectWorkspace: async (actor, id) => {
queried = { actor, id };
getProjectWorkspace: async (actor, id, options) => {
queried = { actor, id, options };
return { project: { projectRef: `project:${id}` }, devices: [] };
},
},
@@ -367,6 +376,7 @@ test("project workspace query accepts only a canonical project path", async () =
assert.equal(response.status, 200);
assert.equal((await response.json()).workspace.devices.length, 0);
assert.equal(queried.id, projectId);
assert.equal(queried.options.edgeChannelStatus, edgeChannels);
const invalid = await fetch(
`${runtime.baseUrl}/internal/v1/query/projects/not-a-project/workspace`,
@@ -53,7 +53,15 @@ test("project list applies direct-grant precedence and returns bounded summaries
test("project workspace returns only masked identity projections", async () => {
const client = workspaceClient();
const workspace = await getDeviceProjectWorkspace(client, actor, projectId);
const workspace = await getDeviceProjectWorkspace(client, actor, projectId, {
edgeChannelStatus: {
edges: [{
edgeRegistrationId: "edge:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
channel: "accepted",
lastErrorCode: null,
}],
},
});
assert.equal(workspace.project.projectRef, `project:${projectId}`);
assert.equal(workspace.devices[0].identifier.masked, "***********0001");
@@ -66,6 +74,12 @@ test("project workspace returns only masked identity projections", async () => {
assert.equal(workspace.adapterPackages[0].packageKey, "generic-tracker");
assert.equal(workspace.modelProfiles[0].modelProfileRef, "vendor.model.v1");
assert.equal(workspace.routes[0].activeSessionCount, 1);
assert.deepEqual(workspace.edges[0].channel, {
lifecycleState: "active",
generationRef: "channel-generation:1",
runtimeState: "accepted",
lastErrorCode: null,
});
assert.equal(workspace.sessions[0].frameCount, 12);
assert.equal(workspace.bindings[0].lifecycleState, "pending_external_approval");
assert.equal(workspace.configurationRevisions[0].revisionNumber, 1);
@@ -103,6 +117,10 @@ test("project read source never selects identifier digests or credential refs",
source,
/\b(?:identifier_digest|expected_identifier_digest|credential_ref|parameters_digest|parameters_projection|transport_message_ref|external_approval_ref|external_approval_digest)\b/,
);
assert.doesNotMatch(
source,
/\b(?:channel_endpoint|channel_servername|channel_trust_bundle_ref|channel_certificate_identities)\b/,
);
assert.doesNotMatch(source, /\b(?:dae\.payload|dcr\.configuration)\b/);
});
@@ -167,6 +185,8 @@ function workspaceClient({ queries = [] } = {}) {
display_name: "Edge one",
deployment_ref: "deployment:edge-one",
lifecycle_state: "active",
channel_lifecycle_state: "active",
channel_generation_ref: "channel-generation:1",
created_at: timestamp,
updated_at: timestamp,
}] };