import type { OracleIsolationVerdict } from "./oracle-isolation.js";
import type { SkillSpec } from "./types.js";
/**
 * The terminal of {@link decideAutoPromote}.
 * - `"auto_promote"` — the ONLY exit that skips human review. Reachable only when all four prior guards
 *   (tripwire / irreversible / statistics / concurrence) clear AND all three gates are present+passing.
 * - `"candidate_only"` — surface the artifact as a candidate; the SOLE shortfall is the oracle isolation class
 *   being downgraded to `in_process_probe_only` (oracle-isolation.ts:34) with everything else passing.
 * - `"needs_human"` — fail-closed default: escalate to a human (any guard tripped, any other gate failing).
 */
export type AutoPromoteTerminal = "auto_promote" | "candidate_only" | "needs_human";
/**
 * Gate-2 handle (裁决净增③): the verdict the promoted artifact's OWN promotable write landed with, held in one place
 * so the decision reads one verdict and never a loose boolean. (It used to carry the artifact's lineage id as the scope
 * key of the retired per-skill narrowing; with that gone the lineage keys nothing the fold reads.)
 */
export interface PromotedRecord {
    /**
     * Gate-2 chokepoint result: the promotable write landed WITH a positive utility verdict (memory.ts:240),
     * or it was absent/ungated. Only `"present_positive"` is gate-passing; `"absent_or_ungated"` ⇒ needs_human.
     */
    gate2Verdict: "present_positive" | "absent_or_ungated";
}
/**
 * Inputs to {@link decideAutoPromote}. Every field is a PRESENCE/boolean/set the profile has already computed —
 * the core fold inspects no numeric threshold, no stats shape, no taxonomy. The caller (a profile-promotion job)
 * has already RE-asserted Gate-1 (`assertOracleIsolation`), run the exec-gate, read the appended Gate-2 verdict,
 * and armed the Gate-4 seam BEFORE building this struct; this function only READS those outputs.
 */
export interface AutoPromoteInputs {
    /** Gate-1 verdict (oracle-isolation.ts:37); the caller re-asserted it before calling this function. */
    oracle: OracleIsolationVerdict;
    /** Gate-2 lineage-bound single handle (净增③). */
    record: PromotedRecord;
    /** Gate-4: the irreversibility tighten is in place (or the artifact touches no irreversible tool). */
    gate4Armed: boolean;
    /**
     * §6 permanent human door (净增①): the artifact touches at least one irreversible TYPED tool. When true the
     * decision is ALWAYS `needs_human`, even if everything else would pass — because G4 is pure in-process
     * gate-PRESENCE (2-of-3 asymmetry) and `gate4Armed` alone ("the gate is wired") is not trustworthy for an
     * irreversible action.
     */
    touchesIrreversibleTool: boolean;
    /**
     * The profile's statistics RAN for this run (it produced a verdict). Core requires PRESENCE only — the
     * statistical THRESHOLDS (train/val/test split, canary, min-samples, cost-per-truly-correct) are profile.
     */
    statisticsPresent: boolean;
    /**
     * The KIND labels of the independent mechanical signals that concur. Core enforces `size >= 2` DISTINCT
     * kinds (净增②) — feeding the SAME kind twice still yields size 1 (it's a Set keyed by kind). WHICH kinds
     * count, and whether they are truly de-correlated, are profile concerns (§7 honest residual).
     */
    concurringSignals: ReadonlySet<string>;
    /** Tripwire result — ESCALATE-ONLY (§5 ③); when `escalate` it always wins and is never cleared. */
    tripwire: TripwireResult;
}
/**
 * The fail-closed PRESENCE AND-chain (design/79 §1 §2). Any missing/failing gate ⇒ NEVER `auto_promote`; it
 * degrades to `needs_human` (or, for the single oracle-isolation-class shortfall, `candidate_only`). This
 * function computes NO numeric threshold — only PRESENCE/boolean/set-size checks.
 *
 * **Guard order (load-bearing):**
 *  1. `tripwire.escalate` → needs_human (tripwire always wins, never clears — §5 ③)
 *  2. `touchesIrreversibleTool` → needs_human (§6 permanent human door — 净增①; even if all else passes)
 *  3. `!statisticsPresent` → needs_human (statistics must have RAN — core-required; thresholds are profile)
 *  4. `concurringSignals.size < 2` → needs_human (≥2 DISTINCT KIND mechanical signals concur — 净增②)
 *  5. the three-gate present-passing check; if not all pass, the SOLE-shortfall arm:
 *     `in_process_probe_only` oracle (and every other gate passing) → `candidate_only`; otherwise needs_human.
 *  6. all three gates present+passing (with guards clear) → `auto_promote` (the only auto exit).
 */
export declare function decideAutoPromote(i: AutoPromoteInputs): AutoPromoteTerminal;
/**
 * The result of {@link deriveTripwire}: an ESCALATE-ONLY advisory. `escalate` can only ever be set to `true`
 * by a scan; a clean scan contributes nothing and NEVER clears another scan's escalation. `reasons` carries
 * the matched tokens / drifted symbols for observability.
 */
export interface TripwireResult {
    escalate: boolean;
    reasons: string[];
}
/**
 * The CORE-FROZEN denylist floor (design/79 §3 净增④ / P0#4): a safety invariant the profile MAY NOT remove.
 * `deriveTripwire`'s `profileTokens` may only UNION (add) to this — never subtract — resolving the tension
 * between "the token set can rot (new exfil primitives appear)" and "constitution-minimal". These are RAW-BYTE
 * substrings scanned against the artifact's raw bytes INCLUDING its markdown bytes (extractSymbols skips
 * markdown, so the byte scan is what covers prose/fenced-code that names an egress/exec primitive).
 */
export declare const FROZEN_DENYLIST_FLOOR: ReadonlySet<string>;
/**
 * Build a tripwire verdict over a promoted artifact's text. **ESCALATE-ONLY** (design/79 §3,守 design/76
 * §5.1 r3): it can ONLY ever raise `escalate:true`; a clean artifact yields `{escalate:false, reasons:[]}` and
 * NEVER clears anything — it contains nothing itself: the real protection is the out-of-process boundary and the
 * permission gate at call time, not this advisory. Two mechanical scans, both escalate-only:
 *
 *  1. **symbol-mention drift** via {@link extractSymbols} (repo-map.ts). extractSymbols is a v1 heuristic:
 *     column-0 / source-only, it skips indented lines and is structurally INCAPABLE of being a containment
 *     proof (it can't enumerate everything the artifact can call). So it is used ONLY to ESCALATE — if a
 *     surfaced top-level symbol names a tool/capability that is NOT in `declared.allowedTools`, escalate.
 *  2. **raw-byte denylist** over the artifact's RAW bytes INCLUDING markdown bytes — {@link FROZEN_DENYLIST_FLOOR}
 *     UNIONed with `profileTokens` (add-only). A substring hit → escalate, with the matched token in `reasons`.
 *
 * **Fail-closed:** if EITHER scan THROWS on real content, return `{escalate:true, ...}` — a scan failure must
 * NOT silently become "no escalation". (This is disjoint from the composition-layer "tripwire DOWN ⇒ zero
 * contribution, never block" case, which is the caller's concern, not a throw inside the scan.)
 *
 * @param artifactText the artifact's raw text (markdown body included).
 * @param declared the artifact's declared tool list (the `SkillSpec` member the loader copies) — its `allowedTools`
 *   entries, read by the list reader as a rule for the tool each names (one it reads as nothing names none), are the
 *   symbol-drift baseline.
 * @param profileTokens optional ADD-ONLY extra denylist tokens (UNIONed onto the frozen floor; cannot remove).
 */
export declare function deriveTripwire(artifactText: string, declared: Pick<SkillSpec, "allowedTools">, profileTokens?: ReadonlySet<string>): TripwireResult;
