Q160: a goal's pace is measured against its target date, and claimed only when it has one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
459dcf0ea2
commit
c9117fe46d
18 files changed
+172
-24
No files matched your search
@@ -2,7 +2,7 @@ import type { TaskDeleteImpact } from "../../shared/domain";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { APPROVAL_STATE, RISK_LEVEL, TASK_STATUS, WRITING_MODE, type RiskLevel } from "../../shared/domain";
|
||||
import { createId } from "../../shared/ids";
|
||||
import type { CreateAgentInput, CreateGoalInput, CreateOrganizationInput, CreateProjectInput, CreateTaskInput, CreateWorkspaceInput, DeleteTaskInput, SetTaskBlockedInput, UpdateAgentInput, UpdateEmployeeProfileInput, UpdateTaskInput } from "../../shared/commands";
|
||||
import type { CreateAgentInput, CreateGoalInput, CreateOrganizationInput, CreateProjectInput, CreateTaskInput, CreateWorkspaceInput, DeleteTaskInput, SetGoalDueDateInput, SetTaskBlockedInput, UpdateAgentInput, UpdateEmployeeProfileInput, UpdateTaskInput } from "../../shared/commands";
|
||||
import { AuditService } from "./audit-service";
|
||||
import { ensureAgentHeartbeat, resolveGoalKickoff, type GoalKickoffPlan } from "./goal-kickoff";
|
||||
import { PolicyService } from "./policy-service";
|
||||
@@ -359,9 +359,9 @@ export class OrganizationService {
|
||||
const now = new Date().toISOString();
|
||||
if (input.ownerAgentId) this.verifyOptionalOrgEntity("agents", input.ownerAgentId, input.orgId, "Owner agent");
|
||||
this.db.prepare(`
|
||||
insert into goals(id, org_id, title, description, status, owner_agent_id, created_at, updated_at)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(id, input.orgId, input.title.trim(), input.description.trim(), "active", input.ownerAgentId, now, now);
|
||||
insert into goals(id, org_id, title, description, status, owner_agent_id, due_at, created_at, updated_at)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(id, input.orgId, input.title.trim(), input.description.trim(), "active", input.ownerAgentId, validDueAt(input.dueAt ?? null), now, now);
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
entityType: "goal",
|
||||
@@ -477,6 +477,14 @@ export class OrganizationService {
|
||||
return this.tasks.deleteImpact(orgId, taskId);
|
||||
}
|
||||
|
||||
/** Sets or clears when a goal is planned to be reached. */
|
||||
setGoalDueDate(input: SetGoalDueDateInput): void {
|
||||
const result = this.db.prepare("update goals set due_at = ?, updated_at = ? where id = ? and org_id = ?")
|
||||
.run(validDueAt(input.dueAt), new Date().toISOString(), input.goalId, input.orgId);
|
||||
if (result.changes === 0) throw new Error("Goal was not found.");
|
||||
this.audit.record({ orgId: input.orgId, entityType: "goal", entityId: input.goalId, actorType: "employee", actorId: null, action: "due_date_set", payload: { dueAt: input.dueAt } });
|
||||
}
|
||||
|
||||
setTaskBlocked(input: SetTaskBlockedInput): void {
|
||||
this.tasks.setBlocked(input);
|
||||
}
|
||||
@@ -493,3 +501,13 @@ export class OrganizationService {
|
||||
return this.tasks.agentSetStatus(input);
|
||||
}
|
||||
}
|
||||
|
||||
/** A target date is an exact ISO UTC time on a real calendar day; anything else is refused rather than stored shifted. */
|
||||
function validDueAt(dueAt: string | null): string | null {
|
||||
if (dueAt === null) return null;
|
||||
const exact = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(dueAt);
|
||||
const time = new Date(dueAt);
|
||||
const normalized = dueAt.length === 20 ? dueAt.replace("Z", ".000Z") : dueAt;
|
||||
if (!exact || Number.isNaN(time.getTime()) || time.toISOString() !== normalized) throw new Error(`"${dueAt}" is not a date.`);
|
||||
return normalized;
|
||||
}
|
||||
Reference in new issue
Block a user