/**
* Decision — channel-adapter registry.
*
* Per Pillar 3 spec §SME Rule Library, delivery rules reference
* channel adapters by name rather than hardcoding email / Slack /
* dashboards in framework code. Organizations register their specific
* channels at bootstrap; the framework only ships the universal
* `audit-log` channel, a no-side-effect adapter that records a
* "would-deliver" event to the audit trail. Real channels are added
* by deployers without touching the framework — same additive pattern
* as src/tools/retrieval/connectors/.
*
* Std 4 (Decision): no delivery outside approved channels — if a
* delivery rule names a channel that isn't registered, the Delivery
* & Distribution agent fails the dispatch and escalates per Std 9.
*
* Std 12: every dispatch result is structured; channels never throw
* raw exceptions across the boundary.
*/
export interface DispatchPayload {
/** Free-text recipient identifier (audience tier descriptor, not a
* PII address). The channel adapter is responsible for translating
* this into the channel-specific addressing, if any. */
readonly recipient: string;
readonly audienceTier: string;
/** Inline content the channel should deliver. */
readonly content: { readonly kind: string; readonly data: unknown };
/** Reference back to the upstream visualization / recommendation
* the dispatch is for, so the audit trail can reconstruct lineage. */
readonly contentReference: string;
/** Caller-supplied severity (low | normal | material | high_impact). */
readonly severity?: string;
/** Whether the channel must request an acknowledgment back. */
readonly acknowledgmentRequired?: boolean;
/** Optional ack window in seconds (rule-driven; the channel decides
* whether to enforce). */
readonly acknowledgmentWindowSec?: number;
}
export interface DispatchOutcome {
readonly ok: boolean;
readonly channel: string;
readonly dispatchedAt: string;
readonly channelMessage?: string;
readonly acknowledgmentRequired: boolean;
readonly acknowledgmentState: 'pending' | 'acknowledged' | 'not-applicable' | 'failed';
readonly error?: { readonly category: string; readonly message: string };
}
export interface Channel {
readonly name: string;
readonly description: string;
dispatch(payload: DispatchPayload): Promise<DispatchOutcome>;
}
const registry = new Map<string, Channel>();
export function registerChannel(channel: Channel): void {
registry.set(channel.name, channel);
}
export function unregisterChannel(name: string): void {
registry.delete(name);
}
export function listChannels(): readonly string[] {
return [...registry.keys()].sort();
}
export function getChannel(name: string): Channel | null {
return registry.get(name) ?? null;
}
/* -------------------------------------------------------------- *
* Built-in: audit-log channel
*
* The framework's default no-side-effect channel. Logs the dispatch
* to the audit trail (via console + an in-memory buffer the orchestrator
* persists). Real channels (email, secure-message, dashboard, etc.)
* are registered by deployers at bootstrap.
* -------------------------------------------------------------- */
interface AuditLogEntry {
readonly at: string;
readonly recipient: string;
readonly audienceTier: string;
readonly contentReference: string;
readonly severity: string;
readonly contentKind: string;
}
const auditBuffer: AuditLogEntry[] = [];
export function drainAuditLog(): readonly AuditLogEntry[] {
const snapshot = [...auditBuffer];
auditBuffer.length = 0;
return snapshot;
}
export function peekAuditLog(): readonly AuditLogEntry[] {
return [...auditBuffer];
}
class AuditLogChannel implements Channel {
readonly name = 'audit-log';
readonly description = 'Framework default: records a would-deliver event to the audit trail. No external side effects.';
async dispatch(payload: DispatchPayload): Promise<DispatchOutcome> {
const at = new Date().toISOString();
auditBuffer.push({
at,
recipient: payload.recipient,
audienceTier: payload.audienceTier,
contentReference: payload.contentReference,
severity: payload.severity ?? 'normal',
contentKind: payload.content.kind,
});
return {
ok: true,
channel: this.name,
dispatchedAt: at,
channelMessage: `audit-log recorded dispatch for ${payload.audienceTier} (ref=${payload.contentReference})`,
acknowledgmentRequired: payload.acknowledgmentRequired ?? false,
acknowledgmentState: payload.acknowledgmentRequired ? 'pending' : 'not-applicable',
};
}
}
/* Self-register the default at module load — same pattern as
* src/tools/retrieval/connectors/ register their connectors. */
registerChannel(new AuditLogChannel());