Q107: a task approval covers the content it was given; edits need a fresh one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c695fdb0f6
commit
4765683b86
12 files changed
+114
-52
No files matched your search
@@ -24,7 +24,7 @@ describe("domain services", () => {
|
||||
const connection = createConnection();
|
||||
|
||||
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84]);
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import { OrganizationService } from "../../src/main/services/organization-servic
|
||||
import { ChatService } from "../../src/main/services/chat-service";
|
||||
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
|
||||
import { ApprovalService } from "../../src/main/services/approval-service";
|
||||
import { requireAssignedAgent } from "../../src/main/services/task-run-preparation";
|
||||
import { executionApprovalProblem, requireAssignedAgent } from "../../src/main/services/task-run-preparation";
|
||||
import { TASK_PRIORITY } from "../../src/shared/domain";
|
||||
import type { TaskExecutionTaskRow } from "../../src/main/services/task-prompts";
|
||||
|
||||
function call(name: string, args: Record<string, unknown>) {
|
||||
@@ -153,4 +154,40 @@ describe("execution modes", () => {
|
||||
expect([lifecycle(bo), identities(bo), memberships(bo)]).toEqual(["declined", 0, 0]);
|
||||
expect(() => runAs(bo)).toThrow("Bo's hire was declined; reassign the task.");
|
||||
});
|
||||
|
||||
test("a task approval covers the content it was given; an edit needs a fresh one", () => {
|
||||
const h = harness("ai_cofounder");
|
||||
const db = h.connection.db;
|
||||
const employeeId = (db.prepare("select id from employees where org_id = ?").get(h.orgId) as { id: string }).id;
|
||||
const approvals = new ApprovalService(db);
|
||||
const taskId = h.connection.transaction(() => h.deps.organization.createTask({
|
||||
orgId: h.orgId, title: "Wipe staging", description: "Drop the staging tables.", status: "backlog",
|
||||
assigneeAgentId: h.ceoId, risk: "high", projectId: null, goalId: null, workspaceId: null, priority: TASK_PRIORITY.NORMAL, requiresFilesystem: false,
|
||||
}));
|
||||
const task = () => db.prepare("select * from tasks where id = ?").get(taskId) as unknown as Parameters<typeof executionApprovalProblem>[2];
|
||||
const taskApprovals = () => db.prepare("select id, state, details from approvals where source_entity_id = ? order by rowid").all(taskId) as Array<{ id: string; state: string; details: string }>;
|
||||
const edit = (description: string) => h.connection.transaction(() => h.deps.organization.updateTask({
|
||||
orgId: h.orgId, taskId, title: "Wipe staging", description, assigneeAgentId: h.ceoId, risk: "high", priority: TASK_PRIORITY.NORMAL, parentTaskId: null,
|
||||
}));
|
||||
const decide = (id: string) => h.connection.transaction(() => approvals.decide({ orgId: h.orgId, approvalId: id, state: "approved", decidedByEmployeeId: employeeId }));
|
||||
|
||||
// Editing while the review waits replaces it with one for the new content.
|
||||
edit("Drop the staging tables, then reseed.");
|
||||
expect(taskApprovals().map((row) => [row.state, row.details])).toEqual([
|
||||
["superseded", "Drop the staging tables."],
|
||||
["pending", "Drop the staging tables, then reseed."],
|
||||
]);
|
||||
decide(taskApprovals()[1].id);
|
||||
expect(executionApprovalProblem(db, h.orgId, task())).toBeNull();
|
||||
|
||||
// An approved task edited afterwards is not covered by the old approval.
|
||||
edit("Drop the production tables.");
|
||||
expect(executionApprovalProblem(db, h.orgId, task())).toBe("Approved task execution approval is required.");
|
||||
expect(taskApprovals().map((row) => row.state)).toEqual(["superseded", "approved", "pending"]);
|
||||
// A save that changes nothing approved keeps the approval.
|
||||
decide(taskApprovals()[2].id);
|
||||
edit("Drop the production tables.");
|
||||
expect(taskApprovals()).toHaveLength(3);
|
||||
expect(executionApprovalProblem(db, h.orgId, task())).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -67,7 +67,7 @@ describe("task review", () => {
|
||||
test("a failed review hand-off leaves the task where it was; deleting reviewed work closes its review", () => {
|
||||
const h = setup();
|
||||
const taskId = h.task(h.devId, "Login page");
|
||||
const failing = new TaskMutations(h.connection.db, () => { throw new Error("review task could not be created"); });
|
||||
const failing = new TaskMutations(h.connection.db, () => { throw new Error("review task could not be created"); }, () => undefined);
|
||||
expect(() => failing.updateStatus(h.orgId, taskId, TASK_STATUS.REVIEW)).toThrow("review task could not be created");
|
||||
expect(h.connection.db.prepare("select status from tasks where id = ?").get(taskId)).toEqual({ status: "in_progress" });
|
||||
|
||||
|
||||
Reference in new issue
Block a user