/**
* Delivery & Distribution agent — runtime entry point.
*
* Fully deterministic in the foundational rule set (per Std 6 cost-
* appropriate execution). For each visualization:
* - find applicable delivery rule(s) via the Rule Library
* - resolve precedence
* - look up the primary channel in the channel registry; fall back
* to secondary then fallback if missing — escalate if all are
* unregistered
* - enforce cadence + disclosure-policy (foundational POC records
* the limits without external state; SMEs encode real state)
* - dispatch via the channel adapter
* - record the outcome, ack-state, and any fallback used
*
* Spec §Std 12: never silently drop a dispatch. Every attempt is
* recorded in the output payload AND the channel-adapter audit log
* (drainAuditLog()) so the orchestrator can persist the complete
* trail.
*/
import {
type AgentResult,
type HITLEscalation,
LOW_CONFIDENCE_THRESHOLD,
makeConfidence,
type ReviewerRole,
} from '../../../standards.js';
import type {
ExecutionContext,
FailureObject,
Handoff,
JobRequest,
Lineage,
UnresolvedIssue,
} from '../../../types.js';
import { nowIso } from '../../../types.js';
import {
findRules,
resolvePrecedence,
type Rule,
} from '../../../decision/library.js';
import {
drainAuditLog,
getChannel,
listChannels,
type Channel,
type DispatchPayload,
} from '../../../decision/channel-registry.js';
import {
AGENT_NAME,
AGENT_VERSION,
deliveryDistributionContract,
} from './matrix.js';
import {
type DeliveryDistributionInput,
type DeliveryDistributionOutput,
type Dispatch,
type DeliveryGap,
deliveryDistributionInputSchema,
} from './schema.js';
import { TOOL_COUNT, TOOL_NAMES } from './llm.js';
export { deliveryDistributionContract } from './matrix.js';
export type { DeliveryDistributionOutput } from './schema.js';
export interface DeliveryDistributionSideContext {
readonly jobRequest: JobRequest;
readonly upstreamLineage: Lineage;
}
function trace(ctx: ExecutionContext, standard: number, step: string, detail: string): void {
ctx.trace.push({ agent: AGENT_NAME, standard, step, detail, at: nowIso() });
// eslint-disable-next-line no-console
console.log(` [${AGENT_NAME}][Std ${standard}] ${step} — ${detail}`);
}
function failure(
ctx: ExecutionContext,
category: FailureObject['category'],
reason: string,
context: Record<string, unknown>,
lineage: Lineage,
): FailureObject {
return {
agent: AGENT_NAME,
agentVersion: AGENT_VERSION,
category,
reason,
context,
lineage,
attempts: ctx.retries,
recursionDepth: ctx.recursionDepth,
occurredAt: nowIso(),
};
}
/* -------------------------------------------------------------- *
* Helpers
* -------------------------------------------------------------- */
function triggersFromVisualization(v: {
audienceTier: string;
flags: readonly string[];
outputSpecification: { chartType: string };
}): string[] {
const out = new Set<string>();
out.add(v.audienceTier.toLowerCase());
/* The carry-through severity flag from Visualization carries
* material / high_impact through to here. */
for (const f of v.flags) {
if (f.startsWith('carry-through-severity:')) {
out.add(f.split(':')[1]!);
}
}
/* Output-type triggers — these come from the rule's own trigger
* list and are matched via substring against the audience-tier
* etc.; we just pass coarse keywords. */
out.add('recommendation');
out.add('alert');
out.add(v.outputSpecification.chartType.toLowerCase());
return [...out];
}
function mapReviewer(s: string | undefined, fallback: ReviewerRole): ReviewerRole {
if (!s) return fallback;
const known: ReviewerRole[] = [
'analyst', 'domain-expert', 'data-steward', 'engineer',
'compliance-reviewer', 'operations-reviewer', 'authorizing-decision-maker',
];
return (known.includes(s as ReviewerRole) ? (s as ReviewerRole) : fallback);
}
/** Try primary → secondary → fallback channels named on the rule
* against the registry. Returns the first registered channel along
* with whether fallback was used. */
function pickChannel(rule: Rule): {
channel: Channel | null;
channelName: string;
fallbackUsed: boolean;
triedNames: string[];
} {
const action = rule.action;
const tried: string[] = [];
const candidates: { name: unknown; isFallback: boolean }[] = [
{ name: action.primary_channel, isFallback: false },
{ name: action.secondary_channel, isFallback: true },
{ name: action.fallback_channel, isFallback: true },
];
for (const c of candidates) {
if (typeof c.name !== 'string') continue;
tried.push(c.name);
const ch = getChannel(c.name);
if (ch) return { channel: ch, channelName: c.name, fallbackUsed: c.isFallback, triedNames: tried };
}
return { channel: null, channelName: '', fallbackUsed: false, triedNames: tried };
}
/* -------------------------------------------------------------- *
* runDeliveryDistribution — agent entry point
* -------------------------------------------------------------- */
export async function runDeliveryDistribution(
rawInput: unknown,
side: DeliveryDistributionSideContext,
ctx: ExecutionContext,
): Promise<AgentResult<DeliveryDistributionOutput>> {
/* Step 1 (Std 2): receive-visualizations. */
const parsed = deliveryDistributionInputSchema.safeParse(rawInput);
if (!parsed.success) {
return {
ok: false,
escalations: [],
failure: failure(
ctx,
'invalid-input',
'Delivery & Distribution input failed schema validation.',
{ issues: parsed.error.issues },
side.upstreamLineage,
),
};
}
const vizPayload: DeliveryDistributionInput = parsed.data;
trace(ctx, 2, deliveryDistributionContract.runbook[0]!.name,
`received ${vizPayload.visualizations.length} visualization(s); ${vizPayload.visualizationGapsEscalated.length} upstream gap(s); ${vizPayload.appliedRules.length} visualization rule(s) used`);
if (vizPayload.visualizations.length === 0) {
return {
ok: false,
escalations: [],
failure: failure(
ctx,
'delivery-rule-gap',
'No visualizations supplied — nothing to dispatch.',
{ input: vizPayload },
side.upstreamLineage,
),
};
}
trace(ctx, 5, 'channel-registry',
`registered channels: [${listChannels().join(', ')}] (channels added by deployers via registerChannel)`);
trace(ctx, 5, 'tool-inventory',
`Pillar 3 rule tools available: ${TOOL_COUNT} — [${TOOL_NAMES.join(', ')}] (not used in deterministic path)`);
const unresolved: UnresolvedIssue[] = [];
const escalations: HITLEscalation[] = [];
const dispatches: Dispatch[] = [];
const deliveryGapsEscalated: DeliveryGap[] = [];
const appliedRules = new Set<string>();
const channelsUsed = new Set<string>();
const audienceTierDefault = side.jobRequest.audience?.tier ?? 'decision_maker';
for (const v of vizPayload.visualizations) {
/* Skip anything not cleared at the disclosure step — Std 4 says
* "no delivery to unverified recipients", and disclosure-restricted
* outputs are by definition not cleared for this audience. */
if (v.disclosurePolicy.decision === 'restricted') {
deliveryGapsEscalated.push({
visualizationReference: v.visualizationId,
reason: 'recipient-verification-failed',
detail: `upstream disclosure policy blocked audience tier "${v.audienceTier}" for visualization "${v.visualizationId}"`,
triedTriggers: [],
});
unresolved.push({
category: 'disclosure-policy-concern',
detail: `dispatch suppressed: "${v.visualizationId}" not cleared for "${v.audienceTier}"`,
blocking: true,
});
escalations.push({
agent: AGENT_NAME,
reason: 'disclosure-policy-concern',
failureContext: `disclosure-restricted upstream — delivery agent suppresses dispatch and routes to compliance`,
lineage: side.upstreamLineage,
validation: {
status: 'review',
confidence: makeConfidence(0, 'disclosure-restricted upstream'),
checks: [{ name: 'audience-cleared', passed: false }],
},
recommendedReviewer: 'compliance-reviewer',
raisedAt: nowIso(),
});
continue;
}
const triggers = triggersFromVisualization(v);
const unique = new Map<string, Rule>();
for (const t of triggers) {
const m = await findRules({ type: 'delivery', agent: 'delivery', triggers: [t] });
for (const r of m) unique.set(r.rule_id, r);
}
const matched = resolvePrecedence([...unique.values()]);
if (matched.length === 0) {
deliveryGapsEscalated.push({
visualizationReference: v.visualizationId,
reason: 'no-rule-matched',
detail: `no delivery rule matched triggers [${triggers.join(', ')}] for visualization "${v.visualizationId}"`,
triedTriggers: triggers,
});
unresolved.push({
category: 'rule-gap',
detail: `no delivery rule for visualization "${v.visualizationId}" (audience=${v.audienceTier})`,
blocking: false,
});
escalations.push({
agent: AGENT_NAME,
reason: 'rule-gap',
failureContext: `no delivery rule for visualization "${v.visualizationId}"`,
lineage: side.upstreamLineage,
validation: {
status: 'review',
confidence: makeConfidence(0, 'no rule available'),
checks: [{ name: 'rule-available', passed: false }],
},
recommendedReviewer: 'operations-reviewer',
raisedAt: nowIso(),
});
continue;
}
const rule = matched[0]!;
appliedRules.add(rule.rule_id);
/* Step 4 (Std 4): verify-channel — try primary / secondary / fallback. */
const pick = pickChannel(rule);
if (!pick.channel) {
deliveryGapsEscalated.push({
visualizationReference: v.visualizationId,
reason: 'channel-unavailable',
detail: `none of the rule's channels are registered: tried [${pick.triedNames.join(', ')}]; registered: [${listChannels().join(', ')}]`,
triedTriggers: triggers,
});
unresolved.push({
category: 'delivery-failure',
detail: `no registered channel for rule ${rule.rule_id}: tried [${pick.triedNames.join(', ')}]`,
blocking: true,
});
escalations.push({
agent: AGENT_NAME,
reason: 'delivery-failure',
failureContext: `no registered channel for rule ${rule.rule_id} — deployer must registerChannel(...)`,
lineage: side.upstreamLineage,
validation: {
status: 'review',
confidence: makeConfidence(0, 'no channel'),
checks: [{ name: 'channel-registered', passed: false }],
},
recommendedReviewer: 'operations-reviewer',
raisedAt: nowIso(),
});
continue;
}
channelsUsed.add(pick.channelName);
/* Step 5 (Std 8): enforce-cadence-and-disclosure.
*
* The POC records the rule's declared cadence limits + the fact
* that this run did not enforce them against external state.
* Real cadence enforcement is the deployer's responsibility
* (typically backed by the repository's dispatch history). */
const cadence = (rule.action.cadence_limits ?? {}) as {
max_per_day?: number;
max_per_week?: number;
};
/* Step 6 (Std 12): dispatch. Channels never throw across the
* boundary — every outcome is structured. */
const ackRequired = rule.action.acknowledgment_required === true;
const ackWindowRaw = rule.action.acknowledgment_window_sec;
const ackWindowSec = typeof ackWindowRaw === 'number' ? ackWindowRaw : undefined;
const dispatchId = `dispatch-${v.visualizationId}`;
const severity = (v.flags.find(f => f.startsWith('carry-through-severity:'))?.split(':')[1]) ?? 'normal';
const recipient = `audience-tier:${v.audienceTier}`;
const payload: DispatchPayload = {
recipient,
audienceTier: v.audienceTier,
content: { kind: v.outputSpecification.chartType, data: v.outputSpecification },
contentReference: v.visualizationId,
severity,
acknowledgmentRequired: ackRequired,
acknowledgmentWindowSec: ackWindowSec,
};
const outcome = await pick.channel.dispatch(payload);
const flags: string[] = [];
if (pick.fallbackUsed) flags.push(`fallback-channel-used:${pick.channelName}`);
if (!outcome.ok) flags.push('dispatch-failed');
if (severity === 'material' || severity === 'high_impact') flags.push(`severity:${severity}`);
let confidence = v.confidence;
if (pick.fallbackUsed) confidence = Math.max(0, confidence - 0.05);
if (!outcome.ok) confidence = Math.max(0, confidence - 0.20);
dispatches.push({
dispatchId,
visualizationReference: v.visualizationId,
recommendationReference: v.recommendationReference,
deliveryRuleApplied: rule.rule_id,
channel: pick.channelName,
channelOutcome: {
ok: outcome.ok,
channelMessage: outcome.channelMessage,
error: outcome.error,
},
recipient,
audienceTier: v.audienceTier,
severity,
contentKind: v.outputSpecification.chartType,
contentReference: v.visualizationId,
dispatchedAt: outcome.dispatchedAt,
acknowledgmentRequired: outcome.acknowledgmentRequired,
acknowledgmentWindowSec: ackWindowSec,
acknowledgmentState: outcome.acknowledgmentState,
cadence: cadence.max_per_day !== undefined || cadence.max_per_week !== undefined
? {
maxPerDay: cadence.max_per_day,
maxPerWeek: cadence.max_per_week,
enforced: false,
enforcementNote: 'POC: cadence limits recorded but not enforced against external state',
}
: undefined,
fallbackUsed: pick.fallbackUsed,
reasoningLineage: [...v.reasoningLineage, rule.sourceFile],
confidence,
flags,
});
/* Dispatch failure → escalate but keep the audit record. */
if (!outcome.ok) {
escalations.push({
agent: AGENT_NAME,
reason: 'delivery-failure',
failureContext: `channel ${pick.channelName} returned !ok for dispatch ${dispatchId}: ${outcome.error?.message ?? 'unknown'}`,
lineage: side.upstreamLineage,
validation: {
status: 'review',
confidence: makeConfidence(confidence, 'dispatch failed'),
checks: [{ name: 'dispatch-succeeded', passed: false }],
},
recommendedReviewer: 'operations-reviewer',
raisedAt: nowIso(),
});
} else if (severity === 'material' || severity === 'high_impact') {
/* Pillar 3 spec §Std 9: material outputs default-escalate. */
const reviewer = mapReviewer(
typeof rule.action.escalation_on_no_ack === 'string'
? rule.action.escalation_on_no_ack
: undefined,
'authorizing-decision-maker',
);
unresolved.push({
category: 'material-impact-finding',
detail: `dispatch ${dispatchId} (${severity}) requires post-delivery authorization sign-off`,
blocking: false,
});
escalations.push({
agent: AGENT_NAME,
reason: 'material-impact-finding',
failureContext: `${severity} severity dispatched via ${pick.channelName}; awaiting ack window ${ackWindowSec ?? 'n/a'}s`,
lineage: side.upstreamLineage,
validation: {
status: 'flagged',
confidence: makeConfidence(confidence, 'material dispatched, ack pending'),
checks: [{ name: 'acknowledged-in-time', passed: false, detail: 'pending' }],
},
recommendedReviewer: reviewer,
raisedAt: nowIso(),
});
}
}
trace(ctx, 6, deliveryDistributionContract.runbook[5]!.name,
`dispatch: ${dispatches.length} dispatched (${dispatches.filter(d => d.channelOutcome.ok).length} ok, ${dispatches.filter(d => !d.channelOutcome.ok).length} failed); ${deliveryGapsEscalated.length} gap(s)`);
/* Std 10: drain the channel-adapter audit log so the orchestrator
* persists the complete trail to the repository. */
const channelAuditLog = [...drainAuditLog()];
trace(ctx, 10, deliveryDistributionContract.runbook[7]!.name,
`channel audit log: ${channelAuditLog.length} entry(ies) drained for write-back`);
const blocking = unresolved.filter(u => u.blocking).length;
const avgConf = dispatches.length === 0
? 0
: dispatches.reduce((s, d) => s + d.confidence, 0) / dispatches.length;
const confidence = makeConfidence(
Math.max(0, avgConf - 0.05 * Math.min(deliveryGapsEscalated.length, 5)),
`avg per-dispatch confidence ${avgConf.toFixed(2)} with ${deliveryGapsEscalated.length} gap(s)`,
);
trace(ctx, 7, deliveryDistributionContract.runbook[6]!.name,
`validation: ${dispatches.length} dispatch(es) avgConf=${avgConf.toFixed(2)}`);
const lineage: Lineage = {
sourceUrl: side.upstreamLineage.sourceUrl,
capturedAt: nowIso(),
effectiveAs: side.upstreamLineage.effectiveAs,
agentVersion: AGENT_VERSION,
upstream: Array.from(new Set([
...side.upstreamLineage.upstream,
...dispatches.flatMap(d => d.reasoningLineage),
])),
};
const validationStatus =
dispatches.length === 0 ? 'review'
: blocking > 0 ? 'review'
: confidence.value < LOW_CONFIDENCE_THRESHOLD ? 'flagged'
: 'passed';
const output: DeliveryDistributionOutput = {
dispatches,
deliveryGapsEscalated,
channelAuditLog,
appliedRules: [...appliedRules].sort(),
channelsUsed: [...channelsUsed].sort(),
notes: dispatches.length === vizPayload.visualizations.length
? [`all visualizations dispatched; default audience tier was "${audienceTierDefault}"`]
: [`${dispatches.length}/${vizPayload.visualizations.length} visualizations dispatched`],
};
const handoff: Handoff<DeliveryDistributionOutput> = {
fromAgent: AGENT_NAME,
fromAgentVersion: AGENT_VERSION,
toAgent: null,
payload: output,
metadata: {
analysisId: ctx.analysisId,
capabilities: deliveryDistributionContract.capabilities,
candidatesConsidered: vizPayload.visualizations.length,
dispatchedOk: dispatches.filter(d => d.channelOutcome.ok).length,
dispatchedFailed: dispatches.filter(d => !d.channelOutcome.ok).length,
gapsEscalated: deliveryGapsEscalated.length,
channelsUsed: [...channelsUsed].sort(),
llmInvocations: 0,
},
confidence,
validation: {
status: validationStatus,
checks: [
{ name: 'at-least-one-dispatch', passed: dispatches.length > 0, detail: `${dispatches.length}` },
{ name: 'every-dispatch-on-registered-channel', passed: dispatches.every(d => channelsUsed.has(d.channel)) },
{ name: 'no-silent-drops', passed: dispatches.length + deliveryGapsEscalated.length >= vizPayload.visualizations.length },
{ name: 'no-blocking-issues', passed: blocking === 0 },
],
},
unresolvedIssues: unresolved,
lineage,
timestamp: nowIso(),
};
trace(ctx, 11, deliveryDistributionContract.runbook[7]!.name,
`handoff → ${handoff.toAgent ?? '(end of pipeline — framework exit)'} (validation=${validationStatus} confidence=${confidence.tier})`);
return { ok: true, handoff, escalations };
}