Skip to content

Commit 04a0a91

Browse files
committed
fix(approvals): surface decision attachments through the listActions contract mapping (#3266)
Browser verification caught a #3268 gap: the `attachments` column landed on sys_approval_action and decide() persisted it, but rowFromAction never mapped it — every listActions consumer (REST /actions, the console timeline) saw none while the raw engine row carried the fileIds. Map it (and type it on the ApprovalActionRow contract) so the audit trail actually exposes what was attached. Regression test added: 137 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ypkQikZ55oWUHUnMebwXA
1 parent e53e059 commit 04a0a91

3 files changed

Lines changed: 21 additions & 0 deletions

File tree

packages/plugins/plugin-approvals/src/approval-service.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,3 +1446,18 @@ describe('ApprovalService — decision_progress & deep links (#2678 P1.5)', () =
14461446
expect(note.payload.actionUrl).toBe(`/system/approvals?request=${encodeURIComponent(req.id)}`);
14471447
});
14481448
});
1449+
1450+
// listActions must surface decision attachments through the contract mapping
1451+
// (#3266 — the column existed but rowFromAction dropped it; caught in browser).
1452+
describe('ApprovalService — listActions attachments mapping (#3266)', () => {
1453+
it('returns the attachments recorded on a decision', async () => {
1454+
const engine = makeFakeEngine();
1455+
let n = 0;
1456+
const svc = new ApprovalService({ engine: engine as any, clock: { now: () => new Date(1757000000000 + (n++) * 1000) } });
1457+
const req = await svc.openNodeRequest(openInput(['u9']), CTX);
1458+
await svc.decideNode(req.id, { decision: 'approve', actorId: 'u9', attachments: ['file_a'] }, SYS);
1459+
const acts = await svc.listActions(req.id, SYS);
1460+
const approve = acts.find(a => a.action === 'approve');
1461+
expect(approve?.attachments).toEqual(['file_a']);
1462+
});
1463+
});

packages/plugins/plugin-approvals/src/approval-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ function rowFromAction(row: any): ApprovalActionRow {
205205
action: row.action,
206206
actor_id: row.actor_id ?? undefined,
207207
comment: row.comment ?? undefined,
208+
// Decision attachments (#3266). The column shipped in #3268 but this
209+
// contract mapping didn't — the raw engine row carried the fileIds while
210+
// every consumer of listActions saw none (caught by browser verification).
211+
attachments: Array.isArray(row.attachments) && row.attachments.length ? row.attachments.map(String) : undefined,
208212
created_at: row.created_at ?? undefined,
209213
};
210214
}

packages/spec/src/contracts/approval-service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ export interface ApprovalActionRow {
145145
action: ApprovalActionKind;
146146
actor_id?: string;
147147
comment?: string;
148+
/** File references attached to this action (decision attachments, #3266). */
149+
attachments?: string[];
148150
created_at?: string;
149151
/** Display name of the actor (`sys_user.name`), when resolvable. */
150152
actor_name?: string;

0 commit comments

Comments
 (0)