BID · Console
Baseline · Intelligence · Decision
src/agents/decision/delivery-distribution/index.ts 19,509 bytes · typescript
/**
 * 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 };
}