Merge pull request #26 from codex/agent-config-tier

feat(agent-config): add typed dashboard mutation API
This commit is contained in:
vince 2026-06-19 12:25:31 +02:00
commit eeac37e58e
5 changed files with 1902 additions and 6 deletions

View file

@ -1,6 +1,11 @@
export { export {
DASHBOARD_SCHEMA_VERSION, DASHBOARD_SCHEMA_VERSION,
DashboardDocumentSchema, DashboardDocumentSchema,
DatasourceReferenceSchema,
ServiceEntrySchema,
ServiceGroupSchema,
TelemetryCardSchema,
ThresholdSchema,
dashboardDocumentJsonSchema, dashboardDocumentJsonSchema,
type DashboardDocument, type DashboardDocument,
type DashboardModule, type DashboardModule,

View file

@ -27,7 +27,7 @@ const LinkSchema = Type.Object(
{ additionalProperties: false }, { additionalProperties: false },
); );
const StaticDatasourceSchema = Type.Object( export const StaticDatasourceSchema = Type.Object(
{ {
type: Type.Literal("static"), type: Type.Literal("static"),
label: Type.Optional(Type.String({ minLength: 1 })), label: Type.Optional(Type.String({ minLength: 1 })),
@ -36,7 +36,7 @@ const StaticDatasourceSchema = Type.Object(
{ additionalProperties: false }, { additionalProperties: false },
); );
const PlaceholderDatasourceSchema = Type.Object( export const PlaceholderDatasourceSchema = Type.Object(
{ {
type: Type.Literal("placeholder"), type: Type.Literal("placeholder"),
reason: Type.String({ minLength: 1 }), reason: Type.String({ minLength: 1 }),
@ -44,7 +44,7 @@ const PlaceholderDatasourceSchema = Type.Object(
{ additionalProperties: false }, { additionalProperties: false },
); );
const ExternalDatasourceSchema = Type.Object( export const ExternalDatasourceSchema = Type.Object(
{ {
type: Type.Literal("external"), type: Type.Literal("external"),
adapter: Type.Union([ adapter: Type.Union([
@ -58,7 +58,7 @@ const ExternalDatasourceSchema = Type.Object(
{ additionalProperties: false }, { additionalProperties: false },
); );
const DatasourceReferenceSchema = Type.Union([ export const DatasourceReferenceSchema = Type.Union([
StaticDatasourceSchema, StaticDatasourceSchema,
PlaceholderDatasourceSchema, PlaceholderDatasourceSchema,
ExternalDatasourceSchema, ExternalDatasourceSchema,
@ -98,13 +98,13 @@ const TextMetricValueSchema = Type.Object(
{ additionalProperties: false }, { additionalProperties: false },
); );
const MetricValueSchema = Type.Union([ export const MetricValueSchema = Type.Union([
PercentMetricValueSchema, PercentMetricValueSchema,
NonNegativeMetricValueSchema, NonNegativeMetricValueSchema,
TextMetricValueSchema, TextMetricValueSchema,
]); ]);
const ThresholdSchema = Type.Object( export const ThresholdSchema = Type.Object(
{ {
warning: Type.Optional(Type.Number({ minimum: 0 })), warning: Type.Optional(Type.Number({ minimum: 0 })),
danger: Type.Optional(Type.Number({ minimum: 0 })), danger: Type.Optional(Type.Number({ minimum: 0 })),

View file

@ -0,0 +1,595 @@
import { existsSync, rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import type { DashboardDocument } from "$lib/model";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store";
import {
AgentConfigAuthorizationError,
authorizeAgentConfigRequest,
handleAgentDashboardRequest,
previewDashboardChanges,
publishDashboardChanges,
rollbackDashboardRevision,
type AgentDashboardOperation,
type JsonPatchOperation,
} from ".";
const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
afterEach(() => {
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
describe("agent dashboard configuration API", () => {
test("previews typed dashboard operations with an RFC 6902-compatible patch", () => {
const operations = exampleOperations();
const result = previewDashboardChanges(genericDashboardFixture, operations);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected preview success");
expect(result.document.serviceGroups[0]?.id).toBe("edge");
expect(result.document.serviceGroups[0]?.services[0]).toMatchObject({
id: "edge-router",
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://edge.example.test/api/status",
},
});
expect(result.document.layout.telemetry).toEqual([
"service-uptime",
"edge-latency",
"queue-depth",
]);
expect(genericDashboardFixture.serviceGroups.map((group) => group.id)).toEqual([
"core-services",
]);
expect(result.patch.length).toBeGreaterThan(0);
expect(result.patch.every((operation) => operation.path.startsWith("/"))).toBe(true);
expect(result.patch.map((operation) => operation.op)).toContain("add");
});
test("returns structured repairable errors for invalid operations", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "add_service",
groupId: "missing-group",
service: exampleService(),
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected preview failure");
expect(result.errors).toEqual([
expect.objectContaining({
code: "group_not_found",
operationIndex: 0,
path: "/serviceGroups",
}),
]);
});
test("rejects unsupported target-specific mutations", () => {
const datasourceResult = previewDashboardChanges(genericDashboardFixture, [
{
type: "connect_datasource",
target: { kind: "statusItem", stripId: "runtime", id: "status" },
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://status.example.test/api",
},
},
]);
expect(datasourceResult.ok).toBe(false);
if (datasourceResult.ok) throw new Error("expected datasource failure");
expect(datasourceResult.errors[0]).toMatchObject({
code: "unsupported_target",
operationIndex: 0,
path: "/statusStrips",
});
const thresholdResult = previewDashboardChanges(genericDashboardFixture, [
{
type: "set_status_rule",
target: { kind: "service", id: "identity" },
thresholds: { warning: 1 },
},
]);
expect(thresholdResult.ok).toBe(false);
if (thresholdResult.ok) throw new Error("expected threshold failure");
expect(thresholdResult.errors[0]).toMatchObject({
code: "unsupported_target",
operationIndex: 0,
path: "/serviceGroups",
});
});
test("removes status strip items through the shared remove operation", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "sync" },
},
]);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected remove success");
expect(result.document.statusStrips[0]?.items.map((item) => item.id)).toEqual([
"status",
]);
expect(result.patch.map((operation) => operation.op)).toContain("remove");
});
test("returns RFC 6902-applicable patches for multiple array removals", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "status" },
},
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "sync" },
},
]);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected remove success");
expect(
result.patch
.filter((operation) => operation.op === "remove")
.map((operation) => operation.path),
).toEqual(["/statusStrips/0/items/1", "/statusStrips/0/items/0"]);
expect(applyJsonPatch(genericDashboardFixture, result.patch)).toMatchObject({
statusStrips: [{ items: [] }],
});
});
test("rejects ambiguous service and status targets", () => {
const duplicateDocument = documentWithDuplicateNestedIds();
const connect = previewDashboardChanges(duplicateDocument, [
{
type: "connect_datasource",
target: { kind: "service", id: "identity" },
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://identity.example.test/health",
},
},
]);
expect(connect.ok).toBe(false);
if (connect.ok) throw new Error("expected ambiguous service failure");
expect(connect.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const remove = previewDashboardChanges(duplicateDocument, [
{
type: "remove_item",
target: { kind: "service", id: "identity" },
},
]);
expect(remove.ok).toBe(false);
if (remove.ok) throw new Error("expected ambiguous removal failure");
expect(remove.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const status = previewDashboardChanges(duplicateDocument, [
{
type: "set_status_rule",
target: { kind: "statusItem", id: "status" },
value: "Healthy",
},
]);
expect(status.ok).toBe(false);
if (status.ok) throw new Error("expected ambiguous status failure");
expect(status.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/statusStrips",
});
});
test("rejects create_dashboard when it is not the first operation", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "telemetry", id: "queue-depth" },
},
{
type: "create_dashboard",
document: genericDashboardFixture,
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected sequence failure");
expect(result.errors[0]).toMatchObject({
code: "invalid_operation_sequence",
operationIndex: 1,
path: "/1",
});
});
test("publishes valid operations as a persisted dashboard revision", async () => {
const { dbPath, store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
message: "initial dashboard",
});
const result = publishDashboardChanges(store, exampleOperations(), {
actor: "agent",
message: "add edge router",
});
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected publish success");
expect(existsSync(dbPath)).toBe(true);
expect(result.revision.operation).toBe("commit");
expect(result.revision.actor).toBe("agent");
expect(result.revision.message).toBe("add edge router");
expect(result.previousRevisionId).toBe(seed.id);
expect(result.patch.length).toBeGreaterThan(0);
expect(store.getActiveDashboard()?.document.serviceGroups[0]?.id).toBe("edge");
expect(store.listRevisions()).toHaveLength(2);
});
test("does not publish invalid operations", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const result = publishDashboardChanges(store, [
{
type: "remove_item",
target: { kind: "telemetry", id: "missing-metric" },
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected publish failure");
expect(result.errors[0]).toMatchObject({
code: "item_not_found",
operationIndex: 0,
});
expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id);
expect(store.listRevisions()).toHaveLength(1);
});
test("rolls back through the agent-safe revision path", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const publish = publishDashboardChanges(store, exampleOperations(), {
actor: "agent",
});
expect(publish.ok).toBe(true);
const rollback = rollbackDashboardRevision(store, seed.id, {
actor: "agent",
message: "restore previous dashboard",
});
expect(rollback.operation).toBe("rollback");
expect(rollback.actor).toBe("agent");
expect(rollback.sourceRevisionId).toBe(seed.id);
expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions().map((revision) => revision.operation)).toEqual([
"rollback",
"commit",
"seed",
]);
});
test("authenticates agent configuration requests with a shared token", () => {
const authorized = new Request("https://dimensionlab.test/api/agent/dashboard", {
headers: { authorization: "Bearer shared-secret" },
});
const rejected = new Request("https://dimensionlab.test/api/agent/dashboard");
expect(authorizeAgentConfigRequest(authorized, "shared-secret")).toEqual({
ok: true,
});
expect(() => authorizeAgentConfigRequest(rejected, "shared-secret")).toThrow(
AgentConfigAuthorizationError,
);
expect(() => authorizeAgentConfigRequest(authorized, "")).toThrow(
AgentConfigAuthorizationError,
);
});
test("handles preview, publish, and rollback requests through the HTTP adapter", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const preview = await handleAgentDashboardRequest(
jsonRequest({
action: "preview_changes",
operations: exampleOperations(),
}),
{ store, token: "shared-secret" },
);
expect(preview.status).toBe(200);
expect(await preview.json()).toMatchObject({
ok: true,
action: "preview_changes",
patch: expect.any(Array),
});
expect(store.listRevisions()).toHaveLength(1);
const publish = await handleAgentDashboardRequest(
jsonRequest({
action: "publish_changes",
actor: "agent",
message: "publish edge router",
operations: exampleOperations(),
}),
{ store, token: "shared-secret" },
);
expect(publish.status).toBe(200);
expect(await publish.json()).toMatchObject({
ok: true,
action: "publish_changes",
revision: { operation: "commit", actor: "agent" },
});
expect(store.listRevisions()).toHaveLength(2);
const rollback = await handleAgentDashboardRequest(
jsonRequest({
action: "rollback_revision",
actor: "agent",
revisionId: seed.id,
}),
{ store, token: "shared-secret" },
);
expect(rollback.status).toBe(200);
expect(await rollback.json()).toMatchObject({
ok: true,
action: "rollback_revision",
revision: { operation: "rollback", sourceRevisionId: seed.id },
});
});
test("returns a structured error for unknown rollback revisions", async () => {
const { store } = await createTestStore();
store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const rollback = await handleAgentDashboardRequest(
jsonRequest({
action: "rollback_revision",
actor: "agent",
revisionId: "missing-revision",
}),
{ store, token: "shared-secret" },
);
expect(rollback.status).toBe(404);
expect(await rollback.json()).toMatchObject({
ok: false,
errors: [
{
code: "revision_not_found",
path: "/revisionId",
},
],
});
});
test("previews create_dashboard requests against an empty store", async () => {
const { store } = await createTestStore();
const preview = await handleAgentDashboardRequest(
jsonRequest({
action: "preview_changes",
operations: [
{
type: "create_dashboard",
document: genericDashboardFixture,
},
],
}),
{ store, token: "shared-secret" },
);
expect(preview.status).toBe(200);
expect(await preview.json()).toMatchObject({
ok: true,
action: "preview_changes",
document: {
metadata: {
title: "Operations Console",
},
},
});
expect(store.listRevisions()).toHaveLength(0);
});
});
async function createTestStore() {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-agent-config-"));
tempRoots.push(root);
const dbPath = join(root, "dashboard.sqlite");
const store = createDashboardStore({
databaseUrl: `file:${dbPath}`,
});
stores.push(store);
return { dbPath, store };
}
function exampleOperations(): AgentDashboardOperation[] {
return [
{
type: "add_section",
section: {
id: "edge",
title: "Edge",
layout: "list",
},
},
{
type: "add_service",
groupId: "edge",
service: exampleService(),
},
{
type: "add_metric_card",
card: {
id: "edge-latency",
label: "Edge Latency",
value: { kind: "latency", value: 12, precision: 0 },
severity: "ok",
detail: "p95",
datasource: {
type: "external",
adapter: "prometheus",
reference: "histogram_quantile(0.95, edge_request_duration_seconds_bucket)",
},
},
position: { afterId: "service-uptime" },
},
{
type: "connect_datasource",
target: {
kind: "service",
groupId: "edge",
id: "edge-router",
},
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://edge.example.test/api/status",
},
},
{
type: "set_status_rule",
target: { kind: "telemetry", id: "edge-latency" },
thresholds: { warning: 50, danger: 100 },
},
{
type: "arrange_item",
area: "serviceGroups",
id: "edge",
index: 0,
},
];
}
function exampleService() {
return {
id: "edge-router",
label: "Edge Router",
description: "Ingress and routing",
icon: "mdi:router-network",
severity: "ok" as const,
detail: "pending datasource",
datasource: { type: "placeholder" as const, reason: "health adapter pending" },
link: {
href: "https://edge.example.test",
label: "Open Edge Router",
external: true,
},
};
}
function documentWithDuplicateNestedIds(): DashboardDocument {
const document = structuredClone(genericDashboardFixture);
document.layout.serviceGroups.push("secondary-services");
document.serviceGroups.push({
id: "secondary-services",
title: "Secondary Services",
layout: "list",
services: [
{
...document.serviceGroups[0].services[0],
label: "Shadow Identity",
},
],
});
document.layout.statusStrips.push("secondary-runtime");
document.statusStrips.push({
id: "secondary-runtime",
items: [
{
...document.statusStrips[0].items[0],
value: "Healthy",
},
],
});
return document;
}
function applyJsonPatch<T>(value: T, patch: JsonPatchOperation[]): T {
const next = structuredClone(value);
for (const operation of patch) {
const { parent, key } = jsonPointerTarget(next, operation.path);
if (operation.op === "remove") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 1);
} else {
delete parent[key];
}
} else if (operation.op === "add") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 0, operation.value);
} else {
parent[key] = operation.value;
}
} else if (operation.op === "replace") {
if (Array.isArray(parent)) {
parent[Number(key)] = operation.value;
} else {
parent[key] = operation.value;
}
}
}
return next;
}
function jsonPointerTarget(value: unknown, path: string) {
const segments = path
.split("/")
.slice(1)
.map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
const key = segments.pop();
if (key === undefined) throw new Error(`Invalid JSON pointer: ${path}`);
let parent = value as Record<string, unknown> | unknown[];
for (const segment of segments) {
parent = Array.isArray(parent)
? (parent[Number(segment)] as Record<string, unknown> | unknown[])
: (parent[segment] as Record<string, unknown> | unknown[]);
}
return { parent, key };
}
function jsonRequest(body: unknown) {
return new Request("https://dimensionlab.test/api/agent/dashboard", {
body: JSON.stringify(body),
headers: {
authorization: "Bearer shared-secret",
"content-type": "application/json",
},
method: "POST",
});
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
import { handleAgentDashboardRequest } from "$lib/server/agent-config";
import type { RequestHandler } from "./$types";
export const POST: RequestHandler = ({ request }) => handleAgentDashboardRequest(request);