/** * 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" | "GITVAULT_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>; 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; /** * gitvault-multi-writer rev 47 (task 5.5) — predicts the role a handoff mint * will actually confer, mirroring the gateway's OWN attenuation exactly * (`services/gitvault/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; /** Permanently consumed handoff_ids — protocol §4.17, a writer_admission_grant is single-use. */ consumedHandoffIds: ReadonlySet; /** * 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. */ /** * gitvault-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 `GitvaultVault.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, acceptance: Record): AddWriterKeyPayload; /** * gitvault-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 `GITVAULT_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; acceptance: Record; }; } /** * 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, 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=gitvault-writer-state.d.ts.map