/**
 * GreenPrAutoMerger — the background watcher that merges a green, mergeable,
 * non-held PR this agent authored, surviving session death (Phase 7 becomes
 * machinery, not memory). green-pr-automerge-enforcement R1–R11.
 *
 * Layer 1 (the guarantee). Ticks run ONLY on the multi-machine lease holder;
 * the first tick after acquiring the lease is OBSERVE-ONLY (warm-up: seed hold
 * memory, build the snapshot, read peer latches). At most one merge attempt per
 * tick, single-flight with a liveness guarantee that survives a server restart
 * (the MergeRunner owns the durable in-flight record + orphan reap). Everything
 * that decides "is this change good" already happened upstream; safe-merge
 * re-verifies at act time. Tier-0 supervision — the only discretionary call is
 * hold/candidate status and its failure direction is fail-toward-skip.
 *
 * All I/O is injected (deps) so every decision path is unit-testable without gh,
 * real processes, or a real lease.
 */
import { EventEmitter } from 'node:events';
import { type PrSummary, type Episode, type BreakerState, type HoldMemoryEntry } from './greenPrLogic.js';
import type { GuardLatchStore } from './GuardLatchStore.js';
/** The arguments a merge attempt is launched with. */
export interface MergeAttempt {
    pr: number;
    headRefOid: string;
    repo: string;
}
/** The classified result of a merge attempt (from the MergeRunner). */
export interface MergeRunResult {
    /**
     * safe-merge's classified result slug, e.g. merged / armed / refused:* /
     * error:* / already-merged / closed. `armed` (mergerunner-auto-arm-handoff)
     * is terminal-success-PENDING: GitHub native auto-merge is armed and now
     * owns the merge — confirmed later by the reconciliation tick, NOT here.
     */
    outcome: string;
    /**
     * True only when an INDEPENDENT gh pr view confirmed MERGED (B10). ALWAYS
     * false for `armed` BY DESIGN — the merge has not landed at arm time, so an
     * independent confirm at arm time is meaningless. `confirmedMerged:false` is
     * CORRECT and EXPECTED for `armed`, NOT a B10 violation (the B10 rewrite line
     * in act() is gated on outcome === 'merged' and MUST NOT touch `armed`).
     */
    confirmedMerged: boolean;
    /**
     * For an `armed` outcome: the head sha GitHub auto-merge was armed against
     * (the `--match-head-commit` we passed). Carried so the episode stamps
     * `armedHead` and reconciliation can compare it to the PR's final head.
     */
    armedHead?: string;
    /** Set when the watcher hard-killed the child at the deadline. */
    deadlineKilled?: boolean;
}
/**
 * The act-path engine (Step 4): probes the safe-merge contract, records a
 * durable in-flight attempt, spawns safe-merge in its own process group, hard-
 * kills at the deadline, and independently confirms the merge. Injected so the
 * orchestrator is testable; the real one lives in MergeRunner.ts.
 */
export interface MergeRunner {
    /** Probe `safe-merge --capabilities`; false → refuse to drive a legacy script. */
    probeContract(): Promise<{
        ok: boolean;
        version?: number;
    }>;
    /** Run one merge attempt to completion (or deadline-kill). */
    run(attempt: MergeAttempt): Promise<MergeRunResult>;
    /** Reap any surviving orphan from a prior crash; re-verify PR state. Boot/warm-up only. */
    reapOrphan(): Promise<{
        reaped: boolean;
        outcome?: string;
    }>;
}
export interface ProtectedPathsVerdict {
    touches: boolean;
    unverifiable: boolean;
}
export interface GreenPrAutoMergerDeps {
    holdsLease(): boolean;
    leaseEpoch(): number;
    /** The single oldest-first GraphQL list call. Throws → tick-failed canary. */
    listOpenPrs(): Promise<PrSummary[]>;
    /** Did this PR's diff touch protected paths? Enumerated to exhaustion. */
    protectedPaths(pr: PrSummary): Promise<ProtectedPathsVerdict>;
    /**
     * Re-fetch hold-relevant + state fields immediately before acting AND for the
     * armed-episode reconciliation read. null → vanished. Widened
     * (mergerunner-auto-arm-handoff Blocker 4): also returns `mergeCommitOid` (the
     * squashed base commit on a MERGED read — informational for the audit line,
     * NOT the head-pin comparison operand) and `autoMergeRequest` (present ⇔
     * GitHub auto-merge armed; `expectedHeadOid` is the PR's FINAL head GitHub
     * will merge — the head-pin comparison operand when present).
     */
    refetchPr(pr: number): Promise<(Pick<PrSummary, 'title' | 'labels' | 'isDraft' | 'headRefOid'> & {
        state: string;
        mergeCommitOid?: string | null;
        autoMergeRequest?: {
            enabledAt?: string | null;
            expectedHeadOid?: string | null;
        } | null;
    }) | null>;
    /**
     * Disarm GitHub native auto-merge on a PR (`gh pr merge <pr> --disable-auto`).
     * Returns a per-PR CONFIRMED-disabled boolean. The operator-authorized reach
     * of the documented kill switch (rollback / pause / pool-disarm / per-PR HOLD
     * on an armed episode — mergerunner-auto-arm-handoff Blocker 3). Idempotent.
     */
    disarmArmedEpisodes(pr: number): Promise<boolean>;
    /** gh api user login (async, never blocks boot). null → unresolved. */
    resolveGhLogin(): Promise<string | null>;
    /**
     * Apply a `[HOLD: …]` title prefix to a PR via gh (the conversational-hold
     * assist, R3). Returns whether it landed; the caller validated namespace + open.
     */
    applyHoldMarker(pr: number, reason: string): Promise<boolean>;
    /** Re-fetch a PR's head namespace + open state for the /hold route's guard. */
    holdEligible(pr: number): Promise<{
        ok: boolean;
        status?: number;
        detail?: string;
    }>;
    runner: MergeRunner;
    latches: Pick<GuardLatchStore, 'isMergeAllowed'>;
    /** Raise/refresh the ONE aggregated attention item (machine-stable id). */
    postAttentionAggregate(lines: string[]): Promise<void>;
    /** Append one transition audit line. */
    audit(event: Record<string, unknown>): void;
    loadState(): GreenPrState;
    saveState(state: GreenPrState): void;
    now(): number;
}
export interface GreenPrSnapshotEntry {
    pr: number;
    headRefName: string;
    headRefOid: string;
    /** 'mergeable' | 'protected-paths' — Layer 2 routes protected to the operator. */
    kind: 'mergeable' | 'protected-paths';
}
export interface GreenPrState {
    lastTickAt?: number;
    lastSuccessfulListAt?: number;
    /** Lease epoch observed at the last acting tick — warm-up detection. */
    lastActingEpoch?: number;
    consecutiveWarmupOnlyTenures: number;
    breaker: BreakerState;
    /** Per-PR ladder episodes, keyed by PR number. */
    episodes: Record<number, Episode>;
    /** Hold-release debounce memory, keyed by PR number. */
    holdMemory: Record<number, HoldMemoryEntry>;
    /** Last-tick snapshot for Layer 2 (the stop-gate belt). */
    snapshot: {
        at: number;
        entries: GreenPrSnapshotEntry[];
    };
    /** Cached gh identity resolution. */
    identity?: {
        login: string | null;
        resolvedAt: number;
    };
    /** Episodes that gave up (surfaced via attention), for the aggregate. */
    attentionLines?: string[];
}
export interface GreenPrAutoMergerConfig {
    enabled?: boolean;
    dryRun?: boolean;
    tickIntervalMs?: number;
    maxAttempts?: number;
    maxRearmEpisodes?: number;
    breakerThreshold?: number;
    deadlineKillBreakerThreshold?: number;
    busySkipBreakerThreshold?: number;
    breakerCooldownMin?: number;
    mergeTimeoutMs?: number;
    mergeKillGraceMs?: number;
    expectedGhLogin?: string;
    identityRecheckTicks?: number;
    holdReleaseTicks?: number;
    staleHoldDays?: number;
    /** The agent's branch namespace, e.g. "echo". */
    agentNamespace: string;
    repo: string;
    backoffBaseMs?: number;
    /**
     * mergerunner-auto-arm-handoff. `auto` (default) arms GitHub native
     * auto-merge and confirms on a later reconciliation tick; `admin` is the
     * legacy synchronous poll+merge. The rollback lever + the escape hatch for a
     * repo without native auto-merge.
     */
    mergeStrategy?: 'auto' | 'admin';
    /** ms an armed episode may stay OPEN before transitioning to armed-overdue (24h). */
    armedConfirmCeilingMs?: number;
    /** deduped re-raise cadence for an armed-overdue episode (24h). */
    armedOverdueReraiseMs?: number;
    /** the `--auto` spawn deadline (60s) — threaded into MergeRunnerConfig. */
    armTimeoutMs?: number;
    /** K-consecutive-unconfirmed-arms-on-same-head threshold before the Blocker-D line. */
    unconfirmedArmCeiling?: number;
}
export declare function freshState(): GreenPrState;
export declare class GreenPrAutoMerger extends EventEmitter {
    private readonly deps;
    private readonly cfg;
    private tickHandle;
    private inFlight;
    private tickCount;
    /** Resolved at boot: does the timeout invariant hold? */
    readonly invariantOk: boolean;
    readonly invariantReason?: string;
    constructor(deps: GreenPrAutoMergerDeps, cfg: GreenPrAutoMergerConfig);
    start(): void;
    stop(): void;
    snapshot(): GreenPrState;
    /**
     * The /hold route's handler (R3): validate the PR is open + in this agent's
     * namespace, then apply the marker via gh. Lease-INDEPENDENT — a hold
     * originates wherever the session lives. Honest non-2xx on failure (a hold
     * that silently failed to apply would be merged ~10 minutes later).
     */
    applyHold(pr: number, reason: string): Promise<{
        ok: boolean;
        status?: number;
        detail?: string;
        pr: number;
    }>;
    /** The cron / manual-trigger entry point. Lease-gated; single-flight; warm-up. */
    tick(opts?: {
        manual?: boolean;
    }): Promise<{
        acted: boolean;
        reason: string;
    }>;
    private gather;
    private act;
    private recordOutcome;
    /**
     * The top-of-acting-tick reconciliation: confirm the eventual GitHub merge of
     * each armed episode one tick after arming (the B10 truth, moved off the
     * synchronous arm). READ-ONLY — never calls `gh pr merge`; the disarm reach is
     * the SEPARATE in-route call (Blocker 3a). Resolves each `armedAt` episode:
     *   MERGED → reap (head-pin verified) · CLOSED → reap (closed-by-other)
     *   OPEN+armed → steady state (leave) · OPEN+disarmed/head-moved → clear + re-evaluate
     *   >ceiling → armed-overdue (keep reconciling + deduped re-raise) · read-fail → fail-open
     */
    private reconcileArmed;
    /**
     * Transition an armed episode to `armed-overdue` past armedConfirmCeilingMs and
     * re-raise a byte-stable deduped attention line on the armedOverdueReraiseMs
     * cadence (Blocker 5). Never clears armedAt — the loop stays open.
     */
    private maybeMarkOverdue;
    /**
     * Enumerate every `armedAt` episode and `gh pr merge <pr> --disable-auto` it.
     * Called IN-LINE from the rollback / pool-disarm route handlers (NOT the
     * latch-gated tick — the latch the operator just set is the very thing that
     * stops the tick reaching this code). LEASE-INDEPENDENT (like applyHold); the
     * operator's kill switch must reach the armed merges from wherever the route
     * is served, and `--disable-auto` is idempotent/safe from any holder.
     *
     * Honest failure (Blocker 3b): on a CONFIRMED disable, clear armedAt/armedHead
     * and add to the disarmed-confirmed set; on a FAILED disable, LEAVE armedAt set
     * (reconciliation keeps watching it) and add to a DISTINCT disarm-FAILED set.
     * The two outcomes are NEVER collapsed into one attention line.
     */
    disarmAllArmed(reason: string): Promise<{
        disarmed: number[];
        failed: number[];
    }>;
    private identityOk;
    private warmupReap;
    private refreshAggregate;
    private breakerCfg;
    private ladderCfg;
}
//# sourceMappingURL=GreenPrAutoMerger.d.ts.map