Q161: the home page measures accepted work, not sessions that merely ended

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 03:02:04 -04:00
1 parent c9117fe46d
commit 58871fbaed
15 files changed
+183 -17

No files matched your search

+1 -1
View File
@@ -35,7 +35,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, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102]);
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, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103]);
connection.close();
});
+11
View File
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
import { tmpdir } from "node:os";
import { join } from "node:path";
import { currentLinks } from "../helpers/task-links";
import type { DatabaseSync } from "node:sqlite";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
@@ -12,6 +13,7 @@ import { ApprovalService } from "../../src/main/services/approval-service";
import { runJournalLines } from "../../src/main/services/run-journal";
import { DocumentService } from "../../src/main/services/document-service";
import { approvalDrifts } from "../../src/main/services/approval-drift";
import { buildUsageStats } from "../../src/main/services/usage-stats";
import { createIsolationContext, executionApprovalProblem, loadWorkspace, requireAssignedAgent } from "../../src/main/services/task-run-preparation";
import { TASK_PRIORITY } from "../../src/shared/domain";
import type { TaskExecutionTaskRow } from "../../src/main/services/task-prompts";
@@ -74,6 +76,11 @@ function pendingApprovals(h: Harness, kind: string): Array<{ state: string; sour
).all(h.orgId, kind) as Array<{ state: string; source_entity_type: string | null }>;
}
/** Work sent back this week, as the home page's acceptance chart counts it. */
function returned(db: DatabaseSync, orgId: string): number {
return buildUsageStats(db, new Date()).find((item) => item.orgId === orgId)!.days.reduce((sum, day) => sum + day.returnedTasks, 0);
}
describe("execution modes", () => {
test("AI Co-Founder mode holds every deliverable and document until the board approves it", () => {
const h = harness("ai_cofounder");
@@ -128,6 +135,8 @@ describe("execution modes", () => {
h.connection.transaction(() => new ApprovalService(db).decide({ orgId: h.orgId, approvalId: review, state: "declined", decidedByEmployeeId: employeeId }));
expect(existsSync(join(h.ctx.workspace!.localPath, "plan.md"))).toBe(false);
expect(db.prepare("select state from held_deliverables").get()).toEqual({ state: "declined" });
// A decline on work still in progress sends it back once, though the task did not move.
expect(returned(db, h.orgId)).toBe(1);
finish();
});
@@ -145,6 +154,8 @@ describe("execution modes", () => {
const review = (db.prepare("select id from approvals where org_id = ? and kind = 'deliverable_review'").get(h.orgId) as { id: string }).id;
h.connection.transaction(() => new ApprovalService(db).decide({ orgId: h.orgId, approvalId: review, state: "declined", decidedByEmployeeId: employeeId, notes: "Cite the market data." }));
expect(db.prepare("select status from tasks where id = ?").get(taskId)).toEqual({ status: "in_progress" });
// The decline and the reopen it caused are one verdict, not two.
expect(returned(db, h.orgId)).toBe(1);
expect(runJournalLines(db, taskId).join("\n")).toContain("The board declined plan.md: Cite the market data. Redo it before closing the task.");
// Another deliverable under the same name elsewhere does not answer the decline; a resubmission of the same one does.
const elsewhere = join(h.ctx.workspace!.localPath, "other");
+32
View File
@@ -66,3 +66,35 @@ describe("usage statistics", () => {
process.env.TZ = previous;
});
});
describe("accepted work", () => {
test("counts plain tasks reaching done and work sent back, not sessions that merely ended", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-acceptance-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
const ids: string[] = [];
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "Acme", employeeName: "Dana", mission: "m", goal: "g" });
for (const title of ["Ship", "Spec", "Chat reply"]) {
ids.push(organization.createTask({
orgId, title, description: "d", status: "backlog", assigneeAgentId: null, risk: "low", projectId: null, goalId: null,
workspaceId: null, priority: "normal", requiresFilesystem: false, ...(title === "Chat reply" ? { origin: "chat_wake" as const } : {}),
}));
}
});
const [ship, spec, reply] = ids;
const move = (taskId: string, status: string) => connection.transaction(() => organization.updateTaskStatus(orgId, taskId, status));
move(ship, "in_progress"); move(ship, "review"); move(ship, "done");
move(spec, "in_progress"); move(spec, "review"); move(spec, "in_progress");
move(reply, "done");
const week = buildUsageStats(connection.db, new Date()).find((item) => item.orgId === orgId)!.days;
const total = (pick: (day: typeof week[number]) => number) => week.reduce((sum, day) => sum + pick(day), 0);
expect([total((day) => day.acceptedTasks), total((day) => day.returnedTasks)]).toEqual([1, 1]);
// Marking done twice is one acceptance; deleting the task keeps the history.
move(ship, "done");
connection.transaction(() => organization.deleteTask({ orgId, taskId: ship }));
const after = buildUsageStats(connection.db, new Date()).find((item) => item.orgId === orgId)!.days;
expect([after.reduce((sum, day) => sum + day.acceptedTasks, 0), after.reduce((sum, day) => sum + day.returnedTasks, 0)]).toEqual([1, 1]);
connection.close();
});
});
+43
View File
@@ -0,0 +1,43 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { workVerdictsSchema } from "../../src/main/database/migrations/103-work-verdicts";
describe("past verdicts on work", () => {
test("are read once from the audit trail: kind known, done once, a decline and its reopen counted once", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-verdict-backfill-${randomUUID()}.db`));
const db = connection.db;
let orgId = "";
connection.transaction(() => {
orgId = new OrganizationService(db).createOrganization({ organizationName: "Acme", employeeName: "Yang", mission: "m", goal: "g" });
});
const task = (id: string, origin: string | null) => db.prepare("insert into tasks(id, org_id, title, description, status, risk, priority, requires_filesystem, writing_mode, origin, created_at, updated_at) values (?, ?, ?, 'd', 'backlog', 'low', 'normal', 0, 'conversational', ?, ?, ?)")
.run(id, orgId, id, origin, "2026-05-01T00:00:00.000Z", "2026-05-01T00:00:00.000Z");
let n = 0;
const event = (taskId: string, status: string, at: string) => db.prepare("insert into audit_events(id, org_id, entity_type, entity_id, actor_type, actor_id, action, payload_json, created_at) values (?, ?, 'task', ?, 'system', null, 'status_changed', ?, ?)")
.run(`aud_${n++}`, orgId, taskId, JSON.stringify({ status }), at);
task("tsk_plain", null);
task("tsk_wake", "chat_wake");
event("tsk_plain", "review", "2026-05-02T00:00:00.000Z");
event("tsk_plain", "done", "2026-05-02T01:00:00.000Z");
event("tsk_plain", "done", "2026-05-02T02:00:00.000Z");
event("tsk_wake", "done", "2026-05-02T03:00:00.000Z");
event("tsk_gone", "done", "2026-05-02T04:00:00.000Z");
// A decline reopened the plain task a moment after it was decided.
db.prepare("insert into approvals(id, org_id, kind, title, details, risk, requested_by_type, requested_by_id, source_entity_type, source_entity_id, state, decided_by_employee_id, created_at, decided_at) values ('app_d', ?, 'deliverable_review', 't', 'd', 'low', 'system', null, 'held_deliverable', 'held_1', 'declined', null, ?, ?)")
.run(orgId, "2026-05-03T00:00:00.000Z", "2026-05-03T00:00:00.000Z");
db.prepare("insert into held_deliverables(id, org_id, approval_id, kind, task_id, content, state, created_at) values ('held_1', ?, 'app_d', 'file', 'tsk_plain', 'x', 'declined', ?)")
.run(orgId, "2026-05-03T00:00:00.000Z");
event("tsk_plain", "in_progress", "2026-05-03T00:00:00.500Z");
db.exec("delete from work_verdicts");
db.exec(workVerdictsSchema.slice(0, workVerdictsSchema.indexOf("insert into schema_migrations")));
expect(db.prepare("select task_id, verdict from work_verdicts order by at").all()).toEqual([
{ task_id: "tsk_plain", verdict: "accepted" },
{ task_id: "tsk_plain", verdict: "returned" },
]);
connection.close();
});
});