mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 14:37:26 +00:00
Enforce AgentStack status jumps (#81)
This commit is contained in:
parent
82e1389b8d
commit
e38688f16e
2 changed files with 35 additions and 5 deletions
|
|
@ -76,6 +76,25 @@ describe("AgentStack coordinator", () => {
|
|||
expect(() => stack.updateTaskStatus(task.id, "running")).toThrow(/cancelled/);
|
||||
});
|
||||
|
||||
it("rejects status jumps outside the AgentStack lifecycle", () => {
|
||||
const stack = new AgentStack();
|
||||
stack.registerAgent(agent);
|
||||
|
||||
const pending = stack.createTask({ ownerDid: owner, sourceApp: "ugig.net", title: "Pending task" });
|
||||
expect(() => stack.updateTaskStatus(pending.id, "running")).toThrow(/pending -> running/);
|
||||
|
||||
const queued = stack.createTask({
|
||||
ownerDid: owner,
|
||||
sourceApp: "ugig.net",
|
||||
title: "Queued task",
|
||||
assigneeDid: agent.did
|
||||
});
|
||||
expect(() => stack.updateTaskStatus(queued.id, "complete")).toThrow(/queued -> complete/);
|
||||
|
||||
const blocked = stack.updateTaskStatus(queued.id, "running");
|
||||
expect(stack.updateTaskStatus(blocked.id, "blocked").status).toBe("blocked");
|
||||
});
|
||||
|
||||
it("refuses to assign a task that is already in a terminal status", () => {
|
||||
const stack = new AgentStack();
|
||||
stack.registerAgent(agent);
|
||||
|
|
@ -89,6 +108,7 @@ describe("AgentStack coordinator", () => {
|
|||
escrowId: "esc_1"
|
||||
});
|
||||
stack.assignTask(task.id, agent.did);
|
||||
stack.updateTaskStatus(task.id, "running");
|
||||
stack.updateTaskStatus(task.id, "complete", { reputationEventId: "rep_1" });
|
||||
|
||||
expect(() => stack.assignTask(task.id, other.did)).toThrow(/already complete and cannot be assigned/);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,13 @@ export function isDidTask(value: unknown): value is DidTask {
|
|||
);
|
||||
}
|
||||
|
||||
const TERMINAL: ReadonlySet<TaskStatus> = new Set(["complete", "failed", "cancelled"]);
|
||||
const TERMINAL: ReadonlySet<TaskStatus> = new Set(["complete", "failed", "cancelled"]);
|
||||
const ALLOWED_TRANSITIONS: Partial<Readonly<Record<TaskStatus, readonly TaskStatus[]>>> = {
|
||||
pending: ["pending", "queued", "cancelled"],
|
||||
queued: ["queued", "running", "cancelled"],
|
||||
running: ["running", "blocked", "complete", "failed", "cancelled"]
|
||||
};
|
||||
// Blocked re-entry is still a PRD open question, so keep its current permissive behavior for now.
|
||||
|
||||
/**
|
||||
* In-memory AgentStack coordinator: registers agents, tracks portable tasks through their
|
||||
|
|
@ -136,10 +142,14 @@ export class AgentStack {
|
|||
patch: Partial<Pick<DidTask, "reputationEventId" | "paymentIntentId" | "escrowId" | "metadata">> = {}
|
||||
): DidTask {
|
||||
const task = this.requireTask(taskId);
|
||||
if (TERMINAL.has(task.status)) {
|
||||
throw new Error(`Task ${taskId} is already ${task.status} and cannot transition to ${status}`);
|
||||
}
|
||||
const updated: DidTask = { ...task, ...patch, status, updatedAt: this.now() };
|
||||
if (TERMINAL.has(task.status)) {
|
||||
throw new Error(`Task ${taskId} is already ${task.status} and cannot transition to ${status}`);
|
||||
}
|
||||
const allowedStatuses = ALLOWED_TRANSITIONS[task.status];
|
||||
if (allowedStatuses && !allowedStatuses.includes(status)) {
|
||||
throw new Error(`Invalid task status transition: ${task.status} -> ${status}`);
|
||||
}
|
||||
const updated: DidTask = { ...task, ...patch, status, updatedAt: this.now() };
|
||||
this.tasks.set(taskId, updated);
|
||||
this.emit({ type: "task.updated", task: updated });
|
||||
return updated;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue