feat(p9): thread purpose + persist consent receipt through OutboundService
Task S2.2: OutboundService.send gains an optional purpose, passes it to the broker, and persists the returned consentReceiptRef on IiosOutboundCommand (new nullable column, migration outbound-consent). RouteService.execute passes 'route_forward'; calendar reminders pass 'meeting_reminder'. CMP-DENY → command BLOCKED, no send. 2 tests: receipt recorded on allow, purpose-DENY blocks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -115,4 +115,22 @@ describe('Adapter outbound (P5)', () => {
|
||||
expect(cmd.status).toBe('BLOCKED');
|
||||
expect(cmd.providerRef).toBe('blocked');
|
||||
});
|
||||
|
||||
it('CMP allow → command records the consent receipt (P9 slice 2)', async () => {
|
||||
const ports = makeFakePorts();
|
||||
ports.cmp.setStatus('ALLOW');
|
||||
const svc = new OutboundService(asService, new CapabilityBroker(ports, new CapabilityProviderRegistry()));
|
||||
const cmd = await svc.send('WEBHOOK', 'user@x', { text: 'hi' }, 'consent-1', 'scope1', 'support');
|
||||
expect(cmd.status).toBe('SENT');
|
||||
expect(cmd.consentReceiptRef).toBe('fake-receipt');
|
||||
});
|
||||
|
||||
it('CMP DENY for the purpose → command BLOCKED, no send (P9 slice 2)', async () => {
|
||||
const ports = makeFakePorts();
|
||||
ports.cmp.denyPurpose('marketing');
|
||||
const svc = new OutboundService(asService, new CapabilityBroker(ports, new CapabilityProviderRegistry()));
|
||||
const cmd = await svc.send('WEBHOOK', 'user@x', { text: 'promo' }, 'consent-2', 'scope1', 'marketing');
|
||||
expect(cmd.status).toBe('BLOCKED');
|
||||
expect(await prisma.iiosDeliveryAttempt.count({ where: { commandId: cmd.id, status: 'SENT' } })).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user