Add board thread interventions
This commit is contained in:
1 parent
28007b3691
commit
78dbcbe7ce
14 files changed
+990
-370
No files matched your search
@@ -0,0 +1,414 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { DatabaseConnection } from "../../src/main/database/connection";
|
||||
import { ChatService } from "../../src/main/services/chat-service";
|
||||
import { OrganizationService } from "../../src/main/services/organization-service";
|
||||
import { SnapshotService } from "../../src/main/services/snapshot-service";
|
||||
|
||||
function createConnection(): DatabaseConnection {
|
||||
return new DatabaseConnection(join(tmpdir(), `agentcompany-new-chat-${randomUUID()}.db`));
|
||||
}
|
||||
|
||||
describe("chat governance", () => {
|
||||
test("records board interventions and enforces thread control", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
const snapshot = new SnapshotService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let agentId = "";
|
||||
let spaceId = "";
|
||||
let threadId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
agentId = organization.createAgent({
|
||||
orgId,
|
||||
name: "Ada",
|
||||
role: "Release reviewer",
|
||||
department: "Engineering",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
});
|
||||
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Release thread",
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
threadId = (connection.db.prepare("select id from chat_threads where space_id = ?").get(spaceId) as { id: string }).id;
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId,
|
||||
senderType: "board",
|
||||
senderId: employeeId,
|
||||
bodyMd: "/board freeze hold the release",
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Proceeding",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Board-frozen thread requires board sender.");
|
||||
|
||||
connection.transaction(() => {
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId,
|
||||
senderType: "board",
|
||||
senderId: employeeId,
|
||||
bodyMd: "/board rollback reopen release coordination",
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId,
|
||||
senderType: "board",
|
||||
senderId: employeeId,
|
||||
bodyMd: `/board takeover @${agentId} Ada owns release checks`,
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Taking back control",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Board takeover controls this thread.");
|
||||
|
||||
connection.transaction(() => {
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId,
|
||||
senderType: "agent",
|
||||
senderId: agentId,
|
||||
bodyMd: "Release checks are in progress.",
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId,
|
||||
senderType: "board",
|
||||
senderId: employeeId,
|
||||
bodyMd: "/board force-approval approve gated release window",
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
const state = snapshot.load();
|
||||
const thread = state.chatThreads.find((item) => item.id === threadId);
|
||||
expect(thread).toMatchObject({ boardState: "takeover", controlledByType: "agent", controlledById: agentId });
|
||||
expect(state.boardInterventions.map((item) => item.action)).toEqual(["freeze", "rollback", "takeover", "force_approval"]);
|
||||
const forceApproval = state.approvals.find((item) => item.kind === "board_force_approval");
|
||||
if (!forceApproval) throw new Error("Board force approval was not created.");
|
||||
const forceIntervention = state.boardInterventions.find((item) => item.action === "force_approval");
|
||||
if (!forceIntervention) throw new Error("Board force approval intervention was not created.");
|
||||
expect(forceIntervention.approvalId).toBe(forceApproval.id);
|
||||
expect(state.chatSlashInvocations.some((item) => item.resultRef === forceIntervention.id)).toBe(true);
|
||||
expect(state.auditEvents.some((event) => event.entityType === "board_intervention" && event.action === "recorded")).toBe(true);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects chat writes outside membership and prevents partial invalid messages", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let agentId = "";
|
||||
let restrictedSpaceId = "";
|
||||
let workspaceSpaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
agentId = organization.createAgent({
|
||||
orgId,
|
||||
name: "Ada",
|
||||
role: "Release reviewer",
|
||||
department: "Engineering",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
});
|
||||
restrictedSpaceId = chat.createSpace({
|
||||
orgId,
|
||||
spaceType: "project",
|
||||
name: "board-review",
|
||||
topic: "Restricted release review",
|
||||
visibility: "restricted",
|
||||
memberEmployeeIds: [employeeId],
|
||||
});
|
||||
workspaceSpaceId = chat.createSpace({
|
||||
orgId,
|
||||
spaceType: "project",
|
||||
name: "release",
|
||||
topic: "Release coordination",
|
||||
visibility: "workspace",
|
||||
});
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: restrictedSpaceId,
|
||||
threadId: null,
|
||||
senderType: "agent",
|
||||
senderId: agentId,
|
||||
bodyMd: "I should not be here.",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Chat membership is required.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: restrictedSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Restricted ping @Ada",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Mention @Ada could not be resolved.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "/unknown command",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Slash command /unknown is unavailable.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Missing file",
|
||||
attachmentFileIds: ["file_missing"],
|
||||
}))).toThrow("Attachment file was not found.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "/escalate",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Slash command /escalate requires details.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Unknown @Nobody",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Mention @Nobody could not be resolved.");
|
||||
|
||||
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
|
||||
expect(count.count).toBe(0);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects reserved mention handles for employees and agents", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
let orgId = "";
|
||||
|
||||
expect(() => connection.transaction(() => organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Board",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
}))).toThrow("Mention handle is reserved.");
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
});
|
||||
expect(() => connection.transaction(() => organization.createAgent({
|
||||
orgId,
|
||||
name: "On Call",
|
||||
role: "Escalation lead",
|
||||
department: "Operations",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
}))).toThrow("Mention handle is reserved.");
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects ambiguous chat mentions", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let spaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
organization.createAgent({ orgId, name: "Ada", role: "Reviewer", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
|
||||
organization.createAgent({ orgId, name: "Ada", role: "Release lead", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
|
||||
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Please review @Ada",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Mention @Ada is ambiguous.");
|
||||
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
|
||||
expect(count.count).toBe(0);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("resolves chat mentions by display handle and principal id", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let agentId = "";
|
||||
let spaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
agentId = organization.createAgent({
|
||||
orgId,
|
||||
name: "Ada Lovelace",
|
||||
role: "Reviewer",
|
||||
department: "Engineering",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
});
|
||||
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
|
||||
});
|
||||
|
||||
connection.transaction(() => {
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: `Review @Ada-Lovelace and @${agentId}`,
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
const mentions = connection.db.prepare(`
|
||||
select target_type, target_id
|
||||
from chat_mentions
|
||||
order by offset_start asc
|
||||
`).all() as Array<{ target_type: string; target_id: string }>;
|
||||
expect(mentions).toEqual([
|
||||
{ target_type: "agent", target_id: agentId },
|
||||
{ target_type: "agent", target_id: agentId },
|
||||
]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects replies to threads from another chat space", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let firstSpaceId = "";
|
||||
let secondSpaceId = "";
|
||||
let threadId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
firstSpaceId = chat.createSpace({ orgId, spaceType: "project", name: "release-a", topic: "A", visibility: "workspace" });
|
||||
secondSpaceId = chat.createSpace({ orgId, spaceType: "project", name: "release-b", topic: "B", visibility: "workspace" });
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: firstSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Root",
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
threadId = (connection.db.prepare("select id from chat_threads where space_id = ?").get(firstSpaceId) as { id: string }).id;
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: secondSpaceId,
|
||||
threadId,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Wrong thread",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Chat thread was not found in this space.");
|
||||
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
|
||||
expect(count.count).toBe(1);
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,7 @@ describe("domain services", () => {
|
||||
const connection = createConnection();
|
||||
|
||||
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
@@ -405,279 +405,6 @@ describe("domain services", () => {
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects chat writes outside membership and prevents partial invalid messages", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let agentId = "";
|
||||
let restrictedSpaceId = "";
|
||||
let workspaceSpaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
agentId = organization.createAgent({
|
||||
orgId,
|
||||
name: "Ada",
|
||||
role: "Release reviewer",
|
||||
department: "Engineering",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
});
|
||||
restrictedSpaceId = chat.createSpace({
|
||||
orgId,
|
||||
spaceType: "project",
|
||||
name: "board-review",
|
||||
topic: "Restricted release review",
|
||||
visibility: "restricted",
|
||||
memberEmployeeIds: [employeeId],
|
||||
});
|
||||
workspaceSpaceId = chat.createSpace({
|
||||
orgId,
|
||||
spaceType: "project",
|
||||
name: "release",
|
||||
topic: "Release coordination",
|
||||
visibility: "workspace",
|
||||
});
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: restrictedSpaceId,
|
||||
threadId: null,
|
||||
senderType: "agent",
|
||||
senderId: agentId,
|
||||
bodyMd: "I should not be here.",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Chat membership is required.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: restrictedSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Restricted ping @Ada",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Mention @Ada could not be resolved.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "/unknown command",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Slash command /unknown is unavailable.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Missing file",
|
||||
attachmentFileIds: ["file_missing"],
|
||||
}))).toThrow("Attachment file was not found.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "/escalate",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Slash command /escalate requires details.");
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: workspaceSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Unknown @Nobody",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Mention @Nobody could not be resolved.");
|
||||
|
||||
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
|
||||
expect(count.count).toBe(0);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects reserved mention handles for employees and agents", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
let orgId = "";
|
||||
|
||||
expect(() => connection.transaction(() => organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Board",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
}))).toThrow("Mention handle is reserved.");
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
});
|
||||
expect(() => connection.transaction(() => organization.createAgent({
|
||||
orgId,
|
||||
name: "On Call",
|
||||
role: "Escalation lead",
|
||||
department: "Operations",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
}))).toThrow("Mention handle is reserved.");
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects ambiguous chat mentions", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let spaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
organization.createAgent({ orgId, name: "Ada", role: "Reviewer", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
|
||||
organization.createAgent({ orgId, name: "Ada", role: "Release lead", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 10000, managerAgentId: null });
|
||||
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Please review @Ada",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Mention @Ada is ambiguous.");
|
||||
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
|
||||
expect(count.count).toBe(0);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("resolves chat mentions by display handle and principal id", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let agentId = "";
|
||||
let spaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
agentId = organization.createAgent({
|
||||
orgId,
|
||||
name: "Ada Lovelace",
|
||||
role: "Reviewer",
|
||||
department: "Engineering",
|
||||
modelProviderId: null,
|
||||
modelId: null,
|
||||
budgetCents: 10000,
|
||||
managerAgentId: null,
|
||||
});
|
||||
spaceId = chat.createSpace({ orgId, spaceType: "project", name: "release", topic: "Release coordination", visibility: "workspace" });
|
||||
});
|
||||
|
||||
connection.transaction(() => {
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: `Review @Ada-Lovelace and @${agentId}`,
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
const mentions = connection.db.prepare(`
|
||||
select target_type, target_id
|
||||
from chat_mentions
|
||||
order by offset_start asc
|
||||
`).all() as Array<{ target_type: string; target_id: string }>;
|
||||
expect(mentions).toEqual([
|
||||
{ target_type: "agent", target_id: agentId },
|
||||
{ target_type: "agent", target_id: agentId },
|
||||
]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("rejects replies to threads from another chat space", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let employeeId = "";
|
||||
let firstSpaceId = "";
|
||||
let secondSpaceId = "";
|
||||
let threadId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
organizationName: "Acme Operations",
|
||||
employeeName: "Dana Lee",
|
||||
mission: "Coordinate agents with auditable approvals.",
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
firstSpaceId = chat.createSpace({ orgId, spaceType: "project", name: "release-a", topic: "A", visibility: "workspace" });
|
||||
secondSpaceId = chat.createSpace({ orgId, spaceType: "project", name: "release-b", topic: "B", visibility: "workspace" });
|
||||
chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: firstSpaceId,
|
||||
threadId: null,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Root",
|
||||
attachmentFileIds: [],
|
||||
});
|
||||
threadId = (connection.db.prepare("select id from chat_threads where space_id = ?").get(firstSpaceId) as { id: string }).id;
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => chat.sendMessage({
|
||||
orgId,
|
||||
spaceId: secondSpaceId,
|
||||
threadId,
|
||||
senderType: "employee",
|
||||
senderId: employeeId,
|
||||
bodyMd: "Wrong thread",
|
||||
attachmentFileIds: [],
|
||||
}))).toThrow("Chat thread was not found in this space.");
|
||||
const count = connection.db.prepare("select count(*) as count from chat_messages").get() as { count: number };
|
||||
expect(count.count).toBe(1);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("creates a read-only migration report from a legacy database", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
|
||||
@@ -107,6 +107,7 @@ describe("migration service", () => {
|
||||
create table chat_attachments(message_id text, file_id text);
|
||||
create table chat_mentions(message_id text, target_type text, target_id text);
|
||||
create table chat_slash_invocations(id text primary key, message_id text);
|
||||
create table board_interventions(id text primary key, org_id text, thread_id text, message_id text, approval_id text);
|
||||
create table chat_presence(principal_type text, principal_id text);
|
||||
create table chat_read_state(org_id text, employee_id text, space_id text, last_read_message_id text);
|
||||
create table providers(id text primary key, org_id text, credential_id text);
|
||||
@@ -148,6 +149,7 @@ describe("migration service", () => {
|
||||
insert into file_access_grants(id, org_id, file_id, source_message_id, source_thread_id) values ('file-grant-1', 'org-1', 'file-1', 'msg-1', 'thread-1');
|
||||
insert into chat_mentions(message_id, target_type, target_id) values ('msg-1', 'agent', 'agent-1');
|
||||
insert into chat_slash_invocations(id, message_id) values ('slash-1', 'msg-1');
|
||||
insert into board_interventions(id, org_id, thread_id, message_id, approval_id) values ('bdi-1', 'org-1', 'thread-1', 'msg-1', 'approval-1');
|
||||
insert into chat_presence(principal_type, principal_id) values ('agent', 'agent-1');
|
||||
insert into chat_read_state(org_id, employee_id, space_id, last_read_message_id) values ('org-1', 'emp-1', 'space-1', 'msg-1');
|
||||
insert into provider_requests(id, org_id, provider_id) values ('req-1', 'org-1', 'prov-1');
|
||||
@@ -178,10 +180,12 @@ describe("migration service", () => {
|
||||
expect(body.tableCounts.credentials).toBe(1);
|
||||
expect(body.tableCounts.skill_artifacts).toBe(1);
|
||||
expect(body.tableCounts.chat_read_state).toBe(1);
|
||||
expect(body.tableCounts.board_interventions).toBe(1);
|
||||
expect(body.tableCounts.file_access_grants).toBe(1);
|
||||
expect(body.tableCounts.provider_requests).toBe(1);
|
||||
expect(body.tableChecksums.audit_events).toHaveLength(64);
|
||||
expect(body.idMaps.skill_runtime_snapshots).toEqual(["snapshot-1"]);
|
||||
expect(body.idMaps.board_interventions).toEqual(["bdi-1"]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user