import { GITVAULT_FORMAT, GITVAULT_SUITE } from "../namespaces/gitvault.crypto.js"; import { type ClaimKind, type HandoffKeyPrefixEntry, type ClaimKeyParts, type HandoffKeyParts, type InviteKeyParts, type HandoffSecrets, HANDOFF_KEY_PREFIXES, uuidToBytes, assembleHandoffKey, assembleInviteKey, parseClaimKey, parseHandoffKey, parseInviteKey, deriveHandoffSecrets, deriveInviteSecrets } from "./bearer-claim-key.js"; export { HANDOFF_KEY_PREFIXES, uuidToBytes, assembleHandoffKey, assembleInviteKey, parseClaimKey, parseHandoffKey, parseInviteKey, deriveHandoffSecrets, deriveInviteSecrets, }; export type { ClaimKind, HandoffKeyPrefixEntry, ClaimKeyParts, HandoffKeyParts, InviteKeyParts, HandoffSecrets }; /** * Third HKDF output of a claim key's `master_secret` (design D4, protocol * §4.17; kind-parameterized by kygit-invite design D3): the one-use * admission Ed25519 seed. Derived directly from `master_secret` — * deliberately NEVER from `auth_secret` or `wrap_key` — so the gateway, * which receives `auth_secret` at claim, stays computationally unable to * derive this seed and manufacture a different claimant completion. The info * string is domain-separated by kind (`kygit//writer-admission/v1`), * so an invite's admission seed never matches a handoff's for the same id * and master secret. */ export declare function deriveClaimWriterAdmissionSeed(kind: ClaimKind, idBytes: Uint8Array, masterSecret: Uint8Array): Uint8Array; /** Handoff-bound alias of {@link deriveClaimWriterAdmissionSeed} — `kygit/handoff/writer-admission/v1`. */ export declare function deriveWriterAdmissionSeed(handoffIdBytes: Uint8Array, masterSecret: Uint8Array): Uint8Array; /** The invite-kind sibling of {@link deriveWriterAdmissionSeed} — `kygit/invite/writer-admission/v1` (kygit-invite design D3). */ export declare function deriveInviteWriterAdmissionSeed(inviteIdBytes: Uint8Array, masterSecret: Uint8Array): Uint8Array; export type GitvaultWriterMintedRole = "owner" | "admin" | "developer" | "billing" | "viewer"; /** Signed by the MINTING writer's own writer key, at handoff mint time. Never chain-stored directly — embedded verbatim inside an admitted `add_writer_key` transition's `authorization.kind:"handoff".grant` once consumed. */ export interface WriterAdmissionGrant { format: typeof GITVAULT_FORMAT; object_kind: "writer_admission_grant"; suite: typeof GITVAULT_SUITE; repo_id: string; handoff_id: string; auth_hash: string; checkpoint_generation: string; checkpoint_head_sha256: string; grantor_writer_key_id: string; handoff_admission_pubkey: string; minted_role: GitvaultWriterMintedRole; claim_not_after: string; created_at: string; signature: string; } export interface BuildWriterAdmissionGrantInput { repo_id: string; handoff_id: string; /** The existing kygit-handoff `auth_hash` (from {@link deriveHandoffSecrets}), unrelated to this grant's own signature. */ auth_hash: string; checkpoint_generation: string; checkpoint_head_sha256: string; minted_role: GitvaultWriterMintedRole; claim_not_after: string; created_at?: string; /** The MINTER's own existing active writer signing seed — NOT {@link deriveWriterAdmissionSeed}'s one-use seed, which belongs to the grant's `handoff_admission_pubkey` field, never to its signer. */ grantor_signing_seed: Uint8Array; /** Raw 32-byte Ed25519 public key: `ed25519PublicKey(deriveWriterAdmissionSeed(...))`. */ handoff_admission_pubkey: Uint8Array; } /** Mint side (D4): builds + signs `writer_admission_grant`. The gateway's own mint-time validation (grantor active + self-consistent, field equality) is server-side; this function only builds a well-formed, correctly-signed object. */ export declare function buildWriterAdmissionGrant(input: BuildWriterAdmissionGrantInput): WriterAdmissionGrant; /** * Structural + signature verification of a `writer_admission_grant` against * the grantor's writer signing pubkey (raw bytes) — does NOT check chain * state (grantor active in the predecessor writer state, `handoff_id` * unconsumed). That is the caller's job: gateway-side at admission, or a * client-side pre-check before spending a grant it did not itself mint. */ export declare function verifyWriterAdmissionGrant(grant: WriterAdmissionGrant, grantorSigningPubkeyRaw: Uint8Array): boolean; /** The statement `writer_acceptance` signs TWICE (design D225's fold of the consultation's separate `writer_key_proof` into a second signature over this same statement). */ export declare const HANDOFF_WRITER_ACCEPT_DOMAIN: "r402s/v0/handoff-writer-accept/v1"; export interface HandoffWriterAcceptStatement { domain: typeof HANDOFF_WRITER_ACCEPT_DOMAIN; handoff_id: string; auth_hash: string; writer_key_id: string; signing_pubkey: string; encryption_pubkey: string; encryption_fingerprint: string; } /** ONE object, TWO signatures over the SAME statement: possession of the one-use admission capability (`acceptance_signature`) AND possession of the claimant's own permanent writer key (`possession_signature`). Never chain-stored directly — embedded verbatim inside an admitted `add_writer_key` transition's `authorization.kind:"handoff".acceptance` once consumed. */ export interface WriterAcceptance { statement: HandoffWriterAcceptStatement; acceptance_signature: string; possession_signature: string; } export interface BuildWriterAcceptanceInput { handoff_id: string; auth_hash: string; /** {@link deriveWriterAdmissionSeed}'s output — B derives it the SAME way A did, from its own copy of `master_secret`. */ admission_seed: Uint8Array; /** The claimant's OWN permanent writer signing seed — becomes `writer_key_id`/`signing_pubkey` on the statement. */ claimant_signing_seed: Uint8Array; claimant_encryption_pubkey_raw: Uint8Array; } /** * Claim side (D4): the claimant can construct BOTH signatures before ever * seeing the stored grant, since it already knows `handoff_id`, `auth_hash` * (recomputed exactly as the existing claim flow already does via * {@link deriveHandoffSecrets}), and its own keys. */ export declare function buildWriterAcceptance(input: BuildWriterAcceptanceInput): WriterAcceptance; /** * Verifies BOTH signatures over the SAME statement: `acceptance_signature` * under the grant's `handoff_admission_pubkey` (proves B legitimately * claimed THIS handoff), `possession_signature` under the statement's own * `signing_pubkey` (proves `writer_key_id`/`signing_pubkey` really belong * to whoever is submitting it, not a replayed statement about someone * else's key). Does not check `handoff_id`/`auth_hash` binding to a stored * grant, or single-use — those are the caller's job. */ export declare function verifyWriterAcceptance(acceptance: WriterAcceptance, admissionPubkeyRaw: Uint8Array): boolean; /** The literal payload the mint side seals and the claim side opens, for the handoff kind. */ export interface HandoffEnvelopePayload { v: 1; kind: "handoff"; repo_id: string; /** The pinned epoch (16-hex), matching the vault's `epoch` at capture time. */ epoch: string; /** `K_e` for `epoch`, lowercase hex — the vault's own symmetric key, delivered directly. */ k_e_hex: string; /** * Every epoch key the minter holds (`epoch` → lowercase hex), so a handoff * minted after an epoch rotation lets the recipient open the pre-rotation * generations too. Optional: absent on an envelope minted before this * field existed, in which case `k_e_hex` for `epoch` is all the recipient * gets (a vault that never rotated needs nothing more). */ epoch_keys?: Record; checkpoint: { generation: string; commit_oid: string; }; note_schema: "kygit.handoff-note.v1"; } /** Frame tag for the handoff envelope format — DISTINCT from `r402s/v0`'s `"R402S0"` object frame (design D3: never stored as a vault object). The LEGACY v1 tag: nothing seals it any more, but a pre-gitvault-multi-writer handoff row still carries one and {@link openHandoffEnvelope} still opens it. */ export declare const HANDOFF_ENVELOPE_KIND: "kygit-handoff-envelope-v1"; /** Seal the handoff payload under `wrap_key` as a LEGACY v1 envelope. Returns the standard-base64 wire form (openapi `format: byte`) + its declared kind tag. Nothing in the shipped flow calls this any more — {@link sealHandoffEnvelopeV2} is what `handoff` seals — but the v1 shape stays openable and sealable for the conformance vectors. */ export declare function sealHandoffEnvelope(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, payload: HandoffEnvelopePayload, nonce?: Uint8Array): { sealed_envelope: string; envelope_kind: string; }; /** Open a sealed handoff envelope under `wrap_key` (standard base64 as the claim response carries it, or base64url). Throws `HANDOFF_ENVELOPE_INVALID` on any header mismatch, a `v:2` (or otherwise non-v1) payload shape, and `HANDOFF_AEAD_AUTH_FAILURE` on a bad key/AAD/ciphertext. */ export declare function openHandoffEnvelope(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, sealedEnvelope: string, envelopeKind?: string): HandoffEnvelopePayload; /** The v2 envelope kind the handoff mint seals. */ export declare const HANDOFF_ENVELOPE_V2_KIND: "kygit-handoff-envelope-v2"; /** The v2 envelope kind the invite mint seals (kygit-invite design D3) — its own tag, never confusable with the handoff one even for the same id. */ export declare const INVITE_ENVELOPE_V2_KIND: "kygit-invite-envelope-v2"; /** * `v: 2` (NOT 1) is the payload's own shape discriminator, independent of * `envelope_kind` — a v1 opener's `p.v !== 1` shape check therefore refuses * a v2 payload outright (`HANDOFF_ENVELOPE_INVALID`) rather than silently * misreading it as v1; `v` and `envelope_kind` agreeing is exactly what * makes either one a meaningful, non-spoofable version signal. */ export interface HandoffEnvelopePayloadV2 extends Omit { v: 2; writer_admission_grant_sha256: string; } /** The invite-kind sibling of {@link HandoffEnvelopePayloadV2} — identical shape but for its `kind`/`note_schema` tags (kygit-invite design D3: "the payload is otherwise identical to the handoff envelope — the room does NOT ride in the envelope"). */ export interface InviteEnvelopePayloadV2 extends Omit { kind: "invite"; note_schema: "kygit.invite-note.v1"; } /** Seal a v2 handoff envelope — identical framing to {@link sealHandoffEnvelope}, tagged `kygit-handoff-envelope-v2` so its AAD (and therefore its ciphertext) is never confusable with a v1 envelope of the same handoff. */ export declare function sealHandoffEnvelopeV2(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, payload: HandoffEnvelopePayloadV2, nonce?: Uint8Array): { sealed_envelope: string; envelope_kind: string; }; /** The invite-kind sibling of {@link sealHandoffEnvelopeV2} — the ONLY invite envelope there is (kygit-invite design D3). */ export declare function sealInviteEnvelope(inviteIdBytes: Uint8Array, wrapKey: Uint8Array, payload: InviteEnvelopePayloadV2, nonce?: Uint8Array): { sealed_envelope: string; envelope_kind: string; }; /** * Open a v2 envelope for a writer-activation flow. Refuses a v1 (or any * other non-v2) `envelope_kind` LOCALLY with `HANDOFF_ENVELOPE_UNSUPPORTED` * BEFORE attempting to decrypt anything — post-gitvault-multi-writer, a * writer-activation-aware `resume` needs `writer_admission_grant_sha256` to * cross-check the returned grant, and a pre-rev-47 v1 envelope structurally * has none. Shares ONLY the frame/AEAD-opening step with * {@link openHandoffEnvelope} ({@link openClaimEnvelope}) — NOT that * function itself, since its `v !== 1` shape check would (correctly) refuse * a v2 payload; this function applies its own `v !== 2` shape check * instead. Callers that only need K_repo/checkpoint delivery, with no * writer activation, keep using {@link openHandoffEnvelope} against a v1 * envelope as before — v1 and v2 are NOT interchangeable at the shape * layer, by design. */ export declare function openHandoffEnvelopeV2(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, sealedEnvelope: string, envelopeKind: string): HandoffEnvelopePayloadV2; /** The invite-kind sibling of {@link openHandoffEnvelopeV2} — throws `INVITE_ENVELOPE_UNSUPPORTED` / `INVITE_ENVELOPE_INVALID` / `INVITE_AEAD_AUTH_FAILURE` (kygit-invite design D3). */ export declare function openInviteEnvelope(inviteIdBytes: Uint8Array, wrapKey: Uint8Array, sealedEnvelope: string, envelopeKind?: string): InviteEnvelopePayloadV2; export interface KygitHandoffNoteCapture { base_head: string; branch: string | null; modified_captured: number; untracked_captured: number; sensitive_excluded: string[]; ignored_not_transferred_count: number; } /** The fields shared by every claim-kind note — free-text fields are Markdown. */ interface KygitClaimNoteBase { created_at: string; from: { agent: string; harness?: string; model?: string; }; summary: string; completed?: string[]; in_progress?: string[]; failing?: string[]; tried?: string[]; next_steps?: string[]; commands?: { test?: string; build?: string; run?: string; }; decisions?: string[]; open_questions?: string[]; capture: KygitHandoffNoteCapture; } /** `kygit.handoff-note.v1` — the handoff commit's message, verbatim JSON. */ export interface KygitHandoffNote extends KygitClaimNoteBase { schema: "kygit.handoff-note.v1"; } /** `kygit.invite-note.v1` — the invite commit's message, verbatim JSON. Same fields as {@link KygitHandoffNote} (kygit-invite design D3). */ export interface KygitInviteNote extends KygitClaimNoteBase { schema: "kygit.invite-note.v1"; } export declare function isKygitHandoffNote(value: unknown): value is KygitHandoffNote; export declare function isKygitInviteNote(value: unknown): value is KygitInviteNote; export interface HandoffNoteSecretFinding { field: string; reason: string; } /** * Refuse a note that carries a bare secret — no override flag exists * (design D10): a note is read by another agent, and secrets have the * secrets API. `null` when the note is clean. */ export declare function scanHandoffNoteForSecrets(note: KygitHandoffNote): HandoffNoteSecretFinding | null; /** The invite-kind sibling of {@link scanHandoffNoteForSecrets} (kygit-invite design D3). */ export declare function scanInviteNoteForSecrets(note: KygitInviteNote): HandoffNoteSecretFinding | null; /** Throws `HANDOFF_NOTE_CONTAINS_SECRET` naming the field — call before the handoff commit is written. */ export declare function assertHandoffNoteHasNoSecret(note: KygitHandoffNote): void; /** Throws `INVITE_NOTE_CONTAINS_SECRET` naming the field — call before the invite commit is written (kygit-invite design D3). */ export declare function assertInviteNoteHasNoSecret(note: KygitInviteNote): void; //# sourceMappingURL=gitvault-handoff.d.ts.map