// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * WU-2728 (INIT-060 WU-2 + WU-3): software-delivery pack emission layer.
 *
 * The pack wires 15 event kinds at call sites across `@lumenflow/cli` and
 * `packages/@lumenflow/packs/software-delivery`. Every kind uses the
 * `software-delivery:` namespace prefix per ADR-013's pack-scoped naming
 * convention (and the WU-2724 hyphenated-slug correction).
 *
 * Envelope stamping reuses the WU-2638 primitives (`resolveKernelEventId`,
 * `resolveKernelEventTimestamp`) so event_id and timestamp semantics match
 * every other v2 conductor event on the wire. Each software-delivery event
 * adds two ordering fields beyond the SDK envelope:
 *
 *   - `channel_id` — the constant `SOFTWARE_DELIVERY_CHANNEL_ID`. ADR-013 §3
 *     defines per-channel ordering: cloud consumers reconcile cross-channel
 *     events by `(channel_id, seq)`. The pack itself is one channel.
 *   - `seq` — monotonic integer within the software-delivery channel.
 *     Increments on every `build*Event` call; resets only via the test-only
 *     `resetSoftwareDeliverySeqCounter` hook so seq assertions are
 *     deterministic in unit tests.
 *
 * Backpressure: all 15 kinds are ephemeral per ADR-013 §4 (observational
 * telemetry). Call sites wrap emission in the WU-2643 fail-silent sink
 * resolver so local-only workspaces (no `control_plane.org_id`) take the
 * zero-emission path.
 *
 * No `@lumenflow/core` imports — the pack boundary is structural (ADR-012).
 *
 * @module kernel-event-sync/software-delivery-emitters
 */
import { SDK_KERNEL_EVENT_SCHEMA_VERSION_V2 } from '@lumenflow/control-plane-sdk';
import { resolveKernelEventId, resolveKernelEventTimestamp, } from './emitters.js';
/**
 * The software-delivery pack is a single logical channel for ordering
 * purposes (ADR-013 §3). Phone or sidekick channels each have their own
 * `channel_id`.
 */
export const SOFTWARE_DELIVERY_CHANNEL_ID = 'software-delivery';
/**
 * The 15 software-delivery event kind literals. Namespaced under the
 * `software-delivery:` pack slug per ADR-013.
 *
 * Kind selection (WU-2728 AC):
 * - 3 `gate_*` kinds carrying `gate_name` payload for the gate lifecycle
 * - 2 gate-policy outcome kinds (`co_change_rule_fired`,
 *   `conditional_command_fired`)
 * - 4 lane lifecycle kinds (`lane_wip_breached`, `lane_locked`,
 *   `lane_unlocked`, `lane_suggestion_emitted`)
 * - 3 initiative lifecycle kinds (`initiative_created`,
 *   `initiative_closed`, `initiative_phase_advanced`)
 * - 3 plan lifecycle kinds (`plan_created`, `plan_linked`, `plan_promoted`)
 *
 * `replay_artifact_published` is deferred: ADR-013 §4 places replay under
 * the queued/replay backpressure class, not ephemeral telemetry, so its
 * emitter will ship with the outbox machinery in a later INIT-060 WU.
 */
export const SOFTWARE_DELIVERY_EVENT_KINDS = {
    GATE_STARTED: 'software-delivery:gate_started',
    GATE_PASSED: 'software-delivery:gate_passed',
    GATE_FAILED: 'software-delivery:gate_failed',
    CO_CHANGE_RULE_FIRED: 'software-delivery:co_change_rule_fired',
    CONDITIONAL_COMMAND_FIRED: 'software-delivery:conditional_command_fired',
    LANE_WIP_BREACHED: 'software-delivery:lane_wip_breached',
    LANE_LOCKED: 'software-delivery:lane_locked',
    LANE_UNLOCKED: 'software-delivery:lane_unlocked',
    LANE_SUGGESTION_EMITTED: 'software-delivery:lane_suggestion_emitted',
    INITIATIVE_CREATED: 'software-delivery:initiative_created',
    INITIATIVE_CLOSED: 'software-delivery:initiative_closed',
    INITIATIVE_PHASE_ADVANCED: 'software-delivery:initiative_phase_advanced',
    PLAN_CREATED: 'software-delivery:plan_created',
    PLAN_LINKED: 'software-delivery:plan_linked',
    PLAN_PROMOTED: 'software-delivery:plan_promoted',
};
export const SOFTWARE_DELIVERY_EVENT_KIND_VALUES = [
    SOFTWARE_DELIVERY_EVENT_KINDS.GATE_STARTED,
    SOFTWARE_DELIVERY_EVENT_KINDS.GATE_PASSED,
    SOFTWARE_DELIVERY_EVENT_KINDS.GATE_FAILED,
    SOFTWARE_DELIVERY_EVENT_KINDS.CO_CHANGE_RULE_FIRED,
    SOFTWARE_DELIVERY_EVENT_KINDS.CONDITIONAL_COMMAND_FIRED,
    SOFTWARE_DELIVERY_EVENT_KINDS.LANE_WIP_BREACHED,
    SOFTWARE_DELIVERY_EVENT_KINDS.LANE_LOCKED,
    SOFTWARE_DELIVERY_EVENT_KINDS.LANE_UNLOCKED,
    SOFTWARE_DELIVERY_EVENT_KINDS.LANE_SUGGESTION_EMITTED,
    SOFTWARE_DELIVERY_EVENT_KINDS.INITIATIVE_CREATED,
    SOFTWARE_DELIVERY_EVENT_KINDS.INITIATIVE_CLOSED,
    SOFTWARE_DELIVERY_EVENT_KINDS.INITIATIVE_PHASE_ADVANCED,
    SOFTWARE_DELIVERY_EVENT_KINDS.PLAN_CREATED,
    SOFTWARE_DELIVERY_EVENT_KINDS.PLAN_LINKED,
    SOFTWARE_DELIVERY_EVENT_KINDS.PLAN_PROMOTED,
];
/**
 * The shared gate_name set — kept in one place so emitters and docs agree
 * on the gate taxonomy (WU-2728 AC).
 */
export const SOFTWARE_DELIVERY_GATE_NAMES = [
    'format',
    'lint',
    'typecheck',
    'test',
    'co-change',
    'conditional_commands',
];
// ─── Per-channel seq counter (ADR-013 §3) ───
/**
 * Module-scoped counter map keyed by channel_id. Software-delivery events
 * all share `SOFTWARE_DELIVERY_CHANNEL_ID`; isolating the counter behind
 * a channel key keeps the shape correct if future pack surfaces (sidekick,
 * agent-runtime) add their own channels against the same primitive.
 */
const channelSeqCounters = new Map();
function nextSeq(channelId) {
    const current = channelSeqCounters.get(channelId) ?? 0;
    const next = current + 1;
    channelSeqCounters.set(channelId, next);
    return next;
}
/**
 * Test-only hook: reset all channel seq counters so assertions start from a
 * clean baseline. Production callers never touch this.
 */
export function resetSoftwareDeliverySeqCounter() {
    channelSeqCounters.clear();
}
/**
 * Stamp schema_version / timestamp / event_id (via WU-2638 primitives) plus
 * channel_id / seq (per ADR-013 §3) onto a software-delivery payload.
 *
 * Every builder funnels through this helper so the envelope contract is
 * enforced in one place. Changes to the envelope shape are a single-file
 * change, not a 15-builder sweep.
 */
function stampSoftwareDeliveryEnvelope(payload, options) {
    const timestamp = resolveKernelEventTimestamp(options);
    const event_id = resolveKernelEventId(payload.kind, payload, timestamp, options);
    const seq = nextSeq(SOFTWARE_DELIVERY_CHANNEL_ID);
    return {
        ...payload,
        schema_version: SDK_KERNEL_EVENT_SCHEMA_VERSION_V2,
        timestamp,
        event_id,
        channel_id: SOFTWARE_DELIVERY_CHANNEL_ID,
        seq,
    };
}
// ─── 15 builder functions ───
export function buildGateStartedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.GATE_STARTED,
        wu_id: input.wu_id,
        lane: input.lane,
        gate_name: input.gate_name,
    }, options);
}
export function buildGatePassedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.GATE_PASSED,
        wu_id: input.wu_id,
        lane: input.lane,
        gate_name: input.gate_name,
        duration_ms: input.duration_ms,
    }, options);
}
export function buildGateFailedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.GATE_FAILED,
        wu_id: input.wu_id,
        lane: input.lane,
        gate_name: input.gate_name,
        duration_ms: input.duration_ms,
        reason: input.reason,
    }, options);
}
export function buildCoChangeRuleFiredEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.CO_CHANGE_RULE_FIRED,
        wu_id: input.wu_id,
        rule_id: input.rule_id,
        triggered_paths: [...input.triggered_paths],
    }, options);
}
export function buildConditionalCommandFiredEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.CONDITIONAL_COMMAND_FIRED,
        wu_id: input.wu_id,
        command_id: input.command_id,
        trigger: input.trigger,
    }, options);
}
export function buildLaneWipBreachedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.LANE_WIP_BREACHED,
        lane: input.lane,
        wip_limit: input.wip_limit,
        current_wip: input.current_wip,
    }, options);
}
export function buildLaneLockedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.LANE_LOCKED,
        lane: input.lane,
        held_by: input.held_by,
    }, options);
}
export function buildLaneUnlockedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.LANE_UNLOCKED,
        lane: input.lane,
    }, options);
}
export function buildLaneSuggestionEmittedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.LANE_SUGGESTION_EMITTED,
        wu_id: input.wu_id,
        suggested_lane: input.suggested_lane,
        confidence: input.confidence,
    }, options);
}
export function buildInitiativeCreatedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.INITIATIVE_CREATED,
        initiative: input.initiative,
        wu_count: input.wu_count,
    }, options);
}
export function buildInitiativeClosedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.INITIATIVE_CLOSED,
        initiative: input.initiative,
        reason: input.reason,
    }, options);
}
export function buildInitiativePhaseAdvancedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.INITIATIVE_PHASE_ADVANCED,
        initiative: input.initiative,
        from_phase: input.from_phase,
        to_phase: input.to_phase,
    }, options);
}
export function buildPlanCreatedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.PLAN_CREATED,
        plan_id: input.plan_id,
        initiative: input.initiative,
    }, options);
}
export function buildPlanLinkedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.PLAN_LINKED,
        plan_id: input.plan_id,
        wu_id: input.wu_id,
    }, options);
}
export function buildPlanPromotedEvent(input, options) {
    return stampSoftwareDeliveryEnvelope({
        kind: SOFTWARE_DELIVERY_EVENT_KINDS.PLAN_PROMOTED,
        plan_id: input.plan_id,
        promoted_wu_id: input.promoted_wu_id,
    }, options);
}
// ─── Fail-silent emission guard (WU-2643 pattern) ───
/**
 * Read `control_plane.org_id` from a workspace-config snapshot. Mirrors
 * `readOrgIdFromConfig` in `lifecycle-emitters.ts` so call sites importing
 * only from this module don't cross the pack boundary.
 *
 * Returns `undefined` when org_id is missing, non-string, or whitespace —
 * callers use that as the "skip emission" signal for local-only workspaces
 * (WU-2728 AC: "Fail-silent when control_plane.org_id unset").
 */
export function readOrgIdForSoftwareDelivery(config) {
    if (!config || typeof config !== 'object') {
        return undefined;
    }
    const controlPlane = config.control_plane;
    if (!controlPlane || typeof controlPlane !== 'object') {
        return undefined;
    }
    const raw = controlPlane.org_id;
    if (typeof raw !== 'string') {
        return undefined;
    }
    const trimmed = raw.trim();
    return trimmed.length > 0 ? trimmed : undefined;
}
/**
 * True when the workspace-config snapshot has a non-empty
 * `control_plane.org_id`. Call sites guard emission with this to preserve
 * the ADR-013 §4 ephemeral-on-fail-silent contract: if cloud can't be
 * reached (or isn't configured), build nothing.
 */
export function shouldEmitSoftwareDeliveryEvents(config) {
    return readOrgIdForSoftwareDelivery(config) !== undefined;
}
//# sourceMappingURL=software-delivery-emitters.js.map