/**
 * EnrollmentWizard — mobile-first login orchestration (P2.1 of the Subscription
 * & Auth Standard). Sits on top of PendingLoginStore and turns a raw framework
 * login into a phone-friendly, expiry-resilient flow.
 *
 * The job (from the spec + the pi live-test specimen):
 *   - START a login: drive the framework's login flow, capture the PUBLIC
 *     device-code / auth URL, and store it with its TTL visible. (Codex →
 *     device-code; Claude → URL+paste-back-code.)
 *   - On EXPIRY: auto-reissue a fresh code/URL WITHOUT the operator asking — the
 *     exact gap the pi live-test exposed (the first code expired before Justin
 *     got to it and re-issuing took a manual round-trip).
 *   - COMPLETE when the operator approves at the provider + the account enrols.
 *
 * The interactive part — actually driving the framework's login CLI to obtain a
 * code/URL — is INJECTED (`driveLogin`), so this orchestrator is pure +
 * hermetically testable (no spawning, no network, no real OAuth). In production
 * the driver runs the framework's device-code/login flow and scrapes the code +
 * URL + TTL; here tests pass a stub. NEVER handles a credential — only the
 * public code/URL the operator types into the provider's own page.
 */
import { PendingLoginStore, type PendingLogin, type LoginFlowKind, type LoginProvider } from './PendingLoginStore.js';
import { type EnsureInteractiveReadyResult } from './ensureInteractiveReady.js';
import type { IdentityOracle } from './CredentialLocationLedger.js';
/** The public artifact a framework login yields (no secret). */
export interface LoginArtifact {
    verificationUrl: string;
    userCode?: string;
    ttlMs?: number;
}
/** Injected: drive the framework's login flow, return its public code/URL. */
export type LoginDriver = (req: {
    provider: LoginProvider;
    framework: PendingLogin['framework'];
    kind: LoginFlowKind;
    /** The new account's CLAUDE_CONFIG_DIR — isolates this login to its own slot
     *  so enrolling a 2nd account never clobbers the 1st. */
    configHome?: string;
    /** WS5.2 R6b — per-call scrape-timeout budget (ms). The follow-me/remote path
     *  passes a larger value (cloud→provider latency + the two-code Claude window);
     *  omitted ⇒ the driver's own default (the local-LAN budget). */
    scrapeTimeoutMs?: number;
}) => Promise<LoginArtifact>;
/**
 * WS5.2 R6b — the honest-failure surface for a DRIVE failure during `start()`.
 * Thrown (NOT swallowed) when `driveLogin` fails, so a remote/cloud enrollment
 * surfaces an operator-facing "couldn't start the login on <nickname> — retry?"
 * instead of either an opaque 500 OR a silently-stuck pending-login. The key
 * invariant it guarantees: a drive failure NEVER leaves a pending-login issued
 * with no artifact (the store is only written AFTER the drive succeeds), so the
 * caller can render this as a retry-able failure with no dangling state.
 */
export declare class EnrollmentDriveError extends Error {
    readonly code: "enrollment-drive-failed";
    /** A short, operator-facing message (machine nickname interpolated by the caller). */
    readonly operatorMessage: string;
    /** The underlying driver error, for logs/audit (never shown to the operator raw). */
    readonly cause?: unknown;
    constructor(opts: {
        operatorMessage: string;
        cause?: unknown;
    });
}
export interface EnrollmentWizardConfig {
    store: PendingLoginStore;
    driveLogin: LoginDriver;
    now?: () => number;
    logger?: {
        log: (m: string) => void;
        warn: (m: string) => void;
    };
    /**
     * Onboarding-readiness seeder run when a claude-code enrollment completes
     * (2026-06-09 incident: `claude auth login` is headless-only — it stores
     * tokens but never sets the interactive first-launch flags, so the new
     * home wedges the first interactive session pinned/swapped onto it).
     * Injectable for hermetic tests; production uses the real util.
     */
    ensureReady?: (configHome: string) => EnsureInteractiveReadyResult;
    /**
     * WS5.2 §5.3/S7 — identity oracle used by `completeFollowMe` to read the freshly-minted
     * login's account email from its config-home slot, for validation against operator
     * expectation. Absent ⇒ the follow-me gate fails CLOSED (the account is HELD, never
     * auto-selected). The plain `complete()` path never touches it.
     */
    oracle?: IdentityOracle;
    /**
     * WS5.2 §5.3/S7 — emit a HIGH attention item when a follow-me completion is HELD
     * (surprise/mismatched/unverifiable email). Best-effort; absence does not change the
     * fail-closed verdict (the account is still NOT selected), only whether the operator
     * is paged.
     */
    emitAttention?: (item: {
        id: string;
        title: string;
        body: string;
        priority: 'high';
        source: 'agent';
    }) => void;
}
export interface StartEnrollmentInput {
    id: string;
    label: string;
    provider: LoginProvider;
    framework: PendingLogin['framework'];
    /** device-code (Codex) or url-code-paste (Claude). Defaults per provider. */
    kind?: LoginFlowKind;
    /** The new account's CLAUDE_CONFIG_DIR — isolates the login to its own slot. */
    configHome?: string;
    /** WS5.2 §5.3/S7 — the operator-expected account email (follow-me path only). Carried
     *  onto the pending login so `completeFollowMe` can validate the minted account. */
    expectedEmail?: string;
    /**
     * WS5.2 R6b — this is a remote/cloud (follow-me) enrollment, not a local-LAN one.
     * When set: (a) the per-provider flow-kind selection prefers the device-code
     *   single-code flow where the provider supports it (Phase-C default — sidesteps
     *   the two-code Claude problem entirely), and (b) `remoteScrapeTimeoutMs` (if
     *   provided) is threaded to the driver as a larger scrape budget. Defaulted-off,
     *   so normal local enrollment is byte-for-byte unchanged.
     */
    remote?: boolean;
    /** WS5.2 R6b — larger scrape-timeout budget (ms) used only when `remote` is true.
     *  Omitted ⇒ the driver's own default (the local-LAN budget). */
    remoteScrapeTimeoutMs?: number;
}
export declare class EnrollmentWizard {
    private readonly store;
    private readonly driveLogin;
    private readonly logger;
    private readonly ensureReady;
    private readonly oracle?;
    private readonly emitAttention?;
    constructor(cfg: EnrollmentWizardConfig);
    /** Default flow kind per provider: Codex/OpenAI = device-code (its endorsed
     *  flow); everyone else = url-code-paste (the phone-friendly Claude path). */
    static defaultKind(provider: LoginProvider): LoginFlowKind;
    /**
     * WS5.2 R6b — flow kind for a REMOTE/cloud (follow-me) enrollment. Prefers the
     * device-code single-code flow wherever the provider supports it (the Phase-C
     * default per R6b), because a single code sidesteps the two-code Claude window
     * entirely on a headless VM. Providers that have a device-code endpoint
     * (OpenAI/Codex) get `device-code`; a provider with no single-code flow
     * (Anthropic/Claude → url-code-paste) keeps its two-code flow, and the caller
     * MUST give it the larger scrape budget so the full two-code interaction fits
     * inside one poll window (the URL appears late on cloud→provider latency). This
     * is a SUPERSET of defaultKind — anything device-code-capable locally is also
     * device-code-capable remotely. */
    static remoteKind(provider: LoginProvider): LoginFlowKind;
    /**
     * Operator-facing heads-up about a flow's quirks, surfaced on the pending login.
     * The url-code-paste (Claude) flow on a brand-new account slot frequently issues
     * TWO codes in sequence: Anthropic first emails an email-VERIFICATION code, then
     * (after that's accepted) the page shows the sign-in code to paste back. Enrolling
     * is always a new slot, so the operator should expect — and not be confused by —
     * the two-step sequence (this confusion was flagged in live testing, topic 20905).
     * device-code (Codex) is a single code, so no notice. Returns undefined when there
     * is nothing to warn about.
     */
    static flowNotice(kind: LoginFlowKind): string | undefined;
    /**
     * Start an enrollment: drive the login, capture the public code/URL, store it
     * as a pending login (TTL visible). Returns the stored PendingLogin — the
     * surface the operator's phone shows.
     */
    start(input: StartEnrollmentInput): Promise<PendingLogin>;
    /**
     * Auto-reissue every EXPIRED pending login without the operator asking — the
     * pi-live-test gap. For each expired login, re-drive the framework flow and
     * refresh the stored code/URL + TTL. Returns the reissued logins. Driver
     * failures are skipped (logged, left expired) so one bad re-drive doesn't
     * abort the sweep.
     */
    reissueExpired(): Promise<PendingLogin[]>;
    /**
     * Mark a login completed once the operator approved + the account enrolled.
     * For a claude-code enrollment with a per-account config home, also seed the
     * interactive onboarding flags — the login itself was headless, so without
     * this the freshly-enrolled home wedges the first interactive session that
     * pins/swaps onto it (2026-06-09 incident). Fail-safe: a seeding failure is
     * logged, never blocks completion (the launch paths re-ensure defensively).
     */
    complete(id: string): PendingLogin | null;
    /**
     * WS5.2 §5.3 step 3 / S7 — the FOLLOW-ME completion path. Completes the pending login
     * (reusing the sync `complete()` so claude-code interactive-readiness still runs), then
     * validates the freshly-minted account's email against the operator's expectation BEFORE
     * the account is allowed to become selectable. Only a verified match returns 'validated'
     * (the caller — the route — then adds it to the SubscriptionPool); everything else FAILS
     * CLOSED to 'held' (a HIGH attention item is emitted, the account is NOT added to the pool).
     *
     * Fail-closed by construction: a missing oracle, an unreadable/missing config-home, an
     * unavailable identity probe, or a missing operator-expected email all resolve to 'held'
     * — an account is NEVER auto-selected unless its email provably matches what the operator
     * approved (this is the FOLLOW-ME path, so the email gate ALWAYS runs).
     */
    completeFollowMe(id: string, targetMachineNickname: string): Promise<{
        outcome: 'validated';
        login: PendingLogin;
        email: string;
    } | {
        outcome: 'held';
        login: PendingLogin;
        reason: string;
    } | {
        outcome: 'not-found';
    }>;
    /** The phone surface: still-valid logins awaiting approval. */
    pending(): PendingLogin[];
}
//# sourceMappingURL=EnrollmentWizard.d.ts.map