fix: include REST creation recipients in composer assignment history

This commit is contained in:
cryppadotta committed 2026-10-13 04:58:49 +00:00
1 parent c41b6bcc12
commit 5a7d5e98a8
5 files changed
+42 -4

No files matched your search

@@ -78,6 +78,7 @@ describeEmbeddedPostgres("issue comment attribution and patch audit routes", ()
async function seed() {
const company = await db.insert(companies).values({
name: "Attribution Company",
issueCounter: 2,
issuePrefix: `AT${randomUUID().replaceAll("-", "").slice(0, 6).toUpperCase()}`,
}).returning().then((rows) => rows[0]!);
const [actorAgent, targetAgent] = await db.insert(agents).values(["Actor", "Target"].map((name) => ({
@@ -162,6 +163,27 @@ describeEmbeddedPostgres("issue comment attribution and patch audit routes", ()
};
}
it("records the human recipient for REST task and child creation", async () => {
const fixture = await seed();
const app = await createApp(db, agentActor(fixture));
for (const [path, action] of [
[`/api/companies/${fixture.company.id}/issues`, "issue.created"],
[`/api/issues/${fixture.issue.id}/children`, "issue.child_created"],
]) {
const response = await request(app).post(path!).send({
title: "Task for the human", status: "todo", assigneeUserId: fixture.responsibleUserId,
});
expect(response.status, JSON.stringify(response.body)).toBe(201);
const events = await db.select().from(activityLog).where(and(
eq(activityLog.entityId, response.body.id), eq(activityLog.action, action!),
));
expect(events).toEqual([expect.objectContaining({
actorType: "agent", actorId: fixture.actorAgent.id,
details: expect.objectContaining({ assigneeAgentId: null, assigneeUserId: fixture.responsibleUserId }),
})]);
}
});
it("attributes foreign-issue agent comments and emits complete agent and human PATCH receipts", async () => {
const fixture = await seed();
const agentApp = await createApp(db, agentActor(fixture));
+3 -1
View File
@@ -653,7 +653,7 @@ const attachmentArtifactMetadataInputSchema = z
.passthrough();
function buildCreateIssueActivityStatusDetails(
issue: { assigneeAgentId: string | null; status: string },
issue: { assigneeAgentId: string | null; assigneeUserId: string | null; status: string },
res: Response,
) {
const statusDefault = res.locals.createIssueStatusDefault as
@@ -661,6 +661,8 @@ function buildCreateIssueActivityStatusDetails(
const assignmentWakeSkipped =
!issue.assigneeAgentId || issue.status === "backlog";
return {
assigneeAgentId: issue.assigneeAgentId,
assigneeUserId: issue.assigneeUserId,
status: issue.status,
statusDefaulted: statusDefault?.defaulted ?? false,
statusDefaultReason: statusDefault?.reason ?? "explicit",
+4 -2
View File
@@ -9,7 +9,7 @@ import {
// Real local Board UI, upload storage, comment HTTP routes, and disposable DB.
// Only the interface flag and explicit transport/upload rejection faults are mocked.
// Tasks belong to the Board user: no agent, runner, or provider is contacted.
// Tasks have no recipient: no agent, runner, or provider is contacted.
type Attachment = {
id: string;
issueCommentId: string | null;
@@ -44,7 +44,9 @@ async function setup(page: Page, request: APIRequestContext, classic: boolean) {
data: {
title: "Inspect the newly uploaded Board files",
status: "todo",
assigneeUserId: "local-board",
// Keep attachment sends on the comment route. A task assigned to the
// current human now suggests an empty recipient and sends a reassignment.
assigneeUserId: null,
},
}),
);
+12
View File
@@ -154,3 +154,15 @@ it("prefers a newer native reassignment over a REST assignment and ignores nativ
];
expect(suggestedCommentAssigneeValue({ assigneeUserId: "me" }, [], "me", undefined, history)).toBe("agent:beta");
});
it.each(["issue.created", "issue.child_created"])("uses a REST %s receipt assigned to the current human", (action) => {
const event: ActivityEvent = {
id: "creation", companyId: "company", entityType: "issue", entityId: "task",
agentId: "creator", runId: null, actorType: "agent", actorId: "creator", action,
createdAt: new Date("2026-02-01"),
details: { title: "Task", identifier: "PAP-1", status: "todo", assigneeAgentId: null, assigneeUserId: "me" },
};
expect(suggestedCommentAssigneeValue({ assigneeUserId: "me" }, [{ authorAgentId: "commenter" }], "me", undefined, [event])).toBe("agent:creator");
expect(suggestedCommentAssigneeValue({ assigneeUserId: "other" }, [], "other", undefined, [event])).toBe("");
});
+1 -1
View File
@@ -41,7 +41,7 @@ export function suggestedCommentAssigneeValue(
if (event.actorType !== "agent" || !event.actorId || event.actorId === currentAgentId) return false;
const details = event.details;
if (!details || details.assigneeUserId !== currentUserId) return false;
if (event.action === "issue.created") return true;
if (event.action === "issue.created" || event.action === "issue.child_created") return true;
if (event.action === "issue.reassigned") {
return details.changed === true
&& Object.hasOwn(details, "previousAssigneeUserId")