/** * `r.gitvault` — the host-blind encrypted Git remote, as the SDK's programmatic * API (add-gitvault task 5.10). * * ARCHITECTURAL LAW (client-surface spec, "All protocol logic lives in the * SDK"): every piece of vault protocol behaviour — crypto core, keystore, * creation journal, snapshot + capture, publication state machines, ref * transactions, verification budget, token exchange, repair — is implemented * ONCE here. `run402 gitvault …`, `git-remote-run402`, and the MCP tools are * adapters over this namespace: argument parsing, TTY output, exit codes, and * local file I/O only. Anything the CLI can do is reachable programmatically * with identical semantics. * * `r402s-verify` is the deliberate exception: an independent second lineage * that must NOT share implementation code with this namespace, because * differential verification is its entire purpose. * * ISOMORPHIC / NODE SPLIT. Vault reads (the record, heads listing, policy, * override completion) need nothing but the HTTP client and run anywhere. The * verbs that touch a git working tree or the on-disk keystore — `init`, * `push`, `compact`, `verify`, `deploy`, `restore` — are Node-only and are * reached through DYNAMIC imports of `../node/*`, so importing `@run402/sdk` * in a browser or worker never pulls `node:fs` into the graph. * * CACHING. Nothing here is memoised. Two of these responses are * secret-bearing — the maintenance lease's `holder_token` (returned exactly * once) and anything derived from the keystore — and per * docs/agent-response-design.md a secret-bearing response is never cached, * never persisted into an agent-surface result store, and never logged. */ import type { Client } from "../kernel.js"; import { GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT, GITVAULT_DURABILITY_STATEMENT, GITVAULT_MIRROR_KEYSTORE_STILL_REQUIRED_STATEMENT, GITVAULT_MIRROR_VALIDITY_NOT_FRESHNESS_STATEMENT, GITVAULT_TERMINAL_LOSS_DOCTOR_TEXT, GITVAULT_TERMINAL_LOSS_STATEMENT, GITVAULT_UNMIRRORED_FINDING_STATEMENT } from "./gitvault.crypto.js"; import type { NextAction } from "../errors.js"; /** The cold open's own verdicts — surfaced from `open()`; anything else leaves the handle lazy (see `open`). */ import type { GitvaultCaptureReceipt, GitvaultHeadsListingPage, GitvaultHeadsListingRequest, GitvaultHeadTarget, GitvaultOpenReceipt, GitvaultRecipientConfirmationReceipt, GitvaultRecoveryReceipt, GitvaultRotationReason } from "./gitvault.types.js"; import type { GitvaultCompactionGrant, GitvaultMaintenanceLease, GitvaultMaintenanceLeaseRequest, GitvaultTransport, GitvaultVaultRecord } from "../node/gitvault-publication.js"; import type { GitvaultDeployOptions, GitvaultDeployResult } from "../node/gitvault-deploy.js"; import type { GitvaultPublishResult, GitvaultRefMap, GitvaultReconcileEnvelopeRecipientsResult, GitvaultReconcileWriterAdmissionsResult, GitvaultVerifiedState } from "../node/gitvault-publication.js"; import type { GitvaultCreationResult } from "../node/gitvault-creation-journal.js"; import type { GitvaultKeystore } from "../node/gitvault-keystore.js"; import type { GitvaultSnapshot } from "../node/gitvault-snapshot.js"; import type { GitvaultMirrorConfig, GitvaultMirrorCredential } from "../node/gitvault-mirror-config.js"; import type { GitvaultByoPresenceReport, GitvaultMirrorPushResult, GitvaultMirrorSyncSummary } from "../node/gitvault-mirror.js"; import type { GitvaultRecoverResult, GitvaultVerifyReport } from "../node/gitvault-recover.js"; import type { GitvaultMemberRecoveryBundle } from "../node/gitvault-member-bundle.js"; import type { GitvaultDegradedReadLive, GitvaultDegradedReadOutcome, GitvaultDegradedReadSource } from "../node/gitvault-degraded-read.js"; import type { RoomPresence } from "./rooms.types.js"; /** What `r.gitvault.status()` reports. Never carries key material. */ export interface GitvaultStatus { repo_id: string | null; project_id: string | null; /** The control plane's view. `null` when no vault is allocated for the project. */ vault: GitvaultVaultRecord | null; /** gitvault-agent-envelopes D5: what this KEY-HOLDER's session-start fulfilment did (`null` on a machine that holds no K_repo, or `reconcile: "forbidden"`). */ reconcile_recipients?: GitvaultSessionReconcileResult | null; /** gitvault-agent-envelopes D3: what the enroll-if-absent step did for this keystore's key (`null` when it did not run). */ enrollment?: GitvaultEnrollmentOutcome | null; keystore: { present: boolean; /** The principal's Ed25519 signing fingerprint, or `null` when identity is absent. */ identity_fingerprint: string | null; /** `false` ⇒ read-only: this principal can decrypt and verify but cannot sign a head. */ can_sign: boolean; /** `true` once this machine holds K_repo for the vault. */ holds_repo_key: boolean; /** * WHERE the keystore lives, and what is in it. * * Terminal loss is stated three times in this surface; until 5.13 the path * to back up was stated nowhere, which made the warning unactionable. These * are file paths, never contents — nothing here is key material. * `repo` / `recovery_receipt` are `null` until a `repo_id` is known. */ root: string; paths: { identity: string; repos: string; receipts: string; journal: string; audit_log: string; repo: string | null; recovery_receipt: string | null; }; }; /** * The `run402` git remote in the local repository, when there is one to read. * `null` when no `repo_dir` was given, the directory is not a repository, or * no such remote is configured. * * `matches` is a TRI-STATE. For an ID-FORM remote * (`run402::/`) it is `true`/`false` from the URL text * alone — the second half is a real project id, so comparing it against * this status's own project needs no lookup. For a SLUG-FORM remote * (`run402::/`) the URL's second half is a repo NAME, not * a project id — comparing it as if it were one made a CORRECTLY * configured slug-form remote always report `matches: false` (the bug). * Instead it is compared against the local id-pin (`git config * r402.repoId`, task 4.5): pin present + equal → `true`; pin present + * different → `false`; no pin yet → `null` with `reason` explaining why * (no network read — `status` stays a pure observation). Render NO * mismatch warning for the `null` case anywhere — it is not evidence of * anything wrong, only of "not yet resolved on this machine". */ remote: { name: string; url: string; matches: boolean | null; reason: string | null; } | null; /** * The id-pinning state of this checkout — `null` * when no `repo_dir` was given, or nothing is pinned there yet. A SLUG-form * remote pins `repo_id` in local git state the first time it resolves; * `resolved_from` names the `org-slug/name` it was resolved from. An * id-form remote never pins (it needs no pin — see * `resolveGitvaultAddress`'s doc comment), so a checkout on one always * reports `null` here even once its vault is otherwise fully resolved. */ pinned: { repo_id: string; resolved_from: { org_slug: string; repo_name: string; } | null; room: string | null; } | null; /** * Whether this checkout's LOCAL `.git/info/exclude` already carries * `.run402/` (kygit-invite design D9's risk list: "`.git/info/exclude` * is per-clone and silent" — this is what makes the state visible). * `null` when no `repo_dir` was given. */ messaging_cache_excluded: boolean | null; /** * The vault's ref map and HEAD target — present only when `refs: true` was * requested. Reading them means MATERIALIZING the chain, which is a * verification and advances the local materialized pin, so plain `status` * (an observation) leaves both `null`. */ refs: Record | null; head_target: GitvaultHeadTarget | null; pins: { highest_authenticated: string | null; highest_materialized: string | null; }; gitvault_policy: "required" | "grandfathered" | null; /** Override journals on this machine that have not yet been completed. */ pending_overrides: number; /** * Best-effort count of this vault's covering `key_envelope` recipients (the * same envelope-recipients read `Gitvault.access` uses), taken inside * `status()` when a vault is allocated. `null` when unknown — no vault, or * the read failed (never a new failure mode for `status`; falls back to the * single-principal V0-A statements below). This is what decides between * `terminal_loss_statement` and `durability_statement`: the V0-A terminal-loss * claim is specifically a single-principal claim, and is factually false to * print for a vault this client can locally prove has >= 2 recipients. */ covering_recipients: number | null; /** * Stated verbatim per the client-surface spec — this sentence is normative * copy, not a summary, and is printed by `status` and `doctor` alike. * `null` exactly when `covering_recipients >= 2` — see `durability_statement` * for what is printed in that case instead. */ terminal_loss_statement: typeof GITVAULT_TERMINAL_LOSS_STATEMENT | null; /** `null` exactly when `terminal_loss_statement` is — same condition, same reasoning. */ terminal_loss_detail: typeof GITVAULT_TERMINAL_LOSS_DOCTOR_TEXT | null; /** * The protocol's durability sentence — printed in place of * `terminal_loss_statement` exactly when `covering_recipients >= 2`, since * the terminal-loss claim is false for a vault this client has locally * proven has a second covering recipient. `null` otherwise (including * "unknown" — the single-principal statements are the honest default). */ durability_statement: typeof GITVAULT_DURABILITY_STATEMENT | null; warnings: { kind: string; message: string; }[]; next_actions: { action: string; command?: string; }[]; } /** * What {@link Gitvault.scaffoldRemote} did, and why (D1). `name` is `origin` * when it was free (or a caller-supplied name), `run402` when `origin` was * already taken by something else, or the caller's explicit `remote_name`. * `reason` is a human-readable sentence naming exactly what happened — * printed to stderr by every CLI caller, never synthesized twice. */ export interface GitvaultScaffoldRemoteResult { /** `scaffolded`: the remote is in place (added now or already present). `skipped`: `repo_dir` lies inside * another repository, which is never touched — `toplevel` names it. */ status: "scaffolded" | "skipped"; /** Always `run402` unless an explicit `remote_name` was given; `origin` is never claimed. */ name: string; url: string; created_repository: boolean; already_present: boolean; existing_url: string | null; reason: string; /** The enclosing repository's toplevel when `status` is `skipped`. */ toplevel?: string; /** * `true` when `repo_dir` lies inside ANOTHER repository and was scaffolded * as its own nested repository (`nested: true`). The enclosing repository * is named by `enclosing_toplevel` and is never touched beyond one * `//` line in its local `.git/info/exclude` * (`excluded_in_enclosing` says whether that line is in place). */ nested?: boolean; enclosing_toplevel?: string; excluded_in_enclosing?: boolean; /** * A `skipped` result names the way out: `create_nested_repo`, pointing at * `run402 repos create --nested --project ` — a nested repository * keeps an encrypted remote for the app without touching the enclosing * repository. Absent on a `scaffolded` result. */ next_actions?: NextAction[]; } export interface GitvaultInitResult { repo_id: string; project_id: string; /** Emitted once at creation — integrity data, not a secret. Print it, copy it, keep many copies. */ recovery_receipt: GitvaultCreationResult["recovery_receipt"]; genesis_sha256: string; /** The git remote that was added, when a working tree was scaffolded. */ remote: GitvaultScaffoldRemoteResult | null; deduplicated: boolean; terminal_loss_statement: typeof GITVAULT_TERMINAL_LOSS_STATEMENT; /** * gitvault-byo-primary-bucket task 3.1/3.5 — the AUTHORITATIVE profile * (read back from the vault record, never assumed from what `init`'s * `byo` option requested). `"managed"` when `byo` was omitted, OR when it * was requested but this project's vault already existed as a managed * one under a different creation attempt. */ storage_profile: "managed" | "byo"; byo_destination: string | null; } export interface GitvaultCompactResult { generation: string; head_sha256: string; form: "wal" | "checkpoint"; /** Set when a maintenance lease was taken and released around the checkpoint. */ maintenance_lease_id: string | null; /** `false` when no `retention_cutoff` ticket could be obtained — roots are RETAINED (expiry is permissive). */ cutoff_bound: boolean; covered_refs: number; covered_roots: number; /** * What the transient-storage preflight saw * (gitvault-compaction-headroom-preflight). `null` when the preflight could * not be answered — see {@link GitvaultCompactHeadroom} for why an * unanswerable check never blocks maintenance. */ headroom: GitvaultCompactHeadroom | null; } /** * Compaction's transient pooled-storage arithmetic. * * Compaction publishes a checkpoint pack roughly the size of the vault's live * content while every superseded object stays stored until prune completes — * a transient footprint of roughly TWICE `source_bytes`, counted against the * ORG's pooled tier storage. Saying nothing about this leaves an org near * its cap paying the full encrypt+upload cost and then taking a mid-flight * quota refusal that names a quota rather than the mechanism. * * This is advisory-grade by construction: it can only refuse EARLIER and more * legibly than the platform's own storage-quota enforcement, never admit * something that enforcement would refuse. */ export interface GitvaultCompactHeadroom { /** Pooled storage the org is already using, across every project it owns. */ pool_used_bytes: number; /** The org's plain, unraised pooled tier storage limit — always the tier's own figure, never the grant-raised one, so a disclosed "used of X pooled" never implies the tier itself grew. */ pool_limit_bytes: number; /** The vault's billed `source_bytes` — the checkpoint-size proxy (design D1). */ vault_source_bytes: number; /** `pool_used_bytes + vault_source_bytes`. */ projected_transient_bytes: number; /** `false` when the projection exceeds the EFFECTIVE limit (`effective_pool_limit_bytes` when a grant is active, else `pool_limit_bytes`). */ ok: boolean; /** `true` when the caller passed the override and a `false` `ok` was proceeded past anyway. */ overridden: boolean; /** * gitvault-checkpoint-cadence design D3: `pool_limit_bytes` PLUS an active * compaction grant's `granted_bytes`, when one is active for this cycle — * the limit `ok`/`projected_transient_bytes` are actually computed * against. Equal to `pool_limit_bytes` (and omittable) when no grant is * active. */ effective_pool_limit_bytes?: number; /** The grant this compaction opened for itself, when one was opened and is still tracked at disclosure time — `null`/absent otherwise (no grant, an older gateway, or one already closed). */ compaction_grant?: { granted_bytes: number; expires_at: string; } | null; } /** * `run402 gitvault snapshot --dry-run`'s report shape * — {@link Gitvault.planPush}'s return type. Every sizing field is `null`, * and `refs`/`objects` are empty, exactly when `allocation_needed` is `true` * — see that method's doc comment for why sizing is genuinely UNKNOWABLE * (not merely unreported) before the vault's encryption key exists. */ export interface GitvaultSnapshotPushPlan { /** `true` when this project has no vault yet — a real push/snapshot would allocate one first (push-to-create). This dry run never does. */ allocation_needed: boolean; base_generation: string | null; would_admit_generation: string | null; /** `would_admit_generation` as a plain decimal string. */ would_admit_generation_decimal: string | null; form: "wal" | "checkpoint" | null; refs: GitvaultRefMap; head_target: GitvaultHeadTarget | null; /** Every object that would be uploaded, with REAL sealed (encrypted) sizes. Empty when `allocation_needed`. */ objects: Array<{ object_kind: string; size_bytes: string; }>; object_count: number | null; /** Sum of `objects[].size_bytes` — the REAL ciphertext byte count. */ encrypted_bytes: string | null; /** Sum of the plaintext pack bytes before sealing. */ raw_bytes: string | null; /** The local capture this dry run computed — real, regardless of `allocation_needed`: capturing the work tree touches no network. */ snapshot: GitvaultSnapshot; gitvault_commit: string; gitvault_commit_line: string; } /** One retention ROOT (a dropped ref tip) and whether its 90-day window has closed. */ export interface GitvaultPruneCandidate { ref: string; oid: string; dropped_at_generation: string; eligible: boolean; reason: string; } /** One STORED OBJECT the plan proposes deleting — what a `prune_intent`'s `delete_set` actually names. */ export interface GitvaultPruneObjectCandidate { object_id: string; object_kind: string; size_bytes: string; } export interface GitvaultPruneResult { /** Retention ROOTS and their windows — the retention view, unchanged. */ candidates: GitvaultPruneCandidate[]; eligible_count: number; retained_count: number; /** * The stored objects the plan proposes deleting, in the intent's canonical * order. A PROPOSAL: the gateway re-checks every candidate against the bound * `retention_cutoff` ticket and its own admission times, and refuses any that * is not retention-eligible. A client cannot prove either fact. */ object_candidates: GitvaultPruneObjectCandidate[]; /** Pruneable objects left out by the 10 000-per-intent cap; chunk into a later intent. */ deferred_object_count: number; /** Why nothing may be pruned yet, or `null` when a submission is structurally possible. */ blocked_reason: string | null; /** * The SIGNED `prune_intent_core` this plan proposes, and its stored-bytes * hash — what BOTH verifier receipts must sign. `null` when blocked. * * Round-trip this object verbatim into `submit`: it carries a random nonce * and object id, so a rebuilt core is a DIFFERENT core and the receipt * `r402s-verify` produced would no longer bind to it. */ intent_core: import("../node/gitvault-prune.js").GitvaultPruneIntentCore | null; intent_core_sha256: string | null; /** What this SDK observed while restoring the latest checkpoint — the receipt's evidence. */ attestation: import("../node/gitvault-publication.js").GitvaultStoredCheckpointAttestation | null; /** `true` only when an intent was accepted by the gateway. */ submitted: boolean; /** The gateway's view of the submitted intent; `null` when nothing was submitted. */ intent: import("../node/gitvault-prune.js").GitvaultPruneIntentRecord | null; /** * What the control-plane-signed completion CONFIRMS. `deleted` is the only * result that means the bytes are gone: `present_after_attempt` is a failed * deletion, not a successful one. */ confirmation: import("../node/gitvault-prune.js").GitvaultPruneConfirmation | null; note: string; } /** Options shared by every Node-only verb. */ export interface GitvaultVaultHandleOptions { /** The vault to act on. Resolved from `project_id` when omitted. */ repo_id?: string; /** Resolve `repo_id` from the project (the cold-restart entry point). */ project_id?: string; /** * gitvault-agent-envelopes D5 — the session-start envelope fulfilment a * KEY-HOLDING client runs on its first ordinary gitvault operation in a * process (read verbs included), so a pending member is covered the next * time ANY key-holder does anything. `"auto"` (default): run once per * process per vault, best-effort, reported on the handle. `"deferred"` * (`--no-reconcile`): skip, report `deferred_by_local_policy` with the * pending count — never pretends coverage. `"forbidden"`: forensic and * offline operations (`fsck`, `--no-write`, `recover`) — investigating a * suspicious pending recipient must not complete the disclosure. */ reconcile?: "auto" | "deferred" | "forbidden"; /** The local git working tree. Defaults to `process.cwd()`. */ repo_dir?: string; /** Keystore root override (defaults to `~/.config/run402/gitvault`; `~/.config/run402/profiles//gitvault` under a named wallet). */ keystore_root?: string; /** Pinned service public key for control-plane signature checks (cutoff tickets). */ service_public_key?: Uint8Array | string; /** Heads verified per call; the verified prefix persists, so a budget-exceeded client resumes. */ verification_budget?: number; } /** An opened vault plus the pieces the caller may want to keep using. */ export interface GitvaultHandle { repo_id: string; keystore: GitvaultKeystore; transport: GitvaultTransport; /** The full protocol object — every verb below is built on it. */ vault: import("../node/gitvault-publication.js").GitvaultVault; /** * gitvault-agent-envelopes D4: non-null when this open restored the repo * file from the keystore's OWN envelope (a cold keystore joining a vault it * is a recipient of). Carries the honest trust tier — `platform_attested` * is not end-to-end authentication. */ restored: import("../node/gitvault-publication.js").GitvaultColdOpenResult | null; /** * gitvault-agent-envelopes D5: the session-start fulfilment outcome for a * key-holding client (`null` when this open did not run one — a restored * keystore has nothing to wrap yet, `reconcile: "forbidden"`, or a later * open in the same process). Reported beside the verb's own result, never * folded into it. */ reconcile_recipients: GitvaultSessionReconcileResult | null; /** * gitvault-multi-writer (task 5.7) — the session-start writer-admission * reconcile's outcome. `null` exactly when `reconcile: "forbidden"` — this * reconcile carries no disclosure risk (it wraps nothing), so unlike * {@link reconcile_recipients} it is otherwise ALWAYS attempted, no * custody-verification gate and no once-per-process memoization: {@link * import("../node/gitvault-publication.js").GitvaultVault. * reconcileWriterAdmissions} is already a fast no-op (one local pin check, * no network call) whenever this session's own key is not an active * writer. */ writer_reconcile: GitvaultReconcileWriterAdmissionsPushResult | null; /** gitvault-agent-envelopes D3: what the enroll-if-absent step did for this keystore's key on this open. */ enrollment: GitvaultEnrollmentOutcome; } /** The session-start reconcile's outcome (gitvault-agent-envelopes D5). */ export interface GitvaultSessionReconcileResult { attempted: boolean; outcome: "reconciled" | "skipped_error" | "deferred_by_local_policy" | "forbidden" | "custody_unverified"; result?: GitvaultReconcileEnvelopeRecipientsResult; /** `deferred_by_local_policy` only: desired recipients this vault does not yet cover (best-effort; `null` when the read failed). */ pending_count?: number | null; error?: string; } /** The enroll-if-absent step's outcome (gitvault-agent-envelopes D3). Never a rotation. */ export interface GitvaultEnrollmentOutcome { /** `already_active`: the directory holds this keystore's key (BOTH halves current — encryption active AND signing_fingerprint already matches, gitvault-multi-writer rev 47). `enrolled`: published + possession-proven in this call. `activated_pending`: an earlier unfinished publish was completed. `signing_republished` (rev 47): the encryption half was ALREADY active and needed no work; only the signing half was (re)published this call — never a rotation, the signing half is always freely republishable. `skipped_no_identity`: no local keystore identity (nothing to enroll — vault creation mints one). `skipped_no_principal`: whoami resolved no enrolling principal (e.g. a service key). `skipped_not_enrollable`: the principal's type is not custody-eligible (ci/system) — no identity is minted. `skipped_error`: whoami/publish/activate failed (older gateway, transport) — the verb still ran, but custody continuity is UNVERIFIED, so no automatic reconcile follows; `error` says why. */ outcome: "already_active" | "enrolled" | "activated_pending" | "signing_republished" | "skipped_no_identity" | "skipped_no_principal" | "skipped_not_enrollable" | "skipped_error"; ek_fingerprint: string | null; /** * gitvault-multi-writer rev 47 (task 5.3) — the keystore's vault-WRITER * signing half, published in the SAME call as the encryption half * whenever this keystore holds a signing seed. `null` when the identity * has no local signing seed (a read-only recovery identity — see * `GitvaultIdentityFile.signing_seed_hex`'s own doc comment) or when the * publish itself was never attempted (`skipped_no_identity`/ * `skipped_no_principal`/`skipped_not_enrollable`). UNLIKE the encryption * half, the signing half is NEVER rotation-gated — a differing published * signing key is simply refreshed in place, never a `KEY_ROTATION_REQUIRED` * refusal, so this field's presence says nothing about whether a * publish actually happened this call vs. was already current. */ signing_fingerprint: string | null; error?: string; } /** {@link Gitvault.push}'s best-effort envelope-recipient reconcile outcome, reported beside (never folded into) the vault result — same non-blocking contract as {@link GitvaultMirrorPushResult}. */ export interface GitvaultReconcileEnvelopeRecipientsPushResult { attempted: boolean; outcome: "reconciled" | "skipped_error"; result?: GitvaultReconcileEnvelopeRecipientsResult; error?: string; } /** * gitvault-multi-writer (task 5.7) — the writer-admission twin of {@link * GitvaultReconcileEnvelopeRecipientsPushResult}: best-effort, reported * beside (never folded into) the vault result. ONE shape used at every * wiring site (push/snapshot/deploy/session-start/read) — unlike the * envelope reconcile's split between this push-result shape and the * richer {@link GitvaultSessionReconcileResult}, `"forbidden"` is included * here too since this reconcile's session-start policy (task 5.7's own, * deliberately simpler than the envelope reconcile's custody-gated one — * see {@link GitvaultHandle.writer_reconcile}) honors only that one value; * it is simply unreachable at the push/snapshot/deploy sites, which never * check `options.reconcile` for this hook. */ export interface GitvaultReconcileWriterAdmissionsPushResult { attempted: boolean; outcome: "reconciled" | "skipped_error" | "forbidden"; result?: GitvaultReconcileWriterAdmissionsResult; error?: string; } /** {@link Gitvault.handoff}'s result. `handoff_key` (the assembled `kgh1_…`) is returned exactly ONCE. */ export interface GitvaultHandoffMintResult { handoff_key: string; handoff_id: string; kind: "handoff"; minted_role: string; expires_at: string; vault: { vault_id: string; address?: string | null; organization_id: string; project_id: string; }; checkpoint: { generation: string; snapshot_oid_hmac: string; }; capture: { modified_captured: number; untracked_captured: number; sensitive_excluded: string[]; ignored_not_transferred_count: number; }; /** The full local capture result, for a caller that wants more than the summarized `capture` block. */ snapshot: import("../node/gitvault-snapshot.js").GitvaultHandoffSnapshot; warnings: { code: string; message: string; }[]; next_actions: NextAction[]; } export interface GitvaultHandoffListEntry { handoff_id: string; kind: string; state: "issued" | "claimed" | "expired" | "revoked"; minted_role: string; minted_by: string; expires_at: string; claimed_by?: string | null; } export interface GitvaultHandoffListResult { handoffs: GitvaultHandoffListEntry[]; } /** {@link Gitvault.resume}'s result. */ export interface GitvaultHandoffResumeResult { handoff_id: string; kind: "handoff"; deduplicated: boolean; /** The Handoff Note, parsed — `null` when the commit message could not be read/parsed (still restored either way). */ note: import("../node/gitvault-handoff.js").KygitHandoffNote | null; /** The note's raw commit-message text, for a caller that wants Markdown rendering over the parsed shape. */ note_raw: string | null; restored: { dir: string; branch: string; base_head_oid: string; stash_oid: string; }; membership: { organization_id: string; role: string; status: string; }; members: unknown[]; expires_at: string; /** gitvault-multi-writer rev 47 (task 5.6, design D5) — this checkout's own writer activation. `outcome: "active"` covers BOTH a fresh submission this call made and the idempotent-skip case (a prior attempt's activation already landed) — the writer IS active either way. */ writer_activation: { outcome: "active"; writer_key_id: string; generation: string; }; reconcile_recipients: GitvaultReconcileEnvelopeRecipientsPushResult; next_actions: NextAction[]; } /** * The gateway names the vault by its three ids on the wire — `repo_id`, * `org_id`, `project_id` (docs/style.md's API-boundary vocabulary) — on * BOTH the handoff mint (`POST /gitvault/v1/vaults/:vault_id/handoffs`) and * claim (`POST /gitvault/v1/handoffs/:handoff_id/claim`) responses. The SDK * groups them under `vault` with the `organization_id` spelling every other * SDK result uses. Neither response carries a slug-form address, so * `address` is `null` unless the caller already knows one (a slug-form * remote at mint time). Pure; exported for tests. */ export declare function handoffVaultFromWire(wire: { repo_id: string; org_id: string; project_id: string; }, address?: string | null): GitvaultHandoffMintResult["vault"]; /** The claim response's `membership` block (`org_id` on the wire) in the SDK's `organization_id` spelling. Pure; exported for tests. */ export declare function handoffMembershipFromWire(wire: { org_id: string; role: string; status: string; }): GitvaultHandoffResumeResult["membership"]; /** * An invite mints at `developer` unless `--role` narrows it or the minter's * own role is narrower — the ONE descriptor difference from a handoff, which * mints at the minter's own role (kygit-invite design D1). Declared here * because the client must predict the gateway's attenuated answer EXACTLY: * the `writer_admission_grant` is signed with `minted_role` inside it before * the mint call, and a disagreement is a `VALIDATION_FAILED` after the * checkpoint has already been captured and pushed. */ export declare const INVITE_DEFAULT_ROLE = "developer"; /** A room's `(org_id, room_key)` pair, as it rides the invite mint/claim responses. */ export interface GitvaultInviteRoom { organization_id: string; room_key: string; } /** Best-effort report of whether {@link Gitvault.invite}'s room fact was posted — a failure here (e.g. the daily quota) never voids the mint (design D4). */ export interface GitvaultInviteRoomFactResult { posted: boolean; message_id?: string; /** The posted fact's room cursor — a CLI advances the inviter's stored cursor past it, so the inviter's own `messages wait` is woken by the joiner's arrival, never by its own fact. */ cursor?: string; reason?: string; } /** Best-effort report of whether the inviter's own presence was registered before minting (design D4) — the mint still proceeds without one. */ export interface GitvaultInvitePresenceResult { registered: boolean; presence_id?: string; name?: string; error?: string; } /** {@link Gitvault.invite}'s result. `invite_key` (the assembled `kgi1_…`) is returned exactly ONCE. */ export interface GitvaultInviteMintResult { invite_key: string; invite_id: string; kind: "invite"; minted_role: string; expires_at: string; vault: { vault_id: string; address?: string | null; organization_id: string; project_id: string; }; room: GitvaultInviteRoom; checkpoint: { generation: string; snapshot_oid_hmac: string; }; capture: { modified_captured: number; untracked_captured: number; sensitive_excluded: string[]; ignored_not_transferred_count: number; }; /** The full local capture result, for a caller that wants more than the summarized `capture` block. */ snapshot: import("../node/gitvault-snapshot.js").GitvaultHandoffSnapshot; inviter_presence: GitvaultInvitePresenceResult; room_fact: GitvaultInviteRoomFactResult; warnings: { code: string; message: string; }[]; next_actions: NextAction[]; } export interface GitvaultInviteListEntry { invite_id: string; kind: string; state: "issued" | "claimed" | "expired" | "revoked"; minted_role: string; minted_by: string; room_key: string; expires_at: string; claimed_by?: string | null; } export interface GitvaultInviteListResult { invites: GitvaultInviteListEntry[]; } /** The inviter's presence, resolved live at claim time (design gitvault-invite's own claim requirement) — `null` when the inviter never registered one. */ export interface GitvaultInviteInviter { presence_id: string; name: string; program: string | null; model: string | null; state: string; last_active: string; } /** {@link Gitvault.join}'s result. */ export interface GitvaultInviteJoinResult { invite_id: string; kind: "invite"; deduplicated: boolean; /** The Invite Note, parsed — `null` when the commit message could not be read/parsed (still restored either way). */ note: import("../node/gitvault-handoff.js").KygitInviteNote | null; /** The note's raw commit-message text, for a caller that wants Markdown rendering over the parsed shape. */ note_raw: string | null; restored: { dir: string; branch: string; base_head_oid: string; stash_oid: string; }; membership: { organization_id: string; role: string; status: string; }; members: unknown[]; room: GitvaultInviteRoom; inviter: GitvaultInviteInviter | null; /** This session's OWN presence in the room (what the arrival fact was posted as), or `null` when registration failed. A CLI persists it into the joined checkout so the first `messages wait` there speaks as the same session. */ presence: { presence_id: string; name: string; } | null; /** Why `presence` is null (the registration refusal, verbatim), else null. A joined checkout without a presence still works — its first `messages wait` registers one — but the reason is never silent. */ presence_failure: string | null; live_presences: RoomPresence[]; cursor: string | null; recent_messages: unknown[]; expires_at: string; /** * gitvault-multi-writer rev 47 (kygit-invite design D5/D9) — this * checkout's own writer activation, driven through the SAME shared * `add_writer_key` door {@link Gitvault.resume} uses, BEFORE `join()` * returns, so the joiner's first `git push` is an ordinary push. * `outcome: "active"` covers both a fresh submission and the * idempotent-skip case. `outcome: "pending"` is D9's not-stranded path: a * concurrent rotation or a lost network left this key a PENDING writer of * the vault; any live writer's next push or `repos access sync` admits * it, and `next_actions` carries `request_writer_sync` saying so. */ writer_activation: { outcome: "active"; writer_key_id: string; generation: string; } | { outcome: "pending"; writer_key_id: string; reason: string; }; reconcile_recipients: GitvaultReconcileEnvelopeRecipientsPushResult; next_actions: NextAction[]; } /** What {@link Gitvault.openOrCreate} did. `created` is `null` exactly when `found` is `true`. */ /** `run402 repos mirror` (no-arg, a READ) and `repos view`'s mirror summary both compose this — what this machine and the mirror each believe (both honesty statements ride every response). */ export interface GitvaultMirrorStatus { repo_id: string; configured: boolean; /** `s3://bucket/prefix` or a directory path — never a credential. */ destination: string | null; credential_kind: "profile" | "ambient" | null; /** The newest generation the MIRROR holds and chain-verifies (keyless). `null` when unconfigured, unreachable, or empty. */ mirrored_generation: string | null; /** The LIVE vault's newest generation (one vault-record read). `null` when unconfigured or the vault has never captured. */ newest_generation: string | null; /** `null` when either side is unknown (never fabricated from a partial read). */ is_current: boolean | null; /** Present exactly when `is_current === false`. */ closing_command: string | null; /** gitvault-mirror-default: when a mirror write/sync last completed with zero failures (local config fact — never transmitted). `null` when unconfigured or never succeeded. */ last_success_at: string | null; /** gitvault-mirror-default: the standing `vault_unmirrored` finding — informational, never blocking; `null` once a mirror write or sync has succeeded (see {@link gitvaultUnmirroredFinding}). */ finding: GitvaultUnmirroredFinding | null; validity_not_freshness: typeof GITVAULT_MIRROR_VALIDITY_NOT_FRESHNESS_STATEMENT; keystore_still_required: typeof GITVAULT_MIRROR_KEYSTORE_STILL_REQUIRED_STATEMENT; } /** * gitvault-mirror-default — the named standing finding for a vault with no * customer-held mirror copy yet. gitvault-byo-primary-bucket task 3.5 * widens `message` to the BYO remedy wording ({@link * GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT}) for a BYO vault — it applies * there too (D7): a single-bucket BYO vault has exactly as few copies as an * unmirrored managed one, and the remedy names a SECOND customer-held * location the same way. */ export interface GitvaultUnmirroredFinding { kind: "vault_unmirrored"; message: typeof GITVAULT_UNMIRRORED_FINDING_STATEMENT | typeof GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT; /** The one command that moves toward clearing it: configure when unconfigured, backfill when configured-but-never-succeeded. */ setup_command: string; } /** * gitvault-byo-primary-bucket task 3.3 — the number of `{key, object_kind}` * entries `GITVAULT_BYO_OBJECT_MISSING`'s `details.missing` lists before * truncating (`details.missing_count` always carries the true total, and * `details.missing_truncated` says whether the list above was cut). A * chain-referenced object list can be arbitrarily large; an error envelope * is not the place to reproduce it whole. Matches the platform's existing * `sample_keys`-class cap (asset-sync's plan response, `PLATFORM_INCIDENT_ * FANOUT_CAP`) rather than inventing a new number. */ export declare const GITVAULT_BYO_OBJECT_MISSING_LIST_CAP = 50; /** * gitvault-byo-primary-bucket task 3.3 (design D6) — `repos fsck`'s wiring of * the shipped {@link import("../node/gitvault-mirror.js").verifyByoObjectsPresent} * read-half primitive. Present on {@link GitvaultFsckResult.byo_presence} * ONLY for a `storage_profile: "byo"` vault — `undefined` (the key absent * from JSON entirely) for a managed vault, so a managed vault's `fsck` * output is byte-identical to before this task. * * `verified: true` NEVER coexists with a missing object: a nonzero absence * throws `GITVAULT_BYO_OBJECT_MISSING` instead of returning here (see * {@link Gitvault.fsck}'s own doc comment) — this shape only ever reports * the two honest non-failure outcomes, "checked, all present" and "could * not check". */ export interface GitvaultFsckByoPresence { /** * `true` iff this call actually HEAD-checked the destination (local BYO * write credentials were configured on this machine). `false` means "not * checked" — see `not_checked_reason` — which is deliberately NOT a * failure: a BYO vault a returning agent has no local credentials for * must never break its ordinary `fsck`. */ verified: boolean; /** The BYO destination's address, from the vault record. Populated even when `verified` is `false` — a BYO vault always names its destination; it is the CREDENTIAL to reach it that may be missing on this machine. */ destination: string | null; /** Objects HEAD-checked against the destination. `0` when `verified` is `false`. */ checked_count: number; /** Present only when `verified` is `false` — why this call could not verify presence. Never blank: silence must never be mistaken for a clean verdict. */ not_checked_reason: string | null; } /** * `repos fsck`'s result. Explicit pin fields make a local mutation visible rather than * implicit — the external review's clause-5 requirement for any verb that * may advance local trust state. */ export interface GitvaultFsckResult { repo_id: string; /** `false` under `--no-write` (`write: false`) — nothing local was persisted, regardless of what was computed. */ write: boolean; verified_from_generation: string | null; /** * Alias for `chain_verified_to_generation` below (kept for back-compat — * every existing caller reads this field). Chain/signature verification * ALWAYS reaches this generation, independent of decrypt capability * (D193, rev 42: an admitted `rotate_epoch` transition is keylessly * structural, never a stopping point for chain verification). */ verified_to_generation: string; /** Same value as `verified_to_generation` — the honest name for what this field actually measures (Request 1's split). */ chain_verified_to_generation: string; /** * The newest generation this call could actually DECRYPT — restoration's * real ceiling. Equals `chain_verified_to_generation` on a healthy vault; * falls short of it when an admitted `rotate_epoch` transition's own * envelope could not be opened by this keystore (`epoch_decrypt_failure` * names exactly which epoch/rotation and why — never a bare, undifferentiated * `GITVAULT_AEAD_AUTH_FAILURE`). `refs`/`head_target`/`pin_after.highest_materialized` * below all reflect THIS generation, not the chain-verified one, when they differ. */ decryptable_to_generation: string; /** Non-null iff `decryptable_to_generation` fell short of `chain_verified_to_generation` — the named epoch boundary (Request 1). */ epoch_decrypt_failure: import("../node/gitvault-publication.js").GitvaultEpochDecryptFailure | null; /** `true` only when `write` was true AND a pin actually moved. */ local_state_changed: boolean; pin_before: { highest_authenticated: string | null; highest_materialized: string | null; }; pin_after: { highest_authenticated: string | null; highest_materialized: string | null; }; /** The real, computed ref map, AS OF `decryptable_to_generation` — present even under `--no-write` (computing is not the same as persisting). */ refs: GitvaultRefMap; head_target: GitvaultHeadTarget | null; /** Present only when `--mirror` was requested. Proves validity, never freshness — see its own honesty statements. */ mirror: GitvaultVerifyReport | null; /** * clone-installs-retained-refs (D2, task 1.3): the healing path for a * pre-change clone (or a checkout whose original write degraded per D3) — * `null` under `--no-write` (a genuine audit mode persists nothing, refs * included) or when no local checkout was addressed (`repo_dir` absent, or * `repo_dir` is not itself a git repository — an ordinary, silent no-op, * never a warning). */ retained_refs: import("../node/gitvault-publication.js").GitvaultRetainedRefsReconcileResult | null; /** * D210 (rev 44) — best-effort submission of THIS call's OWN * `chain_verified_to_generation`/`decryptable_to_generation` as * recipient-attested proof-of-open evidence (`POST * …/recipients/:principal_id/proof-of-open`), the SDK/CLI follow-up * D210's own decision log names ("an `fsck --attest-open`-class * submitter"). Automatic in WRITE mode when this keystore holds a local * encryption identity; `{attempted: false, ...}` under `--no-write` (a * genuine audit mode creates no server-side state) or when there is no * local identity to submit evidence for. This NEVER changes `fsck`'s own * verdict above — a submission failure (principal resolution, network, * `OPEN_PROOF_MISMATCH`) is recorded in `error` and nothing else; a * `recipient_open_receipt` is EVIDENCE, never authorization (§4.14). */ open_proof: GitvaultOpenProofOutcome; /** * gitvault-byo-primary-bucket task 3.3 — see {@link GitvaultFsckByoPresence}. * Absent entirely (not `null`) for a `storage_profile: "managed"` vault, so * `JSON.stringify` drops the key and a managed vault's `fsck` output stays * byte-identical to before this task existed. A `verified: true` result * with objects missing is never returned here — see the interface's own * doc comment for why that case throws `GITVAULT_BYO_OBJECT_MISSING` * instead. */ byo_presence?: GitvaultFsckByoPresence; } /** {@link GitvaultFsckResult.open_proof} — the outcome of `fsck`'s best-effort D210 proof-of-open submission. */ export interface GitvaultOpenProofOutcome { /** `false` iff this call decided LOCALLY there was nothing to submit (write:false, or no local encryption identity) — no network call was made. */ attempted: boolean; /** `true` iff the gateway accepted the submission (200 or 201) — `receipt`/`deduplicated` are populated. */ submitted: boolean; /** `true` — the gateway returned the tuple's EXISTING receipt (200, an idempotent replay). `false` — a fresh receipt was minted (201). `null` when `submitted` is `false`. */ deduplicated: boolean | null; /** The `recipient_open_receipt` the gateway returned, present iff `submitted`. */ receipt: GitvaultOpenReceipt | null; /** Present iff `attempted` and NOT `submitted` — why the gateway (or principal resolution before it) refused. */ error: { code: string; message: string; } | null; } /** * D210 (rev 44) — the decision function behind `fsck`'s best-effort * proof-of-open submission, factored out of {@link Gitvault}'s private * `#submitFsckOpenProof` so it is directly unit-testable with injected * (fake) `resolvePrincipalId`/`submit` dependencies, independent of a real * HTTP-backed vault. `fsck()` itself supplies the REAL dependencies (`GET * /agent/v1/whoami`, {@link Gitvault.submitProofOfOpen}) — this function * contains no HTTP/transport code of its own. * * Gating (all LOCAL, no network call made when either holds): * - `write === false` — a genuine audit mode; submitting a receipt is a * real server-side mutation (idempotent or not), so it never fires. * - `ekFingerprint === null` — no local encryption identity to submit * evidence FOR. * * Otherwise: resolve `principal_id` (`resolvePrincipalId`), then submit * `evidence` VERBATIM — `chain_verified_to_generation`/ * `decryptable_to_generation` pass through completely unchanged from the * caller, never recomputed or rounded here. * * FAILURE CONTAINMENT (the load-bearing property): this function NEVER * throws. Every failure — `resolvePrincipalId` resolving `null`, * `resolvePrincipalId`/`readerEntrypoint`/`submit` throwing for any reason * (network, `OPEN_PROOF_MISMATCH`, anything) — is caught and reported in * the returned outcome's `error`; the caller's own already-computed result * (`fsck`'s verdict) is never touched by this function's own failure. */ export declare function computeOpenProofOutcome(input: { write: boolean; ekFingerprint: string | null; evidence: { chain_verified_to_generation: string; decryptable_to_generation: string; }; resolvePrincipalId: () => Promise; readerEntrypoint: () => Promise; submit: (principalId: string, ekFingerprint: string, evidence: { chain_verified_to_generation: string; decryptable_to_generation: string; reader_entrypoint: string; }) => Promise<{ receipt: GitvaultOpenReceipt; deduplicated: boolean; }>; }): Promise; /** * gitvault-byo-primary-bucket task 3.3 (design D6) — the decision function * behind `fsck`'s BYO presence check, factored out of {@link Gitvault}'s * private `#checkByoPresence` the same way {@link computeOpenProofOutcome} * is factored out of `#submitFsckOpenProof`, so it is directly unit-testable * with an injected (fake) `hasLocalConfig`/`verifyPresence`, independent of * a real keystore or HTTP client. `#checkByoPresence` supplies the REAL * dependencies (`readByoConfig` against the real keystore, * `verifyByoObjectsPresent` against the real client) — this function * contains no filesystem/HTTP code of its own. * * Runs AUTOMATICALLY for a `storage_profile: "byo"` vault — never behind a * new flag, so a returning agent's ordinary `fsck` catches a * silently-emptied bucket without having to know to ask for it. Returns * `undefined` for a managed vault (`vault?.storage_profile !== "byo"` is * the FIRST check, before `hasLocalConfig`/`verifyPresence` are ever * called) — a managed vault pays zero extra network/filesystem work and * this task changes nothing about it (design D6/D9). * * NO local credentials configured (`hasLocalConfig()` returns `false`) is * reported, never thrown — a BYO vault this machine cannot reach is an * honest "not checked", not a failure that should break `fsck` for a * credential-less returning agent (see {@link GitvaultFsckByoPresence}'s * own doc comment). A confirmed absence is the opposite: it THROWS * `GITVAULT_BYO_OBJECT_MISSING` — the same severity class as {@link * Gitvault.mirrorVerify}'s own `GITVAULT_MIRROR_NOT_CONFIGURED` throw when * `--mirror` is requested against an unconfigured vault — this is exactly * the honesty D6 names: "we can tell the customer what SHOULD exist and * does not", stated as a real refusal rather than a silently-embedded * finding. The listed entries are capped at * {@link GITVAULT_BYO_OBJECT_MISSING_LIST_CAP} (a chain-referenced object * list can be arbitrarily large and this is an error envelope, not a * report); `missing_count` always carries the true total regardless of how * many are listed. * * `verifyPresence()` is a pure read (HEAD checks only, via * `verifyByoObjectsPresent`) — this function persists nothing itself, so * it behaves identically whether `fsck` was called with `write: true` or * `write: false`. */ export declare function computeByoPresenceOutcome(input: { repoId: string; vault: { storage_profile?: "managed" | "byo"; byo_destination?: string | null; } | null; hasLocalConfig: () => boolean; verifyPresence: () => Promise; }): Promise; /** * `repos access`'s result — a READ over whatever the live gateway surface * exposes today. The gateway's `envelope-recipients` read reports * server-authoritative, membership-driven desired-recipient state * (`desired[]` + `desired_state_version`), so per-recipient `envelope_state` * (`converged` when the desired key is wrapped on this vault, `pending` when * it is not, `pending_removal` when membership removal has not yet been * enforced) is real. `envelope_state_available` reflects this at RUNTIME — * it is `false` only against an older gateway that has not shipped * `desired[]` yet (a rolling deploy window, or a pinned older `apiBase`), * never a hardcoded claim. * * ONE gap remains genuine and gateway-independent: `history_scope` (which * epochs each recipient can read) has no substrate to report, because * gitvault protocol v0 pins a single fixed epoch for a vault's entire * lifetime — there is no per-epoch scope to have. `stale_access` below is * the closest observable proxy for the same underlying limitation: a * `pending_removal` recipient whose fingerprint is STILL covered still * decrypts every commit (past AND future) under that one epoch, because * epoch rotation — the mechanism that would actually revoke them — is * mid-fold in `gitvault-human-envelopes` under adversarial protocol review. * * `access` reports what the read surface HAS: the org's directory of * encryption-key-holding members, which of the vault's current * envelope-recipient fingerprints match a directory entry, the * server-reported desired state per recipient when available, and * (Node-only, best-effort) this machine's own local TOFU pin for each * principal, when it has ever wrapped one. */ export interface GitvaultAccessRecipient { principal_id: string; display_name: string | null; /** This recipient's fingerprint per the org directory. */ fingerprint: string; /** `true` when this fingerprint has a `key_envelope` on the vault today. */ covered: boolean; /** * The server's desired-recipient state for this principal, cross-referenced * against `covered`: `"converged"` (desired + covered), `"pending"` * (desired, not yet wrapped), `"pending_removal"` (membership removed * them but this vault has not been re-keyed away from them — see * `stale_access`), or `null` when the gateway did not report desired-state * for this principal (older gateway, or the directory and desired-state * reads disagree, which should not happen but is reported honestly rather * than papered over). */ envelope_state: "converged" | "pending" | "pending_removal" | null; /** THIS machine's own local trust-on-first-use record for this principal, or `null` if this machine never wrapped them. Never a server-side fact. */ tofu_pin: { fingerprint: string; matches_directory: boolean; } | null; } /** A principal whose membership was removed (desired state `pending_removal`) but who STILL holds a covering `key_envelope` on this vault — real, continuing access that removal did not revoke, because gitvault v0 has no epoch-rotation mechanism yet. See {@link GitvaultAccessResult}'s doc comment. */ export interface GitvaultStaleAccessEntry { principal_id: string; display_name: string | null; fingerprint: string; } export interface GitvaultAccessResult { repo_id: string; org_id: string | null; /** gitvault-agent-envelopes D5: this KEY-HOLDER's session-start fulfilment outcome (`null` when this machine holds no K_repo for the vault, or `reconcile: "forbidden"`). */ reconcile_recipients?: GitvaultSessionReconcileResult | null; /** gitvault-agent-envelopes D3: the enroll-if-absent step's outcome (`null` when it did not run). */ enrollment?: GitvaultEnrollmentOutcome | null; recipients: GitvaultAccessRecipient[]; /** Vault-covering fingerprints (from the server) that match neither a directory entry nor a desired-state row — genuinely orphaned, revoked outside this org's membership model, or external. Excludes fingerprints already explained by `stale_access` AND by `this_keystore`. */ unmatched_covered_fingerprints: string[]; /** * Node-only, best-effort: set when an otherwise-unmatched covering * fingerprint equals THIS keystore's own encryption-key fingerprint — the * one case an unmatched fingerprint is locally provable rather than a * genuine unknown. Typically the vault creator's own wallet-principal * keystore, which the org directory never lists (it only enrolls human * keys). `null` when no local match was found (including non-Node * callers, or a machine that never held this identity). */ this_keystore: { fingerprint: string; /** `true` iff the directory holds this keystore's key as ACTIVE (gitvault-agent-envelopes: agents enroll too). */ enrolled: boolean; /** `absent` = no published key (the next gitvault operation enrolls); `pending` = published, possession unproven; `rotation_required` = the principal's published key is a DIFFERENT key; `unknown` = whoami unavailable. */ publish_state: "active" | "pending" | "absent" | "rotation_required" | "unknown"; covered_on_this_vault: boolean; /** @deprecated alias of `covered_on_this_vault`. */ covered: boolean; next_actions?: Array<{ action: string; why: string; }>; } | null; /** Removed members who still decrypt this vault — see {@link GitvaultStaleAccessEntry}. Always `[]` when `envelope_state_available` is `false` (no desired-state substrate to compute it from). */ stale_access: GitvaultStaleAccessEntry[]; /** `true` when the gateway reported desired-recipient state (`desired[]`) for this read, making `recipients[].envelope_state` and `stale_access` real rather than absent. `false` only against an older gateway. */ envelope_state_available: boolean; /** Always `false` — see this type's own doc comment for why this is a protocol-level absence, not a missing gateway feature. */ history_scope_available: false; /** The honest, human-readable statement of the gap above. Read it before assuming `covered: true` means "converged," and before assuming `pending_removal` means access was actually revoked. */ gap: string; /** Present only when `stale_access` is nonempty — the exact owner-driven remedy (D193-D203, rev 42): `repos access repair` for a general re-key, `repos access revoke-key ` for one targeted principal. */ next_actions?: { action: string; why: string; }[]; } /** * `repos list`'s bulk read — * `GET /gitvault/v1/vaults?org_id=`. * FROZEN response shape, agreed with the gateway * team ahead of the route landing; the route may still 404 on a gateway * that has not shipped it yet, and callers should fall back to the * per-project walk (`status()` in a loop) until then — see * `cli/lib/repos.mjs`'s `list()` for that fallback, kept only until every * deployed gateway answers this route. */ export interface GitvaultOrgVaultSummary { repo_id: string; project_id: string; project_name: string | null; repo_name: string | null; org_slug: string | null; gitvault_policy: "required" | "grandfathered" | null; newest_generation: string | null; source_bytes: string; genesis_admitted_at: string | null; created_at: string; /** gitvault-byo-primary-bucket task 3.5 — absent-or-`"managed"` is byte-identical to today. */ storage_profile?: "managed" | "byo"; } export interface GitvaultOrgVaultsListing { vaults: GitvaultOrgVaultSummary[]; /** Keyset pagination (agent-response-design): opaque, store-and-echo. */ has_more?: boolean; next_cursor?: string | null; } export interface GitvaultOpenOrCreateResult { handle: GitvaultHandle; /** `true` when the vault already existed — nothing was allocated by this call. */ found: boolean; created: { /** Mirrors `GitvaultInitResult.deduplicated`: an existing local creation journal was resumed to completion rather than started fresh — nothing was re-minted. */ deduplicated: boolean; /** Emitted once at creation — integrity data, not a secret. Print it, copy it, keep many copies. Persisted into the keystore regardless. */ recovery_receipt: GitvaultCreationResult["recovery_receipt"]; genesis_sha256: string; } | null; terminal_loss_statement: typeof GITVAULT_TERMINAL_LOSS_STATEMENT; } /** * {@link Gitvault.deploy}'s post-push glue: attach the best-effort * mirror/reconcile hooks to a deploy result, gated on whether this deploy * actually landed a new generation in the vault. Extracted as a standalone * function (rather than inlined in `deploy()`) purely so the gating can be * unit-tested with fake thunks — the real hooks (`#tryMirrorPush` / * `#tryReconcileEnvelopeRecipients`) are private class methods that already * catch everything and never reject, so this function does not need its own * try/catch: it only decides WHETHER to call them and how to merge what they * resolve to. * * `DEPLOYED_AND_VAULTED` and `DEPLOY_FAILED_VAULTED` are the only two * outcomes that carry a `generation` — a vault push actually landed. The * other three (`DEPLOY_BLOCKED_PUSH_FAILED`, `DEPLOY_FAILED_UNVAULTED`, * `DEPLOYED_UNVAULTED_OVERRIDE`) published nothing new, so `mirror_push` / * `reconcile_recipients` are OMITTED rather than a faked `skipped_*` value — * there is nothing this deploy did that either hook could report on. */ export declare function attachGitvaultDeployHooks(result: GitvaultDeployResult, mirror: () => Promise, reconcile: () => Promise): Promise; export declare class Gitvault { #private; constructor(client: Client); /** The vault record — policy, allocation generation, storage + maintenance state. */ get(repoId: string): Promise; /** * Resolve a project's vault with no local state. This is the cold-restart * entry point: an agent that lost its machine but still holds authority on * the project learns its `repo_id` here. */ forProject(projectId: string): Promise; /** * Every vault the organization owns, one round trip — `repos list`'s bulk * read. See {@link * GitvaultOrgVaultsListing}'s doc comment for the FROZEN response shape and * the 404-until-shipped fallback contract. */ listByOrg(orgId: string): Promise; /** * Resolve a vault by its address-form `org-slug/name` — * `GET /gitvault/v1/vaults?repo=/`. * `RESOURCE_NOT_FOUND` for no such org OR no such name (deliberately * collapsed — see the design's slug-namespace-probing note); * `SLUG_RELEASED` (read it with {@link gitvaultSlugReleasedInfo}) while the * slug is in its post-rename cooldown — never auto-followed. */ forRepo(address: { org_slug: string; repo_name: string; }): Promise; /** * Resolve a parsed remote address (`parseGitvaultRemoteUrl`'s output), * dispatching on its form: id-form resolves exactly like * {@link forProject}; slug-form resolves via {@link forRepo}. A pure read — * no pinning, no creation. Node-only callers wanting BOTH should use * {@link resolveOrCreateAddress} instead, which also drives the local pin * and (opt-in) push-to-create. */ resolveAddress(address: GitvaultRemoteAddress): Promise; /** * One page of the heads listing (D186). * * `after_generation` is the REQUIRED verification anchor — a semantic input, * never a paging knob — and must stay CONSTANT across a page sequence. * `limit` is required. `cursor` is omitted on the first request and is then * the prior page's `next_cursor` echoed UNCHANGED: store and echo, never * parse. A malformed or stale cursor is `INVALID_CURSOR`; recover by * restarting from `after_generation` with no cursor. */ heads(repoId: string, request: GitvaultHeadsListingRequest): Promise; /** * Walk the whole listing above the anchor, verifying page coupling, ordering, * and gaplessness as it goes (a gap is `CHAIN_BROKEN`, never a silent skip). */ allHeads(repoId: string, options: { after_generation: string; limit?: string; }): Promise<{ heads: GitvaultHeadsListingPage["heads"]; pages: number; total: string | null; }>; /** * Set the activation policy. Owner + step-up, audited, reason required. * `grandfathered` leaves a doctor-persistent warning until it returns to * `required`. */ setPolicy(repoId: string, input: { gitvault_policy: "required" | "grandfathered"; reason?: string; }): Promise<{ gitvault_policy: string; gitvault_policy_version: string; changed: boolean; warnings: { kind: string; message: string; }[]; }>; /** * Clear an unvaulted-override advisory by presenting a capture receipt that * matches the journaled operation on EVERY field. A partial match never * clears it. */ completeOverride(repoId: string, input: { operation_id: string; capture_receipt: GitvaultCaptureReceipt; }): Promise<{ operation_id: string; advisory_cleared: boolean; generation: string; head_sha256: string; }>; /** * Take the owner's maintenance reservation for a compact/prune cycle. * * SECRET-BEARING: `holder_token` is returned exactly ONCE and is the liveness * instrument for heartbeat/release. Never log it, never cache it, never place * it in an agent-surface result store. */ /** `POST …/recipients/:principal_id/confirm` (D197) — first-seen pin confirmation. */ confirmRecipient(repoId: string, principalId: string, newFingerprint: string): Promise; /** `POST …/recipients/:principal_id/repin` (D197) — re-pin ceremony. */ repinRecipient(repoId: string, principalId: string, input: { old_ek_fingerprint: string; new_fingerprint: string; }): Promise; /** * gitvault-agent-envelopes D3 — a KEY-HOLDER explicitly accepts a recipient's * CHANGED key. The session-start reconcile refuses to wrap under a * fingerprint that differs from this keystore's TOFU pin * (`pinned_key_mismatch`) — that refusal is the substitution defence and * is never bypassed automatically, not even after an owner's revoke: from * this machine's view an owner-driven re-key and a platform substitution * look identical, and the audit event is not proof against the platform. * Acceptance is a deliberate act naming the new fingerprint (the * out-of-band verification point — read it back with the recipient over any * channel; it is public data): this records the D197 re-pin receipt with the * gateway AND moves the local pin, so the next reconcile wraps. Refuses when * `new_fingerprint` is not what the org directory currently serves for the * principal (a stale or mistyped fingerprint never pins). */ acceptRecipientKeyChange(options: GitvaultVaultHandleOptions & { principal_id: string; new_fingerprint: string; }): Promise<{ repo_id: string; principal_id: string; old_fingerprint: string | null; new_fingerprint: string; receipt: GitvaultRecipientConfirmationReceipt | null; }>; /** * `POST …/recipients/:principal_id/key-revocation` (D199) — declares * `reason:"recipient_key_revoked"` admissible for the NEXT rotation this * org's vaults submit; org-scoped, advances the same watermark a member * removal does. Returns the D194 counters — the ONE client-visible read of * them, which is why {@link rotateEpochForKeyRevocation} exists as the * fully self-contained entry point. */ declareRecipientKeyRevoked(repoId: string, principalId: string): Promise<{ recipient_state_version: string; recipient_revocation_version: string; }>; /** * `POST …/epoch-secret-exposure` (D199) — declares `reason:"epoch_secret_exposed"` * admissible for THIS vault (deliberately vault-scoped, not org-wide — one * vault's leaked `K_repo`/`K_e` is not evidence any sibling vault is * compromised). This is the rekey remedy for a leaked/exposed vault key: * declare exposure here, then drive a `rotate_epoch` with * `reason:"epoch_secret_exposed"` (the required counters must be supplied * from a source other than this call — see {@link rotateEpoch}'s doc * comment on the confirmed gap in what the gateway exposes today). */ declareEpochSecretExposed(repoId: string): Promise<{ epoch_secret_exposure_version: string; }>; /** `POST …/writer-authority/declare-unavailable` (D202) — an explicit, audited fact that the writer signing key is gone. */ declareWriterAuthorityUnavailable(repoId: string): Promise<{ declared_at: string; declared_by: string | null; }>; /** * `POST …/recipients/:principal_id/proof-of-open` (D210, rev 44) — submit * `fsck`'s OWN `chain_verified_to_generation`/`decryptable_to_generation` * evidence VERBATIM as proof that `principalId` can open this vault under * its current epoch. `fsck()` itself calls this automatically (in write * mode, when a local encryption identity exists) — call it directly only * for a manual/explicit submission (e.g. resubmitting after fixing a * local keystore issue, or from a caller that already resolved its own * `principal_id` and does not want the extra `whoami` round trip * `fsck()`'s own auto-submission pays). * * Self-match only: the gateway requires `principalId` to equal the * AUTHENTICATED caller, never overridable by any credential class — a * mismatch is the ordinary 403 `GITVAULT_ACCESS_DENIED`. Idempotent on * `(repo_id, principal_id, ek_fingerprint, decryptable_to_generation)` — * `deduplicated: true` means the gateway returned the tuple's EXISTING * receipt (HTTP 200) rather than minting a fresh one (HTTP 201). */ submitProofOfOpen(repoId: string, principalId: string, evidence: { ek_fingerprint: string; chain_verified_to_generation: string; decryptable_to_generation: string; reader_entrypoint: string; }): Promise<{ receipt: GitvaultOpenReceipt; deduplicated: boolean; }>; acquireMaintenanceLease(request: GitvaultMaintenanceLeaseRequest): Promise; /** * Open the vault: keystore + HTTP transport + the protocol object. Every verb * below builds on this; call it directly when you need the raw protocol * surface (ref transactions, repair, checkpoint building). */ open(options?: GitvaultVaultHandleOptions): Promise; /** * Open a vault, allocating it first when it does not exist yet (D2 — lazy * allocation on first push). * * This is the ONE primitive the remote helper and the capture lane * (`run402 gitvault push`/`snapshot`) drive so that a push against an * unallocated project runs the six-stage creation journal inline instead of * refusing: it resolves `repo_id` from `project_id` exactly like * {@link open}, and when that resolution fails, tries to create the vault — * but ONLY when `org_id` was supplied. Without `org_id` this method is * byte-identical to `open()`: the original resolution failure is rethrown * unchanged, so every existing caller of `open()` that has no reason to * create anything sees no behavior change at all by switching to this. * * RESUMABILITY (client-surface spec, D2): before starting a fresh creation * attempt this looks for an INCOMPLETE local journal already matching this * exact `(org_id, project_id)` and resumes ITS `client_creation_id`, rather * than starting a second competing attempt every time the process is * interrupted mid-creation. Kill the process after ALLOCATED and call this * again: the same journal — not a new one — drives to ACTIVE, and exactly * one vault exists either way. An explicit `client_creation_id` (tests, or * a caller resuming a specific attempt by hand) always wins over that search. */ openOrCreate(options: GitvaultVaultHandleOptions & { org_id?: string; client_creation_id?: string; }): Promise; /** * Resolve a parsed remote address to an OPEN handle, pinning `repo_id` in * local git state on the first successful resolution of EITHER form * (gitvault-client-round-trips design D4 widens the original slug-form-only * pin to id-form too — an id-form address needs no pin to survive a * rename, but the resolution round trip it skips is the same either way), * and — when `allow_create` is set and resolution misses — push-to-create * it. This is what the remote helper and `gitvault snapshot` drive for a * `run402::/` remote, whichever form it names, so a caller need * not branch on the address's form itself. A pinned id/address that no * longer resolves (404) clears the pin and re-resolves once. * * `SLUG_RELEASED` is NEVER auto-followed — it rethrows unchanged; read it * with {@link gitvaultSlugReleasedInfo} for the successor slug and cooldown. */ resolveOrCreateAddress(options: GitvaultVaultHandleOptions & { address: GitvaultRemoteAddress; /** Push-to-create on a slug-form miss (D6). `false` (default): a miss is an ordinary not-found refusal — the read path (`list`/`fetch`, `status`). */ allow_create?: boolean; client_creation_id?: string; /** Fires once, only when THIS call allocated the vault. Awaited before the handle is returned. */ onVaultCreated?: (created: NonNullable) => void | Promise; }): Promise; /** * Stale-pin recovery for OFFLINE address resolutions * (gitvault-force-spelling-and-pin-fold): an id-carrying pin resolves with * zero network reads, so a pin gone stale (its vault deleted and the * project re-allocated a new one) surfaces as the VERB's first repo-scoped * read failing rather than as a resolution failure. Call this with that * failure: it answers a fresh opened handle + resolution to retry the verb * against exactly once, or `null` when there is nothing to recover — the * error is not a vault-absent signal, the resolution was not offline, or * re-resolution lands on the SAME `repo_id` (the pin was fine and the * original refusal is real; it is restored, and recovery never widens what * an unauthorized caller learns). */ recoverStalePin(options: GitvaultVaultHandleOptions & { address: GitvaultRemoteAddress; repo_dir: string; resolution: import("../node/gitvault-address.js").GitvaultAddressResolution; error: unknown; }): Promise<(GitvaultOpenOrCreateResult & { resolution: import("../node/gitvault-address.js").GitvaultAddressResolution; }) | null>; /** * Create the vault for a project and scaffold the git remote. * * Runs the six-stage creation journal — `LOCAL_KEYS_PREPARED → ALLOCATED → * OBJECTS_PREPARED → OBJECTS_FINALIZED → GENESIS_PREPARED → ACTIVE`, each * fsynced before the next step, with no ciphertext existing before allocation * supplies the `repo_id` the key derivation needs. Crash-safe and resumable * on `client_creation_id`. * * An existing `origin` remote is never modified or claimed; only a remote * named `run402` is added. */ init(options: { org_id: string; project_id: string; repo_dir?: string; keystore_root?: string; /** Resume a specific creation attempt (or pin it in tests). */ client_creation_id?: string; /** Skip the git scaffold — allocate the vault only. */ scaffold_git?: boolean; /** * Scaffold `repo_dir` as its OWN repository even when it lies inside * another one (see {@link Gitvault.scaffoldRemote}'s `nested`). Default * `false`: an app root inside another repository is reported `skipped`. */ nested?: boolean; remote_name?: string; remote_url?: string; service_public_key?: Uint8Array | string; /** * gitvault-byo-primary-bucket task 3.1/3.2/3.5 — request a BYO vault. * Same raw-string shape `mirrorSet` already takes (`destination_url` + * `credential` + `region`/`endpoint`), so a CLI edge stays a thin * adapter — no destination parsing lives outside the SDK. Omitted (the * default) is byte-identical to today. This method RUNS the * allocation-time bucket probe (D6) itself, BEFORE any allocation * request — a failed probe throws `GITVAULT_BYO_BUCKET_PROBE_FAILED` * and nothing is created. */ byo?: { destination_url: string; credential?: import("../node/gitvault-mirror-config.js").GitvaultMirrorCredential; region?: string; endpoint?: string; }; }): Promise; /** * Add the run402 git remote, initialising the repository if absent. * * D1 — claim `origin` additively. LLM muscle memory is `git push origin * main` from a billion training examples, and a side-remote name costs * every agent a correction cycle, so when the repository has no `origin` * remote ours BECOMES `origin`. An existing `origin` is NEVER modified or * reclaimed — no matter what it points at, including a prior run of this * exact call — so the fallback is `run402`, and when THAT is also taken by * something else, nothing is added at all: the additive discipline holds * for every remote name this method ever touches, not just `origin`. * * An explicit `remote_name` is a caller override (no current caller passes * one) and skips the origin/run402 dance entirely — the caller already * decided the name; this method still never modifies an existing remote * under it. * * No key material or allocation is required — the cold-start path gains no * prompts or network dependencies from this. * * `nested: true` — the monorepo case. An app root that lies INSIDE another * repository (an agent workspace with many apps under one checkout) is * still made its own repository: `git init -b main` in `repo_dir`, the * remote added there, and ONE line — `//` — * appended to the ENCLOSING repository's local `.git/info/exclude` * (located via `rev-parse --git-path info/exclude`, so a worktree resolves * to its common dir) so the enclosing checkout never lists the nested * repository as untracked noise. Never `.gitignore` (tracked content), * never the index, never a submodule: nothing else in the enclosing * repository changes, and its remotes are untouched. Idempotent — a second * call on the already-nested app root adds nothing and re-reports the same * shape. Without `nested`, the same situation is reported `skipped` with a * `create_nested_repo` next_action naming this way out. */ scaffoldRemote(options: { repo_dir: string; org_id: string; project_id: string; remote_name?: string; remote_url?: string; nested?: boolean; }): Promise; /** * What this machine and the control plane each believe about the vault. * * Truthful for a VAULT-ONLY project (protocol D183): a project that has never * deployed raises no deploy-related warning, and the terminal-loss statement * is stated verbatim exactly as for any other vault. */ status(options?: GitvaultVaultHandleOptions & { /** * Also report the vault's ref map and HEAD target. * * Opt-in because it is not free and not read-only in the local sense: * materializing walks the head chain (a verification) and advances the * keystore's materialized pin. `status` without it stays a pure * observation, which is what makes it safe to run anywhere. */ refs?: boolean; }): Promise; /** * Capture the working tree and publish it — the push half of a deploy, run on * its own. Never gated on a deploy: a vault-only project pushes for months * without one. * * WHAT THIS PUBLISHES, and what it deliberately does not. This is the CAPTURE * lane: it publishes the protocol-owned `refs/run402/deploys/latest` plus the * `head_target` read off the local HEAD. The repository's OWN refs — * `refs/heads/*` and `refs/tags/*` — reach the vault through * `git push run402 …` via `git-remote-run402`, which is the lane the spec * charges with reproducing them ("the helper SHALL reproduce the exact set of * objects reachable from the declared canonical refs (`refs/heads/*`, * `refs/tags/*`, protocol-owned `refs/run402/*`), plus the `HEAD` target" — * gitvault-client-surface, "Faithful helper"; `run402 gitvault push` is * separately defined as "capture and push outside a deploy" under "Source * verbs"). The split is forced by §6.6: a dirty tree captures as a SYNTHETIC * commit that sits on no branch, so moving `refs/heads/main` onto it would * rewrite the user's branch on every dirty push — exactly the history * clobbering §6.1's fast-forward + force-with-lease rules exist to prevent. * * Hence `protocol_refs: "allow"`: this lane BUILDS the protocol ref itself, * so it opts in. The remote helper must keep the `"refuse"` default, because * there the refnames are user-supplied and a user must not be able to squat * the `refs/run402/*` namespace. * * Before reporting a push as landed the vault compares finalization receipts * against its expected manifest and reads the admitted head back from * storage. A 200 alone is never enough (§0 client obligations). */ push(options: GitvaultVaultHandleOptions & { /** * The owning org. Supply it to run D2's lazy allocation: when this * project's vault does not exist yet, push() runs the six-stage * creation journal inline (via {@link openOrCreate}) before capturing * and publishing — one command, no prior `gitvault init`. Without it, * push() behaves exactly as it always did: a push against an * unallocated vault throws the resolution failure unchanged. */ org_id?: string; /** * A parsed remote address (`parseGitvaultRemoteUrl`'s output). * When given, this push resolves * (and, for a slug-form address, pins) through * {@link resolveOrCreateAddress} INSTEAD of `openOrCreate`'s * project_id-based path; `org_id`/`project_id`/`repo_id` are ignored. * Mirrors `git push`'s own push-to-create dispatch in the remote * helper, for callers (`gitvault snapshot`) that resolve the target * from the local git remote instead of `--project`. */ address?: GitvaultRemoteAddress; /** Resume a specific creation attempt (or pin it in tests); auto-discovered from the local keystore otherwise. */ client_creation_id?: string; /** * Fires once, only when THIS call allocated the vault — the moment to * print the one-shot recovery receipt and keystore path. Awaited * before capture/publish continue, so any async work inside it (e.g. * resolving the keystore path to print) completes and stays ordered * BEFORE the rest of this push's own output. */ onVaultCreated?: (created: NonNullable) => void | Promise; /** * Capture options, forwarded verbatim to `captureSnapshot`. The commit * message for the synthetic commit a dirty tree produces lives HERE * (`snapshot: { message }`) — there is deliberately no second top-level * `message` field, because a `snapshot` passthrough plus a sibling * shortcut is two ways to say one thing with no defined precedence. */ snapshot?: Omit; onCommitLine?: (line: string) => void; checkpoint?: boolean; }): Promise; /** * Mint a Handoff Key: capture a stash-shaped checkpoint (design D1), * push it (retained, on no branch — {@link GITVAULT_DEPLOY_REF} carries * it exactly like an ordinary `push()`, never `refs/heads/*`), seal the * vault's current epoch key under a fresh `wrap_key`, and mint through * the gateway. The assembled `kgh1_…` key is returned exactly ONCE — * nothing here or downstream persists it. * * `options.note` omits `capture` — this method fills it with the real * capture figures and runs the client-side secret scan BEFORE the * handoff commit is written (design D10: no override flag). */ handoff(options: GitvaultVaultHandleOptions & { address?: GitvaultRemoteAddress; role?: string; ttlSeconds?: number; note: Omit; includeSensitive?: string[]; onCommitLine?: (line: string) => void; }): Promise; /** List a vault's handoffs (ids, kind, state, role, expiry, claimed_by — never the hash or envelope). */ listHandoffs(options: GitvaultVaultHandleOptions): Promise; /** Revoke a handoff (idempotent — a second revoke of an already-revoked/claimed/expired row still answers `200`). */ revokeHandoff(handoffId: string, options: GitvaultVaultHandleOptions): Promise<{ handoff_id: string; state: string; }>; /** * Mint an Invite Key: capture a stash-shaped checkpoint exactly as * {@link Gitvault.handoff} does (design D1), push it (retained, on no * branch), register the INVITER's own presence in the invite's room * (design D4 — before the mint, so the row can carry * `inviter_presence_id`; a registration failure is reported and the mint * proceeds without one), seal the vault's current epoch key under a fresh * `wrap_key`, mint through the gateway at `developer` (or `role`, * attenuated to never exceed the minter's own), and post ONE fact from * the inviter's presence naming the checkpoint and the invite id (never * the key) — a fact-post failure is reported in `room_fact` and never * voids the mint (design D4). The assembled `kgi1_…` key is returned * exactly ONCE — nothing here or downstream persists it. Creating an * invite never touches the inviter's worktree, index, branch, refs, or * access. * * `options.note` omits `capture` — this method fills it with the real * capture figures and runs the client-side secret scan BEFORE the invite * commit is written (design D10/kygit-invite D3: no override flag). */ invite(options: GitvaultVaultHandleOptions & { address?: GitvaultRemoteAddress; role?: string; ttlSeconds?: number; /** Named org room to invite into. Omitted: the project's default room (its own id). */ roomKey?: string; note: Omit; includeSensitive?: string[]; onCommitLine?: (line: string) => void; /** Harness-derived presence labels (design D8) — never guessed by the SDK itself. */ program?: string; model?: string; /** Opaque session identity for the inviter's own presence registration (see {@link RegisterPresenceOptions.sessionKey}). */ sessionKey?: string; /** Display-only "what I'm working on" for the inviter's presence. */ task?: string; }): Promise; /** List a vault's invites (ids, kind, state, role, room, expiry, claimed_by — never the hash or envelope). */ listInvites(options: GitvaultVaultHandleOptions): Promise; /** Revoke an invite (idempotent — a second revoke of an already-revoked/claimed/expired row still answers `200`). */ revokeInvite(inviteId: string, options: GitvaultVaultHandleOptions): Promise<{ invite_id: string; state: string; }>; /** * Resume a Handoff Key: parse → ensure this machine has a wallet (the * claim is bare SIWX, so on a fresh machine the allowance file is created * here — a keypair on disk, no faucet, no tier, no payment, ever; design * D5) → claim → open the * sealed envelope → write the repo file to the keystore BEFORE touching * disk → clone at the base HEAD → `git stash apply --index` → local * git-config pins only → the session-start reconcile so a principal * envelope supersedes the bearer one. */ /** * gitvault-multi-writer rev 47 (task 5.6, design D4/D5) — order: parse → * ensure wallet → ensure identity → derive auth/wrap/admission → build * acceptance → claim → verify grant → open envelope → check the grant * hash → persist repo file with `pending_writer_admission` → clone → * verify chain → submit the ref-neutral activation head → reconcile * principal envelope → apply the checkpoint. Reported as * `writer_activation` and `reconcile_recipients` (D5's "recipient * coverage"), two blocks, because they are two properties. * * Crash-resumable by construction, not by a special-cased retry branch: * the acceptance is derived fresh every call from the (deterministic) * admission seed + this checkout's own identity, so re-running this * ENTIRE method after a crash anywhere before activation lands is safe — * the claim route is itself idempotent (same-claimant replay returns the * SAME grant). The one step that is NOT safely repeatable is submitting * the activation head a second time (the chain burns `handoff_id` * single-use) — `submitWriterActivationHead` handles that by checking * whether this checkout's own key is ALREADY in the freshly-verified * writer set before ever building a transition, exactly mirroring * `handoff()`'s own writer precheck (task 5.5), just inverted: there * "not yet a writer" refuses; here "already a writer" means skip. */ resume(options: { key: string; to?: string; keystore_root?: string; onLine?: (line: string) => void; }): Promise; /** * Join an Invite Key: parse (refusing a `kgh1_` handoff key by name, * pointing at `resume` — design D9) → ensure this machine has a wallet * (design D5, mirroring {@link Gitvault.resume}'s own bare-wallet * backstop; the CALLER folds the fuller cold-start chain — allowance, * faucet, one x402 prototype payment — before invoking this, never * blocking the claim itself) → claim → open the sealed envelope → write * the repo file to the keystore BEFORE touching disk → clone at the base * HEAD → `git stash apply --index` → local git-config pins (including * `r402.room` set to the invite's OWN room, never just the project id) → * append `.run402/` to `.git/info/exclude` → the session-start reconcile * → register THIS session's presence in the room and post ONE arrival * fact naming it and the checkpoint (idempotent on the invite id; * best-effort — a presence or fact failure never blocks arrival) → read * the most recent messages for the arrival view. */ join(options: { key: string; to?: string; keystore_root?: string; onLine?: (line: string) => void; /** Harness-derived presence labels (design D8) — never guessed by the SDK itself. */ program?: string; model?: string; /** Opaque session identity for this session's own presence registration (see {@link RegisterPresenceOptions.sessionKey}). */ sessionKey?: string; /** Display-only "what I'm working on" for this session's presence; defaults to naming the invite. */ task?: string; /** How many recent messages to read for the arrival view. Default 10. */ recentMessagesLimit?: number; }): Promise; /** * The capture-time copies a plain `git push` through the remote helper * owes exactly as {@link push} and {@link deploy} do: the opt-in mirror * dual-push and, on a BYO vault, the signed-chain copy into the customer * bucket (gitvault-byo-primary-bucket task 3.3). The helper publishes * through `GitvaultVault.push` directly, so it calls this right after a * generation lands. Same contract as the private hooks it composes: * best-effort, NEVER throws, never alters the push already committed — * each outcome is reported on its own field (`skipped_no_mirror` costs no * network call). */ postPublishCopies(input: { repo_id: string; keystore: GitvaultKeystore; }): Promise<{ mirror_push: GitvaultMirrorPushResult; byo_chain_copy: GitvaultMirrorPushResult; }>; /** * A REAL preview of what {@link push} would publish * — never publishes anything, and never allocates. `run402 gitvault * snapshot --dry-run` and `git-remote-run402`'s `option dry-run true` are * both thin adapters over this method. * * "Refusing beats fake success" (this file's own architectural law) governs * the shape of what happens when the vault does not exist yet: unlike * {@link push}, this NEVER allocates one (push-to-create must not allocate * on a dry run) — it resolves READ-ONLY (`open`/`resolveOrCreateAddress` * with `allow_create: false`), and when that resolution misses, returns * `allocation_needed: true` with every sizing field `null` rather than a * guess. Sizing is genuinely unknowable before allocation: encryption uses * a `K_repo` this project has not been assigned yet, so there is no key to * preview a push under — only the real capture (this method still runs * `captureSnapshot`, which is local, filter-free work) can be computed. * * Everything else is the SAME real local pipeline `push` runs — capture, * pack building, sealing/encryption — computed by * {@link import("../node/gitvault-publication.js").GitvaultVault.planPush}, * which is what actually stops short of the two network mutations * (`uploadObjects`, `admitHead`). See its doc comment for what * `would_admit_generation` does and does not promise. */ planPush(options: GitvaultVaultHandleOptions & { /** Same as {@link push}'s `address` — a parsed remote address. Resolved READ-ONLY (`allow_create: false`); never push-to-creates. */ address?: GitvaultRemoteAddress; snapshot?: Omit; onCommitLine?: (line: string) => void; checkpoint?: boolean; }): Promise; /** * Compaction's transient-storage preflight * (gitvault-compaction-headroom-preflight, design D1/D2). * * Three postures, in order of information quality: * 1. the check answers "won't fit" → typed refusal, before any upload; * 2. the caller passed the override → proceed, marked `overridden`; * 3. the check CANNOT be answered → proceed after one stderr note. * * (3) is deliberate: failing closed on ignorance would make compaction's * availability depend on an ADVISORY read, which is wrong for a maintenance * verb whose refusals must come from the authority that owns the quota. * * The size proxy is the vault's billed `source_bytes` (D1). The true * checkpoint-pack size is unknowable before packing, but it is bounded above * by — and usually near — the vault's live content. Deliberately * conservative when history exceeds live content; the override exists * precisely because a proxy can overestimate. */ /** * Read compaction's transient-storage arithmetic WITHOUT compacting — the * same figures `compact` preflights on, with none of its policy. `repos gc` * uses it to disclose headroom on the `--submit` half, where no compaction * runs but the numbers are just as worth showing. `null` when the pooled * figures cannot be read. */ compactHeadroom(options?: GitvaultVaultHandleOptions): Promise; /** * Open this vault's compaction headroom grant directly (gitvault-checkpoint-cadence * design D3) — `compact()` already does this internally; this standalone * entry point exists for callers that need to inspect or drive the grant * without also running a full compaction cycle (tests; a future * operator/diagnostic surface). Throws `GITVAULT_COMPACTION_GRANT_ACTIVE` * (409) verbatim when another compaction already holds this project's * grant. */ openCompactionGrant(options?: GitvaultVaultHandleOptions): Promise; /** Close this vault's compaction headroom grant directly — idempotent; `{closed: false}` when nothing was active. */ closeCompactionGrant(options?: GitvaultVaultHandleOptions): Promise<{ closed: boolean; }>; /** * Publish a checkpoint covering the canonical refs, every root unexpired at * the cutoff, and the `HEAD` target — under a maintenance lease so a * concurrent cycle cannot race it. * * The lease's `holder_token` is held in memory for the duration and released * in a `finally`; it is never returned to the caller, logged, or cached. * A repository beyond the V0 checkpoint maximum is refused at preflight with * `CHECKPOINT_SET_LIMIT_EXCEEDED`. */ compact(options?: GitvaultVaultHandleOptions & { /** Skip the lease entirely (single-writer situations, tests). */ lease?: boolean; /** * The cycle's reservation sizes. Both default to `"0"` — a ZERO * reservation, which is honest but not free: the control plane reserves * nothing, so a cycle whose objects exceed the (absent) headroom can be * refused mid-flight rather than at preflight. Supply real figures once * the caller can size the checkpoint it is about to write. */ r1_size_bytes?: string; r2_cap_size_bytes?: string; /** * Proceed even when the transient-storage preflight says the org's * pooled tier storage cannot hold both the new checkpoint and the * not-yet-pruned history. The platform's own quota enforcement remains * the authority either way — this only skips the earlier, more legible * refusal. CLI: `--force-headroom`. */ ignoreHeadroom?: boolean; }): Promise; /** * Plan a prune — and, with both verifier receipts in hand, submit it. * * TWO PHASES, because the protocol is two-phase (§7.3) and no amount of API * sugar can collapse it: * * 1. `prune()` walks the verified chain, computes the GC root set, subtracts * it from the pruneable universe, and returns a SIGNED * `prune_intent_core` plus its `intent_core_sha256`. Nothing is * submitted and nothing is deleted. * 2. Run `r402s-verify` against that core, then call * `prune({ submit: { core, verifier_receipt } })` with the core * ROUND-TRIPPED VERBATIM. This SDK produces its own `run402-cli` receipt * by restoring the latest checkpoint and recomputing its commitments; * the second receipt is `r402s-verify`'s and is never synthesized here, * because two receipts from one lineage prove nothing. * * The `deleted` list in the result comes from the control-plane-signed * completion and nothing else. `present_after_attempt` is a FAILED deletion * and is never counted as one. */ prune(options?: GitvaultVaultHandleOptions & { now?: () => Date; /** * `effective_admitted_at` for a root's drop generation, or `null` when * this client cannot resolve it. A `null` RETAINS the root: expiry is * permissive, and `effective_admitted_at = max(prepared_at, the admission * record's storage creation time)` — deriving it from `prepared_at` alone * would SHORTEN the retention lane, which the protocol forbids. */ effective_admitted_at?: (droppedAtGeneration: string) => string | null; /** * Submit the plan. `core` MUST be the exact object a prior `prune()` * returned, and `verifier_receipt` MUST be `r402s-verify`'s receipt over * that core's `intent_core_sha256`. */ submit?: { core: import("../node/gitvault-prune.js").GitvaultPruneIntentCore; verifier_receipt: import("../node/gitvault-prune.js").GitvaultVerifierReceipt; /** Poll budget for the signed completion. Absent ⇒ submit and report the intent without waiting. */ wait?: { attempts?: number; interval_ms?: number; }; }; }): Promise; /** * Verify the head chain from the authenticated pin up to the newest listed * generation, then return the verified state. * * Fails closed: a regression below the pin is `GENERATION_REGRESSION`, a gap * or bad link is `CHAIN_BROKEN`, an unvalidatable transition descriptor is * `UPGRADE_REQUIRED` (read-only past it, never skipped). The verification * budget is resumable — a `VERIFICATION_BUDGET_EXCEEDED` client continues * from its verified prefix rather than restarting. */ verify(options?: GitvaultVaultHandleOptions & { persist?: boolean; }): Promise; /** * `repos fsck` (repo-surface-consolidation D2/D3): walk the head chain * AND materialize the ref map, reporting * BOTH local trust pins — authenticated and materialized — before and * after, with an explicit `local_state_changed` flag. This is the one * place chain materialization and pin advance live; `view` never * calls it. * * `options.write` (default `true`) is the inverse of the CLI's * `--no-write`: `false` still walks, verifies, and decrypt-validates * everything (the returned `refs`/`chain_verified_to_generation`/ * `decryptable_to_generation` are real, computed answers, not estimates) * but persists neither pin — a genuine audit mode, not a simulation. * `options.mirror` additionally runs the keyless mirror verification * ({@link mirrorVerify}) and folds its report in; that half proves the * mirror's validity, never its freshness (its own honesty statements ride * the result unchanged). * * **Request 1 (decrypt-validation, D193-D203 rev 42).** `fsck` walks the * chain in TOLERANT decrypt-validation mode * (`verifyToNewest({decryptValidate: true, strict: false})`) rather than * the ordinary fail-closed `materialize()` — chain/signature verification * ALWAYS reaches the true newest generation regardless of decrypt * capability (an admitted `rotate_epoch` is keylessly structural), and a * generation this call cannot actually DECRYPT is never again reported as * bare "verified": `chain_verified_to_generation` and * `decryptable_to_generation` are reported separately and can genuinely * differ — the incident-shape truth ("chain verified to 10, decryptable * to 7, `GITVAULT_EPOCH_NOT_OPENABLE` at epoch 2/rotation …") is exactly * what this split makes representable. `refs`/`head_target`/ * `pin_after.highest_materialized` all reflect `decryptable_to_generation`. * * **gitvault-byo-primary-bucket task 3.3 (design D6).** For a * `storage_profile: "byo"` vault, `fsck` ALSO adjudicates the customer's * own bucket against run402's signed chain — see {@link * #checkByoPresence} for the full contract (automatic, never a flag; * fails soft with no local credentials; throws * `GITVAULT_BYO_OBJECT_MISSING` on a confirmed absence). A managed vault * is byte-identical to before this task. */ fsck(options?: GitvaultVaultHandleOptions & { write?: boolean; mirror?: boolean; }): Promise; /** * Wrap this vault's current epoch key to every org member who has * published an encryption key but has no `key_envelope` on this vault yet * — gitvault-human-envelopes task 4.1's ADD-path workaround. See {@link * import("../node/gitvault-publication.js").GitvaultVault. * reconcileEnvelopeRecipients} for the full design (TOFU pinning, the * gateway `public_key` gap, and why this is a workaround rather than the * eventual epoch-rotation design). * * `run402 gitvault reconcile` has no `repos` equivalent — `reconcile` is * a workaround, not a permanent verb — and answers `COMMAND_REMOVED` * pointing at `repos access` for inspection. This method itself stays: * `deploy()` runs it, best-effort, * whenever a deploy lands a new generation in the vault — design D5's * "deploy time" hook, "the same 'one command every agent runs' argument * that decided deploy-implies-capture." `push()` (capture-and-publish * outside a deploy) runs the identical hook after every successful * publish, for the vault-only-project cadence. See * `#tryReconcileEnvelopeRecipients` below for both call sites, and * {@link Gitvault.access} for the READ half repo-surface-consolidation * ships in its place. */ reconcileEnvelopeRecipients(options?: GitvaultVaultHandleOptions): Promise; /** * gitvault-multi-writer (task 5.7) — admit every eligible `pending_writers[]` * candidate (active org membership at role developer+, a published, * possession-verified signing key, not yet in the writer set) via a fresh * `add_writer_key{"writer"}` head per candidate. This session's OWN key * must already be an active writer — see {@link * import("../node/gitvault-publication.js").GitvaultVault. * reconcileWriterAdmissions}'s doc comment for why that is checked * locally, once, rather than surfaced as N identical gateway refusals. * * The explicit standalone entry point for the SAME reconcile task 5.7 * also wires onto session-start/push/snapshot/deploy — calling it here * is idempotent-safe alongside whatever `open()` above already ran * internally (an already-admitted candidate is simply reported under * `already_covered`, never re-admitted), mirroring {@link * reconcileEnvelopeRecipients}'s own identical redundancy-tolerant shape. */ reconcile(options?: GitvaultVaultHandleOptions): Promise; /** * Drive one epoch rotation (D193-D203, rev 42) — the client half of * epoch rotation: sample a fresh `K_e`, compute the H-partition from live * desired-recipient state + the effective pin manifest, seal one * `key_envelope` per included recipient, submit the create-only * `rotation_attempt_descriptor`, submit the `rotate_epoch` head, verify * this principal's own envelope opens to the committed key (when it is * itself a recipient), and advance the local keystore's epoch pointer. * See {@link import("../node/gitvault-publication.js").GitvaultVault. * rotateEpoch}'s own doc comment for the full obligations and — load- * bearing — the confirmed gap in what the gateway exposes for * `recipient_state_version`/`recipient_revocation_version` outside the * `recipient_key_revoked` reason, and (also load-bearing) `options. * pending_confirmations` for folding a receipted `/confirm`/`/repin` * result into THIS rotation's head instead of a separately-gated * `publishPinManifestUpdate` call. */ rotateEpoch(options: GitvaultVaultHandleOptions & { reason: GitvaultRotationReason; recipient_state_version: string; recipient_revocation_version: string; client_idempotency_key?: string; pending_confirmations?: { principal_id: string; ek_fingerprint: string; receipt: GitvaultRecipientConfirmationReceipt; }[]; }): Promise; /** * The ONE fully self-contained rotation entry point: declares * `reason:"recipient_key_revoked"` for `principalId` (owner + step-up) * and drives the rotation from that call's OWN returned counters — no * external counter source needed. The rekey remedy for the exact witness * task 5.0 records: a specific member's key is compromised/should no * longer be trusted. */ /** * The writer-capable rotation that completes an org membership removal * (`reason:"member_removed"`, gitvault-multi-writer D6): counters come off * the envelope-recipients read, no declaration, no owner step-up — any * surviving writer can run it. `push()` runs it automatically when the * gate names an outstanding removal; this is the explicit entry point * (`run402 org member rm` drives it on every vault the caller can). */ rotateEpochForMemberRemoval(options?: GitvaultVaultHandleOptions & { client_idempotency_key?: string; }): Promise; rotateEpochForKeyRevocation(principalId: string, options?: GitvaultVaultHandleOptions & { client_idempotency_key?: string; }): Promise; /** * Publish a receipted `recipient_pin_manifest` update (D197) — the * publication half of the `/confirm`/`/repin` ceremonies above. Call * after {@link confirmRecipient}/{@link repinRecipient} returns a receipt; * this is `gitvault.writer`-sufficient (the owner-gated half already * happened at the ceremony route). * * **This publish is an ORDINARY admission** and is therefore itself * refused `EPOCH_ROTATION_REQUIRED` while this vault has a migration/ * revocation/exposure condition outstanding (D193). * `#enrichEpochRotationRequiredForPinManifest` * below decorates that refusal with the remedy: fold the SAME receipt * into `rotateEpoch({..., pending_confirmations: [...]})` instead, which * durably publishes it on a `rotate_epoch` admission (the gate's own * escape valve) rather than a separately-gated ordinary one. */ publishPinManifestUpdate(input: { principal_id: string; ek_fingerprint: string; receipt: GitvaultRecipientConfirmationReceipt; } & GitvaultVaultHandleOptions): Promise; /** * `repos access` (repo-surface-consolidation D5/D10) — a READ-ONLY report * of who can open this vault, composed from whatever the live gateway * surface exposes today. Never wraps, never mutates a `key_envelope` — * that mutating half stays {@link reconcileEnvelopeRecipients}, reachable * only through the deploy/push best-effort hooks until `access repair` * ships (gated on `gitvault-human-envelopes`' epoch-rotation work). See * {@link GitvaultAccessResult}'s doc comment for the honest gap this * reports rather than invents. */ access(options?: GitvaultVaultHandleOptions): Promise; /** * The push-gated deploy: both lanes under one fresh `capture_id`, resolving * to exactly one of five outcomes — `DEPLOYED_AND_VAULTED`, * `DEPLOY_BLOCKED_PUSH_FAILED`, `DEPLOY_FAILED_VAULTED`, * `DEPLOY_FAILED_UNVAULTED`, `DEPLOYED_UNVAULTED_OVERRIDE`. * * The push is never gated on deploy success, and a build that fails before a * canonical apply plan exists still captures (with a null plan digest, so no * activation token can be minted from it). * * `run402 deploy` is design D5's PRIMARY envelope-recipient reconcile hook * ("the same 'one command every agent runs' argument that decided * deploy-implies-capture") and design D6's primary dual-push mirror trigger * — both fire HERE, not inside {@link runGitvaultDeploy} itself, mirroring * {@link push}'s exact non-blocking contract: best-effort, NEVER throw, * NEVER alter the deploy outcome already resolved above, reported BESIDE it * on `mirror_push` / `reconcile_recipients`. They fire only when this * deploy actually landed a new generation in the vault * (`DEPLOYED_AND_VAULTED` / `DEPLOY_FAILED_VAULTED` — the outcomes carrying * a `generation`); the other three outcomes published nothing new, so * there is nothing to mirror or reconcile against ({@link * import("../node/gitvault-mirror.js").mirrorPushForGeneration}'s own * contract: "fires after an ordinary vault push/deploy produces a new * generation"). Both fields are therefore OMITTED — never a faked * `skipped_*` outcome — when no generation landed. */ deploy(options: Omit & GitvaultVaultHandleOptions): Promise; /** * Drain every unvaulted-override journal on this machine: push the exact * journaled snapshot and present the capture receipt for full-field * comparison. A partial match never clears the advisory. */ drainOverrides(options?: GitvaultVaultHandleOptions): Promise; /** * Reproduce the vault's object database into a git repository — the clone-back * path. Needs only git and a surviving keystore: no deployment artifact, CAS * entry, or apply operation is consulted. */ restore(options: GitvaultVaultHandleOptions & { target_dir: string; }): Promise<{ refs: Record; generation: string; retained_refs: import("../node/gitvault-publication.js").GitvaultRetainedRefsReconcileResult; }>; /** * Wrap an ALREADY-RESOLVED vault read with degraded-read fallback: run * `options.attemptLive` (the caller's own live call — its * `vault.materialize()` for a `list`-shaped read, or * `vault.restoreObjectsInto(dir)` for a `fetch`-shaped one), and on a * network-class failure (never on an authorization refusal or any other * 4xx — see `isNetworkClassGitvaultReadError`), fall back to the vault's * configured mirror via the SAME `r402s-recover` engine `recover()` uses. * * Deliberately takes an already-open `vault`/`keystore`/`repo_id` rather * than resolving them itself: named-address resolution (slug-form vs. * id-form), stale-pin recovery, and cross-command vault-instance reuse * within one `git-remote-run402` session are CLI-layer concerns that * already live in `cli/lib/remote-helper-session.mjs` — this method owns * only the trigger discipline and the fallback engine, never vault * resolution, so it composes with that existing flow instead of * duplicating it. * * `out_dir` is REQUIRED for the fallback to ever run — pass the resolved, * git-proven repository directory (`resolveGitInvocationRepo`'s own * `repo_dir`, never a guess from `cwd`). `null` (no resolvable repository, * e.g. a bare `git ls-remote` outside any checkout) disables the fallback * entirely: the original gateway error surfaces exactly as it always did. */ withDegradedRead(options: { attemptLive: () => Promise; keystore: GitvaultKeystore; repo_id: string; out_dir: string | null; }): Promise>; /** * gitvault-byo-primary-bucket (design D4): the degraded read for the OPEN * itself. {@link withDegradedRead} wraps a handle's live read, but a plain * `git fetch`/`clone` needs the gateway once BEFORE any read — to open the * handle (enrollment, the vault record). When that open fails * network-class and this checkout already knows the vault — a pinned * `repo_id`, or the keystore's own project→repo file for an id-form * address (gitvault-offline-clone-resolve) — the whole read is served from * the vault's BYO destination or configured mirror, exactly as * `withDegradedRead` would have after a failed live read. `null` when this * cannot help (a non-network failure, no local identity for the vault, no * local copy source), so the caller rethrows its ORIGINAL error unchanged. * Writes are never rerouted. */ degradedOpenFallback(input: { repo_dir: string; error: unknown; address?: import("../node/gitvault-address.js").ResolveGitvaultAddressOptions["address"]; repo_id?: string; }): Promise | null>; /** * Configure (or replace) the customer-owned mirror destination for one * vault. Client-side ONLY — the destination + credential NAME are written * beside the keystore (design D2); run402 never sees or stores a raw * secret value. `s3:///` needs `credential`; a plain * filesystem path needs none. */ mirrorSet(options: GitvaultVaultHandleOptions & { destination_url: string; credential?: GitvaultMirrorCredential; region?: string; endpoint?: string; }): Promise; /** Remove the mirror config for one vault. Never touches the mirror's own bytes (design: config removal ≠ data deletion). */ mirrorRemove(options?: GitvaultVaultHandleOptions): Promise<{ repo_id: string; removed: boolean; }>; /** * What this machine and the mirror each believe: whether a mirror is * configured, the newest generation the MIRROR holds and can chain-verify * (keyless — never touches keys), the LIVE vault's newest generation (one * read of the vault record), and — when they disagree — the closing * command. Both honesty statements (design D8) ride every response. */ mirrorStatus(options?: GitvaultVaultHandleOptions & { is_byo?: boolean; }): Promise; /** List the vault's stored objects, diff against the mirror, fetch + hash-verify + write what's missing, in admission order. Resumable and idempotent. */ mirrorSync(options?: GitvaultVaultHandleOptions): Promise; /** Keyless integrity probe against the CONFIGURED mirror: discovery + chain verification + closure/absence adjudication, never decryption (`run402 repos fsck --mirror`). */ mirrorVerify(options?: GitvaultVaultHandleOptions): Promise; /** * `r402s-recover` (design D4): rebuild a working git repository straight * from a mirrored `source//` prefix — NO SERVER INVOLVED. `source` * is `s3://[/]` or a local directory; when it names more * than one mirrored vault, pass `repo_id` explicitly. Recovery proves * validity, never freshness (both honesty statements ride every result). * * gitvault-recovery-custody: a human member with no keystore recovers with * `member_bundle` (the exported `r402s-member-recovery-bundle/v1`, or the * mirror's own `member-recovery-bundles/` sidecar when omitted) + * `source_recovery_code`; a raw WebAuthn PRF output is NOT a supported * input. The recovery-receipt pin stays the trust anchor either way — * pass `recovery_receipt` when there is no keystore holding one. */ recover(options: { source: string; out_dir: string; repo_id?: string; credential?: GitvaultMirrorCredential; region?: string; endpoint?: string; recovery_receipt?: GitvaultRecoveryReceipt; keystore_root?: string; member_bundle?: GitvaultMemberRecoveryBundle; source_recovery_code?: string; rp_id?: string; }): Promise; /** * Re-apply the local immutable-object cache's eviction window (design D3, * task 4.2) for one vault — a periodic backstop wired into `run402 repos * gc`, catching any orphaned cache file a crashed write might have left * behind. Purely local: no network call beyond resolving `repo_id` when * only `project_id` was supplied. Idempotent and safe to call on a repo * with no cache directory at all (a no-op). */ sweepObjectCache(options: GitvaultVaultHandleOptions): Promise; } /** * The composite escalation trigger (design D7): ANY of these crossing its * threshold escalates the quiet genesis-time note into a standing warning. * Deliberately an OR — any single metric alone is gameable by triviality * (an agent that pads one dimension to stay under a lone threshold). Shipped * as defaults; tunable in this ONE place, not a protocol contract. */ export declare const GITVAULT_LOSS_WARNING_THRESHOLDS: { readonly generations: 10; readonly source_bytes: number; readonly days_since_genesis: 14; }; /** * gitvault-clone-scaling (bench P3): generations-since-checkpoint at or * above this advises `run402 repos gc`. Same one-place-to-tune convention * as {@link GITVAULT_LOSS_WARNING_THRESHOLDS}; an advisory, never a gate. */ export declare const GITVAULT_CHECKPOINT_ADVISORY_GENERATIONS = 25; /** {@link gitvaultCheckpointStaleness}'s result. */ export interface GitvaultCheckpointStaleness { generations_since_checkpoint: number; advised: boolean; } /** * Pure staleness computation over two 16-hex generations. TOTAL: garbage * or unknown coverage (`null`) yields `{0, advised: false}` — advisory * call sites must never be able to break a verb, so unparseable input is * silence, not an error. Coverage `null` means "this checkout has not * locally learned the vault's checkpoint coverage" (see the keystore's * `checkpoint_covers_through` doc), which is deliberately indistinguishable * from fresh here. */ export declare function gitvaultCheckpointStaleness(input: { newest_generation: string; covers_through_generation: string | null; }): GitvaultCheckpointStaleness; /** Which composite metric(s) crossed their D7 threshold, if any. */ export interface GitvaultLossWarningTrip { generations: boolean; source_bytes: boolean; days_since_genesis: boolean; } /** * D7: has this vault crossed the composite terminal-loss escalation trigger? * Pure — no I/O, no clock dependency beyond the optional `now` override * (tests). `admitted_generations` and `storage.source_bytes` are decimal * strings (protocol convention for values that could exceed safe-integer * precision in principle; comfortably within it here). * * There is deliberately no companion "is this resolved" function on the * TRIP computation itself: whether the generations/bytes/days thresholds * crossed is independent of how many principals cover the vault, and this * function stays a pure threshold check forever — a caller does not un-trip * it, ever, in V0. What DOES now exist, one call site up in `status()`, is a * way to downgrade the COPY once tripped: a locally-provable read of the * vault's covering-recipient count (the same envelope-recipients read * `Gitvault.access` uses) can show this is no longer the single-principal * case the message below describes. See * {@link gitvaultKeystoreBackupReminderMessage} for that downgraded form. */ export declare function gitvaultLossWarningTrip(record: Pick, now?: Date): GitvaultLossWarningTrip; /** Any composite metric tripped — the standing warning threshold itself. */ export declare function gitvaultLossWarningTripped(trip: GitvaultLossWarningTrip): boolean; /** * The standing warning text (design D7): names what tripped, states the * resolution honestly (a second principal or human envelope — nothing this * client can verify yet), and never claims an attestation would clear it. * * This is the SINGLE-PRINCIPAL form — use it only when the vault's * covering-recipient count is unknown or <= 1. Once a caller has locally * proven >= 2 covering recipients, its premise ("only one principal can open * it") is false; use {@link gitvaultKeystoreBackupReminderMessage} instead. */ export declare function gitvaultLossWarningMessage(trip: GitvaultLossWarningTrip): string; /** * D7's downgraded form (dogfood item 2): once `status()` has locally proven * this vault carries >= 2 covering recipients (via the same envelope-recipients * read `Gitvault.access` uses), the single-principal premise behind * {@link gitvaultLossWarningMessage} is false for THIS vault — printing it * anyway would be a false terminal-loss claim. The composite threshold still * fires the SAME way (this function does not touch `gitvaultLossWarningTrip`/ * `gitvaultLossWarningTripped` at all — a real value-at-risk signal is still * worth a reminder), but the copy switches to the protocol's own * keystore-qualified durability sentence instead of asserting single-principal * risk, and never claims terminal loss. */ export declare function gitvaultKeystoreBackupReminderMessage(trip: GitvaultLossWarningTrip, coveringRecipients: number): string; /** * gitvault-mirror-default — the pure `vault_unmirrored` computation, in ONE * place so doctor and `repos view` echo the same finding instead of each * deriving their own (the loss-warning pattern above). Present when no mirror * is configured, OR when one is configured but has no success evidence yet — * either the local `last_success_at` stamp (survives a transiently unreachable * mirror) or a chain-verified `mirrored_generation` read from the mirror * itself (covers mirrors synced before the stamp existed). Informational, * never blocking; every input is client-local or read from the CUSTOMER'S * mirror — nothing here touches the gateway. */ export declare function gitvaultUnmirroredFinding(state: { configured: boolean; last_success_at: string | null; mirrored_generation: string | null; /** gitvault-byo-primary-bucket task 3.5 — when true, `message` uses the BYO remedy wording (a SECOND customer-held location) instead of the plain unmirrored statement. */ is_byo?: boolean; }): GitvaultUnmirroredFinding | null; /** * gitvault-byo-primary-bucket (design D4) — the ONE stderr line a degraded * chain/payload read prints: the fallback's own destination (never a * credential) plus the canonical, mechanism-only statement. `list`/`fetch` * degrading in the SAME `git-remote-run402` session each print their own * line (one per degraded READ, not one per session) — see * `Gitvault.withDegradedRead`'s own doc comment. */ export declare function gitvaultDegradedReadNote(source: GitvaultDegradedReadSource): string; /** * The remote door (kygit-handoff design D8): `"run402"` (the canonical, * plumbing spelling — accepted forever) or `"kygit"` (what the * `@kychee/kygit` shim renders once it sets `RUN402_REMOTE_SCHEME=kygit` * before exec). The gateway never sees this — `address` and every registry * `next_actions` command stay `run402::`; only client-side RENDERING reads * it. Any other value falls back to `"run402"` rather than emitting an * unparseable scheme. */ export declare function gitvaultRemoteScheme(): "run402" | "kygit"; /** `::/` — what `git-remote-run402`/`git-remote-kygit` resolves. */ export declare function gitvaultRemoteUrl(orgId: string, projectId: string): string; /** * `::/` — the address-form remote builder * (repo-first-onramp task 4, design D6). Same string shape as * {@link gitvaultRemoteUrl} (the wire slot admits both forms undiscriminated * — see {@link gitvaultRemoteAddressForm}); kept as its own named function so * a call site states which form it means rather than reusing the id-form * builder for a semantically different pair of arguments. Rendered by * {@link gitvaultRemoteScheme} (kygit-handoff design D8) — `run402 repos * create` renders `run402::`, `kygit create` renders `kygit::`. */ export declare function gitvaultRemoteUrlForRepo(orgSlug: string, repoName: string): string; /** What {@link parseGitvaultRemoteUrl} returns — the two undiscriminated address halves. */ export interface GitvaultRemoteAddress { org_id: string; project_id: string; } /** * Parse a `run402::/` OR `kygit::/` remote URL * (kygit-handoff design D8) into ONE canonical, scheme-less address — the * door never changes resolution, only rendering. `null` when it is neither. */ export declare function parseGitvaultRemoteUrl(url: string): GitvaultRemoteAddress | null; /** Which address form a parsed `run402::` remote is (repo-first-onramp task 4, design D6). */ export type GitvaultRemoteAddressForm = "id" | "slug"; /** * Discriminate a parsed address's form (design D6, "resolved"): "org UUIDs * and `prj_` prefixes make slug-vs-id in the same slot unambiguous." Both * halves must look id-shaped for the address to be treated as id-form — a * genuine `run402::/` address always satisfies both at * once, since real org ids are UUIDs and real project ids are always * `prj_`-prefixed; anything else (an org slug half, or a bare repo-name half) * is slug-form. Resolution accepts either (task 4.3). */ export declare function gitvaultRemoteAddressForm(address: GitvaultRemoteAddress): GitvaultRemoteAddressForm; /** What a `SLUG_RELEASED` refusal names — the successor slug and the cooldown window (design D6). */ export interface GitvaultSlugReleasedInfo { successor_slug: string | null; released_at: string | null; cooldown_until: string | null; } /** * `SLUG_RELEASED` (a renamed/deleted org slug, still inside its ~90-day * cooldown) surfaces as a typed, actionable fact — NEVER auto-followed * (design D6: "no redirects, deliberately"). `null` when `err` is not a * `SLUG_RELEASED` refusal. */ export declare function gitvaultSlugReleasedInfo(err: unknown): GitvaultSlugReleasedInfo | null; //# sourceMappingURL=gitvault.d.ts.map