run402 4.68.2 → 4.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/cli.mjs +2 -2
  2. package/core-dist/allowance-auth.js +1 -1
  3. package/core-dist/allowance.js +1 -1
  4. package/core-dist/config.js +4 -4
  5. package/core-dist/keystore.js +7 -7
  6. package/core-dist/profiles.js +1 -1
  7. package/core-dist/redact.js +6 -7
  8. package/git-remote-run402.mjs +2 -2
  9. package/gitvault-surface.json +2 -1
  10. package/lib/agent.mjs +1 -1
  11. package/lib/allowance.mjs +3 -4
  12. package/lib/argparse.mjs +7 -9
  13. package/lib/billing.mjs +33 -35
  14. package/lib/buzz-notifications.mjs +3 -2
  15. package/lib/buzz.mjs +14 -8
  16. package/lib/buzz.test.mjs +1 -1
  17. package/lib/command-manifest.mjs +38 -20
  18. package/lib/config.mjs +1 -1
  19. package/lib/deploy-v2.mjs +3 -3
  20. package/lib/doctor-source-scan.mjs +1 -1
  21. package/lib/doctor.mjs +42 -25
  22. package/lib/email.mjs +2 -3
  23. package/lib/harness-context.mjs +1 -2
  24. package/lib/init.mjs +1 -1
  25. package/lib/next-actions.mjs +1 -1
  26. package/lib/org-context.mjs +83 -2
  27. package/lib/org.mjs +231 -114
  28. package/lib/projects.mjs +2 -2
  29. package/lib/remote-helper-session.mjs +26 -32
  30. package/lib/repos.mjs +117 -15
  31. package/lib/rooms-context.mjs +5 -5
  32. package/lib/sdk-errors.mjs +1 -2
  33. package/lib/status.mjs +1 -1
  34. package/lib/update-check.test.mjs +1 -1
  35. package/lib/wallet-context.mjs +10 -10
  36. package/lib/wallet-context.test.mjs +6 -7
  37. package/lib/wallets.mjs +2 -2
  38. package/lib/webhooks.mjs +2 -2
  39. package/package.json +1 -1
  40. package/sdk/core-dist/allowance-auth.js +1 -1
  41. package/sdk/core-dist/allowance.js +1 -1
  42. package/sdk/core-dist/config.js +4 -4
  43. package/sdk/core-dist/keystore.js +7 -7
  44. package/sdk/core-dist/profiles.js +1 -1
  45. package/sdk/core-dist/redact.js +6 -7
  46. package/sdk/dist/delegate-credentials.d.ts +1 -1
  47. package/sdk/dist/delegate-credentials.js +1 -1
  48. package/sdk/dist/errors.d.ts +10 -4
  49. package/sdk/dist/errors.d.ts.map +1 -1
  50. package/sdk/dist/errors.js +2 -3
  51. package/sdk/dist/errors.js.map +1 -1
  52. package/sdk/dist/kernel.js +1 -1
  53. package/sdk/dist/kernel.js.map +1 -1
  54. package/sdk/dist/namespaces/allowance.d.ts +1 -1
  55. package/sdk/dist/namespaces/allowance.js +1 -1
  56. package/sdk/dist/namespaces/allowance.js.map +1 -1
  57. package/sdk/dist/namespaces/assets.js +1 -1
  58. package/sdk/dist/namespaces/assets.js.map +1 -1
  59. package/sdk/dist/namespaces/assets.types.d.ts +1 -1
  60. package/sdk/dist/namespaces/ci.d.ts.map +1 -1
  61. package/sdk/dist/namespaces/ci.js +4 -6
  62. package/sdk/dist/namespaces/ci.js.map +1 -1
  63. package/sdk/dist/namespaces/deploy.js +7 -8
  64. package/sdk/dist/namespaces/deploy.js.map +1 -1
  65. package/sdk/dist/namespaces/gitvault.crypto.d.ts +86 -3
  66. package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
  67. package/sdk/dist/namespaces/gitvault.crypto.js +102 -3
  68. package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
  69. package/sdk/dist/namespaces/gitvault.d.ts +197 -17
  70. package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
  71. package/sdk/dist/namespaces/gitvault.js +698 -58
  72. package/sdk/dist/namespaces/gitvault.js.map +1 -1
  73. package/sdk/dist/namespaces/gitvault.types.d.ts +91 -3
  74. package/sdk/dist/namespaces/gitvault.types.d.ts.map +1 -1
  75. package/sdk/dist/namespaces/gitvault.types.js.map +1 -1
  76. package/sdk/dist/namespaces/operator.d.ts +1 -1
  77. package/sdk/dist/namespaces/operator.js +1 -1
  78. package/sdk/dist/namespaces/org.d.ts +14 -1
  79. package/sdk/dist/namespaces/org.d.ts.map +1 -1
  80. package/sdk/dist/namespaces/org.js +15 -0
  81. package/sdk/dist/namespaces/org.js.map +1 -1
  82. package/sdk/dist/namespaces/org.types.d.ts +30 -0
  83. package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
  84. package/sdk/dist/namespaces/projects.types.d.ts +1 -1
  85. package/sdk/dist/namespaces/subdomains.js +1 -1
  86. package/sdk/dist/namespaces/subdomains.js.map +1 -1
  87. package/sdk/dist/namespaces/transfers.d.ts +3 -4
  88. package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
  89. package/sdk/dist/namespaces/transfers.js +1 -2
  90. package/sdk/dist/namespaces/transfers.js.map +1 -1
  91. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  92. package/sdk/dist/node/actions-node.js +3 -4
  93. package/sdk/dist/node/actions-node.js.map +1 -1
  94. package/sdk/dist/node/deploy-manifest.js +2 -2
  95. package/sdk/dist/node/deploy-manifest.js.map +1 -1
  96. package/sdk/dist/node/gitvault-address.js +2 -2
  97. package/sdk/dist/node/gitvault-address.js.map +1 -1
  98. package/sdk/dist/node/gitvault-creation-journal.d.ts +1 -1
  99. package/sdk/dist/node/gitvault-creation-journal.js +2 -2
  100. package/sdk/dist/node/gitvault-creation-journal.js.map +1 -1
  101. package/sdk/dist/node/gitvault-deploy.d.ts +2 -2
  102. package/sdk/dist/node/gitvault-deploy.js +2 -2
  103. package/sdk/dist/node/gitvault-handoff.d.ts +142 -3
  104. package/sdk/dist/node/gitvault-handoff.d.ts.map +1 -1
  105. package/sdk/dist/node/gitvault-handoff.js +251 -21
  106. package/sdk/dist/node/gitvault-handoff.js.map +1 -1
  107. package/sdk/dist/node/gitvault-keystore.d.ts +78 -3
  108. package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -1
  109. package/sdk/dist/node/gitvault-keystore.js +46 -5
  110. package/sdk/dist/node/gitvault-keystore.js.map +1 -1
  111. package/sdk/dist/node/gitvault-mirror.d.ts.map +1 -1
  112. package/sdk/dist/node/gitvault-mirror.js +3 -5
  113. package/sdk/dist/node/gitvault-mirror.js.map +1 -1
  114. package/sdk/dist/node/gitvault-profile-scan.js +1 -1
  115. package/sdk/dist/node/gitvault-publication.d.ts +379 -21
  116. package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
  117. package/sdk/dist/node/gitvault-publication.js +664 -41
  118. package/sdk/dist/node/gitvault-publication.js.map +1 -1
  119. package/sdk/dist/node/gitvault-recover.d.ts.map +1 -1
  120. package/sdk/dist/node/gitvault-recover.js +7 -10
  121. package/sdk/dist/node/gitvault-recover.js.map +1 -1
  122. package/sdk/dist/node/gitvault-snapshot.d.ts +1 -2
  123. package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -1
  124. package/sdk/dist/node/gitvault-snapshot.js +1 -2
  125. package/sdk/dist/node/gitvault-snapshot.js.map +1 -1
  126. package/sdk/dist/node/gitvault-writer-state.d.ts +201 -0
  127. package/sdk/dist/node/gitvault-writer-state.d.ts.map +1 -0
  128. package/sdk/dist/node/gitvault-writer-state.js +357 -0
  129. package/sdk/dist/node/gitvault-writer-state.js.map +1 -0
  130. package/sdk/dist/node/index.d.ts +4 -0
  131. package/sdk/dist/node/index.d.ts.map +1 -1
  132. package/sdk/dist/node/index.js +20 -1
  133. package/sdk/dist/node/index.js.map +1 -1
  134. package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
  135. package/sdk/dist/node/paid-fetch.js +3 -4
  136. package/sdk/dist/node/paid-fetch.js.map +1 -1
  137. package/sdk/dist/node/sites-node.js +1 -1
  138. package/sdk/dist/node/sites-node.js.map +1 -1
@@ -277,9 +277,15 @@ export declare function checkChainLink(input: GitvaultChainLinkInput): void;
277
277
  * rotation is ACTIVATED") — `checkChainLink`'s own D194 epoch-continuity
278
278
  * check already validates its structural admissibility, and
279
279
  * {@link parseRotateEpochPayload} / the caller's own envelope-open step
280
- * (`GitvaultVault.verifyToNewest`) handle it fully. The other three kinds
281
- * (`add_envelope`, `add_writer_key`, `transfer_binding`) remain genuinely
282
- * unactivated and stay fail-closed exactly as before.
280
+ * (`GitvaultVault.verifyToNewest`) handle it fully. `add_writer_key` is
281
+ * EXEMPT as of rev 47 (gitvault-multi-writer) the same way — {@link
282
+ * parseAddWriterKeyPayload} + `validateAddWriterKeyPayload`
283
+ * (`gitvault-writer-state.js`) handle it fully, INSIDE `verifyToNewest`'s
284
+ * own loop, BEFORE this function ever runs on that head (so a transition
285
+ * that fails writer validation never reaches here at all — this function
286
+ * only ever sees ones that already passed). The other two kinds
287
+ * (`add_envelope`, `transfer_binding`) remain genuinely unactivated and stay
288
+ * fail-closed exactly as before.
283
289
  */
284
290
  export declare function assertNoTransition(head: GitvaultHead): void;
285
291
  /** One object to upload — identity fixed BEFORE the PUT; the receipt is compared against it. */
@@ -439,8 +445,7 @@ export interface GitvaultCompactionGrant {
439
445
  * client-declared. NOTE the byte fields arrive as STRINGS on the wire (the
440
446
  * gateway serializes Postgres BIGINTs as strings, verified live) — consumers
441
447
  * MUST `Number(...)` before arithmetic; `Number.isFinite` on the raw value
442
- * is false and silently discards the grant (the bug the live acceptance
443
- * run caught on 2026-08-31).
448
+ * is false and silently discards the grant.
444
449
  */
445
450
  granted_bytes: number | string;
446
451
  expires_at: string;
@@ -799,8 +804,8 @@ export interface GitvaultTransport extends GitvaultCreationTransport {
799
804
  *
800
805
  * The gateway route (`GET /orgs/v1/:org_id/encryption-keys`, `routes/org.ts`
801
806
  * in run402-private) returns `public_key` on every row — the raw key
802
- * material is what makes the directory usable for wrapping at all
803
- * (deployed 2026-08-26). The field stays OPTIONAL in this wire type the
807
+ * material is what makes the directory usable for wrapping at all.
808
+ * The field stays OPTIONAL in this wire type the
804
809
  * same way `desired[]` does on {@link GitvaultEnvelopeRecipientsResponse}:
805
810
  * a response is network data, and an older/rolling-deploy gateway that
806
811
  * omits the field must degrade to a per-entry `skipped` report
@@ -815,6 +820,16 @@ export interface GitvaultOrgEncryptionKeyEntry {
815
820
  created_at: string;
816
821
  /** Raw base64url X25519 public key. Present on every current-gateway row; tolerated as absent (per-entry `missing_public_key` skip) for wire robustness only. */
817
822
  public_key?: string;
823
+ /**
824
+ * gitvault-multi-writer (rev 47) D9 — the SAME row's vault-WRITER signing
825
+ * half (`routes/org.ts` in run402-private, deployed alongside `public_key`
826
+ * D9). Raw base64url Ed25519 public key; `null` when this principal has
827
+ * never published a signing half (a pre-rev47 or encryption-only
828
+ * enrollment), absent entirely on an older gateway that predates D9.
829
+ */
830
+ signing_pubkey?: string | null;
831
+ signing_fingerprint?: string | null;
832
+ signing_possession_verified_at?: string | null;
818
833
  [key: string]: unknown;
819
834
  }
820
835
  /** `GET /orgs/v1/:org_id/encryption-keys` — {@link GitvaultTransport.listOrgEncryptionKeys}'s result. */
@@ -905,11 +920,79 @@ export interface GitvaultVaultRecord {
905
920
  } | null;
906
921
  pending_repair_attempt_id: string | null;
907
922
  };
923
+ /**
924
+ * gitvault-multi-writer (rev 47) task 3.7 — the writer chain state's read
925
+ * surface, mirrored client-side from the gateway's `VaultRecord` (see
926
+ * `services/gitvault/reads.ts` in run402-private for the authoritative doc
927
+ * comment). `writer_set` is the on-chain-recognized set exactly as the
928
+ * chain itself would verify (regardless of any gateway-only block — that's
929
+ * `ineligible_members`). `pending_writers` is the reverse direction:
930
+ * active org members at role developer+ with a published,
931
+ * possession-verified signing key who are NOT yet in `writer_set.writers`
932
+ * — candidates for the "writer"-door `add_writer_key` (task 5.7's
933
+ * `reconcile()`). `ineligible_members` is a chain-recognized active writer
934
+ * the gateway has flagged for removal, still active on-chain pending the
935
+ * next `rotate_epoch{writer_set_update}`.
936
+ */
937
+ writer_set?: {
938
+ version: string;
939
+ sha256: string | null;
940
+ writers: GitvaultVaultWriterSetEntry[];
941
+ };
942
+ pending_writers?: GitvaultVaultPendingWriter[];
943
+ ineligible_members?: GitvaultVaultIneligibleMember[];
944
+ /** `gitvault_vaults.read_only_terminal_at IS NOT NULL` — the D228 forced sole-writer-removal terminal state: no further writer-authenticated push is admissible. Absent (never `undefined`-checked as `true`) on an older gateway — treat as `false`. */
945
+ read_only_terminal?: boolean;
946
+ /**
947
+ * gitvault-multi-writer (rev 47) D6/D227, task 5.9 — decimal-string
948
+ * uint64 counter, bumped by the gateway every time a membership/role/key
949
+ * change gateway-blocks one or more writer keys. A client building a
950
+ * `writer_set_update` freezes THIS value into `rotation_attempt_
951
+ * descriptor.writer_revocation_version` at its own admission fence —
952
+ * there is no writer-side "declare" round-trip (unlike the encryption
953
+ * side's `declareRecipientKeyRevoked`), so this read is the only source.
954
+ * Absent on an older gateway that predates this field.
955
+ */
956
+ writer_revocation_version?: string;
908
957
  warnings: {
909
958
  kind: string;
910
959
  message: string;
911
960
  }[];
912
961
  created_at: string | null;
962
+ /**
963
+ * The control plane's SIGNED allocation record for the vault (present on
964
+ * gateways that wrap it into the vault read; `null`/absent otherwise).
965
+ * gitvault-agent-envelopes D4: a cold open compares genesis's creator
966
+ * fingerprints against these — platform-attested consistency, never
967
+ * independent authentication (the platform serves both sides).
968
+ */
969
+ allocation?: {
970
+ creator_signing_fingerprint: string;
971
+ creator_encryption_fingerprint: string;
972
+ status?: string;
973
+ service_key_id?: string;
974
+ [key: string]: unknown;
975
+ } | null;
976
+ }
977
+ /** One chain-recognized active writer on {@link GitvaultVaultRecord.writer_set} — the on-chain shape only; `gateway_blocked_at` (gateway-only) rides {@link GitvaultVaultIneligibleMember} instead. */
978
+ export interface GitvaultVaultWriterSetEntry {
979
+ writer_key_id: string;
980
+ principal_id: string;
981
+ authorization_kind: "writer" | "handoff";
982
+ admitted_generation: string;
983
+ admitted_head_sha256: string;
984
+ }
985
+ /** An org member eligible to become a writer (active membership at role developer+, a published possession-verified signing key) who is NOT yet in {@link GitvaultVaultRecord.writer_set}'s `writers` — a candidate for the "writer"-door `add_writer_key` ({@link GitvaultVault.reconcileWriterAdmissions}). */
986
+ export interface GitvaultVaultPendingWriter {
987
+ principal_id: string;
988
+ writer_key_id: string;
989
+ }
990
+ /** A chain-recognized active writer the gateway has flagged for removal — still active on-chain, pending the next `rotate_epoch{writer_set_update}`. */
991
+ export interface GitvaultVaultIneligibleMember {
992
+ principal_id: string;
993
+ writer_key_id: string;
994
+ gateway_blocked_at: string | null;
995
+ reason: "membership_revoked" | "role_below_developer" | "encryption_key_revoked" | "gateway_blocked_pending_removal";
913
996
  }
914
997
  /**
915
998
  * {@link GitvaultTransport.getState}'s result — the SAME bytes the per-object
@@ -1080,6 +1163,26 @@ export declare const GITVAULT_AUTO_GC_GENERATIONS_DEFAULT = 32;
1080
1163
  export declare function readGitvaultAutoGcThreshold(targetRepoDir: string): Promise<number>;
1081
1164
  /** Set this checkout's auto-gc threshold. `0` disables auto-gc entirely. */
1082
1165
  export declare function writeGitvaultAutoGcThreshold(targetRepoDir: string, generations: number): Promise<void>;
1166
+ /**
1167
+ * {@link GitvaultVault.ensureRepoState}'s report when a cold keystore was
1168
+ * restored from its own envelope (gitvault-agent-envelopes D4). `trust` is
1169
+ * `receipt` only when a creator-held recovery receipt pinned genesis;
1170
+ * `platform_attested` means the control plane's signed allocation matched —
1171
+ * consistency the platform itself vouches for, so `independently_verified`
1172
+ * is `false` there and this label must never be read as end-to-end
1173
+ * authentication. `continuity` is `pinned` when this keystore had already
1174
+ * seen this genesis (a later open), `first_seen` on the first.
1175
+ */
1176
+ export interface GitvaultColdOpenResult {
1177
+ repo_id: string;
1178
+ org_id: string;
1179
+ provenance: "restored_from_envelope";
1180
+ trust: "receipt" | "platform_attested" | "unauthenticated_salvage";
1181
+ continuity: "first_seen" | "pinned";
1182
+ independently_verified: boolean;
1183
+ epoch: string;
1184
+ recipient_fingerprint: string;
1185
+ }
1083
1186
  export interface GitvaultVaultOptions {
1084
1187
  keystore: GitvaultKeystore;
1085
1188
  transport: GitvaultTransport;
@@ -1092,6 +1195,15 @@ export interface GitvaultVaultOptions {
1092
1195
  conflict_retries?: number;
1093
1196
  /** The signing service key resolved through the registry — when supplied, control-plane-signed messages (cutoff tickets) are signature-checked. */
1094
1197
  service_public_key?: Uint8Array | string;
1198
+ /**
1199
+ * Consult round 2 §5: an ordinary open REFUSES a cold restore whose genesis
1200
+ * the control plane cannot attest (no signed allocation on the vault
1201
+ * record) — a data-path that cannot forge the allocation could otherwise
1202
+ * simply omit it and hand the client a fabricated vault. Only the explicit
1203
+ * recovery flow sets this, and the result still says
1204
+ * `unauthenticated_salvage`.
1205
+ */
1206
+ allow_unauthenticated_salvage?: boolean;
1095
1207
  }
1096
1208
  /** How a checkpoint-bearing head binds a `retention_cutoff` ticket and expires roots (§4.5a / §4.5). */
1097
1209
  export interface GitvaultCutoffOptions {
@@ -1110,6 +1222,19 @@ export interface GitvaultEncounteredRotation {
1110
1222
  /** == `payload.new_epoch` == the head's own `epoch`. */
1111
1223
  epoch: string;
1112
1224
  payload: GitvaultRotateEpochPayload;
1225
+ /**
1226
+ * gitvault-multi-writer rev 47 — the writer who actually signed THIS
1227
+ * rotation head (already resolved + signature-verified by the forward
1228
+ * chain walk at the point this was collected — never the vault's fixed
1229
+ * genesis creator in a multi-writer vault). Optional: the BACKWARD
1230
+ * catch-up walk in `verifyToNewest` (decrypt-lag recovery) does not yet
1231
+ * resolve this for a rotation it discovers walking `prev_sha256`
1232
+ * backward from `lastHead` — a documented, narrower remaining gap
1233
+ * distinct from the forward walk's own full fix; `openEpochRotationForRecipient`
1234
+ * falls back to the vault's genesis-creator key when absent, byte-identical
1235
+ * to this field's pre-rev-47 non-existence.
1236
+ */
1237
+ signing_pubkey?: string;
1113
1238
  }
1114
1239
  /** Named detail for a reader's own `GITVAULT_EPOCH_NOT_OPENABLE` / decrypt stop point (Part C: `repos fsck`'s honest `chain_verified_to` vs `decryptable_to` split). */
1115
1240
  export interface GitvaultEpochDecryptFailure {
@@ -1185,6 +1310,51 @@ export interface GitvaultReconcileEnvelopeRecipientsResult {
1185
1310
  /** Directory entries this call could not (or, for `pinned_key_mismatch`, would not) wrap. */
1186
1311
  skipped: GitvaultReconcileEnvelopeRecipientsSkipped[];
1187
1312
  }
1313
+ /** One `pending_writers[]` entry {@link GitvaultVault.reconcileWriterAdmissions} admitted via a fresh `add_writer_key{"writer"}` head. */
1314
+ export interface GitvaultReconcileWriterAdmissionsAdmitted {
1315
+ principal_id: string;
1316
+ writer_key_id: string;
1317
+ generation: string;
1318
+ }
1319
+ /** Why {@link GitvaultVault.reconcileWriterAdmissions} did NOT admit a `pending_writers[]` entry it otherwise would have. */
1320
+ export type GitvaultReconcileWriterAdmissionsSkipReason = "missing_signing_pubkey" | "invalid_signing_pubkey";
1321
+ export interface GitvaultReconcileWriterAdmissionsSkipped {
1322
+ principal_id: string;
1323
+ writer_key_id: string;
1324
+ reason: GitvaultReconcileWriterAdmissionsSkipReason;
1325
+ }
1326
+ /**
1327
+ * {@link GitvaultVault.reconcileWriterAdmissions}'s full per-candidate
1328
+ * breakdown (task 5.7). Structurally parallel to
1329
+ * {@link GitvaultReconcileEnvelopeRecipientsResult} (same repo/wrapped-or-
1330
+ * admitted/already-covered/skipped shape), but this reconcile submits a REAL
1331
+ * chain head per admission (one `add_writer_key{"writer"}` transition per
1332
+ * candidate — the protocol allows only one added writer per head) rather
1333
+ * than an out-of-band object upload, so it is never fully parallel: each
1334
+ * admission materializes fresh against the PRIOR admission's own updated
1335
+ * writer set.
1336
+ */
1337
+ export interface GitvaultReconcileWriterAdmissionsResult {
1338
+ repo_id: string;
1339
+ org_id: string;
1340
+ /**
1341
+ * `false` exactly when THIS session's own key is not (or is no longer) an
1342
+ * active writer — the "writer" door's authorization is the carrying
1343
+ * head's own signer, so an ineligible session cannot admit anyone, and
1344
+ * this call returns immediately with every other field empty. Distinct
1345
+ * from a genuinely empty `pending_writers[]` (still `eligible: true`,
1346
+ * there was simply nothing to do) — otherwise both cases produce the
1347
+ * SAME all-empty shape and a caller could not tell "nothing needed
1348
+ * doing" from "I have no authority to do anything here."
1349
+ */
1350
+ eligible: boolean;
1351
+ /** `pending_writers[]` entries this call admitted this call, each via its own head. */
1352
+ admitted: GitvaultReconcileWriterAdmissionsAdmitted[];
1353
+ /** `writer_key_id`s already active before (or, for a raced admission, as of) this call — no action taken. */
1354
+ already_covered: string[];
1355
+ /** `pending_writers[]` entries this call could not admit. */
1356
+ skipped: GitvaultReconcileWriterAdmissionsSkipped[];
1357
+ }
1188
1358
  export interface GitvaultPushOptions {
1189
1359
  transaction: GitvaultRefTransaction;
1190
1360
  /** New `HEAD` target for the published ref_state; carried forward when omitted. */
@@ -1294,9 +1464,22 @@ export interface GitvaultRotationResult {
1294
1464
  stored_bytes_sha256: string;
1295
1465
  principal_ids: string[];
1296
1466
  } | null;
1467
+ /**
1468
+ * gitvault-multi-writer (task 5.9, D6/D227) — writer keys this SAME head
1469
+ * removed via a folded `writer_set_update`, each with the reason it was
1470
+ * removed for. Empty (never omitted) when nothing needed removing — the
1471
+ * common case, matching `excluded_keyless_principal_ids`' own
1472
+ * always-present-possibly-empty shape rather than `pin_manifest_published`'s
1473
+ * `| null`.
1474
+ */
1475
+ writers_removed: {
1476
+ writer_key_id: string;
1477
+ principal_id: string;
1478
+ reason: "member_removed" | "writer_key_revoked" | "epoch_secret_exposed";
1479
+ }[];
1297
1480
  }
1298
1481
  /**
1299
- * What {@link GitvaultVault.planPush} reports (kychee-com/run402#565) — a REAL
1482
+ * What {@link GitvaultVault.planPush} reports — a REAL
1300
1483
  * local computation, not an estimate: every number here comes from actually
1301
1484
  * building the packs (or checkpoint set) and actually sealing/encrypting
1302
1485
  * them, exactly as a real `push` would. `would_admit_generation` is what this
@@ -1360,6 +1543,7 @@ export interface GitvaultBuiltCheckpoint {
1360
1543
  }
1361
1544
  /** A transport-agnostic view of git ops the publication needs (the local repository). */
1362
1545
  export declare class GitvaultVault {
1546
+ #private;
1363
1547
  readonly keystore: GitvaultKeystore;
1364
1548
  readonly transport: GitvaultTransport;
1365
1549
  readonly repoId: string;
@@ -1405,6 +1589,7 @@ export declare class GitvaultVault {
1405
1589
  private stateRestorePlan;
1406
1590
  private readonly retries;
1407
1591
  private readonly servicePublicKey;
1592
+ private readonly allowUnauthenticatedSalvage;
1408
1593
  private genesisCache;
1409
1594
  /**
1410
1595
  * gitvault-byo-primary-bucket task 3.2 — this vault's resolved BYO write
@@ -1419,6 +1604,40 @@ export declare class GitvaultVault {
1419
1604
  constructor(options: GitvaultVaultOptions);
1420
1605
  static open(options: GitvaultVaultOptions): GitvaultVault;
1421
1606
  repoFile(): GitvaultRepoFile;
1607
+ /**
1608
+ * gitvault-agent-envelopes D4 — the COLD OPEN. A keystore that holds an
1609
+ * identity but no repo file for this vault (a member joining from a fresh
1610
+ * machine, or a creator whose repo file was lost) restores `K_repo` from
1611
+ * its OWN `key_envelope` instead of dying `GITVAULT_REPO_STATE_MISSING`:
1612
+ *
1613
+ * 1. genesis (the writer-key source) is fetched and signature-verified;
1614
+ * 2. its creator fingerprints are compared against the control plane's
1615
+ * SIGNED allocation record — `platform_attested`, never `receipt`
1616
+ * (the platform serves both sides of that comparison; a substituted
1617
+ * genesis needs a substituted allocation, which the org's owners can
1618
+ * see — TOFU + audit, human-envelopes D4's tier);
1619
+ * 3. the envelope-recipients read says whether THIS fingerprint is
1620
+ * covered — if not, `GITVAULT_ENVELOPE_PENDING` names the key-holders
1621
+ * who can fulfil and the exact next actions (never a terminal error:
1622
+ * the desired state already records this member; any key-holder's
1623
+ * next gitvault operation wraps);
1624
+ * 4. the base envelope is fetched, opened, and the repo file written
1625
+ * `restored_from_envelope` with the genesis hash PINNED (a later open
1626
+ * seeing a different genesis for this repo_id refuses
1627
+ * `VAULT_CREATION_CONFLICT`).
1628
+ *
1629
+ * Rotation epochs are NOT opened here — `verifyToNewest` walks them and
1630
+ * opens each rotation-scoped envelope this identity is included in, exactly
1631
+ * as it does for every other reader (`openEpochRotationForRecipient`).
1632
+ *
1633
+ * Returns `null` when the repo file already existed (nothing restored).
1634
+ */
1635
+ ensureRepoState(): Promise<GitvaultColdOpenResult | null>;
1636
+ /** {@link open}, but a missing repo file triggers {@link ensureRepoState} first. */
1637
+ static openOrRestore(options: GitvaultVaultOptions): Promise<{
1638
+ vault: GitvaultVault;
1639
+ restored: GitvaultColdOpenResult | null;
1640
+ }>;
1422
1641
  private kRepo;
1423
1642
  private epoch;
1424
1643
  /**
@@ -1660,10 +1879,9 @@ export declare class GitvaultVault {
1660
1879
  * **This is task 1.1's residual WORKAROUND, not the design D5 ideal.** D5
1661
1880
  * describes a recipient-set change as an epoch rotation — "history epochs
1662
1881
  * stay wrapped as they were; a new member reads from their first covered
1663
- * epoch forward" — which needs a protocol revision (task 1, BLOCKED as of
1664
- * 2026-08-26: V0 pins `epoch` to the single constant
1665
- * `GITVAULT_GENESIS_EPOCH` on every head, so there is no "forward" to
1666
- * speak of yet). What this method actually does, legally, without any
1882
+ * epoch forward" — which needs a protocol revision: V0 pins `epoch` to
1883
+ * the single constant `GITVAULT_GENESIS_EPOCH` on every head, so there
1884
+ * is no "forward" to speak of. What this method actually does, legally, without any
1667
1885
  * protocol change: `key_envelope` objects are never head-referenced (not
1668
1886
  * even genesis's own envelope is), so uploading an ADDITIONAL one at
1669
1887
  * `envelopes/<current epoch>/<recipient fingerprint>` for a missing
@@ -1686,7 +1904,7 @@ export declare class GitvaultVault {
1686
1904
  * this SDK does not make unattended.
1687
1905
  *
1688
1906
  * **The gateway directory route carries `public_key` on every row**
1689
- * (`GET /orgs/v1/:org_id/encryption-keys`, deployed 2026-08-26 — see the
1907
+ * (`GET /orgs/v1/:org_id/encryption-keys` — see the
1690
1908
  * doc comment on {@link GitvaultOrgEncryptionKeyEntry}), so against a
1691
1909
  * current gateway entries actually wrap. A directory entry that arrives
1692
1910
  * WITHOUT the field (an older/rolling-deploy gateway) is still tolerated
@@ -1719,6 +1937,35 @@ export declare class GitvaultVault {
1719
1937
  */
1720
1938
  signer(): Uint8Array;
1721
1939
  private writerKeyId;
1940
+ /**
1941
+ * gitvault-multi-writer (task 5.8) — the push pre-check: before signing an
1942
+ * ORDINARY head (any operation that expects this session to already be an
1943
+ * active writer), refuse EARLY and LOCALLY when this session's own key is
1944
+ * not (or is no longer) an active writer, rather than let real
1945
+ * crypto/upload work run only to be refused by the gateway with a less
1946
+ * specific message once the head finally reaches it. Reads the FRESHLY-
1947
+ * pinned `writer_set_pin`; every call site below calls this immediately
1948
+ * after its own `materialize()`/`verifyToNewest()` (same generation the
1949
+ * caller is about to build against), so this performs no verification of
1950
+ * its own — it only reads what the caller already froze.
1951
+ *
1952
+ * `removedMidRace` (design D8/D10) distinguishes the two shapes this
1953
+ * refusal takes: `false` (the default) is the ORDINARY pre-check — this
1954
+ * session was never (or is not currently) an admitted writer, thrown as
1955
+ * `GITVAULT_WRITER_NOT_ADMITTED`. `true` is the CAS-LOSER path
1956
+ * specifically: a retry, after re-materializing from the winner, that
1957
+ * discovers THIS session's own key was removed by whatever won the race —
1958
+ * "stop if removed" in D8's loser-rule sequence (fetch winner → verify →
1959
+ * apply writer transition → stop if removed → rebase → rebuild → bounded
1960
+ * backoff) — thrown as the more specific, client-local
1961
+ * `GITVAULT_WRITER_REMOVED` (D10) instead: a real prior attempt just lost
1962
+ * to a removal, not a caller who was never eligible.
1963
+ *
1964
+ * Deliberately NOT called for a "handoff"-door `add_writer_key` head
1965
+ * ({@link submitWriterActivationHead}): there the signer is BY DESIGN not
1966
+ * yet a writer — becoming one is what that exact head does.
1967
+ */
1968
+ private assertCallerIsWriter;
1722
1969
  /** The owner's full signing keypair, or `GITVAULT_READ_ONLY` — same refusal path as {@link signer}, which returns only the seed; {@link sealKeyEnvelope} needs both halves. */
1723
1970
  private signingKeypair;
1724
1971
  private buildRefState;
@@ -1833,7 +2080,7 @@ export declare class GitvaultVault {
1833
2080
  */
1834
2081
  push(options: GitvaultPushOptions): Promise<GitvaultPublishResult>;
1835
2082
  /**
1836
- * A REAL preview of what {@link push} would publish (kychee-com/run402#565)
2083
+ * A REAL preview of what {@link push} would publish
1837
2084
  * — runs the SAME local pipeline `push` runs (materialize → evaluate →
1838
2085
  * evolve retention roots → build refState/retentionRoots → build packs or a
1839
2086
  * checkpoint set → seal/encrypt) and stops BEFORE the two network
@@ -1925,9 +2172,9 @@ export declare class GitvaultVault {
1925
2172
  * `POST …/object-reads` rejected every `recipient_pin_manifest` read
1926
2173
  * (its null-`idScalar` validation was hardcoded to `key_envelope`'s
1927
2174
  * `{epoch, recipient_fingerprint}` shape, never generalized when D197
1928
- * shipped the second path-addressed kind), so the network fallback below
1929
- * always 400'd. That gateway bug is fixed (deployed and live-verified
1930
- * 2026-08-28): the network path works for any keystore, including
2175
+ * shipped the second path-addressed kind), which 400'd the network
2176
+ * fallback below. That gateway bug is fixed: the network path works for
2177
+ * any keystore, including
1931
2178
  * §4.11's fresh-client "SEEDS its local pin file from it" onboarding.
1932
2179
  * The cache stays purely as the round-trip saver described above.
1933
2180
  */
@@ -2012,10 +2259,10 @@ export declare class GitvaultVault {
2012
2259
  * `publishPinManifestUpdate` is an ORDINARY admission (`transition:
2013
2260
  * null`) and is therefore itself refused `EPOCH_ROTATION_REQUIRED` while
2014
2261
  * this vault has an urgent/migration condition outstanding — the exact
2015
- * state a `rotateEpoch` call is being made to clear. Reproduced live in
2016
- * production 2026-08-27 (a vault's first `epoch_secret_exposed` rekey:
2017
- * `/confirm` minted a receipt server-side, but the ordinary push that
2018
- * would publish it never admitted). Pass the pending receipted updates
2262
+ * state a `rotateEpoch` call is being made to clear. On an
2263
+ * `epoch_secret_exposed` rekey `/confirm` mints a receipt server-side,
2264
+ * but the ordinary push that would publish it never admits. Pass the
2265
+ * pending receipted updates
2019
2266
  * here instead — they ride the SAME head as this rotation's `transition`,
2020
2267
  * which IS `EPOCH_ROTATION_REQUIRED`'s own escape valve, so the publish
2021
2268
  * is no longer blocked. **This does NOT include these principals in
@@ -2050,6 +2297,19 @@ export declare class GitvaultVault {
2050
2297
  ek_fingerprint: string;
2051
2298
  receipt: GitvaultRecipientConfirmationReceipt;
2052
2299
  }[];
2300
+ /**
2301
+ * gitvault-multi-writer (task 5.9, D7/D228) — an outstanding
2302
+ * gateway-blocked writer set (`ineligible_members`, checked fresh on
2303
+ * every call — see the `writer_set_update` fold-in below) is ALWAYS
2304
+ * folded into this rotation automatically; this flag is the EXPLICIT
2305
+ * owner + step-up acknowledgment D7 requires ONLY when that fold-in
2306
+ * would empty the vault's writer set entirely (the sole surviving
2307
+ * writer was itself gateway-blocked) — the declared read-only terminal.
2308
+ * Every OTHER case that would empty the writer set is refused
2309
+ * `EPOCH_ROTATION_WOULD_LEAVE_VAULT_UNCOVERED` regardless of this flag;
2310
+ * it widens nothing beyond that one specific, named exception.
2311
+ */
2312
+ force_empty_writer_set?: boolean;
2053
2313
  }): Promise<GitvaultRotationResult>;
2054
2314
  /**
2055
2315
  * The ONE fully self-contained rotation entry point: declares
@@ -2060,6 +2320,104 @@ export declare class GitvaultVault {
2060
2320
  rotateEpochForKeyRevocation(principalId: string, options?: {
2061
2321
  client_idempotency_key?: string;
2062
2322
  }): Promise<GitvaultRotationResult>;
2323
+ /**
2324
+ * Submit a REF-NEUTRAL `add_writer_key{authorization.kind:"handoff"}`
2325
+ * activation head — no ref/checkpoint/repair changes ride it (protocol
2326
+ * §4.17's own "ref-neutral head shape... for the activation head" rule),
2327
+ * built by `publishGeneration` the SAME way every ordinary no-op push
2328
+ * would be (`refs`/`head_target` carried forward from `base` UNCHANGED),
2329
+ * just with `transition` set. Retries `HEAD_CAS_CONFLICT` like every
2330
+ * other publish path here. The caller (`resume()`) has already built and
2331
+ * locally sanity-checked the grant + acceptance; this method only reads
2332
+ * the FRESHLY-verified `writer_set_pin` (set by the `materialize()` this
2333
+ * same call performs, mirroring `push()`'s own base-then-retry shape) to
2334
+ * compute `base_writer_set`/`next_writer_set`, signs the head under
2335
+ * `this.signer()` (the ADDED key itself — protocol §4.17's "head signed
2336
+ * by the added key" — since `signer()` reads straight from THIS
2337
+ * keystore's identity, and `resume()` mints/loads that identity before
2338
+ * ever reaching this call), and advances the local `writer_set_pin` on
2339
+ * success so a subsequent read reflects the new writer without a second
2340
+ * chain walk.
2341
+ *
2342
+ * Crash-resumable (task 5.6): the chain burns `handoff_id` single-use, so
2343
+ * a naive resubmission after a crash between a prior attempt's successful
2344
+ * admission and this checkout learning about it would be refused
2345
+ * VALIDATION_FAILED. Returns `{outcome:"already_admitted", generation}`
2346
+ * instead of submitting anything whenever the freshly-verified writer set
2347
+ * ALREADY contains `input.addedWriterKeyId` — the caller treats both
2348
+ * outcomes as success (the writer IS active either way) and proceeds.
2349
+ */
2350
+ submitWriterActivationHead(input: {
2351
+ addedWriterKeyId: string;
2352
+ addedSigningPubkeyB64u: string;
2353
+ addedPrincipalId: string;
2354
+ handoffId: string;
2355
+ grant: Record<string, unknown>;
2356
+ acceptance: Record<string, unknown>;
2357
+ }): Promise<{
2358
+ outcome: "activated";
2359
+ result: GitvaultPublishResult;
2360
+ } | {
2361
+ outcome: "already_admitted";
2362
+ generation: string;
2363
+ }>;
2364
+ /**
2365
+ * gitvault-multi-writer (task 5.7) — the "writer"-door twin of {@link
2366
+ * submitWriterActivationHead}: an ALREADY-ACTIVE writer (this vault
2367
+ * session's own key) admits `input`, an eligible org member with a
2368
+ * published signing key but no writer standing yet, via a fresh
2369
+ * `add_writer_key{"writer"}` head. No grant/acceptance — the carrying
2370
+ * head's own signer being an active writer at the predecessor generation
2371
+ * IS the authorization (`validateAddWriterKeyPayload`'s `"writer"`
2372
+ * branch), re-verified admission-side by `checkTransitionAdmissible`.
2373
+ * Task 5.8 adds the SAME local pre-check every other head-signing path
2374
+ * gets: if THIS session's own key is not (or is no longer) an active
2375
+ * writer, refuse EARLY and LOCALLY (`GITVAULT_WRITER_NOT_ADMITTED` /
2376
+ * `GITVAULT_WRITER_REMOVED`) rather than let the head reach the gateway
2377
+ * only to be refused there — redundant with `reconcileWriterAdmissions`'s
2378
+ * own upfront gate for that caller, but this method is public and a
2379
+ * direct caller should get the same fast, specific refusal.
2380
+ */
2381
+ admitPendingWriter(input: {
2382
+ addedWriterKeyId: string;
2383
+ addedSigningPubkeyB64u: string;
2384
+ addedPrincipalId: string;
2385
+ }): Promise<{
2386
+ outcome: "activated";
2387
+ result: GitvaultPublishResult;
2388
+ } | {
2389
+ outcome: "already_admitted";
2390
+ generation: string;
2391
+ }>;
2392
+ /**
2393
+ * gitvault-multi-writer (task 5.7) — the vault's OWN writer-admission
2394
+ * reconcile: reads `pending_writers[]` off the vault record (eligible org
2395
+ * members with no writer standing yet), resolves each candidate's
2396
+ * published signing key off the org's encryption-key directory (the SAME
2397
+ * `/orgs/v1/:org_id/encryption-keys` row {@link
2398
+ * reconcileEnvelopeRecipients} reads, widened D9 to also carry the
2399
+ * signing half), and admits each via {@link admitPendingWriter} — one
2400
+ * `add_writer_key{"writer"}` head per candidate, sequentially: each
2401
+ * admission materializes fresh against the PRIOR one's own updated writer
2402
+ * set, so there is no batched/parallel form.
2403
+ *
2404
+ * Requires THIS session's own key to already be an active writer (the
2405
+ * chain's own authorization rule for the "writer" door) — checked ONCE,
2406
+ * upfront, off the locally-pinned `writer_set_pin` (no network call),
2407
+ * rather than discovered N times over from N identical gateway refusals:
2408
+ * a session that reached this call already pushed successfully as an
2409
+ * active writer in every wired call site (push/snapshot/deploy) OR is a
2410
+ * member that simply is not one yet (session-start/read) — the SAME
2411
+ * "not yet admitted" case {@link Gitvault.push}'s own pre-push check
2412
+ * (task 5.8) names, not a new refusal shape. Returns `{eligible: false}`
2413
+ * with every other field empty (never throws) for that case: every OTHER
2414
+ * `pending_writers[]` entry would fail identically, so there is nothing
2415
+ * this call can usefully do, and "I am not a writer" is not this vault's
2416
+ * fault — but it is distinguishable from "there was nothing pending"
2417
+ * (`eligible: true`, still all empty), which matters to a caller trying
2418
+ * to explain an all-empty result to a human.
2419
+ */
2420
+ reconcileWriterAdmissions(): Promise<GitvaultReconcileWriterAdmissionsResult>;
2063
2421
  /**
2064
2422
  * Publish a repair head over `base_generation` (§4.3): superseded tips that
2065
2423
  * the repaired state no longer reaches enter the retention-root map with