Remove final AppDatabase pass-through methods with ESLint guard (C1 part 3/3)
Migrate all remaining pass-through call sites (social, skills, budget,
entityCrud, documents, knowledge, automation, wakeup, backup, threads,
settings, snapshot, connectors, workspaces, activity) to their owning
domain modules. `AppDatabase` now carries only three methods:
- createHireRequest (multi-module composite: agents + approvalsMod)
- rollbackConfigRevision (multi-module composite: documents + entityCrud)
- queryAll (raw-SQL escape hatch, @internal)
The vault/env-binding infrastructure previously passed argument-by-
argument into `entityCrud.saveSecret`/`resolveSecretEnv` now binds at
module construction, letting callers invoke `db.entityCrud.saveSecret({...})`
without carrying vault plumbing.
Structural types that narrowed AppDatabase to a bag of flat methods
(`ConnectorModelPolicyDeps` callers, `CapabilityMatchDb`, test mocks in
budget-enforcement / budget-incidents / task-quality-gates /
failure-escalation / task-quality-gates-sweep) are reshaped to the new
namespace. `connectorPolicyDepsFromDb` replaces the old pattern of
handing the whole AppDatabase to `validateConnectorModelSelection`.
A new ESLint rule in eslint.config.mjs blocks new top-level methods on
AppDatabase so the pass-through layer cannot silently grow back.
`AppDatabase.ts` dropped from 193 lines to 58; `AppDatabaseBase` lost
the 270-line trailing pass-through block.
This commit is contained in:
1 parent
a69fbb053b
commit
118e3d8795
120 files changed
+838
-933
No files matched your search
@@ -70,7 +70,7 @@ describe("agent model switching", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -83,7 +83,7 @@ describe("agent model switching", () => {
|
||||
description: "Tests post-hire model switching",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
|
||||
@@ -24,7 +24,7 @@ async function createHarness() {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -37,7 +37,7 @@ async function createHarness() {
|
||||
description: "Tests manager-driven recursive hires",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
|
||||
@@ -25,7 +25,7 @@ async function createHarness() {
|
||||
db.init();
|
||||
|
||||
const companyId = db.company.saveCompany({ name: "Orbit Foundry", description: "API test", status: "active" });
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
|
||||
@@ -11,7 +11,7 @@ const { createAgent } = shared;
|
||||
|
||||
describe("Automation Rules CRUD", () => {
|
||||
it("creates, reads, toggles, and deletes an automation rule", () => {
|
||||
const ruleId = shared.db.saveAutomationRule({
|
||||
const ruleId = shared.db.automationMod.saveAutomationRule({
|
||||
companyId: shared.companyId,
|
||||
name: "Auto-escalate blocked tasks",
|
||||
description: "When a task is blocked, notify the manager",
|
||||
@@ -28,7 +28,7 @@ describe("Automation Rules CRUD", () => {
|
||||
expect(ruleId).toBeTruthy();
|
||||
|
||||
// Rule appears in snapshot
|
||||
const snapshot = shared.db.listSnapshot();
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const rule = snapshot.automationRules.find(r => r.id === ruleId);
|
||||
expect(rule).toBeTruthy();
|
||||
expect(rule!.name).toBe("Auto-escalate blocked tasks");
|
||||
@@ -39,36 +39,36 @@ describe("Automation Rules CRUD", () => {
|
||||
expect(rule!.executionCount).toBe(0);
|
||||
|
||||
// Toggle to paused
|
||||
shared.db.toggleAutomationRule(ruleId, shared.companyId, "paused");
|
||||
const updated = shared.db.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
shared.db.automationMod.toggleAutomationRule(ruleId, shared.companyId, "paused");
|
||||
const updated = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(updated!.status).toBe("paused");
|
||||
|
||||
// Record execution
|
||||
shared.db.recordAutomationExecution(ruleId, shared.companyId, "Auto-escalate blocked tasks", "task_blocked", "escalate_to_manager", "Escalated task-123");
|
||||
const afterExec = shared.db.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
shared.db.automationMod.recordAutomationExecution(ruleId, shared.companyId, "Auto-escalate blocked tasks", "task_blocked", "escalate_to_manager", "Escalated task-123");
|
||||
const afterExec = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(afterExec!.executionCount).toBe(1);
|
||||
expect(afterExec!.lastExecutedAt).toBeTruthy();
|
||||
|
||||
// Automation log
|
||||
const log = shared.db.getAutomationLog(shared.companyId);
|
||||
const log = shared.db.automationMod.getAutomationLog(shared.companyId);
|
||||
expect(log).toHaveLength(1);
|
||||
expect(log[0].ruleName).toBe("Auto-escalate blocked tasks");
|
||||
|
||||
// Delete
|
||||
shared.db.deleteAutomationRule(ruleId, shared.companyId);
|
||||
const afterDelete = shared.db.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
shared.db.automationMod.deleteAutomationRule(ruleId, shared.companyId);
|
||||
const afterDelete = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(afterDelete).toBeUndefined();
|
||||
});
|
||||
|
||||
it("updates an existing rule", () => {
|
||||
const ruleId = shared.db.saveAutomationRule({
|
||||
const ruleId = shared.db.automationMod.saveAutomationRule({
|
||||
companyId: shared.companyId,
|
||||
name: "Original name",
|
||||
trigger: "task_created",
|
||||
action: "assign_task",
|
||||
});
|
||||
|
||||
shared.db.saveAutomationRule({
|
||||
shared.db.automationMod.saveAutomationRule({
|
||||
id: ruleId,
|
||||
companyId: shared.companyId,
|
||||
name: "Updated name",
|
||||
@@ -77,7 +77,7 @@ describe("Automation Rules CRUD", () => {
|
||||
priority: 90,
|
||||
});
|
||||
|
||||
const rule = shared.db.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
const rule = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId);
|
||||
expect(rule!.name).toBe("Updated name");
|
||||
expect(rule!.action).toBe("notify_agent");
|
||||
expect(rule!.priority).toBe(90);
|
||||
@@ -171,7 +171,7 @@ describe("Agent Messages", () => {
|
||||
const engineeringLeadId = createAgent({ name: "Engineering Lead", role: "Lead", department: "engineering" });
|
||||
const productLeadId = createAgent({ name: "Product Lead", role: "Lead", department: "product" });
|
||||
|
||||
const projectId = shared.db.saveProject({
|
||||
const projectId = shared.db.entityCrud.saveProject({
|
||||
companyId: shared.companyId,
|
||||
name: "Cross-team Project",
|
||||
description: "Scope",
|
||||
@@ -229,7 +229,7 @@ describe("Agent Messages", () => {
|
||||
|
||||
it("appears in snapshot", () => {
|
||||
shared.db.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, channel: "company", subject: "Snapshot test", body: "Hi" });
|
||||
const snapshot = shared.db.listSnapshot();
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
expect(snapshot.agentMessages.length).toBeGreaterThanOrEqual(1);
|
||||
expect(snapshot.agentMessages[0].subject).toBe("Snapshot test");
|
||||
});
|
||||
@@ -316,7 +316,7 @@ describe("Auto Task Assignment", () => {
|
||||
|
||||
describe("Workflow Pipelines", () => {
|
||||
it("creates, activates, and deletes a workflow", () => {
|
||||
const wfId = shared.db.saveWorkflow({
|
||||
const wfId = shared.db.automationMod.saveWorkflow({
|
||||
companyId: shared.companyId,
|
||||
name: "Code Review Pipeline",
|
||||
description: "Auto-review flow for PRs",
|
||||
@@ -330,14 +330,14 @@ describe("Workflow Pipelines", () => {
|
||||
});
|
||||
|
||||
expect(wfId).toBeTruthy();
|
||||
const snapshot = shared.db.listSnapshot();
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const wf = snapshot.workflows.find(w => w.id === wfId);
|
||||
expect(wf!.name).toBe("Code Review Pipeline");
|
||||
expect(wf!.status).toBe("draft");
|
||||
expect(wf!.runCount).toBe(0);
|
||||
|
||||
// Delete
|
||||
shared.db.deleteWorkflow(wfId, shared.companyId);
|
||||
expect(shared.db.listSnapshot().workflows.find(w => w.id === wfId)).toBeUndefined();
|
||||
shared.db.automationMod.deleteWorkflow(wfId, shared.companyId);
|
||||
expect(shared.db.snap.listSnapshot().workflows.find(w => w.id === wfId)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -34,7 +34,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
shared.db.runs.markRunStatus(run.id, "running", "Started");
|
||||
|
||||
// Verify it's running
|
||||
let snapshot = shared.db.listSnapshot();
|
||||
let snapshot = shared.db.snap.listSnapshot();
|
||||
expect(snapshot.runs.find((r) => r.id === run.id)?.status).toBe("running");
|
||||
|
||||
// Simulate app restart — close and re-init
|
||||
@@ -44,7 +44,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
shared.db = new AppDatabase(profileDir);
|
||||
shared.db.init(); // This calls interruptStaleRuns()
|
||||
|
||||
snapshot = shared.db.listSnapshot();
|
||||
snapshot = shared.db.snap.listSnapshot();
|
||||
const recoveredRun = snapshot.runs.find((r) => r.id === run.id);
|
||||
expect(recoveredRun?.status).toBe("interrupted");
|
||||
});
|
||||
@@ -83,7 +83,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => {
|
||||
unattributedCost: true,
|
||||
});
|
||||
|
||||
const snapshot = shared.db.listSnapshot();
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const recovered = snapshot.runs.find((r) => r.id === run.id);
|
||||
expect(recovered?.status).toBe("interrupted");
|
||||
expect(recovered?.errorMessage).toBe("Worker process exited unexpectedly.");
|
||||
|
||||
@@ -111,7 +111,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
expect(ctoChain[0].id).toBe(ceoId);
|
||||
|
||||
// 8. Create task tree
|
||||
const projectId = shared.db.saveProject({
|
||||
const projectId = shared.db.entityCrud.saveProject({
|
||||
companyId: shared.companyId,
|
||||
name: "Launch MVP",
|
||||
description: "Build and launch MVP",
|
||||
@@ -150,8 +150,8 @@ describe("E2E: Full autonomous CEO cold start flow", () => {
|
||||
expect(subtask.parentId).toBe(parentTaskId);
|
||||
|
||||
// 9. Wakeup request test: enqueue wake while IC is busy
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, icId, "comment", "comment");
|
||||
const queuedWakes = shared.db.listWakeupRequests(shared.companyId, { agentId: icId, status: "queued" });
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, icId, "comment", "comment");
|
||||
const queuedWakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: icId, status: "queued" });
|
||||
expect(queuedWakes).toHaveLength(1);
|
||||
expect(queuedWakes[0].source).toBe("comment");
|
||||
|
||||
@@ -175,12 +175,12 @@ describe("E2E: Wakeup request lifecycle with run lifecycle", () => {
|
||||
|
||||
// Simulate: agent is running, wakes come in. All triggers are coalesced
|
||||
// into a single wakeup request with JSON array of trigger details.
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, agentId, "report_blocked", "report_blocked");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "report_blocked", "report_blocked");
|
||||
|
||||
// Verify coalesced into single row with all triggers
|
||||
const wakes = shared.db.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
expect(wakes).toHaveLength(1);
|
||||
expect(wakes[0].coalescedCount).toBe(2);
|
||||
|
||||
@@ -191,7 +191,7 @@ describe("E2E: Wakeup request lifecycle with run lifecycle", () => {
|
||||
expect(triggers[2].trigger).toBe("report_blocked");
|
||||
|
||||
// Wakes remain queued until consumed by the run lifecycle
|
||||
expect(shared.db.listWakeupRequests(shared.companyId, { agentId, status: "queued" })).toHaveLength(1);
|
||||
expect(shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" })).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("Step 6: Run failure escalation (database layer)", () => {
|
||||
|
||||
expect(outcome.budgetHardStopped).toBe(false);
|
||||
|
||||
const snapshot = shared.db.listSnapshot();
|
||||
const snapshot = shared.db.snap.listSnapshot();
|
||||
const finishedRun = snapshot.runs.find((r) => r.id === run.id);
|
||||
expect(finishedRun?.status).toBe("failed");
|
||||
expect(finishedRun?.errorMessage).toBe("Process crashed");
|
||||
|
||||
@@ -60,7 +60,7 @@ export function installSharedSetup(): SharedHandle {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
state.workspaceId = state.db.saveWorkspace({
|
||||
state.workspaceId = state.db.workspacesMod.saveWorkspace({
|
||||
companyId: state.companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
|
||||
@@ -15,11 +15,11 @@ describe("Step 3: Wakeup request lifecycle (database)", () => {
|
||||
|
||||
// Enqueue two wakes for the same busy agent. Coalesced into one row
|
||||
// with trigger_detail JSON array containing both triggers.
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment");
|
||||
|
||||
// List returns the coalesced wake
|
||||
const wakes = shared.db.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
expect(wakes).toHaveLength(1);
|
||||
expect(wakes[0].companyId).toBe(shared.companyId);
|
||||
expect(wakes[0].coalescedCount).toBe(1);
|
||||
@@ -32,7 +32,7 @@ describe("Step 3: Wakeup request lifecycle (database)", () => {
|
||||
|
||||
it("returns empty array when no wakeup requests exist", () => {
|
||||
const agentId = createAgent({ name: "TestAgent" });
|
||||
const wakes = shared.db.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" });
|
||||
expect(wakes).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -40,15 +40,15 @@ describe("Step 3: Wakeup request lifecycle (database)", () => {
|
||||
const agent1 = createAgent({ name: "Agent1" });
|
||||
const agent2 = createAgent({ name: "Agent2" });
|
||||
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, agent1, "comment", "comment");
|
||||
shared.db.enqueueWakeupRequest(shared.companyId, agent2, "subtask_completed", "subtask_completed");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agent1, "comment", "comment");
|
||||
shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agent2, "subtask_completed", "subtask_completed");
|
||||
|
||||
const wakes1 = shared.db.listWakeupRequests(shared.companyId, { agentId: agent1, status: "queued" });
|
||||
const wakes1 = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: agent1, status: "queued" });
|
||||
expect(wakes1).toHaveLength(1);
|
||||
expect(wakes1[0].source).toBe("comment");
|
||||
|
||||
// Agent2's wake should still be there
|
||||
const wakes2 = shared.db.listWakeupRequests(shared.companyId, { agentId: agent2, status: "queued" });
|
||||
const wakes2 = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: agent2, status: "queued" });
|
||||
expect(wakes2).toHaveLength(1);
|
||||
expect(wakes2[0].source).toBe("subtask_completed");
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ describe("connector model enforcement", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -82,7 +82,7 @@ describe("connector model enforcement", () => {
|
||||
description: "Blocks runs when agent models are disabled",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -113,7 +113,7 @@ describe("connector model enforcement", () => {
|
||||
status: "todo",
|
||||
});
|
||||
|
||||
db.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
|
||||
db.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
|
||||
|
||||
const { taskRunner } = createOrchestrationHarness(db);
|
||||
const result = taskRunner.queueTaskRun(taskId, "manual", "board");
|
||||
@@ -123,7 +123,7 @@ describe("connector model enforcement", () => {
|
||||
throw new Error("Expected queueTaskRun to reject the disabled model.");
|
||||
}
|
||||
expect(result.error.code).toBe("CONNECTOR_MODEL_NOT_ALLOWED");
|
||||
expect(db.listSnapshot().runs).toHaveLength(0);
|
||||
expect(db.snap.listSnapshot().runs).toHaveLength(0);
|
||||
|
||||
db.close();
|
||||
});
|
||||
@@ -133,7 +133,7 @@ describe("connector model enforcement", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -146,7 +146,7 @@ describe("connector model enforcement", () => {
|
||||
description: "Rejects invalid queued runs during recovery",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -187,12 +187,12 @@ describe("connector model enforcement", () => {
|
||||
const checkoutSuccess = db.tasks.checkoutTask(taskId, run.id);
|
||||
expect(checkoutSuccess).toBe(true);
|
||||
|
||||
db.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
|
||||
db.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
|
||||
|
||||
const { taskRunner } = createOrchestrationHarness(db);
|
||||
taskRunner.resumeQueuedRuns();
|
||||
|
||||
const recoveredRun = db.listSnapshot().runs.find((entry) => entry.id === run.id);
|
||||
const recoveredRun = db.snap.listSnapshot().runs.find((entry) => entry.id === run.id);
|
||||
expect(recoveredRun?.status).toBe("failed");
|
||||
expect(recoveredRun?.errorMessage).toContain("Connector model is disabled or invalid");
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ const mockLogger = {
|
||||
function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult {
|
||||
const connectorId: ConnectorId = "gemini-cli";
|
||||
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: connectorId,
|
||||
model: "gemini-2.5-pro",
|
||||
envBindingText: "",
|
||||
@@ -70,7 +70,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary Workspace",
|
||||
localPath: "/tmp/pipeline-test",
|
||||
@@ -93,7 +93,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult {
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
|
||||
const goalId = db.saveGoal({
|
||||
const goalId = db.entityCrud.saveGoal({
|
||||
companyId,
|
||||
title: "Implement Authentication",
|
||||
description: "Build full authentication system",
|
||||
@@ -101,7 +101,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
goalId,
|
||||
name: "Auth Project",
|
||||
@@ -112,7 +112,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult {
|
||||
});
|
||||
|
||||
// Budget policy: company-level with hardStop
|
||||
const policyId = db.saveBudgetPolicy({
|
||||
const policyId = db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
|
||||
@@ -43,7 +43,7 @@ describe("AppDatabase", () => {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -66,7 +66,7 @@ describe("AppDatabase", () => {
|
||||
budgetMonthlyUsd: 200,
|
||||
});
|
||||
|
||||
const goalId = db.saveGoal({
|
||||
const goalId = db.entityCrud.saveGoal({
|
||||
companyId,
|
||||
title: "Ship the desktop app",
|
||||
description: "Reach packaged desktop parity.",
|
||||
@@ -74,7 +74,7 @@ describe("AppDatabase", () => {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
goalId,
|
||||
name: "Desktop rebuild",
|
||||
@@ -95,21 +95,21 @@ describe("AppDatabase", () => {
|
||||
status: "todo",
|
||||
});
|
||||
|
||||
db.saveSecret({
|
||||
db.entityCrud.saveSecret({
|
||||
companyId,
|
||||
name: "vault-openai",
|
||||
description: "Test key",
|
||||
value: "sk-test-123",
|
||||
});
|
||||
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: null,
|
||||
envBindingText: "OPENAI_API_KEY=vault-openai",
|
||||
notes: "Connector note",
|
||||
});
|
||||
|
||||
expect(db.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({
|
||||
expect(db.entityCrud.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({
|
||||
OPENAI_API_KEY: "sk-test-123",
|
||||
});
|
||||
|
||||
@@ -136,22 +136,22 @@ describe("AppDatabase", () => {
|
||||
unattributedCost: false,
|
||||
});
|
||||
|
||||
const snapshotBeforeBackup = db.listSnapshot();
|
||||
const snapshotBeforeBackup = db.snap.listSnapshot();
|
||||
expect(snapshotBeforeBackup.companies).toHaveLength(1);
|
||||
expect(snapshotBeforeBackup.runs[0]?.status).toBe("succeeded");
|
||||
expect(snapshotBeforeBackup.tasks[0]?.activeRunId).toBeNull();
|
||||
|
||||
await db.exportBackup(backupDir);
|
||||
await db.backupMod.exportBackup(backupDir);
|
||||
|
||||
db.company.saveCompany({
|
||||
name: "Temporary company",
|
||||
description: "Should disappear after restore",
|
||||
status: "active",
|
||||
});
|
||||
expect(db.listSnapshot().companies).toHaveLength(2);
|
||||
expect(db.snap.listSnapshot().companies).toHaveLength(2);
|
||||
|
||||
await db.restoreFromBackup(backupDir);
|
||||
const restoredSnapshot = db.listSnapshot();
|
||||
await db.backupMod.restoreFromBackup(backupDir);
|
||||
const restoredSnapshot = db.snap.listSnapshot();
|
||||
expect(restoredSnapshot.companies).toHaveLength(1);
|
||||
expect(restoredSnapshot.companies[0]?.name).toBe("Orbit Foundry");
|
||||
expect(restoredSnapshot.runs[0]?.summary).toBe("Completed successfully");
|
||||
@@ -176,7 +176,7 @@ describe("AppDatabase", () => {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
const alphaWorkspaceId = db.saveWorkspace({
|
||||
const alphaWorkspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId: alphaCompanyId,
|
||||
name: "Alpha Workspace",
|
||||
localPath: profileDir,
|
||||
@@ -185,7 +185,7 @@ describe("AppDatabase", () => {
|
||||
isPrimary: true,
|
||||
});
|
||||
|
||||
const betaWorkspaceId = db.saveWorkspace({
|
||||
const betaWorkspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId: betaCompanyId,
|
||||
name: "Beta Workspace",
|
||||
localPath: profileDir,
|
||||
@@ -242,9 +242,9 @@ describe("AppDatabase", () => {
|
||||
status: "todo",
|
||||
});
|
||||
|
||||
db.setCurrentCompany(alphaCompanyId);
|
||||
db.settings.setCurrentCompany(alphaCompanyId);
|
||||
|
||||
const rendererSnapshot = db.listRendererSnapshot();
|
||||
const rendererSnapshot = db.snap.listRendererSnapshot();
|
||||
expect(rendererSnapshot.currentCompanyId).toBe(alphaCompanyId);
|
||||
expect(rendererSnapshot.companies).toHaveLength(2);
|
||||
expect(rendererSnapshot.connectors.length).toBeGreaterThan(0);
|
||||
|
||||
@@ -26,7 +26,7 @@ async function createHarness() {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -39,7 +39,7 @@ async function createHarness() {
|
||||
description: "Tests document revision provenance through run completion",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -71,7 +71,7 @@ async function createHarness() {
|
||||
});
|
||||
db.tasks.setTaskType(taskId, "research");
|
||||
|
||||
const existingDocumentId = db.saveDocument({
|
||||
const existingDocumentId = db.documentsMod.saveDocument({
|
||||
companyId,
|
||||
type: "knowledge_article",
|
||||
title: "Market report",
|
||||
@@ -149,7 +149,7 @@ describe("document run provenance", () => {
|
||||
)[0];
|
||||
expect(updatedDocument?.sourceRunId).toBe(harness.run.id);
|
||||
|
||||
const persistedRun = harness.db.listSnapshot().runs.find((entry) => entry.id === harness.run.id);
|
||||
const persistedRun = harness.db.snap.listSnapshot().runs.find((entry) => entry.id === harness.run.id);
|
||||
if (!persistedRun) {
|
||||
throw new Error("Expected persisted run record for provenance test.");
|
||||
}
|
||||
@@ -307,7 +307,7 @@ describe("document run provenance", () => {
|
||||
);
|
||||
|
||||
expect(createResponse.status).toBe(201);
|
||||
const approvals = harness.db.listSnapshot().approvals.filter((approval) => approval.type === "deliverable_review");
|
||||
const approvals = harness.db.snap.listSnapshot().approvals.filter((approval) => approval.type === "deliverable_review");
|
||||
expect(approvals).toHaveLength(1);
|
||||
expect(approvals[0]?.relatedTaskId).toBe(harness.taskId);
|
||||
} finally {
|
||||
|
||||
@@ -54,9 +54,9 @@ function buildKickoffPayload(db: AppDatabase, summary: { companyId: string; lead
|
||||
|
||||
const agent = db.entityCrud.getAgent(summary.leadAgentId);
|
||||
const task = db.entityCrud.getTask(summary.taskId);
|
||||
const connector = db.getConnector(agent.connectorId);
|
||||
const workspace = summary.workspaceId ? db.getWorkspace(summary.workspaceId) : null;
|
||||
const company = db.listSnapshot().companies.find((entry) => entry.id === summary.companyId);
|
||||
const connector = db.connectorsCrud.getConnector(agent.connectorId);
|
||||
const workspace = summary.workspaceId ? db.workspacesMod.getWorkspace(summary.workspaceId) : null;
|
||||
const company = db.snap.listSnapshot().companies.find((entry) => entry.id === summary.companyId);
|
||||
|
||||
if (!company) {
|
||||
throw new Error(`Company ${summary.companyId} not found in snapshot.`);
|
||||
@@ -72,7 +72,7 @@ function buildKickoffPayload(db: AppDatabase, summary: { companyId: string; lead
|
||||
workspaceMode: "exclusive",
|
||||
companyName: company.name,
|
||||
companyDescription: company.description,
|
||||
snapshot: db.listSnapshot(),
|
||||
snapshot: db.snap.listSnapshot(),
|
||||
trigger: "bootstrap",
|
||||
}, {
|
||||
db,
|
||||
@@ -98,7 +98,7 @@ describe("onboarding model pool integration", () => {
|
||||
throw new Error("openai-codex requires at least two models for this integration test.");
|
||||
}
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -128,8 +128,8 @@ describe("onboarding model pool integration", () => {
|
||||
const agent = db.entityCrud.getAgent(summary.leadAgentId);
|
||||
expect(agent.connectorId).toBe("openai-codex");
|
||||
expect(agent.model).toBe(enabledModel);
|
||||
expect(db.getDisabledModels(summary.companyId, "openai-codex")).toEqual([{ id: disabledModel, label: disabledModel }]);
|
||||
expect(db.getCurrentCompanyId()).toBe(summary.companyId);
|
||||
expect(db.settings.getDisabledModels(summary.companyId, "openai-codex")).toEqual([{ id: disabledModel, label: disabledModel }]);
|
||||
expect(db.settings.getCurrentCompanyId()).toBe(summary.companyId);
|
||||
|
||||
const payload = buildKickoffPayload(db, summary);
|
||||
expect(payload.model).toBe(enabledModel);
|
||||
@@ -161,7 +161,7 @@ describe("onboarding model pool integration", () => {
|
||||
throw new Error("anthropic provider must expose at least two models from pi-ai.");
|
||||
}
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "anthropic",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -205,7 +205,7 @@ describe("onboarding model pool integration", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -254,7 +254,7 @@ describe("onboarding model pool integration", () => {
|
||||
throw new Error("openai-codex requires a default and fallback model for reroute testing.");
|
||||
}
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -296,7 +296,7 @@ describe("onboarding model pool integration", () => {
|
||||
throw new Error("openai-codex requires a model to validate onboarding rejection.");
|
||||
}
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
|
||||
@@ -106,7 +106,7 @@ describe("pi-mono migrations 27 + 28", () => {
|
||||
const db = new AppDatabase(dir);
|
||||
db.init();
|
||||
|
||||
const rows = db.listSnapshot().connectors.map((entry) => entry.id);
|
||||
const rows = db.snap.listSnapshot().connectors.map((entry) => entry.id);
|
||||
expect(rows.length).toBe(26);
|
||||
expect(rows).toContain("claude-pro");
|
||||
expect(rows).toContain("amazon-bedrock");
|
||||
|
||||
@@ -54,7 +54,7 @@ function scaffold(db: AppDatabase): ScaffoldResult {
|
||||
const connectorId: ConnectorId = "gemini-cli";
|
||||
|
||||
// Connector
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: connectorId,
|
||||
model: "gemini-2.5-pro",
|
||||
envBindingText: "",
|
||||
@@ -69,7 +69,7 @@ function scaffold(db: AppDatabase): ScaffoldResult {
|
||||
});
|
||||
|
||||
// Workspace
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary Workspace",
|
||||
localPath: "/tmp/recovery-test",
|
||||
@@ -94,7 +94,7 @@ function scaffold(db: AppDatabase): ScaffoldResult {
|
||||
});
|
||||
|
||||
// Goal
|
||||
const goalId = db.saveGoal({
|
||||
const goalId = db.entityCrud.saveGoal({
|
||||
companyId,
|
||||
title: "Build login page",
|
||||
description: "Implement login page with authentication",
|
||||
@@ -103,7 +103,7 @@ function scaffold(db: AppDatabase): ScaffoldResult {
|
||||
});
|
||||
|
||||
// Project
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
goalId,
|
||||
name: "Auth Module",
|
||||
@@ -169,7 +169,7 @@ function createAndStartRun(
|
||||
db.runs.markRunStatus(runId, "running", "Starting execution");
|
||||
|
||||
if (opts.claimWorkspace !== false) {
|
||||
db.claimWorkspaceLock(opts.workspaceId, runId);
|
||||
db.workspacesMod.claimWorkspaceLock(opts.workspaceId, runId);
|
||||
}
|
||||
|
||||
// Set agent status to "running" to simulate active execution
|
||||
@@ -385,7 +385,7 @@ describe("Process Recovery -- Worker Crash Detection", () => {
|
||||
workspaceId: s.workspaceId,
|
||||
connectorId: s.connectorId,
|
||||
});
|
||||
expect(db.claimWorkspaceLock(s.workspaceId, newRunId)).toBe(true);
|
||||
expect(db.workspacesMod.claimWorkspaceLock(s.workspaceId, newRunId)).toBe(true);
|
||||
});
|
||||
|
||||
it("tasks set to blocked after crash", () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("skill-aware capability registry", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -37,7 +37,7 @@ describe("skill-aware capability registry", () => {
|
||||
description: "Tests skill-aware assignment",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -75,7 +75,7 @@ describe("skill-aware capability registry", () => {
|
||||
|
||||
const skillId = randomUUID();
|
||||
const now = new Date().toISOString();
|
||||
db.upsertCompanySkill({
|
||||
db.skills.upsertCompanySkill({
|
||||
id: skillId,
|
||||
companyId,
|
||||
key: "frontend/react-ui",
|
||||
@@ -92,7 +92,7 @@ describe("skill-aware capability registry", () => {
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
db.assignSkillToAgent(styledEngineerId, skillId);
|
||||
db.skills.assignSkillToAgent(styledEngineerId, skillId);
|
||||
|
||||
const taskId = db.tasks.saveTask({
|
||||
companyId,
|
||||
@@ -114,7 +114,7 @@ describe("skill-aware capability registry", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -127,7 +127,7 @@ describe("skill-aware capability registry", () => {
|
||||
description: "Tests capability registry prompt injection",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -151,7 +151,7 @@ describe("skill-aware capability registry", () => {
|
||||
|
||||
const skillId = randomUUID();
|
||||
const now = new Date().toISOString();
|
||||
db.upsertCompanySkill({
|
||||
db.skills.upsertCompanySkill({
|
||||
id: skillId,
|
||||
companyId,
|
||||
key: "frontend/react-ui",
|
||||
@@ -169,7 +169,7 @@ describe("skill-aware capability registry", () => {
|
||||
updatedAt: now,
|
||||
});
|
||||
|
||||
const snapshot = db.listSnapshot();
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const prompt = buildTaskPrompt({
|
||||
agent: db.entityCrud.getAgent(agentId),
|
||||
apiUrl: "http://127.0.0.1:3210",
|
||||
@@ -193,7 +193,7 @@ describe("skill-aware capability registry", () => {
|
||||
allConnectors: snapshot.connectors
|
||||
.filter((connector) => connector.id === "openai-codex")
|
||||
.map((connector) => ({ id: connector.id, label: connector.label, status: connector.status, enabledModels: ["gpt-5.4"] })),
|
||||
companySkillCatalog: db.listCompanySkills(companyId).map((skill) => ({
|
||||
companySkillCatalog: db.skills.listCompanySkills(companyId).map((skill) => ({
|
||||
name: skill.name,
|
||||
slug: skill.slug,
|
||||
description: skill.description,
|
||||
|
||||
@@ -28,7 +28,7 @@ function createNoopLogger() {
|
||||
}
|
||||
|
||||
function createScaffold(db: AppDatabase) {
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -42,7 +42,7 @@ function createScaffold(db: AppDatabase) {
|
||||
status: "active",
|
||||
executionMode: "ai_cofounder",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: "/tmp/thread-wait",
|
||||
@@ -116,7 +116,7 @@ describe("thread wait + heartbeat integration", () => {
|
||||
threadIntegration.onTaskCreated(taskId, companyId);
|
||||
threadIntegration.onRunFinished("run-1", taskId, engineerId, companyId, "succeeded", null);
|
||||
|
||||
const thread = db.listThreads({
|
||||
const thread = db.threads.listThreads({
|
||||
companyId,
|
||||
type: "task",
|
||||
relatedEntityType: "task",
|
||||
@@ -128,7 +128,7 @@ describe("thread wait + heartbeat integration", () => {
|
||||
throw new Error("Expected a task thread to exist for the completed run.");
|
||||
}
|
||||
|
||||
let waitStates = db.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id);
|
||||
let waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id);
|
||||
expect(waitStates).toHaveLength(1);
|
||||
expect(waitStates[0]?.waitingFor).toBe("user_reply");
|
||||
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
|
||||
@@ -165,7 +165,7 @@ describe("thread wait + heartbeat integration", () => {
|
||||
body: "Board reviewed this deliverable. Continue.",
|
||||
});
|
||||
|
||||
waitStates = db.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id);
|
||||
waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id);
|
||||
expect(waitStates).toHaveLength(0);
|
||||
expect(threadIntegration.hasUnresolvedWaitStatesForAgent(companyId, engineerId)).toBe(false);
|
||||
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(false);
|
||||
@@ -192,7 +192,7 @@ describe("thread wait + heartbeat integration", () => {
|
||||
});
|
||||
|
||||
threadIntegration.onTaskCreated(taskId, companyId);
|
||||
const thread = db.listThreads({
|
||||
const thread = db.threads.listThreads({
|
||||
companyId,
|
||||
type: "task",
|
||||
relatedEntityType: "task",
|
||||
@@ -215,7 +215,7 @@ describe("thread wait + heartbeat integration", () => {
|
||||
});
|
||||
|
||||
threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "draft_review", "Draft review needed");
|
||||
let waitStates = db.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
|
||||
let waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
|
||||
expect(waitStates).toHaveLength(1);
|
||||
expect(waitStates[0]?.waitingFor).toBe("user_approval");
|
||||
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
|
||||
@@ -234,7 +234,7 @@ describe("thread wait + heartbeat integration", () => {
|
||||
body: "Board left a comment but did not decide approval yet.",
|
||||
});
|
||||
|
||||
waitStates = db.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
|
||||
waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
|
||||
expect(waitStates).toHaveLength(1);
|
||||
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true);
|
||||
|
||||
@@ -244,7 +244,7 @@ describe("thread wait + heartbeat integration", () => {
|
||||
decisionNote: "Approved by board",
|
||||
});
|
||||
threadIntegration.onApprovalResolved(approvalId, companyId, "approved");
|
||||
waitStates = db.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
|
||||
waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId);
|
||||
expect(waitStates).toHaveLength(0);
|
||||
expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(false);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ async function createHarness(): Promise<Harness> {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -58,7 +58,7 @@ async function createHarness(): Promise<Harness> {
|
||||
description: "Tests archived visibility, bulk retirement, deliverable signal",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: workspacePath,
|
||||
@@ -107,7 +107,7 @@ async function createHarness(): Promise<Harness> {
|
||||
}
|
||||
|
||||
function registerArtifact(harness: Harness, relativePath: string): string {
|
||||
return harness.db.createWorkProduct({
|
||||
return harness.db.documentsMod.createWorkProduct({
|
||||
companyId: harness.companyId,
|
||||
taskId: harness.taskId,
|
||||
workspaceId: harness.workspaceId,
|
||||
@@ -126,12 +126,12 @@ describe("work_products archived visibility", () => {
|
||||
try {
|
||||
const activeId = registerArtifact(harness, "docs/active.md");
|
||||
const archivedId = registerArtifact(harness, "docs/retired.md");
|
||||
harness.db.updateWorkProduct(archivedId, harness.companyId, {
|
||||
harness.db.documentsMod.updateWorkProduct(archivedId, harness.companyId, {
|
||||
status: "archived",
|
||||
lastTouchedByRunId: harness.runId,
|
||||
});
|
||||
|
||||
const snapshot = harness.db.listSnapshot();
|
||||
const snapshot = harness.db.snap.listSnapshot();
|
||||
const visibleIds = snapshot.workProducts.map((wp) => wp.id);
|
||||
expect(visibleIds).toContain(activeId);
|
||||
expect(visibleIds).not.toContain(archivedId);
|
||||
@@ -145,12 +145,12 @@ describe("work_products archived visibility", () => {
|
||||
try {
|
||||
const activeId = registerArtifact(harness, "src/feature.ts");
|
||||
const archivedId = registerArtifact(harness, "src/removed.ts");
|
||||
harness.db.updateWorkProduct(archivedId, harness.companyId, {
|
||||
harness.db.documentsMod.updateWorkProduct(archivedId, harness.companyId, {
|
||||
status: "archived",
|
||||
lastTouchedByRunId: harness.runId,
|
||||
});
|
||||
|
||||
const rows = harness.db.listWorkProductsForTask(harness.taskId, harness.companyId);
|
||||
const rows = harness.db.documentsMod.listWorkProductsForTask(harness.taskId, harness.companyId);
|
||||
const ids = rows.map((r) => r.id);
|
||||
expect(ids).toContain(activeId);
|
||||
expect(ids).not.toContain(archivedId);
|
||||
@@ -164,16 +164,16 @@ describe("work_products archived visibility", () => {
|
||||
try {
|
||||
const id = registerArtifact(harness, "notes/ghost.md");
|
||||
expect(
|
||||
harness.db.resolveWorkProductTarget(id, harness.companyId),
|
||||
harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId),
|
||||
).not.toBeNull();
|
||||
|
||||
harness.db.updateWorkProduct(id, harness.companyId, {
|
||||
harness.db.documentsMod.updateWorkProduct(id, harness.companyId, {
|
||||
status: "archived",
|
||||
lastTouchedByRunId: harness.runId,
|
||||
});
|
||||
|
||||
expect(
|
||||
harness.db.resolveWorkProductTarget(id, harness.companyId),
|
||||
harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId),
|
||||
).toBeNull();
|
||||
} finally {
|
||||
harness.db.close();
|
||||
@@ -193,7 +193,7 @@ describe("work_products bulk retirement", () => {
|
||||
ids.push(registerArtifact(harness, rel));
|
||||
}
|
||||
|
||||
const archived = harness.db.archiveWorkProductsByExternalPaths({
|
||||
const archived = harness.db.documentsMod.archiveWorkProductsByExternalPaths({
|
||||
companyId: harness.companyId,
|
||||
workspaceId: harness.workspaceId,
|
||||
provider: "filesystem",
|
||||
@@ -206,7 +206,7 @@ describe("work_products bulk retirement", () => {
|
||||
for (const id of ids) {
|
||||
expect(archivedIdSet.has(id)).toBe(true);
|
||||
}
|
||||
const visible = harness.db.listSnapshot().workProducts;
|
||||
const visible = harness.db.snap.listSnapshot().workProducts;
|
||||
expect(visible.length).toBe(0);
|
||||
} finally {
|
||||
harness.db.close();
|
||||
@@ -217,7 +217,7 @@ describe("work_products bulk retirement", () => {
|
||||
const harness = await createHarness();
|
||||
try {
|
||||
const id = registerArtifact(harness, "tmp/scratch.log");
|
||||
harness.db.archiveWorkProductsByExternalPaths({
|
||||
harness.db.documentsMod.archiveWorkProductsByExternalPaths({
|
||||
companyId: harness.companyId,
|
||||
workspaceId: harness.workspaceId,
|
||||
provider: "filesystem",
|
||||
@@ -225,7 +225,7 @@ describe("work_products bulk retirement", () => {
|
||||
lastTouchedByRunId: harness.runId,
|
||||
});
|
||||
|
||||
const second = harness.db.archiveWorkProductsByExternalPaths({
|
||||
const second = harness.db.documentsMod.archiveWorkProductsByExternalPaths({
|
||||
companyId: harness.companyId,
|
||||
workspaceId: harness.workspaceId,
|
||||
provider: "filesystem",
|
||||
@@ -235,7 +235,7 @@ describe("work_products bulk retirement", () => {
|
||||
|
||||
expect(second).toHaveLength(0);
|
||||
expect(
|
||||
harness.db.resolveWorkProductTarget(id, harness.companyId),
|
||||
harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId),
|
||||
).toBeNull();
|
||||
} finally {
|
||||
harness.db.close();
|
||||
@@ -252,7 +252,7 @@ describe("work_products bulk retirement", () => {
|
||||
|
||||
const workProductId = registerArtifact(harness, relPath);
|
||||
expect(
|
||||
harness.db.resolveWorkProductTarget(workProductId, harness.companyId),
|
||||
harness.db.documentsMod.resolveWorkProductTarget(workProductId, harness.companyId),
|
||||
).not.toBeNull();
|
||||
|
||||
const baseline = {
|
||||
@@ -277,7 +277,7 @@ describe("work_products bulk retirement", () => {
|
||||
expect(retired.map((o) => (o as { relativePath: string }).relativePath)).toContain(relPath);
|
||||
|
||||
expect(
|
||||
harness.db.resolveWorkProductTarget(workProductId, harness.companyId),
|
||||
harness.db.documentsMod.resolveWorkProductTarget(workProductId, harness.companyId),
|
||||
).toBeNull();
|
||||
} finally {
|
||||
harness.db.close();
|
||||
@@ -290,19 +290,19 @@ describe("work_products deliverable signal", () => {
|
||||
const harness = await createHarness();
|
||||
try {
|
||||
const id = registerArtifact(harness, "deliverables/final.md");
|
||||
const beforeArchive = harness.db.collectRunDeliverableSignals(
|
||||
const beforeArchive = harness.db.knowledgeMod.collectRunDeliverableSignals(
|
||||
harness.runId,
|
||||
harness.companyId,
|
||||
harness.taskId,
|
||||
);
|
||||
expect(beforeArchive.hasWorkProduct).toBe(true);
|
||||
|
||||
harness.db.updateWorkProduct(id, harness.companyId, {
|
||||
harness.db.documentsMod.updateWorkProduct(id, harness.companyId, {
|
||||
status: "archived",
|
||||
lastTouchedByRunId: harness.runId,
|
||||
});
|
||||
|
||||
const afterArchive = harness.db.collectRunDeliverableSignals(
|
||||
const afterArchive = harness.db.knowledgeMod.collectRunDeliverableSignals(
|
||||
harness.runId,
|
||||
harness.companyId,
|
||||
harness.taskId,
|
||||
|
||||
@@ -27,14 +27,14 @@ describe("agent budget policy synchronization", () => {
|
||||
description: "Budget sync regression coverage",
|
||||
status: "active",
|
||||
});
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
version: "test",
|
||||
lastError: null,
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -57,7 +57,7 @@ describe("agent budget policy synchronization", () => {
|
||||
department: "engineering",
|
||||
});
|
||||
|
||||
const firstPolicyId = db.saveBudgetPolicy({
|
||||
const firstPolicyId = db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "agent",
|
||||
scopeId: agentId,
|
||||
@@ -86,7 +86,7 @@ describe("agent budget policy synchronization", () => {
|
||||
department: "engineering",
|
||||
});
|
||||
|
||||
const secondPolicyId = db.saveBudgetPolicy({
|
||||
const secondPolicyId = db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "agent",
|
||||
scopeId: agentId,
|
||||
@@ -99,13 +99,13 @@ describe("agent budget policy synchronization", () => {
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const agentPolicies = db.listBudgetPolicies(companyId).filter(
|
||||
const agentPolicies = db.budget.listBudgetPolicies(companyId).filter(
|
||||
(policy) => policy.scopeType === "agent" && policy.scopeId === agentId,
|
||||
);
|
||||
expect(secondPolicyId).toBe(firstPolicyId);
|
||||
expect(agentPolicies).toHaveLength(1);
|
||||
expect(agentPolicies[0]?.amount).toBe(25_000);
|
||||
expect(db.getActivePoliciesForRun(companyId, agentId, null).map((policy) => policy.amount)).toEqual([25_000]);
|
||||
expect(db.budget.getActivePoliciesForRun(companyId, agentId, null).map((policy) => policy.amount)).toEqual([25_000]);
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
@@ -28,14 +28,14 @@ describe("hire routing guards", () => {
|
||||
description: "Hire routing regression coverage",
|
||||
status: "active",
|
||||
});
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
version: "test",
|
||||
lastError: null,
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("canManageOrganization", () => {
|
||||
description: "Tests recursive org authority",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -105,7 +105,7 @@ describe("canManageOrganization", () => {
|
||||
description: "Tests permission-based org authority",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -159,7 +159,7 @@ describe("canManageOrganization", () => {
|
||||
description: "Tests that branch growth does not unlock org-wide admin powers",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("hire approval routing", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -36,7 +36,7 @@ describe("hire approval routing", () => {
|
||||
description: "Tests recursive hire wake routing",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("deliverable review feedback writeback", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -36,7 +36,7 @@ describe("deliverable review feedback writeback", () => {
|
||||
description: "Tests review feedback knowledge writeback",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -67,7 +67,7 @@ describe("deliverable review feedback writeback", () => {
|
||||
priority: "high",
|
||||
status: "in_review",
|
||||
});
|
||||
const documentId = db.saveDocument({
|
||||
const documentId = db.documentsMod.saveDocument({
|
||||
companyId,
|
||||
type: "knowledge_article",
|
||||
title: "Market report",
|
||||
@@ -107,7 +107,7 @@ describe("deliverable review feedback writeback", () => {
|
||||
trackTimer: vi.fn(),
|
||||
}, approvalId, companyId, "revision_requested");
|
||||
|
||||
const knowledgeEntries = db.listSnapshot().knowledgeBase.filter((entry) => entry.companyId === companyId);
|
||||
const knowledgeEntries = db.snap.listSnapshot().knowledgeBase.filter((entry) => entry.companyId === companyId);
|
||||
expect(knowledgeEntries).toHaveLength(1);
|
||||
expect(knowledgeEntries[0]?.category).toBe("lesson_learned");
|
||||
expect(knowledgeEntries[0]?.importance).toBe("high");
|
||||
@@ -117,7 +117,7 @@ describe("deliverable review feedback writeback", () => {
|
||||
expect(knowledgeEntries[0]?.content).toContain("stronger competitor evidence");
|
||||
expect(wakeAgentIfPossible).toHaveBeenCalledWith(agentId, companyId, "revision_needed");
|
||||
expect(db.entityCrud.getTask(taskId).status).toBe("in_progress");
|
||||
const document = db.listSnapshot().documents.find((entry) => entry.id === documentId);
|
||||
const document = db.snap.listSnapshot().documents.find((entry) => entry.id === documentId);
|
||||
expect(document?.status).toBe("draft");
|
||||
|
||||
db.close();
|
||||
|
||||
@@ -36,7 +36,7 @@ function createTestCompany(db: AppDatabase): string {
|
||||
}
|
||||
|
||||
function createTestConnector(db: AppDatabase): void {
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "",
|
||||
@@ -45,7 +45,7 @@ function createTestConnector(db: AppDatabase): void {
|
||||
}
|
||||
|
||||
function createTestWorkspace(db: AppDatabase, companyId: string): string {
|
||||
return db.saveWorkspace({
|
||||
return db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Test Workspace",
|
||||
localPath: "/tmp/test-workspace",
|
||||
|
||||
@@ -84,10 +84,7 @@ function createMockDb() {
|
||||
createdAt: string;
|
||||
}> = [];
|
||||
|
||||
return {
|
||||
policies,
|
||||
costEntries,
|
||||
|
||||
const budget = {
|
||||
saveBudgetPolicy(input: Partial<BudgetPolicyRecord> & { companyId: string; scopeType: string; scopeId: string; amount: number }) {
|
||||
const id = input.id ?? crypto.randomUUID();
|
||||
const existing = policies.findIndex((p) => p.id === id);
|
||||
@@ -150,6 +147,7 @@ function createMockDb() {
|
||||
return computeSpentCents(filtered);
|
||||
},
|
||||
};
|
||||
return { policies, costEntries, budget };
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
@@ -164,7 +162,7 @@ describe("Budget Policy CRUD", () => {
|
||||
});
|
||||
|
||||
it("saveBudgetPolicy creates new policy and returns its ID", () => {
|
||||
const id = db.saveBudgetPolicy({
|
||||
const id = db.budget.saveBudgetPolicy({
|
||||
companyId: "c1",
|
||||
scopeType: "company",
|
||||
scopeId: "c1",
|
||||
@@ -177,13 +175,13 @@ describe("Budget Policy CRUD", () => {
|
||||
});
|
||||
|
||||
it("saveBudgetPolicy updates existing policy when ID provided", () => {
|
||||
const id = db.saveBudgetPolicy({
|
||||
const id = db.budget.saveBudgetPolicy({
|
||||
companyId: "c1",
|
||||
scopeType: "company",
|
||||
scopeId: "c1",
|
||||
amount: 10000,
|
||||
});
|
||||
db.saveBudgetPolicy({
|
||||
db.budget.saveBudgetPolicy({
|
||||
id,
|
||||
companyId: "c1",
|
||||
scopeType: "company",
|
||||
@@ -195,14 +193,14 @@ describe("Budget Policy CRUD", () => {
|
||||
});
|
||||
|
||||
it("deleteBudgetPolicy removes policy by ID", () => {
|
||||
const id = db.saveBudgetPolicy({
|
||||
const id = db.budget.saveBudgetPolicy({
|
||||
companyId: "c1",
|
||||
scopeType: "company",
|
||||
scopeId: "c1",
|
||||
amount: 10000,
|
||||
});
|
||||
expect(db.policies).toHaveLength(1);
|
||||
db.deleteBudgetPolicy("c1", id);
|
||||
db.budget.deleteBudgetPolicy("c1", id);
|
||||
expect(db.policies).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -215,27 +213,27 @@ describe("getActivePoliciesForRun", () => {
|
||||
});
|
||||
|
||||
it("returns all active policies matching company+agent+project scopes", () => {
|
||||
db.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
|
||||
db.saveBudgetPolicy({ companyId: "c1", scopeType: "agent", scopeId: "a1", amount: 10000 });
|
||||
db.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
|
||||
db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
|
||||
db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "agent", scopeId: "a1", amount: 10000 });
|
||||
db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
|
||||
// Different company — should not match
|
||||
db.saveBudgetPolicy({ companyId: "c2", scopeType: "company", scopeId: "c2", amount: 99999 });
|
||||
db.budget.saveBudgetPolicy({ companyId: "c2", scopeType: "company", scopeId: "c2", amount: 99999 });
|
||||
|
||||
const result = db.getActivePoliciesForRun("c1", "a1", "p1");
|
||||
const result = db.budget.getActivePoliciesForRun("c1", "a1", "p1");
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result.map((p) => p.scopeType).sort()).toEqual(["agent", "company", "project"]);
|
||||
});
|
||||
|
||||
it("returns empty array when no policies exist (D-02: no policy = unlimited)", () => {
|
||||
const result = db.getActivePoliciesForRun("c1", "a1", null);
|
||||
const result = db.budget.getActivePoliciesForRun("c1", "a1", null);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("excludes project scope when projectId is null", () => {
|
||||
db.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
|
||||
db.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
|
||||
db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
|
||||
db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
|
||||
|
||||
const result = db.getActivePoliciesForRun("c1", "a1", null);
|
||||
const result = db.budget.getActivePoliciesForRun("c1", "a1", null);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]!.scopeType).toBe("company");
|
||||
});
|
||||
@@ -261,7 +259,7 @@ describe("getSpentInWindow", () => {
|
||||
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
|
||||
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
|
||||
};
|
||||
const spent = db.getSpentInWindow(policy);
|
||||
const spent = db.budget.getSpentInWindow(policy);
|
||||
expect(spent).toBe(380); // (1.50 + 2.30) * 100 = 380 cents
|
||||
});
|
||||
|
||||
@@ -278,7 +276,7 @@ describe("getSpentInWindow", () => {
|
||||
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
|
||||
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
|
||||
};
|
||||
const spent = db.getSpentInWindow(policy);
|
||||
const spent = db.budget.getSpentInWindow(policy);
|
||||
expect(spent).toBe(500); // Only a1's $5.00 = 500 cents
|
||||
});
|
||||
|
||||
@@ -295,7 +293,7 @@ describe("getSpentInWindow", () => {
|
||||
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
|
||||
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
|
||||
};
|
||||
const spent = db.getSpentInWindow(policy);
|
||||
const spent = db.budget.getSpentInWindow(policy);
|
||||
expect(spent).toBe(750); // Only p1's $7.50 = 750 cents
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,8 +23,7 @@ function createMockIncidentDb() {
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
return {
|
||||
incidents,
|
||||
const budget = {
|
||||
getCalendarMonthWindow,
|
||||
|
||||
upsertBudgetIncident(
|
||||
@@ -132,6 +131,7 @@ function createMockIncidentDb() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return { incidents, budget };
|
||||
}
|
||||
|
||||
function makePolicy(overrides: Partial<BudgetPolicyRecord> = {}): BudgetPolicyRecord {
|
||||
@@ -166,7 +166,7 @@ describe("upsertBudgetIncident", () => {
|
||||
|
||||
it("creates new incident when none exists for policy+window+threshold_type", () => {
|
||||
const policy = makePolicy();
|
||||
const id = db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
const id = db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
expect(id).toBeTruthy();
|
||||
expect(db.incidents).toHaveLength(1);
|
||||
expect(db.incidents[0]!.thresholdType).toBe("warn_threshold");
|
||||
@@ -176,8 +176,8 @@ describe("upsertBudgetIncident", () => {
|
||||
|
||||
it("updates amount_observed when open incident already exists (D-08 dedup)", () => {
|
||||
const policy = makePolicy();
|
||||
const id1 = db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
const id2 = db.upsertBudgetIncident(policy, "warn_threshold", 9200);
|
||||
const id1 = db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
const id2 = db.budget.upsertBudgetIncident(policy, "warn_threshold", 9200);
|
||||
expect(id1).toBe(id2); // Same incident, not a new one
|
||||
expect(db.incidents).toHaveLength(1); // Still just one
|
||||
expect(db.incidents[0]!.amountObserved).toBe(9200); // Updated
|
||||
@@ -185,8 +185,8 @@ describe("upsertBudgetIncident", () => {
|
||||
|
||||
it("creates separate incidents for different threshold types", () => {
|
||||
const policy = makePolicy();
|
||||
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
expect(db.incidents).toHaveLength(2);
|
||||
expect(db.incidents[0]!.thresholdType).toBe("warn_threshold");
|
||||
expect(db.incidents[1]!.thresholdType).toBe("hard_stop");
|
||||
@@ -202,10 +202,10 @@ describe("resolveIncidentsForPolicy", () => {
|
||||
|
||||
it("sets status to provided resolution reason for all open incidents", () => {
|
||||
const policy = makePolicy();
|
||||
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
|
||||
const count = db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
const count = db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
expect(count).toBe(2);
|
||||
expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase");
|
||||
expect(db.incidents[1]!.status).toBe("auto_resolved_limit_increase");
|
||||
@@ -214,10 +214,10 @@ describe("resolveIncidentsForPolicy", () => {
|
||||
|
||||
it("does not affect already-resolved incidents", () => {
|
||||
const policy = makePolicy();
|
||||
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.resolveIncidentsForPolicy("pol-1", "resolved");
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.budget.resolveIncidentsForPolicy("pol-1", "resolved");
|
||||
// Now try to resolve again
|
||||
const count = db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
const count = db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
expect(count).toBe(0);
|
||||
expect(db.incidents[0]!.status).toBe("resolved"); // Unchanged
|
||||
});
|
||||
@@ -252,7 +252,7 @@ describe("autoCloseExpiredIncidents", () => {
|
||||
updatedAt: "2025-01-15T00:00:00.000Z",
|
||||
});
|
||||
|
||||
const count = db.autoCloseExpiredIncidents("c1");
|
||||
const count = db.budget.autoCloseExpiredIncidents("c1");
|
||||
expect(count).toBe(1);
|
||||
expect(db.incidents[0]!.status).toBe("auto_closed");
|
||||
expect(db.incidents[0]!.resolvedAt).toBeTruthy();
|
||||
@@ -260,10 +260,10 @@ describe("autoCloseExpiredIncidents", () => {
|
||||
|
||||
it("does not close incidents whose window_end is in the future", () => {
|
||||
const policy = makePolicy();
|
||||
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
// The auto-created incident has window_end in the future (end of current month)
|
||||
|
||||
const count = db.autoCloseExpiredIncidents("c1");
|
||||
const count = db.budget.autoCloseExpiredIncidents("c1");
|
||||
expect(count).toBe(0);
|
||||
expect(db.incidents[0]!.status).toBe("open");
|
||||
});
|
||||
@@ -278,22 +278,22 @@ describe("listBudgetIncidents", () => {
|
||||
|
||||
it("returns all incidents for a company when status is 'all'", () => {
|
||||
const policy = makePolicy();
|
||||
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
db.resolveIncidentsForPolicy("pol-1", "resolved");
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
db.budget.resolveIncidentsForPolicy("pol-1", "resolved");
|
||||
|
||||
const all = db.listBudgetIncidents("c1", "all");
|
||||
const all = db.budget.listBudgetIncidents("c1", "all");
|
||||
expect(all).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("filters by status when specified", () => {
|
||||
const policy = makePolicy();
|
||||
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
// Resolve only warn
|
||||
db.resolveWarnIncidentsForPolicy("pol-1", "resolved");
|
||||
db.budget.resolveWarnIncidentsForPolicy("pol-1", "resolved");
|
||||
|
||||
const open = db.listBudgetIncidents("c1", "open");
|
||||
const open = db.budget.listBudgetIncidents("c1", "open");
|
||||
expect(open).toHaveLength(1);
|
||||
expect(open[0]!.thresholdType).toBe("hard_stop");
|
||||
});
|
||||
@@ -308,7 +308,7 @@ describe("checkAutoResolve (D-06)", () => {
|
||||
|
||||
it("auto-resolves incidents when policy limit increases above observed amount", () => {
|
||||
const policy = makePolicy({ amount: 10000 });
|
||||
db.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
db.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
expect(db.incidents[0]!.status).toBe("open");
|
||||
|
||||
// Simulate limit increase: now checking if spent < new limit
|
||||
@@ -316,7 +316,7 @@ describe("checkAutoResolve (D-06)", () => {
|
||||
const spent = 10500;
|
||||
const newAmount = 20000;
|
||||
if (spent < newAmount) {
|
||||
db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
}
|
||||
|
||||
expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase");
|
||||
@@ -324,14 +324,14 @@ describe("checkAutoResolve (D-06)", () => {
|
||||
|
||||
it("auto-resolves only warn incidents when below warn threshold but above hard stop", () => {
|
||||
const policy = makePolicy({ amount: 10000, warnPercent: 80 });
|
||||
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500);
|
||||
db.budget.upsertBudgetIncident(policy, "hard_stop", 10500);
|
||||
|
||||
// Spent is now 7500 (below warn threshold of 8000 = 10000*80/100)
|
||||
const spent = 7500;
|
||||
const _warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
|
||||
if (spent < policy.amount) {
|
||||
db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
|
||||
}
|
||||
// Both resolved since spent < policy.amount
|
||||
expect(db.incidents.every((i) => i.status === "auto_resolved_limit_increase")).toBe(true);
|
||||
|
||||
@@ -32,7 +32,7 @@ describe("company mode switching", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -48,7 +48,7 @@ describe("company mode switching", () => {
|
||||
autoApproveHires: false,
|
||||
reviewDeliverables: true,
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -112,7 +112,7 @@ describe("company mode switching", () => {
|
||||
});
|
||||
threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "deliverable_review", "Review deliverable");
|
||||
|
||||
expect(db.listThreadWaitStates(companyId).length).toBeGreaterThan(0);
|
||||
expect(db.threads.listThreadWaitStates(companyId).length).toBeGreaterThan(0);
|
||||
expect(db.entityCrud.getTask(taskId).requiresUserReview).toBe(true);
|
||||
expect(db.approvalsMod.getApprovalRecord(approvalId).state).toBe("pending");
|
||||
|
||||
@@ -122,7 +122,7 @@ describe("company mode switching", () => {
|
||||
handlers.set(channel, handler);
|
||||
},
|
||||
db,
|
||||
ensureCompanyExists: (id) => (db.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
|
||||
ensureCompanyExists: (id) => (db.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
|
||||
publishDomainChanged: vi.fn(),
|
||||
handleApprovalResolved: (resolvedApprovalId, resolvedCompanyId, decision) => {
|
||||
threadIntegration.onApprovalResolved(resolvedApprovalId, resolvedCompanyId, decision);
|
||||
@@ -148,7 +148,7 @@ describe("company mode switching", () => {
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(db.company.getCompanyExecutionMode(companyId)).toBe("ai_ceo");
|
||||
expect(db.listThreadWaitStates(companyId)).toHaveLength(0);
|
||||
expect(db.threads.listThreadWaitStates(companyId)).toHaveLength(0);
|
||||
expect(db.entityCrud.getTask(taskId).requiresUserReview).toBe(false);
|
||||
expect(db.approvalsMod.getApprovalRecord(approvalId).state).toBe("approved");
|
||||
|
||||
|
||||
+35
-35
@@ -39,7 +39,7 @@ describe("AppDatabase", () => {
|
||||
description: "Operator company",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary Workspace",
|
||||
localPath: "/tmp/northline",
|
||||
@@ -60,14 +60,14 @@ describe("AppDatabase", () => {
|
||||
capabilities: "Directs execution",
|
||||
budgetMonthlyUsd: 250,
|
||||
});
|
||||
const goalId = db.saveGoal({
|
||||
const goalId = db.entityCrud.saveGoal({
|
||||
companyId,
|
||||
title: "Ship the desktop product",
|
||||
description: "Stabilize the local operator console.",
|
||||
ownerAgentId: agentId,
|
||||
status: "active",
|
||||
});
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
goalId,
|
||||
name: "Desktop Rebuild",
|
||||
@@ -88,20 +88,20 @@ describe("AppDatabase", () => {
|
||||
status: "todo",
|
||||
});
|
||||
|
||||
db.saveSecret({
|
||||
db.entityCrud.saveSecret({
|
||||
companyId,
|
||||
name: "vault-openai",
|
||||
description: "OpenAI API token",
|
||||
value: "sk-test-secret",
|
||||
});
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "OPENAI_API_KEY=vault-openai",
|
||||
notes: "Primary coding connector",
|
||||
});
|
||||
|
||||
const snapshot = db.listSnapshot();
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
|
||||
expect(snapshot.currentCompanyId).toBe(companyId);
|
||||
expect(snapshot.workspaces[0]?.id).toBe(workspaceId);
|
||||
@@ -109,7 +109,7 @@ describe("AppDatabase", () => {
|
||||
expect(snapshot.goals[0]?.id).toBe(goalId);
|
||||
expect(snapshot.projects[0]?.id).toBe(projectId);
|
||||
expect(snapshot.tasks[0]?.id).toBe(taskId);
|
||||
expect(db.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({
|
||||
expect(db.entityCrud.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({
|
||||
OPENAI_API_KEY: "sk-test-secret",
|
||||
});
|
||||
});
|
||||
@@ -122,7 +122,7 @@ describe("AppDatabase", () => {
|
||||
});
|
||||
const backupDir = await mkdtemp(join(tmpdir(), "agentcompany-backup-"));
|
||||
|
||||
await db.exportBackup(backupDir);
|
||||
await db.backupMod.exportBackup(backupDir);
|
||||
db.company.saveCompany({
|
||||
id: companyId,
|
||||
name: "Mutated Company",
|
||||
@@ -130,9 +130,9 @@ describe("AppDatabase", () => {
|
||||
status: "paused",
|
||||
});
|
||||
|
||||
await db.restoreFromBackup(backupDir);
|
||||
await db.backupMod.restoreFromBackup(backupDir);
|
||||
|
||||
const snapshot = db.listSnapshot();
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
expect(snapshot.companies[0]?.name).toBe("Backup Test Co");
|
||||
|
||||
await rm(backupDir, { recursive: true, force: true });
|
||||
@@ -150,19 +150,19 @@ describe("AppDatabase", () => {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
db.saveSecret({
|
||||
db.entityCrud.saveSecret({
|
||||
companyId: alphaCompanyId,
|
||||
name: "shared-secret-name",
|
||||
description: "Alpha secret",
|
||||
value: "alpha-secret",
|
||||
});
|
||||
|
||||
expect(db.resolveSecretEnv("API_KEY=shared-secret-name", alphaCompanyId)).toEqual({
|
||||
expect(db.entityCrud.resolveSecretEnv("API_KEY=shared-secret-name", alphaCompanyId)).toEqual({
|
||||
API_KEY: "alpha-secret",
|
||||
});
|
||||
// Beta company has no secret named "shared-secret-name", so resolveSecretEnv
|
||||
// falls back to using the raw value literally (allows direct env bindings).
|
||||
expect(db.resolveSecretEnv("API_KEY=shared-secret-name", betaCompanyId)).toEqual({
|
||||
expect(db.entityCrud.resolveSecretEnv("API_KEY=shared-secret-name", betaCompanyId)).toEqual({
|
||||
API_KEY: "shared-secret-name",
|
||||
});
|
||||
});
|
||||
@@ -173,7 +173,7 @@ describe("AppDatabase", () => {
|
||||
description: "Atomic checkout validation",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Execution Workspace",
|
||||
localPath: "/tmp/checkout",
|
||||
@@ -194,7 +194,7 @@ describe("AppDatabase", () => {
|
||||
capabilities: "Leads",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "",
|
||||
@@ -238,7 +238,7 @@ describe("AppDatabase", () => {
|
||||
description: "Budget hard stop validation",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Budget Workspace",
|
||||
localPath: "/tmp/budget",
|
||||
@@ -395,7 +395,7 @@ describe("AppDatabase", () => {
|
||||
|
||||
it("runs forward-only migrations and creates schema_migrations table", () => {
|
||||
// The migration system runs during init() — verify the tracking table exists
|
||||
const snapshot = db.listSnapshot();
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
expect(snapshot).toBeTruthy();
|
||||
|
||||
// Verify we can re-init without errors (migrations are idempotent)
|
||||
@@ -403,7 +403,7 @@ describe("AppDatabase", () => {
|
||||
db = new AppDatabase(tempDir);
|
||||
db.init();
|
||||
|
||||
const snapshot2 = db.listSnapshot();
|
||||
const snapshot2 = db.snap.listSnapshot();
|
||||
expect(snapshot2).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -413,7 +413,7 @@ describe("AppDatabase", () => {
|
||||
description: "Workspace lock test",
|
||||
status: "active",
|
||||
});
|
||||
const wsId = db.saveWorkspace({
|
||||
const wsId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Lockable WS",
|
||||
localPath: "/tmp/lock-test",
|
||||
@@ -423,24 +423,24 @@ describe("AppDatabase", () => {
|
||||
});
|
||||
|
||||
// Acquire lock for run-1
|
||||
expect(db.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
|
||||
let ws = db.getWorkspace(wsId);
|
||||
expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
|
||||
let ws = db.workspacesMod.getWorkspace(wsId);
|
||||
expect(ws?.state).toBe("busy");
|
||||
|
||||
// Same run can re-acquire (idempotent)
|
||||
expect(db.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
|
||||
expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-1")).toBe(true);
|
||||
|
||||
// Different run cannot acquire while run-1 holds the lock
|
||||
expect(db.acquireWorkspaceLock(wsId, "run-2")).toBe(false);
|
||||
expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-2")).toBe(false);
|
||||
|
||||
// Release lock
|
||||
db.releaseWorkspaceLock(wsId, "run-1");
|
||||
ws = db.getWorkspace(wsId);
|
||||
db.workspacesMod.releaseWorkspaceLock(wsId, "run-1");
|
||||
ws = db.workspacesMod.getWorkspace(wsId);
|
||||
expect(ws?.state).toBe("ready");
|
||||
|
||||
// Now run-2 can acquire
|
||||
expect(db.acquireWorkspaceLock(wsId, "run-2")).toBe(true);
|
||||
db.releaseWorkspaceLock(wsId, "run-2");
|
||||
expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-2")).toBe(true);
|
||||
db.workspacesMod.releaseWorkspaceLock(wsId, "run-2");
|
||||
});
|
||||
|
||||
it("releases all workspace locks on crash recovery", () => {
|
||||
@@ -449,7 +449,7 @@ describe("AppDatabase", () => {
|
||||
description: "Lock recovery test",
|
||||
status: "active",
|
||||
});
|
||||
const wsId = db.saveWorkspace({
|
||||
const wsId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Recovery WS",
|
||||
localPath: "/tmp/recovery-test",
|
||||
@@ -458,11 +458,11 @@ describe("AppDatabase", () => {
|
||||
isPrimary: true,
|
||||
});
|
||||
|
||||
db.acquireWorkspaceLock(wsId, "stale-run");
|
||||
expect(db.getWorkspace(wsId)?.state).toBe("busy");
|
||||
db.workspacesMod.acquireWorkspaceLock(wsId, "stale-run");
|
||||
expect(db.workspacesMod.getWorkspace(wsId)?.state).toBe("busy");
|
||||
|
||||
db.releaseAllWorkspaceLocks();
|
||||
expect(db.getWorkspace(wsId)?.state).toBe("ready");
|
||||
db.workspacesMod.releaseAllWorkspaceLocks();
|
||||
expect(db.workspacesMod.getWorkspace(wsId)?.state).toBe("ready");
|
||||
});
|
||||
|
||||
it("purges expired run logs based on retention setting", async () => {
|
||||
@@ -471,7 +471,7 @@ describe("AppDatabase", () => {
|
||||
description: "Log purge test",
|
||||
status: "active",
|
||||
});
|
||||
const wsId = db.saveWorkspace({
|
||||
const wsId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "PurgeWS",
|
||||
localPath: "/tmp/purge",
|
||||
@@ -521,7 +521,7 @@ describe("AppDatabase", () => {
|
||||
expect(db.runs.readRunLog(run.id)).toContain("test log line");
|
||||
|
||||
// Set retention to 1 day and manually backdate the run's finished_at
|
||||
db.updateSettings({ logRetentionDays: 1 });
|
||||
db.settings.updateSettings({ logRetentionDays: 1 });
|
||||
// Backdate finished_at to 10 days ago
|
||||
const tenDaysAgo = new Date(Date.now() - 10 * 86_400_000).toISOString();
|
||||
// Direct SQL update for test only
|
||||
@@ -529,7 +529,7 @@ describe("AppDatabase", () => {
|
||||
.prepare("update runs set finished_at = ? where id = ?")
|
||||
.run(tenDaysAgo, run.id);
|
||||
|
||||
const purged = db.purgeExpiredRunLogs();
|
||||
const purged = db.settings.purgeExpiredRunLogs();
|
||||
expect(purged).toBe(1);
|
||||
|
||||
// Log should be gone after purge
|
||||
|
||||
@@ -25,13 +25,13 @@ function scaffold(db: AppDatabase) {
|
||||
description: "Budget exhaustion mid-run edge case tests (D-05.2)",
|
||||
status: "active",
|
||||
});
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "gemini-cli",
|
||||
model: "gemini-pro",
|
||||
envBindingText: "",
|
||||
notes: "Test connector",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: "/tmp/budget-edge-test",
|
||||
@@ -52,14 +52,14 @@ function scaffold(db: AppDatabase) {
|
||||
capabilities: "Executes tasks",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
const goalId = db.saveGoal({
|
||||
const goalId = db.entityCrud.saveGoal({
|
||||
companyId,
|
||||
title: "Ship feature",
|
||||
description: "Build budget edge case test",
|
||||
ownerAgentId: agentId,
|
||||
status: "active",
|
||||
});
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
goalId,
|
||||
name: "Budget Project",
|
||||
@@ -119,7 +119,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
|
||||
});
|
||||
|
||||
// Create company budget policy: 500 cents ($5), hard stop enabled
|
||||
db.saveBudgetPolicy({
|
||||
db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
@@ -205,7 +205,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
|
||||
});
|
||||
|
||||
// Create company budget policy: 500 cents ($5)
|
||||
db.saveBudgetPolicy({
|
||||
db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
@@ -253,7 +253,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
|
||||
});
|
||||
|
||||
// Create company budget policy with known ID
|
||||
const policyId = db.saveBudgetPolicy({
|
||||
const policyId = db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
@@ -301,7 +301,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
|
||||
});
|
||||
|
||||
// Company-level: plenty of room (10000 cents = $100)
|
||||
db.saveBudgetPolicy({
|
||||
db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
@@ -310,7 +310,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
|
||||
});
|
||||
|
||||
// Agent-level: tight budget (100 cents = $1)
|
||||
db.saveBudgetPolicy({
|
||||
db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "agent",
|
||||
scopeId: agentId,
|
||||
@@ -358,7 +358,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
|
||||
});
|
||||
|
||||
// Create policy with 300 cents ($3) budget
|
||||
db.saveBudgetPolicy({
|
||||
db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
@@ -408,7 +408,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => {
|
||||
});
|
||||
|
||||
// Create tight budget
|
||||
db.saveBudgetPolicy({
|
||||
db.budget.saveBudgetPolicy({
|
||||
companyId,
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
|
||||
@@ -36,7 +36,7 @@ function createTestCompany(db: AppDatabase): string {
|
||||
}
|
||||
|
||||
function createTestConnector(db: AppDatabase): void {
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "gemini-cli",
|
||||
model: "gemini-pro",
|
||||
envBindingText: "",
|
||||
@@ -45,7 +45,7 @@ function createTestConnector(db: AppDatabase): void {
|
||||
}
|
||||
|
||||
function createTestWorkspace(db: AppDatabase, companyId: string): string {
|
||||
return db.saveWorkspace({
|
||||
return db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Test Workspace",
|
||||
localPath: "/tmp/concurrent-test",
|
||||
|
||||
@@ -28,14 +28,14 @@ function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") {
|
||||
});
|
||||
|
||||
// Always save gemini-cli (needed for most tests)
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "gemini-cli",
|
||||
model: "gemini-pro",
|
||||
envBindingText: "",
|
||||
notes: "Test connector - no native context management",
|
||||
});
|
||||
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: "/tmp/session-edge-test",
|
||||
@@ -56,14 +56,14 @@ function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") {
|
||||
capabilities: "Executes tasks",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
const goalId = db.saveGoal({
|
||||
const goalId = db.entityCrud.saveGoal({
|
||||
companyId,
|
||||
title: "Ship feature",
|
||||
description: "Build session rotation edge case test",
|
||||
ownerAgentId: agentId,
|
||||
status: "active",
|
||||
});
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
goalId,
|
||||
name: "Session Project",
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => {
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -43,8 +43,8 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => {
|
||||
description: "Tests saveAgent model validation",
|
||||
status: "active",
|
||||
});
|
||||
db.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
|
||||
const workspaceId = db.saveWorkspace({
|
||||
db.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]);
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: profileDir,
|
||||
@@ -72,7 +72,7 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => {
|
||||
handlers.set(channel, handler);
|
||||
},
|
||||
db,
|
||||
ensureCompanyExists: (id) => (db.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
|
||||
ensureCompanyExists: (id) => (db.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }),
|
||||
findInvalidReference: () => null,
|
||||
publishDomainChanged: vi.fn(),
|
||||
handleApprovalResolved: vi.fn(),
|
||||
|
||||
@@ -63,7 +63,7 @@ describe("ipc-core-admin-handlers connector saves", () => {
|
||||
}) as { ok: boolean; error?: { code: string; message: string } };
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(db.getConnector("anthropic").model).toBe(anthropicModel);
|
||||
expect(db.connectorsCrud.getConnector("anthropic").model).toBe(anthropicModel);
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("knowledge provenance", () => {
|
||||
status: "active",
|
||||
});
|
||||
|
||||
const knowledgeId = db.saveKnowledgeEntry({
|
||||
const knowledgeId = db.knowledgeMod.saveKnowledgeEntry({
|
||||
companyId,
|
||||
category: "lesson_learned",
|
||||
topic: "Original lesson",
|
||||
@@ -37,7 +37,7 @@ describe("knowledge provenance", () => {
|
||||
sourceRunId: null,
|
||||
});
|
||||
|
||||
db.saveKnowledgeEntry({
|
||||
db.knowledgeMod.saveKnowledgeEntry({
|
||||
id: knowledgeId,
|
||||
companyId,
|
||||
category: "lesson_learned",
|
||||
|
||||
@@ -19,7 +19,7 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
function createScaffold(db: AppDatabase) {
|
||||
db.updateConnectorHealth({
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
id: "openai-codex",
|
||||
status: "ready",
|
||||
authState: "authenticated",
|
||||
@@ -33,7 +33,7 @@ function createScaffold(db: AppDatabase) {
|
||||
status: "active",
|
||||
executionMode: "ai_cofounder",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: "/tmp/message-dispatch",
|
||||
@@ -282,7 +282,7 @@ describe("message dispatch", () => {
|
||||
});
|
||||
|
||||
threadIntegration.onTaskCreated(taskId, companyId);
|
||||
const thread = db.listThreads({
|
||||
const thread = db.threads.listThreads({
|
||||
companyId,
|
||||
type: "task",
|
||||
relatedEntityType: "task",
|
||||
@@ -294,21 +294,21 @@ describe("message dispatch", () => {
|
||||
throw new Error("Expected task thread to exist.");
|
||||
}
|
||||
|
||||
const replyWait = db.setThreadWaitState({
|
||||
const replyWait = db.threads.setThreadWaitState({
|
||||
threadId: thread.id,
|
||||
companyId,
|
||||
waitingFor: "user_reply",
|
||||
relatedApprovalId: null,
|
||||
reason: "Reply needed",
|
||||
});
|
||||
const reviewWait = db.setThreadWaitState({
|
||||
const reviewWait = db.threads.setThreadWaitState({
|
||||
threadId: thread.id,
|
||||
companyId,
|
||||
waitingFor: "user_review",
|
||||
relatedApprovalId: null,
|
||||
reason: "Review needed",
|
||||
});
|
||||
const approvalWait = db.setThreadWaitState({
|
||||
const approvalWait = db.threads.setThreadWaitState({
|
||||
threadId: thread.id,
|
||||
companyId,
|
||||
waitingFor: "user_approval",
|
||||
@@ -328,7 +328,7 @@ describe("message dispatch", () => {
|
||||
body: "Board-wide update",
|
||||
});
|
||||
|
||||
let unresolved = db.listThreadWaitStates(companyId);
|
||||
let unresolved = db.threads.listThreadWaitStates(companyId);
|
||||
expect(unresolved.some((entry) => entry.id === replyWait.id)).toBe(false);
|
||||
expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(true);
|
||||
|
||||
@@ -346,7 +346,7 @@ describe("message dispatch", () => {
|
||||
body: "Specific reply for the thread",
|
||||
});
|
||||
|
||||
unresolved = db.listThreadWaitStates(companyId);
|
||||
unresolved = db.threads.listThreadWaitStates(companyId);
|
||||
expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(false);
|
||||
expect(unresolved.some((entry) => entry.id === approvalWait.id)).toBe(true);
|
||||
|
||||
|
||||
@@ -32,13 +32,13 @@ function seedCompany(db: AppDatabase) {
|
||||
description: "Regression fixture",
|
||||
status: "active",
|
||||
});
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "",
|
||||
notes: "fixture",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Default",
|
||||
localPath: "/tmp/fixture",
|
||||
@@ -91,13 +91,13 @@ describe("resolveWorkspaceWithLockMode", () => {
|
||||
|
||||
it("project-bound workspace → exclusive (single writer per repo)", () => {
|
||||
const { companyId } = seedCompany(db);
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
name: "Platform",
|
||||
description: "",
|
||||
status: "active",
|
||||
});
|
||||
db.saveWorkspace({
|
||||
db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Project repo",
|
||||
localPath: "/tmp/project-ws",
|
||||
@@ -131,7 +131,7 @@ describe("resolveWorkspaceWithLockMode", () => {
|
||||
|
||||
it("task workspace wins over agent workspace even when both are set", () => {
|
||||
const { companyId, workspaceId: agentWsId } = seedCompany(db);
|
||||
const taskWsId = db.saveWorkspace({
|
||||
const taskWsId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Task-only",
|
||||
localPath: "/tmp/task-ws",
|
||||
|
||||
@@ -26,7 +26,7 @@ function scaffold(db: AppDatabase) {
|
||||
description: "Testing session management",
|
||||
status: "active",
|
||||
});
|
||||
const workspaceId = db.saveWorkspace({
|
||||
const workspaceId = db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Primary",
|
||||
localPath: "/tmp/session-test",
|
||||
@@ -47,14 +47,14 @@ function scaffold(db: AppDatabase) {
|
||||
capabilities: "Writes code",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
const goalId = db.saveGoal({
|
||||
const goalId = db.entityCrud.saveGoal({
|
||||
companyId,
|
||||
title: "Ship feature",
|
||||
description: "Build session management",
|
||||
ownerAgentId: agentId,
|
||||
status: "active",
|
||||
});
|
||||
const projectId = db.saveProject({
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
companyId,
|
||||
goalId,
|
||||
name: "Session Project",
|
||||
|
||||
@@ -154,8 +154,10 @@ describe("checkCapabilityMatch", () => {
|
||||
entityCrud: {
|
||||
getAgent: (_id: string) => agent,
|
||||
},
|
||||
listAgentSkillAssignments: (_id: string) => skillAssignments,
|
||||
getCompanySkill: (_companyId: string, skillId: string) => skills[skillId] ?? null,
|
||||
skills: {
|
||||
listAgentSkillAssignments: (_id: string) => skillAssignments,
|
||||
getCompanySkill: (_companyId: string, skillId: string) => skills[skillId] ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ function createTestCompany(db: AppDatabase): string {
|
||||
}
|
||||
|
||||
function createTestConnector(db: AppDatabase): void {
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "",
|
||||
@@ -44,7 +44,7 @@ function createTestConnector(db: AppDatabase): void {
|
||||
}
|
||||
|
||||
function createTestWorkspace(db: AppDatabase, companyId: string): string {
|
||||
return db.saveWorkspace({
|
||||
return db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Phase Workspace",
|
||||
localPath: "/tmp/phase-workspace",
|
||||
|
||||
@@ -27,7 +27,7 @@ function createTestCompany(db: AppDatabase): string {
|
||||
}
|
||||
|
||||
function createTestConnector(db: AppDatabase): void {
|
||||
db.saveConnector({
|
||||
db.connectorsCrud.saveConnector({
|
||||
id: "openai-codex",
|
||||
model: "gpt-5",
|
||||
envBindingText: "",
|
||||
@@ -36,7 +36,7 @@ function createTestConnector(db: AppDatabase): void {
|
||||
}
|
||||
|
||||
function createTestWorkspace(db: AppDatabase, companyId: string): string {
|
||||
return db.saveWorkspace({
|
||||
return db.workspacesMod.saveWorkspace({
|
||||
companyId,
|
||||
name: "Wakeup Workspace",
|
||||
localPath: "/tmp/wakeup-workspace",
|
||||
@@ -87,9 +87,9 @@ describe("wakeup lifecycle", () => {
|
||||
const workspaceId = createTestWorkspace(db, companyId);
|
||||
const agentId = createTestAgent(db, companyId, workspaceId);
|
||||
|
||||
db.enqueueWakeupRequest(companyId, agentId, "task.created", "New task assigned");
|
||||
db.wakeup.enqueueWakeupRequest(companyId, agentId, "task.created", "New task assigned");
|
||||
|
||||
const wakes = db.listWakeupRequests(companyId);
|
||||
const wakes = db.wakeup.listWakeupRequests(companyId);
|
||||
expect(wakes.length).toBe(1);
|
||||
expect(wakes[0].agentId).toBe(agentId);
|
||||
expect(wakes[0].status).toBe("queued");
|
||||
@@ -107,10 +107,10 @@ describe("wakeup lifecycle", () => {
|
||||
const workspaceId = createTestWorkspace(db, companyId);
|
||||
const agentId = createTestAgent(db, companyId, workspaceId);
|
||||
|
||||
db.enqueueWakeupRequest(companyId, agentId, "task.created", "First task");
|
||||
db.enqueueWakeupRequest(companyId, agentId, "task.status_changed", "Second trigger");
|
||||
db.wakeup.enqueueWakeupRequest(companyId, agentId, "task.created", "First task");
|
||||
db.wakeup.enqueueWakeupRequest(companyId, agentId, "task.status_changed", "Second trigger");
|
||||
|
||||
const wakes = db.listWakeupRequests(companyId);
|
||||
const wakes = db.wakeup.listWakeupRequests(companyId);
|
||||
// D-07: Per-agent scope -- only one wakeup row should exist
|
||||
expect(wakes.length).toBe(1);
|
||||
expect(wakes[0].coalescedCount).toBe(1);
|
||||
@@ -128,15 +128,15 @@ describe("wakeup lifecycle", () => {
|
||||
const agent1Id = createTestAgent(db, companyId, workspaceId, "Agent1");
|
||||
const agent2Id = createTestAgent(db, companyId, workspaceId, "Agent2");
|
||||
|
||||
db.enqueueWakeupRequest(companyId, agent1Id, "task.created", "Agent1 task");
|
||||
db.enqueueWakeupRequest(companyId, agent2Id, "task.created", "Agent2 task");
|
||||
db.wakeup.enqueueWakeupRequest(companyId, agent1Id, "task.created", "Agent1 task");
|
||||
db.wakeup.enqueueWakeupRequest(companyId, agent2Id, "task.created", "Agent2 task");
|
||||
|
||||
// List all for company
|
||||
const allWakes = db.listWakeupRequests(companyId);
|
||||
const allWakes = db.wakeup.listWakeupRequests(companyId);
|
||||
expect(allWakes.length).toBe(2);
|
||||
|
||||
// Filter by agentId
|
||||
const agent1Wakes = db.listWakeupRequests(companyId, { agentId: agent1Id });
|
||||
const agent1Wakes = db.wakeup.listWakeupRequests(companyId, { agentId: agent1Id });
|
||||
expect(agent1Wakes.length).toBe(1);
|
||||
expect(agent1Wakes[0].agentId).toBe(agent1Id);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user