import { describe, it, expect } from 'vitest'; import { makeFakePorts } from '@insignia/iios-testkit'; import { PolicyDeniedError, type CapabilityRequest } from '@insignia/iios-contracts'; import { CapabilityBroker } from './capability.broker'; import { CapabilityProviderRegistry } from './capability.registry'; import type { FakePorts } from '@insignia/iios-testkit'; // No DB — the broker is pure governance over an in-memory provider registry. const req = (over: Partial = {}): CapabilityRequest => ({ capability: 'channel.send', scopeId: 'scope1', channelType: 'WEBHOOK', target: 'user@x', payload: { text: 'hello' }, idempotencyKey: 'k1', ...over, }); const broker = (ports: FakePorts) => new CapabilityBroker(ports, new CapabilityProviderRegistry()); describe('CapabilityBroker (P9 — governed egress)', () => { it('allow + no obligations → provider SENT', async () => { const r = await broker(makeFakePorts()).invoke(req()); expect(r.outcome).toBe('SENT'); expect(r.providerRef).toContain('sim-WEBHOOK'); }); it('fails closed: opa deny → PolicyDeniedError, provider never called', async () => { const ports = makeFakePorts(); ports.opa.deny('no egress'); await expect(broker(ports).invoke(req())).rejects.toBeInstanceOf(PolicyDeniedError); }); it('DENY_OUTBOUND obligation → BLOCKED, provider never called', async () => { const ports = makeFakePorts(); ports.opa.setDecision({ allow: true, obligations: [{ kind: 'DENY_OUTBOUND', reason: 'user opted out' }] }); const r = await broker(ports).invoke(req()); expect(r.outcome).toBe('BLOCKED'); expect(r.errorCode).toBe('DENY_OUTBOUND'); }); it('REDACT obligation → provider receives masked payload', async () => { const ports = makeFakePorts(); ports.opa.setDecision({ allow: true, obligations: [{ kind: 'REDACT', target: 'text', reason: 'PII' }] }); const registry = new CapabilityProviderRegistry(); let seen: unknown; registry.register({ name: 'spy', channelTypes: ['WEBHOOK'], capabilities: { canSend: true }, async send(r) { seen = r.payload; return { providerRef: 'spy-1', outcome: 'SENT' }; } }); const r = await new CapabilityBroker(ports, registry).invoke(req()); expect(r.outcome).toBe('SENT'); expect((seen as { text: string }).text).toBe('[REDACTED]'); }); it('unknown channel → fails closed (throws, no silent egress)', async () => { await expect(broker(makeFakePorts()).invoke(req({ channelType: 'CARRIER_PIGEON' }))).rejects.toThrow(); }); // ── CMP consent-at-egress (P9 slice 2) ────────────────────────── it('CMP allow → SENT and carries the consent receipt', async () => { const ports = makeFakePorts(); ports.cmp.setStatus('ALLOW'); const r = await broker(ports).invoke(req()); expect(r.outcome).toBe('SENT'); expect(r.consentReceiptRef).toBe('fake-receipt'); }); it('CMP DENY → BLOCKED (CONSENT_DENIED), provider never called', async () => { const ports = makeFakePorts(); ports.cmp.setStatus('DENY'); const registry = new CapabilityProviderRegistry(); let called = false; registry.register({ name: 'spy', channelTypes: ['WEBHOOK'], capabilities: { canSend: true }, async send() { called = true; return { providerRef: 'x', outcome: 'SENT' }; } }); const r = await new CapabilityBroker(ports, registry).invoke(req()); expect(r.outcome).toBe('BLOCKED'); expect(r.errorCode).toBe('CONSENT_DENIED'); expect(called).toBe(false); }); it('CMP NOT_REQUIRED → SENT (no receipt required)', async () => { const r = await broker(makeFakePorts()).invoke(req()); // default fake is NOT_REQUIRED expect(r.outcome).toBe('SENT'); expect(r.consentReceiptRef).toBeUndefined(); }); it('purpose-aware: deny "marketing" but allow "support" for the same target', async () => { const ports = makeFakePorts(); ports.cmp.denyPurpose('marketing'); const blocked = await broker(ports).invoke(req({ purpose: 'marketing' })); const sent = await broker(ports).invoke(req({ purpose: 'support', idempotencyKey: 'k2' })); expect(blocked.outcome).toBe('BLOCKED'); expect(sent.outcome).toBe('SENT'); }); it('OPA is consulted before CMP (policy deny short-circuits consent)', async () => { const ports = makeFakePorts(); ports.opa.deny('not allowed'); ports.cmp.setStatus('ALLOW'); await expect(broker(ports).invoke(req())).rejects.toBeInstanceOf(PolicyDeniedError); }); });