feat(p9): CMP consent gate in the Capability Broker
Task S2.1: the broker now checks consent (cmp.checkPurpose) after the OPA gate + obligations and before the provider — a DENY blocks the send (BLOCKED/CONSENT_DENIED, provider never called); ALLOW/NOT_REQUIRED proceed, ALLOW carrying the consent receipt. Contracts gain CapabilityRequest.purpose + CapabilityResult.consentReceiptRef. FakeCmp is now purpose-aware (denyPurpose/allowOnly). 5 new tests: allow+receipt, deny→blocked, not-required, purpose-aware, OPA-before-CMP. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,36 @@
|
||||
import type { IiosPlatformPorts } from '@insignia/iios-contracts';
|
||||
|
||||
type CmpPort = IiosPlatformPorts['cmp'];
|
||||
type CmpStatus = 'ALLOW' | 'DENY' | 'NOT_REQUIRED';
|
||||
|
||||
/** Deterministic CMP (consent) fake. Defaults to NOT_REQUIRED. */
|
||||
/** Deterministic CMP (consent) fake. Defaults to NOT_REQUIRED; supports purpose-aware decisions. */
|
||||
export class FakeCmp implements CmpPort {
|
||||
private status: 'ALLOW' | 'DENY' | 'NOT_REQUIRED' = 'NOT_REQUIRED';
|
||||
private status: CmpStatus = 'NOT_REQUIRED';
|
||||
private deniedPurposes = new Set<string>();
|
||||
private allowedPurpose?: string;
|
||||
|
||||
setStatus(s: 'ALLOW' | 'DENY' | 'NOT_REQUIRED'): this {
|
||||
setStatus(s: CmpStatus): this {
|
||||
this.status = s;
|
||||
return this;
|
||||
}
|
||||
|
||||
async checkPurpose(_input: unknown): Promise<{ receiptRef?: string; status: 'ALLOW' | 'DENY' | 'NOT_REQUIRED' }> {
|
||||
return {
|
||||
receiptRef: this.status === 'ALLOW' ? 'fake-receipt' : undefined,
|
||||
status: this.status,
|
||||
};
|
||||
/** Deny one specific purpose while other purposes follow the default status. */
|
||||
denyPurpose(purpose: string): this {
|
||||
this.deniedPurposes.add(purpose);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Allow only this purpose; every other purpose is denied. */
|
||||
allowOnly(purpose: string): this {
|
||||
this.allowedPurpose = purpose;
|
||||
return this;
|
||||
}
|
||||
|
||||
async checkPurpose(input: unknown): Promise<{ receiptRef?: string; status: CmpStatus }> {
|
||||
const purpose = (input as { purpose?: string } | undefined)?.purpose;
|
||||
let status = this.status;
|
||||
if (purpose && this.deniedPurposes.has(purpose)) status = 'DENY';
|
||||
if (this.allowedPurpose !== undefined) status = purpose === this.allowedPurpose ? 'ALLOW' : 'DENY';
|
||||
return { receiptRef: status === 'ALLOW' ? 'fake-receipt' : undefined, status };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user