import type { DoctorCheck } from "./doctor.js";
/**
 * Whether the REUSABLE workflow's own `permissions:` block grants `actions`.
 *
 * This is the part no caller can supply. A called workflow's `permissions:`
 * block is a ceiling that zeroes everything unlisted, so a caller's grant is
 * discarded at the reusable-workflow boundary no matter where it is placed.
 * While this is false, `serialize_platform_legs` cannot order anything in ANY
 * repository, and reporting a fully configured caller as `ok` would certify a
 * configuration measured to fail. It is now TRUE: the reusable workflow
 * declares `actions: read` for itself, so a fully configured caller can
 * genuinely order its legs and `ok` is an honest verdict again.
 *
 * Measured 2026-08-17 against a caller granting `actions: read` at BOTH job and
 * workflow level: the job's own token grant showed `Contents: read` and
 * `Metadata: read` with no Actions scope, the jobs API answered 403, and the
 * two platform legs started two seconds apart.
 *
 * A constant rather than a read of the workflow file, because consumers
 * reference the reusable workflow at `@main` and do not have a copy of it to
 * read. `serialize-legs-callee-grant.test.ts` pins this to the actual
 * `permissions:` block in `.github/workflows/maestro-native-e2e.yml`, so it
 * cannot drift from what Lisa ships — flipping it and forgetting the workflow,
 * or the reverse, fails that test.
 *
 * Residual limit worth naming: a consumer's `lisa doctor` comes from its
 * installed package while the workflow it runs comes from `@main`, so a stale
 * install can disagree with what actually runs. That is a property of `@main`
 * refs, not of this check.
 */
export declare const CALLEE_GRANTS_ACTIONS_READ = true;
/** Which of the three required parts a caller declares. */
export interface SerializeContract {
    /** `serialize_platform_legs: true` in the `with:` block. */
    readonly optedIn: boolean;
    /** `LEG_ORDER_TOKEN` forwarded in the `secrets:` block. */
    readonly forwardsToken: boolean;
    /** `actions: read` on the WORKFLOW's own top-level `permissions:` block. */
    readonly grantsActionsRead: boolean;
}
/**
 * Which required parts are missing from a declared opt-in.
 *
 * Order matters for the report: the token is named first because its absence is
 * the one the ordering job can detect and annotate at runtime, while a missing
 * or mis-scoped `actions: read` surfaces only as an HTTP 403 inside that same
 * degrade path.
 * @param contract - What the caller declares
 * @returns Human-readable names of the missing parts
 */
export declare function missingParts(contract: SerializeContract): readonly string[];
/**
 * Whether a caller has opted in without completing the contract.
 * @param contract - What the caller declares
 * @returns True when the opt-in is declared but cannot take effect
 */
export declare function isIncompleteOptIn(contract: SerializeContract): boolean;
/**
 * Report an incomplete `serialize_platform_legs` opt-in.
 *
 * The opt-in is a THREE-part contract — the input, a forwarded
 * `LEG_ORDER_TOKEN`, and `actions: read` on the calling workflow — and getting
 * any part wrong **fails open with a green job**. Measured by a fleet session on
 * 2026-08-17: `actions: read` declared at job level instead of workflow level
 * produced HTTP 403 → warning annotation → job concluded `success` → both
 * platform legs still started within the same second.
 *
 * The runtime behaviour is deliberate and is NOT what this check changes. The
 * ordering job releases Android rather than failing, because failing turns a
 * night when both legs were green into a blocked merge gate, and releasing is
 * strictly the smaller harm. That reasoning is sound. What it leaves is the gap
 * this check closes: an operator who has opted in has no way to learn the opt-in
 * is inert except by reading a warning annotation on a green nightly run, which
 * is to say no way at all.
 *
 * So this reports at CONFIG time what the workflow can only annotate at 2am.
 *
 * Deliberately silent for a caller that has not opted in. Serialization is off
 * by default on purpose — a project whose legs authenticate as separate personas
 * gets its whole suite in the length of its slowest leg and should keep that.
 * Reporting every non-adopter would be advocacy, not a health check, and a check
 * that fires on correct configuration is one nobody reads.
 * @param targetPath - Project path to inspect
 * @returns Doctor check result
 */
export declare function checkSerializeLegsContract(targetPath: string): Promise<DoctorCheck>;
/**
 * Read which parts of the contract a caller workflow declares.
 *
 * Text matching rather than YAML parsing, deliberately. The check must work on
 * a workflow it cannot fully parse — a caller with a syntax error elsewhere, or
 * one using an anchor form the parser rejects, still deserves this answer. The
 * three markers are distinctive enough that a false positive would require
 * someone to write the exact strings in a comment.
 * @param source - Raw workflow YAML
 * @returns Declared parts of the contract
 */
export declare function readContract(source: string): SerializeContract;
//# sourceMappingURL=doctor-serialize-legs-contract.d.ts.map