/**
 * MentorOnboardingTick — the structural core of one mentor heartbeat tick
 * (FRAMEWORK-ONBOARDING-MENTOR-SPEC §3, §4, §6, §19.4).
 *
 * This is PURE orchestration logic with every side-effect injected as a
 * callback, so the whole tick is unit-testable without tmux, an LLM, or a
 * server. The structural guarantees the spec demands live HERE (in code), not
 * in a prompt:
 *
 *   - The leak canary runs FIRST every tick; a dead detector halts the tick and
 *     self-reports (§4.3) — the detector can't silently rot.
 *   - The budget check is a fail-closed PRE-tick gate: under budget pressure the
 *     ENTIRE tick is skipped before any spend or any contact (§6) — never a
 *     partial Stage A without Stage B.
 *   - The safe-window check gates on a durable mentee state, not a clock (§12 Q3).
 *   - Stage A is driven via the injected `spawnStageA` (the §19.4 runner passes
 *     the empty-tool-grant spawn); its transcript is run through the leakage
 *     detector and any leak is captured as an instar-integration-gap (§4.3).
 *   - Stage B forensics + capture run via injected callbacks; the ledger's
 *     captureRun logs the run to the funnel even when nothing is found (§19.2).
 */
import { buildStageAContext, detectStageALeak, runLeakCanary, leakToFinding, } from '../monitoring/MentorStageA.js';
/**
 * Run one mentor tick. Returns a structured result; the only side effects are
 * via the injected callbacks. Order is load-bearing (canary → budget →
 * safe-window → Stage A → leak → Stage B → capture).
 */
export async function runMentorTick(deps) {
    const tickId = deps.tickId ?? `mentor-${(deps.now ?? Date.now)()}`;
    // 1. Leak canary FIRST — a dead detector can't be trusted to police Stage A,
    //    so we halt and self-report rather than run blind (§4.3).
    const canary = deps.canaryCheck ?? runLeakCanary;
    if (!canary()) {
        deps.capture({
            framework: deps.framework,
            tickId,
            findings: [
                {
                    bucket: 'instar-integration-gap',
                    title: 'Mentor leak detector canary failed — detector may be inert',
                    dedupKey: `${deps.framework}::leak-canary-failed`,
                    signature: 'leak-canary-failed',
                    severity: 'high',
                    episodeKey: tickId,
                },
            ],
        });
        return { ran: false, reason: 'canary-failed' };
    }
    // 2. Budget gate — fail-closed BEFORE any spend or contact (§6).
    if (!deps.budgetOk)
        return { ran: false, reason: 'budget' };
    // 2b. LLM rate-limit gate — when the shared provider circuit is open, the
    // tick's Stage A + Stage B are LLM-backed and would just fail and RE-TRIP the
    // circuit (re-pausing all LLM work ~900s). Back off; we'll run when it closes.
    if (!deps.llmAvailable)
        return { ran: false, reason: 'llm-rate-limited' };
    // 3. Safe-window — only act at a durable mentee state transition (§12 Q3).
    if (!deps.safeWindowOpen)
        return { ran: false, reason: 'unsafe-window' };
    // 4. Stage A — drive the mentee from the conversation surface only.
    let transcript;
    try {
        transcript = await deps.spawnStageA(buildStageAContext(deps.surface));
    }
    catch (err) {
        // Surface the real cause instead of swallowing it. The Stage-A
        // compose-session can fail to spawn/produce/capture (e.g. session-cap
        // refusal or reaping under load), and an opaque 'stage-a-failed' with the
        // error discarded made this undebuggable from GET /mentor/status.
        const message = err instanceof Error ? err.message : String(err);
        deps.capture({
            framework: deps.framework,
            tickId,
            findings: [
                {
                    bucket: 'instar-integration-gap',
                    title: `Stage-A spawn failed during a mentor tick: ${message}`,
                    dedupKey: `${deps.framework}::stage-a-spawn-failed`,
                    signature: 'stage-a-spawn-failed',
                    severity: 'medium',
                    episodeKey: tickId,
                },
            ],
        });
        return { ran: false, reason: 'stage-a-failed', error: message };
    }
    // 5. Leakage detection on the Stage-A transcript (§4.3).
    const leak = detectStageALeak(transcript, deps.surface);
    const findings = [];
    if (leak.leaked)
        findings.push(leakToFinding(deps.framework, leak, tickId));
    // 6. Stage B forensics (separate step, full tool access in the runner).
    const stageBFindings = await deps.runStageBForensics();
    findings.push(...stageBFindings);
    // 7. Capture — writes findings + logs the run to the funnel (§19.2),
    //    even if findings is empty (inert-writer guard).
    const captured = deps.capture({ framework: deps.framework, tickId, findings });
    // 7b. Record the differential CYCLE (the keystone `mentor-mentee-differential`
    //     axis) alongside the per-finding ledger capture. The mentee's transcript is
    //     its output; the forensics findings ARE the differential. No-ops unless the
    //     host wired `recordCycle` (i.e. a `mentor.apprenticeshipInstanceId` is set).
    //     Guarded on a non-empty transcript — an empty/failed Stage A is not a cycle.
    if (deps.recordCycle && transcript.trim()) {
        deps.recordCycle({
            framework: deps.framework,
            task: `mentor-onboarding tick (${deps.framework})`,
            menteeOutput: transcript,
            differential: findings.map((f) => f.title),
        });
    }
    // 8. Deliver — ONLY in live mode, and ONLY via the host's persist-only path
    //    (§6). In dry-run we observe + capture but never contact the mentee.
    let delivered = false;
    if (deps.mode === 'live' && deps.deliverToMentee && transcript.trim()) {
        deps.deliverToMentee(deps.framework, transcript);
        delivered = true;
    }
    return {
        ran: true,
        reason: 'ran',
        mode: deps.mode,
        delivered,
        leakDetected: leak.leaked,
        observationsWritten: captured.observationsWritten,
        findingsCount: findings.length,
        // The tick SURFACES the Stage-A message it produced; it does NOT deliver it.
        // No mentee-delivery path is wired yet — `live` mode is not reachable until
        // the persist-only delivery (§6) is built + tested. Until then both dry-run
        // and live only observe + capture. Delivery is a live-promotion blocker.
        // <!-- tracked: topic-13435 -->
        stageAMessage: transcript,
    };
}
//# sourceMappingURL=MentorOnboardingTick.js.map