/**
 * The exact, closed set of refusal codes this module can ever return —
 * mirrors the gateway's `WriterStateRefusalCode` exactly (same union, same
 * names) so a caller's error mapping needs no translation table.
 */
export type WriterStateRefusalCode = "VALIDATION_FAILED" | "VAULT_WRITER_NOT_ADMITTED" | "HANDOFF_KEY_REVOKED" | "RECIPIENT_SET_MISMATCH" | "EPOCH_ROTATION_WOULD_LEAVE_VAULT_UNCOVERED";
export type WriterStateVerdict = {
    ok: true;
} | {
    ok: false;
    code: WriterStateRefusalCode;
    detail: string;
};
export declare const ORG_ROLE_RANK: Readonly<Record<string, number>>;
export declare const WRITER_ELIGIBLE_ROLE_RANK: number;
/** Does `role` meet the writer-eligibility threshold (developer or above)? An unknown/absent role never does. */
export declare function meetsWriterEligibleRole(role: string | null | undefined): boolean;
/**
 * vault-multi-writer rev 47 (task 5.5) — predicts the role a handoff mint
 * will actually confer, mirroring the gateway's OWN attenuation exactly
 * (`services/vault/claims.ts mintHandoff`: `requestedRole &&
 * roleRank(requestedRole) < roleRank(minterRole) ? requestedRole :
 * minterRole`). The mint's `writer_admission_grant.minted_role` must equal
 * this prediction byte-for-byte or the gateway refuses VALIDATION_FAILED —
 * after the checkpoint push has already been paid for — so getting the
 * comparison direction right matters. An unrecognized `requestedRole`
 * (never a legal `OrgRole` server-side either) is treated as "no request":
 * a garbage role string fails the gateway's OWN mint validation before its
 * attenuation logic — and therefore before this grant-mismatch check —
 * ever runs, so predicting `minterRole` for that case is never observed as
 * wrong, only moot.
 */
export declare function predictMintedRole(requestedRole: string | undefined, minterRole: string): string;
export interface WriterKeyEntry {
    writer_key_id: string;
    signing_pubkey: string;
}
/** The full chain-derived writer state at some generation — protocol §4.15. */
export interface WriterChainState {
    version: string;
    writers: readonly WriterKeyEntry[];
    sha256: string;
    /** Permanently burned writer_key_ids — protocol §4.15, never re-addable, on this vault, ever. */
    burnedWriterKeyIds: ReadonlySet<string>;
    /** Permanently consumed handoff_ids — protocol §4.17, a writer_admission_grant is single-use. */
    consumedHandoffIds: ReadonlySet<string>;
    /**
     * Writers a `writer_set_update` REMOVED, with their keys — never part of
     * the hashed writer set (`writers`/`sha256` are the chain state), only
     * local bookkeeping so a head those writers signed WHILE active can still
     * be re-read and verified later (a decrypt catch-up, a restore, a
     * checkpoint walk). Admission of a NEW head consults `writers` alone.
     */
    retiredWriters?: readonly WriterKeyEntry[];
}
/**
 * `vk_` fingerprint of a base64url-encoded Ed25519 public key, or `null` for
 * anything that doesn't decode as a canonical 32-byte base64url scalar —
 * mirrors the gateway's `writerKeyIdOf` (never throws on a malformed key).
 */
export declare function writerKeyIdOf(signingPubkeyB64u: string): string | null;
/** protocol §4.15: writer_set_sha256 = SHA-256(JCS({format, repo_id, version, writers})). */
export declare function writerSetSha256(repoId: string, version: string, writers: readonly WriterKeyEntry[]): string;
/**
 * protocol §4.15: the version-0 singleton, derived from `creator_signing_pubkey`
 * (see the module doc for why this diverges from reading a `writer_key_id`
 * field directly).
 */
export declare function initialWriterState(repoId: string, genesis: {
    creator_signing_pubkey: string;
}): WriterChainState;
/**
 * protocol §4.16: apply an ALREADY-ADMITTED `add_writer_key` transition.
 * Caller MUST have validated it first (`validateAddWriterKeyPayload`) — this
 * function performs no checks, it only advances state.
 */
/**
 * vault-multi-writer (task 5.6) — builds a well-formed `handoff`-door
 * `add_writer_key` payload (schema `r402s.add-writer-key/v1`) from a
 * predecessor writer-set pin and the claimant's own key — the pure
 * assembly step `Vault.submitWriterActivationHead` delegates to,
 * kept separate so it can be unit-tested directly against
 * {@link validateAddWriterKeyPayload} without a full vault/keystore/git
 * harness. `predecessorPin` needs only `{version, sha256, writers}` — the
 * caller's persisted `writer_set_pin` shape already matches, no
 * `WriterChainState` (with its Sets) is required to build an OUTGOING
 * payload; only to VALIDATE an incoming one.
 */
export declare function buildAddWriterKeyActivationPayload(repoId: string, predecessorPin: {
    version: string;
    sha256: string;
    writers: readonly WriterKeyEntry[];
}, addedWriter: WriterKeyEntry & {
    principal_id: string;
}, handoffId: string, grant: Record<string, unknown>, acceptance: Record<string, unknown>): AddWriterKeyPayload;
/**
 * vault-multi-writer (task 5.7) — builds a well-formed `writer`-door
 * `add_writer_key` payload from a predecessor writer-set pin and the
 * candidate's own published signing key. Unlike {@link
 * buildAddWriterKeyActivationPayload} (the handoff door), this authorization
 * carries no grant/acceptance — `validateAddWriterKeyPayload`'s `"writer"`
 * branch licenses it purely by the CARRYING HEAD's own signer already being
 * an active writer (checked at admission time, both client-side replay and
 * gateway-side `checkTransitionAdmissible`), so the acting writer's identity
 * never appears IN the payload itself, only in the head that carries it.
 */
export declare function buildWriterDoorAddWriterKeyPayload(repoId: string, predecessorPin: {
    version: string;
    sha256: string;
    writers: readonly WriterKeyEntry[];
}, addedWriter: WriterKeyEntry & {
    principal_id: string;
}): AddWriterKeyPayload;
export declare function applyAddWriterKey(repoId: string, state: WriterChainState, addedWriter: WriterKeyEntry, consumedHandoffId: string | null): WriterChainState;
/**
 * protocol §4.18: apply an ALREADY-ADMITTED `rotate_epoch{writer_set_update}`.
 * Caller MUST have validated it first (`validateWriterSetUpdate`). `writers`
 * MAY be empty — the explicit sole-writer `writer_key_revoked` read-only
 * terminal (D228) is a legal resulting state, not an error at this layer.
 */
export declare function applyWriterSetUpdate(repoId: string, state: WriterChainState, removedWriterKeyIds: readonly string[]): WriterChainState;
/**
 * The FIRST, ordinary-case lookup (protocol §5A step 1): is this head's own
 * `writer_key_id` already active in the predecessor writer state? A null
 * result does not by itself mean refusal — the caller still checks for a
 * qualifying `add_writer_key{"handoff"}` transition naming exactly this key
 * before concluding `VAULT_WRITER_NOT_ADMITTED`.
 */
export declare function resolveActiveWriter(state: WriterChainState, headWriterKeyId: string): WriterKeyEntry | null;
/**
 * The HISTORICAL lookup: a writer that is active OR was removed by a later
 * `writer_set_update` (`retiredWriters`). For re-reading a head the chain
 * ALREADY admitted — never for admitting a new one, which is
 * {@link resolveActiveWriter}'s job alone.
 */
export declare function resolveKnownWriter(state: WriterChainState, headWriterKeyId: string): WriterKeyEntry | null;
export interface AddWriterKeyPayload {
    schema: "r402s.add-writer-key/v1";
    repo_id: string;
    base_writer_set: {
        version: string;
        sha256: string;
    };
    next_writer_set: {
        version: string;
        writers: WriterKeyEntry[];
        sha256: string;
    };
    added_writer: {
        writer_key_id: string;
        signing_pubkey: string;
        principal_id: string;
    };
    authorization: {
        kind: "writer";
    } | {
        kind: "handoff";
        grant: Record<string, unknown>;
        acceptance: Record<string, unknown>;
    };
}
/**
 * The FULL cryptographic + structural validation of an `add_writer_key`
 * payload (protocol §4.16/§4.17), given the writer state at the PREDECESSOR
 * generation and the carrying head's own `writer_key_id`. Does NOT check
 * `authorization.kind:"writer"`'s org-membership eligibility (active
 * membership >= developer, directory possession-verification) — that is
 * gateway-only, live-state-dependent, and re-checked under the admission
 * fence server-side; a client validating its OWN candidate transition before
 * submission relies on the gateway's fence-time re-check as the backstop.
 */
export declare function validateAddWriterKeyPayload(repoId: string, predecessorState: WriterChainState, payload: AddWriterKeyPayload, headWriterKeyId: string): WriterStateVerdict;
export interface WriterSetUpdatePayload {
    base_version: string;
    base_sha256: string;
    next_version: string;
    next_sha256: string;
    removed: readonly {
        writer_key_id: string;
        principal_id: string;
        reason: string;
    }[];
    writers: readonly WriterKeyEntry[];
}
/**
 * The FULL cryptographic + structural validation of a `rotate_epoch`
 * transition's `writer_set_update` field (protocol §4.18, D227), given the
 * writer state at the PREDECESSOR generation, the carrying head's own
 * `writer_key_id`, and the caller's own belief about which writer keys are
 * due for removal (`gatewayBlockedWriterKeyIds` — for a client building a
 * CANDIDATE rotation this is `state read (blocked set)`; the gateway's own
 * fence-time re-check is the source of truth at admission). `allowEmptyResult`
 * is true ONLY for the explicit, owner+step-up, sole remaining writer
 * `writer_key_revoked` removal (D228); every other call site passes `false`.
 */
export declare function validateWriterSetUpdate(repoId: string, predecessorState: WriterChainState, update: WriterSetUpdatePayload, headWriterKeyId: string, gatewayBlockedWriterKeyIds: ReadonlySet<string>, allowEmptyResult: boolean): WriterStateVerdict;
/**
 * DR / offline replay (protocol §5A "Writer-set DR"): rebuild the writer
 * state purely from genesis + admitted writer-changing transitions, in
 * order. Every transition MUST already be admitted (this performs no
 * validation — apply-only, mirroring `applyAddWriterKey`/`applyWriterSetUpdate`).
 */
export type AdmittedWriterTransition = {
    kind: "add_writer_key";
    addedWriter: WriterKeyEntry;
    consumedHandoffId: string | null;
} | {
    kind: "writer_set_update";
    removedWriterKeyIds: readonly string[];
};
export declare function replayWriterState(repoId: string, genesis: {
    creator_signing_pubkey: string;
}, transitions: readonly AdmittedWriterTransition[]): WriterChainState;
/** protocol §4.15: `MAX_VAULT_WRITERS`. A writer-changing transition producing more active writers than this is refused. */
export declare const MAX_VAULT_WRITERS = 64;
//# sourceMappingURL=vault-writer-state.d.ts.map