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
@@ -0,0 +1,201 @@
1
+ /**
2
+ * The exact, closed set of refusal codes this module can ever return —
3
+ * mirrors the gateway's `WriterStateRefusalCode` exactly (same union, same
4
+ * names) so a caller's error mapping needs no translation table.
5
+ */
6
+ export type WriterStateRefusalCode = "VALIDATION_FAILED" | "GITVAULT_WRITER_NOT_ADMITTED" | "HANDOFF_KEY_REVOKED" | "RECIPIENT_SET_MISMATCH" | "EPOCH_ROTATION_WOULD_LEAVE_VAULT_UNCOVERED";
7
+ export type WriterStateVerdict = {
8
+ ok: true;
9
+ } | {
10
+ ok: false;
11
+ code: WriterStateRefusalCode;
12
+ detail: string;
13
+ };
14
+ export declare const ORG_ROLE_RANK: Readonly<Record<string, number>>;
15
+ export declare const WRITER_ELIGIBLE_ROLE_RANK: number;
16
+ /** Does `role` meet the writer-eligibility threshold (developer or above)? An unknown/absent role never does. */
17
+ export declare function meetsWriterEligibleRole(role: string | null | undefined): boolean;
18
+ /**
19
+ * gitvault-multi-writer rev 47 (task 5.5) — predicts the role a handoff mint
20
+ * will actually confer, mirroring the gateway's OWN attenuation exactly
21
+ * (`services/gitvault/claims.ts mintHandoff`: `requestedRole &&
22
+ * roleRank(requestedRole) < roleRank(minterRole) ? requestedRole :
23
+ * minterRole`). The mint's `writer_admission_grant.minted_role` must equal
24
+ * this prediction byte-for-byte or the gateway refuses VALIDATION_FAILED —
25
+ * after the checkpoint push has already been paid for — so getting the
26
+ * comparison direction right matters. An unrecognized `requestedRole`
27
+ * (never a legal `OrgRole` server-side either) is treated as "no request":
28
+ * a garbage role string fails the gateway's OWN mint validation before its
29
+ * attenuation logic — and therefore before this grant-mismatch check —
30
+ * ever runs, so predicting `minterRole` for that case is never observed as
31
+ * wrong, only moot.
32
+ */
33
+ export declare function predictMintedRole(requestedRole: string | undefined, minterRole: string): string;
34
+ export interface WriterKeyEntry {
35
+ writer_key_id: string;
36
+ signing_pubkey: string;
37
+ }
38
+ /** The full chain-derived writer state at some generation — protocol §4.15. */
39
+ export interface WriterChainState {
40
+ version: string;
41
+ writers: readonly WriterKeyEntry[];
42
+ sha256: string;
43
+ /** Permanently burned writer_key_ids — protocol §4.15, never re-addable, on this vault, ever. */
44
+ burnedWriterKeyIds: ReadonlySet<string>;
45
+ /** Permanently consumed handoff_ids — protocol §4.17, a writer_admission_grant is single-use. */
46
+ consumedHandoffIds: ReadonlySet<string>;
47
+ }
48
+ /**
49
+ * `vk_` fingerprint of a base64url-encoded Ed25519 public key, or `null` for
50
+ * anything that doesn't decode as a canonical 32-byte base64url scalar —
51
+ * mirrors the gateway's `writerKeyIdOf` (never throws on a malformed key).
52
+ */
53
+ export declare function writerKeyIdOf(signingPubkeyB64u: string): string | null;
54
+ /** protocol §4.15: writer_set_sha256 = SHA-256(JCS({format, repo_id, version, writers})). */
55
+ export declare function writerSetSha256(repoId: string, version: string, writers: readonly WriterKeyEntry[]): string;
56
+ /**
57
+ * protocol §4.15: the version-0 singleton, derived from `creator_signing_pubkey`
58
+ * (see the module doc for why this diverges from reading a `writer_key_id`
59
+ * field directly).
60
+ */
61
+ export declare function initialWriterState(repoId: string, genesis: {
62
+ creator_signing_pubkey: string;
63
+ }): WriterChainState;
64
+ /**
65
+ * protocol §4.16: apply an ALREADY-ADMITTED `add_writer_key` transition.
66
+ * Caller MUST have validated it first (`validateAddWriterKeyPayload`) — this
67
+ * function performs no checks, it only advances state.
68
+ */
69
+ /**
70
+ * gitvault-multi-writer (task 5.6) — builds a well-formed `handoff`-door
71
+ * `add_writer_key` payload (schema `r402s.add-writer-key/v1`) from a
72
+ * predecessor writer-set pin and the claimant's own key — the pure
73
+ * assembly step `GitvaultVault.submitWriterActivationHead` delegates to,
74
+ * kept separate so it can be unit-tested directly against
75
+ * {@link validateAddWriterKeyPayload} without a full vault/keystore/git
76
+ * harness. `predecessorPin` needs only `{version, sha256, writers}` — the
77
+ * caller's persisted `writer_set_pin` shape already matches, no
78
+ * `WriterChainState` (with its Sets) is required to build an OUTGOING
79
+ * payload; only to VALIDATE an incoming one.
80
+ */
81
+ export declare function buildAddWriterKeyActivationPayload(repoId: string, predecessorPin: {
82
+ version: string;
83
+ sha256: string;
84
+ writers: readonly WriterKeyEntry[];
85
+ }, addedWriter: WriterKeyEntry & {
86
+ principal_id: string;
87
+ }, handoffId: string, grant: Record<string, unknown>, acceptance: Record<string, unknown>): AddWriterKeyPayload;
88
+ /**
89
+ * gitvault-multi-writer (task 5.7) — builds a well-formed `writer`-door
90
+ * `add_writer_key` payload from a predecessor writer-set pin and the
91
+ * candidate's own published signing key. Unlike {@link
92
+ * buildAddWriterKeyActivationPayload} (the handoff door), this authorization
93
+ * carries no grant/acceptance — `validateAddWriterKeyPayload`'s `"writer"`
94
+ * branch licenses it purely by the CARRYING HEAD's own signer already being
95
+ * an active writer (checked at admission time, both client-side replay and
96
+ * gateway-side `checkTransitionAdmissible`), so the acting writer's identity
97
+ * never appears IN the payload itself, only in the head that carries it.
98
+ */
99
+ export declare function buildWriterDoorAddWriterKeyPayload(repoId: string, predecessorPin: {
100
+ version: string;
101
+ sha256: string;
102
+ writers: readonly WriterKeyEntry[];
103
+ }, addedWriter: WriterKeyEntry & {
104
+ principal_id: string;
105
+ }): AddWriterKeyPayload;
106
+ export declare function applyAddWriterKey(repoId: string, state: WriterChainState, addedWriter: WriterKeyEntry, consumedHandoffId: string | null): WriterChainState;
107
+ /**
108
+ * protocol §4.18: apply an ALREADY-ADMITTED `rotate_epoch{writer_set_update}`.
109
+ * Caller MUST have validated it first (`validateWriterSetUpdate`). `writers`
110
+ * MAY be empty — the explicit sole-writer `writer_key_revoked` read-only
111
+ * terminal (D228) is a legal resulting state, not an error at this layer.
112
+ */
113
+ export declare function applyWriterSetUpdate(repoId: string, state: WriterChainState, removedWriterKeyIds: readonly string[]): WriterChainState;
114
+ /**
115
+ * The FIRST, ordinary-case lookup (protocol §5A step 1): is this head's own
116
+ * `writer_key_id` already active in the predecessor writer state? A null
117
+ * result does not by itself mean refusal — the caller still checks for a
118
+ * qualifying `add_writer_key{"handoff"}` transition naming exactly this key
119
+ * before concluding `GITVAULT_WRITER_NOT_ADMITTED`.
120
+ */
121
+ export declare function resolveActiveWriter(state: WriterChainState, headWriterKeyId: string): WriterKeyEntry | null;
122
+ export interface AddWriterKeyPayload {
123
+ schema: "r402s.add-writer-key/v1";
124
+ repo_id: string;
125
+ base_writer_set: {
126
+ version: string;
127
+ sha256: string;
128
+ };
129
+ next_writer_set: {
130
+ version: string;
131
+ writers: WriterKeyEntry[];
132
+ sha256: string;
133
+ };
134
+ added_writer: {
135
+ writer_key_id: string;
136
+ signing_pubkey: string;
137
+ principal_id: string;
138
+ };
139
+ authorization: {
140
+ kind: "writer";
141
+ } | {
142
+ kind: "handoff";
143
+ grant: Record<string, unknown>;
144
+ acceptance: Record<string, unknown>;
145
+ };
146
+ }
147
+ /**
148
+ * The FULL cryptographic + structural validation of an `add_writer_key`
149
+ * payload (protocol §4.16/§4.17), given the writer state at the PREDECESSOR
150
+ * generation and the carrying head's own `writer_key_id`. Does NOT check
151
+ * `authorization.kind:"writer"`'s org-membership eligibility (active
152
+ * membership >= developer, directory possession-verification) — that is
153
+ * gateway-only, live-state-dependent, and re-checked under the admission
154
+ * fence server-side; a client validating its OWN candidate transition before
155
+ * submission relies on the gateway's fence-time re-check as the backstop.
156
+ */
157
+ export declare function validateAddWriterKeyPayload(repoId: string, predecessorState: WriterChainState, payload: AddWriterKeyPayload, headWriterKeyId: string): WriterStateVerdict;
158
+ export interface WriterSetUpdatePayload {
159
+ base_version: string;
160
+ base_sha256: string;
161
+ next_version: string;
162
+ next_sha256: string;
163
+ removed: readonly {
164
+ writer_key_id: string;
165
+ principal_id: string;
166
+ reason: string;
167
+ }[];
168
+ writers: readonly WriterKeyEntry[];
169
+ }
170
+ /**
171
+ * The FULL cryptographic + structural validation of a `rotate_epoch`
172
+ * transition's `writer_set_update` field (protocol §4.18, D227), given the
173
+ * writer state at the PREDECESSOR generation, the carrying head's own
174
+ * `writer_key_id`, and the caller's own belief about which writer keys are
175
+ * due for removal (`gatewayBlockedWriterKeyIds` — for a client building a
176
+ * CANDIDATE rotation this is `state read (blocked set)`; the gateway's own
177
+ * fence-time re-check is the source of truth at admission). `allowEmptyResult`
178
+ * is true ONLY for the explicit, owner+step-up, sole remaining writer
179
+ * `writer_key_revoked` removal (D228); every other call site passes `false`.
180
+ */
181
+ export declare function validateWriterSetUpdate(repoId: string, predecessorState: WriterChainState, update: WriterSetUpdatePayload, headWriterKeyId: string, gatewayBlockedWriterKeyIds: ReadonlySet<string>, allowEmptyResult: boolean): WriterStateVerdict;
182
+ /**
183
+ * DR / offline replay (protocol §5A "Writer-set DR"): rebuild the writer
184
+ * state purely from genesis + admitted writer-changing transitions, in
185
+ * order. Every transition MUST already be admitted (this performs no
186
+ * validation — apply-only, mirroring `applyAddWriterKey`/`applyWriterSetUpdate`).
187
+ */
188
+ export type AdmittedWriterTransition = {
189
+ kind: "add_writer_key";
190
+ addedWriter: WriterKeyEntry;
191
+ consumedHandoffId: string | null;
192
+ } | {
193
+ kind: "writer_set_update";
194
+ removedWriterKeyIds: readonly string[];
195
+ };
196
+ export declare function replayWriterState(repoId: string, genesis: {
197
+ creator_signing_pubkey: string;
198
+ }, transitions: readonly AdmittedWriterTransition[]): WriterChainState;
199
+ /** protocol §4.15: `MAX_VAULT_WRITERS`. A writer-changing transition producing more active writers than this is refused. */
200
+ export declare const MAX_VAULT_WRITERS = 64;
201
+ //# sourceMappingURL=gitvault-writer-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitvault-writer-state.d.ts","sourceRoot":"","sources":["../../src/node/gitvault-writer-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,357 @@
1
+ /**
2
+ * r402s/v0 rev 47 — the writer set as CHAIN STATE (protocol §4.15), and the
3
+ * pure cryptographic half of `add_writer_key` / `rotate_epoch{writer_set_update}`
4
+ * admission (§4.16–§4.18, D221–D227). Client-side mirror of the gateway's
5
+ * `services/gitvault/writer-state.ts` (task gitvault-multi-writer 2.3) — same
6
+ * functions, same Verdict shape, same crypto rules — so both sides reach the
7
+ * SAME conclusion from the SAME chain bytes with no shared network call.
8
+ *
9
+ * Deliberately pure and isomorphic (no I/O, no Node-only APIs): a recovering
10
+ * client, the browser viewer, and `r402s-verify` all need to replay writer
11
+ * state from nothing but genesis + admitted chain objects. `checkChainLink`'s
12
+ * own asserts stay ignorant of transition PAYLOADS (protocol discipline); this
13
+ * module is where a transition's writer-set content gets opened and verified,
14
+ * once the caller has already established the transition is admitted or is
15
+ * validating a candidate before submission.
16
+ *
17
+ * One deliberate divergence from the gateway's file: `initialWriterState`
18
+ * DERIVES the version-0 writer_key_id from `creator_signing_pubkey` rather
19
+ * than reading a `genesis.writer_key_id` field — the gateway's own genesis
20
+ * admission already asserts `writerKeyIdOf(creator_signing_pubkey) ===
21
+ * genesis.writer_key_id` (chain.ts), so for any validly-admitted genesis both
22
+ * approaches agree; deriving is simpler for a client that has no reason to
23
+ * trust an extra redundant field over the pubkey it already verified.
24
+ */
25
+ import { LocalError } from "../errors.js";
26
+ import { GITVAULT_B64U_32_RE, GITVAULT_B64U_64_RE, ed25519VerifyStrict, fromBase64url, gitvaultWithoutSignature, isCanonicalBase64url, jcs, sha256Hex, signaturePreimage, vkFingerprint, } from "../namespaces/gitvault.crypto.js";
27
+ // ─── Org role lattice (mirrors org-owned-control-plane's owner > admin > developer > billing > viewer) ──
28
+ export const ORG_ROLE_RANK = Object.freeze({
29
+ viewer: 0,
30
+ billing: 1,
31
+ developer: 2,
32
+ admin: 3,
33
+ owner: 4,
34
+ });
35
+ export const WRITER_ELIGIBLE_ROLE_RANK = ORG_ROLE_RANK.developer;
36
+ /** Does `role` meet the writer-eligibility threshold (developer or above)? An unknown/absent role never does. */
37
+ export function meetsWriterEligibleRole(role) {
38
+ return !!role && (ORG_ROLE_RANK[role] ?? -1) >= WRITER_ELIGIBLE_ROLE_RANK;
39
+ }
40
+ /**
41
+ * gitvault-multi-writer rev 47 (task 5.5) — predicts the role a handoff mint
42
+ * will actually confer, mirroring the gateway's OWN attenuation exactly
43
+ * (`services/gitvault/claims.ts mintHandoff`: `requestedRole &&
44
+ * roleRank(requestedRole) < roleRank(minterRole) ? requestedRole :
45
+ * minterRole`). The mint's `writer_admission_grant.minted_role` must equal
46
+ * this prediction byte-for-byte or the gateway refuses VALIDATION_FAILED —
47
+ * after the checkpoint push has already been paid for — so getting the
48
+ * comparison direction right matters. An unrecognized `requestedRole`
49
+ * (never a legal `OrgRole` server-side either) is treated as "no request":
50
+ * a garbage role string fails the gateway's OWN mint validation before its
51
+ * attenuation logic — and therefore before this grant-mismatch check —
52
+ * ever runs, so predicting `minterRole` for that case is never observed as
53
+ * wrong, only moot.
54
+ */
55
+ export function predictMintedRole(requestedRole, minterRole) {
56
+ return requestedRole !== undefined && requestedRole in ORG_ROLE_RANK && ORG_ROLE_RANK[requestedRole] < ORG_ROLE_RANK[minterRole] ? requestedRole : minterRole;
57
+ }
58
+ const WRITER_SET_FORMAT = "r402s/writer-set/v1";
59
+ function asStr(v) {
60
+ return typeof v === "string" ? v : null;
61
+ }
62
+ function sortWriters(writers) {
63
+ return [...writers].sort((a, b) => (a.writer_key_id < b.writer_key_id ? -1 : a.writer_key_id > b.writer_key_id ? 1 : 0));
64
+ }
65
+ function writersArraysEqual(a, b) {
66
+ return a.length === b.length && a.every((w, i) => w.writer_key_id === b[i].writer_key_id && w.signing_pubkey === b[i].signing_pubkey);
67
+ }
68
+ function nextVersionHex16(hex16) {
69
+ return (BigInt("0x" + hex16) + 1n).toString(16).padStart(16, "0");
70
+ }
71
+ /**
72
+ * `vk_` fingerprint of a base64url-encoded Ed25519 public key, or `null` for
73
+ * anything that doesn't decode as a canonical 32-byte base64url scalar —
74
+ * mirrors the gateway's `writerKeyIdOf` (never throws on a malformed key).
75
+ */
76
+ export function writerKeyIdOf(signingPubkeyB64u) {
77
+ if (!GITVAULT_B64U_32_RE.test(signingPubkeyB64u) || !isCanonicalBase64url(signingPubkeyB64u))
78
+ return null;
79
+ return vkFingerprint(fromBase64url(signingPubkeyB64u));
80
+ }
81
+ /**
82
+ * Strict Ed25519 verification of a detached (b64u-encoded) signature over
83
+ * `message`, given a b64u-encoded public key. Never throws — a malformed
84
+ * scalar is simply `false`, matching `ed25519VerifyStrict`'s own contract.
85
+ */
86
+ function verifyDetachedB64u(publicKeyB64u, signatureB64u, message) {
87
+ if (!GITVAULT_B64U_32_RE.test(publicKeyB64u) || !isCanonicalBase64url(publicKeyB64u))
88
+ return false;
89
+ if (!GITVAULT_B64U_64_RE.test(signatureB64u) || !isCanonicalBase64url(signatureB64u))
90
+ return false;
91
+ return ed25519VerifyStrict(fromBase64url(signatureB64u), message, fromBase64url(publicKeyB64u));
92
+ }
93
+ /** protocol §4.15: writer_set_sha256 = SHA-256(JCS({format, repo_id, version, writers})). */
94
+ export function writerSetSha256(repoId, version, writers) {
95
+ const writersJson = writers.map((w) => ({ writer_key_id: w.writer_key_id, signing_pubkey: w.signing_pubkey }));
96
+ return sha256Hex(jcs({ format: WRITER_SET_FORMAT, repo_id: repoId, version, writers: writersJson }));
97
+ }
98
+ /**
99
+ * protocol §4.15: the version-0 singleton, derived from `creator_signing_pubkey`
100
+ * (see the module doc for why this diverges from reading a `writer_key_id`
101
+ * field directly).
102
+ */
103
+ export function initialWriterState(repoId, genesis) {
104
+ const writerKeyId = writerKeyIdOf(genesis.creator_signing_pubkey);
105
+ if (writerKeyId === null) {
106
+ throw new LocalError("genesis.creator_signing_pubkey is not a canonical base64url Ed25519 public key", "computing the vault's initial writer state", { code: "GITVAULT_BAD_GENESIS" });
107
+ }
108
+ const writers = [{ writer_key_id: writerKeyId, signing_pubkey: genesis.creator_signing_pubkey }];
109
+ return {
110
+ version: "0000000000000000",
111
+ writers,
112
+ sha256: writerSetSha256(repoId, "0000000000000000", writers),
113
+ burnedWriterKeyIds: new Set(),
114
+ consumedHandoffIds: new Set(),
115
+ };
116
+ }
117
+ /**
118
+ * protocol §4.16: apply an ALREADY-ADMITTED `add_writer_key` transition.
119
+ * Caller MUST have validated it first (`validateAddWriterKeyPayload`) — this
120
+ * function performs no checks, it only advances state.
121
+ */
122
+ /**
123
+ * gitvault-multi-writer (task 5.6) — builds a well-formed `handoff`-door
124
+ * `add_writer_key` payload (schema `r402s.add-writer-key/v1`) from a
125
+ * predecessor writer-set pin and the claimant's own key — the pure
126
+ * assembly step `GitvaultVault.submitWriterActivationHead` delegates to,
127
+ * kept separate so it can be unit-tested directly against
128
+ * {@link validateAddWriterKeyPayload} without a full vault/keystore/git
129
+ * harness. `predecessorPin` needs only `{version, sha256, writers}` — the
130
+ * caller's persisted `writer_set_pin` shape already matches, no
131
+ * `WriterChainState` (with its Sets) is required to build an OUTGOING
132
+ * payload; only to VALIDATE an incoming one.
133
+ */
134
+ export function buildAddWriterKeyActivationPayload(repoId, predecessorPin, addedWriter, handoffId, grant, acceptance) {
135
+ const predecessorState = { version: predecessorPin.version, writers: predecessorPin.writers, sha256: predecessorPin.sha256, burnedWriterKeyIds: new Set(), consumedHandoffIds: new Set() };
136
+ const nextState = applyAddWriterKey(repoId, predecessorState, { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey }, handoffId);
137
+ return {
138
+ schema: "r402s.add-writer-key/v1",
139
+ repo_id: repoId,
140
+ base_writer_set: { version: predecessorPin.version, sha256: predecessorPin.sha256 },
141
+ next_writer_set: { version: nextState.version, writers: [...nextState.writers], sha256: nextState.sha256 },
142
+ added_writer: { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey, principal_id: addedWriter.principal_id },
143
+ authorization: { kind: "handoff", grant, acceptance },
144
+ };
145
+ }
146
+ /**
147
+ * gitvault-multi-writer (task 5.7) — builds a well-formed `writer`-door
148
+ * `add_writer_key` payload from a predecessor writer-set pin and the
149
+ * candidate's own published signing key. Unlike {@link
150
+ * buildAddWriterKeyActivationPayload} (the handoff door), this authorization
151
+ * carries no grant/acceptance — `validateAddWriterKeyPayload`'s `"writer"`
152
+ * branch licenses it purely by the CARRYING HEAD's own signer already being
153
+ * an active writer (checked at admission time, both client-side replay and
154
+ * gateway-side `checkTransitionAdmissible`), so the acting writer's identity
155
+ * never appears IN the payload itself, only in the head that carries it.
156
+ */
157
+ export function buildWriterDoorAddWriterKeyPayload(repoId, predecessorPin, addedWriter) {
158
+ const predecessorState = { version: predecessorPin.version, writers: predecessorPin.writers, sha256: predecessorPin.sha256, burnedWriterKeyIds: new Set(), consumedHandoffIds: new Set() };
159
+ const nextState = applyAddWriterKey(repoId, predecessorState, { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey }, null);
160
+ return {
161
+ schema: "r402s.add-writer-key/v1",
162
+ repo_id: repoId,
163
+ base_writer_set: { version: predecessorPin.version, sha256: predecessorPin.sha256 },
164
+ next_writer_set: { version: nextState.version, writers: [...nextState.writers], sha256: nextState.sha256 },
165
+ added_writer: { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey, principal_id: addedWriter.principal_id },
166
+ authorization: { kind: "writer" },
167
+ };
168
+ }
169
+ export function applyAddWriterKey(repoId, state, addedWriter, consumedHandoffId) {
170
+ const writers = sortWriters([...state.writers, addedWriter]);
171
+ const version = nextVersionHex16(state.version);
172
+ return {
173
+ version,
174
+ writers,
175
+ sha256: writerSetSha256(repoId, version, writers),
176
+ burnedWriterKeyIds: state.burnedWriterKeyIds,
177
+ consumedHandoffIds: consumedHandoffId === null ? state.consumedHandoffIds : new Set([...state.consumedHandoffIds, consumedHandoffId]),
178
+ };
179
+ }
180
+ /**
181
+ * protocol §4.18: apply an ALREADY-ADMITTED `rotate_epoch{writer_set_update}`.
182
+ * Caller MUST have validated it first (`validateWriterSetUpdate`). `writers`
183
+ * MAY be empty — the explicit sole-writer `writer_key_revoked` read-only
184
+ * terminal (D228) is a legal resulting state, not an error at this layer.
185
+ */
186
+ export function applyWriterSetUpdate(repoId, state, removedWriterKeyIds) {
187
+ const removed = new Set(removedWriterKeyIds);
188
+ const writers = sortWriters(state.writers.filter((w) => !removed.has(w.writer_key_id)));
189
+ const version = nextVersionHex16(state.version);
190
+ return {
191
+ version,
192
+ writers,
193
+ sha256: writerSetSha256(repoId, version, writers),
194
+ burnedWriterKeyIds: new Set([...state.burnedWriterKeyIds, ...removed]),
195
+ consumedHandoffIds: state.consumedHandoffIds,
196
+ };
197
+ }
198
+ /**
199
+ * The FIRST, ordinary-case lookup (protocol §5A step 1): is this head's own
200
+ * `writer_key_id` already active in the predecessor writer state? A null
201
+ * result does not by itself mean refusal — the caller still checks for a
202
+ * qualifying `add_writer_key{"handoff"}` transition naming exactly this key
203
+ * before concluding `GITVAULT_WRITER_NOT_ADMITTED`.
204
+ */
205
+ export function resolveActiveWriter(state, headWriterKeyId) {
206
+ return state.writers.find((w) => w.writer_key_id === headWriterKeyId) ?? null;
207
+ }
208
+ /**
209
+ * The FULL cryptographic + structural validation of an `add_writer_key`
210
+ * payload (protocol §4.16/§4.17), given the writer state at the PREDECESSOR
211
+ * generation and the carrying head's own `writer_key_id`. Does NOT check
212
+ * `authorization.kind:"writer"`'s org-membership eligibility (active
213
+ * membership >= developer, directory possession-verification) — that is
214
+ * gateway-only, live-state-dependent, and re-checked under the admission
215
+ * fence server-side; a client validating its OWN candidate transition before
216
+ * submission relies on the gateway's fence-time re-check as the backstop.
217
+ */
218
+ export function validateAddWriterKeyPayload(repoId, predecessorState, payload, headWriterKeyId) {
219
+ if (payload.base_writer_set.version !== predecessorState.version || payload.base_writer_set.sha256 !== predecessorState.sha256) {
220
+ return { ok: false, code: "VALIDATION_FAILED", detail: "base_writer_set does not match the writer state at the predecessor generation" };
221
+ }
222
+ const addedKeyId = writerKeyIdOf(payload.added_writer.signing_pubkey);
223
+ if (addedKeyId === null || addedKeyId !== payload.added_writer.writer_key_id) {
224
+ return { ok: false, code: "VALIDATION_FAILED", detail: "added_writer.writer_key_id does not derive from added_writer.signing_pubkey" };
225
+ }
226
+ if (predecessorState.burnedWriterKeyIds.has(addedKeyId)) {
227
+ return { ok: false, code: "VALIDATION_FAILED", detail: `writer_key_id ${addedKeyId} was permanently removed and can never be re-added` };
228
+ }
229
+ if (predecessorState.writers.some((w) => w.writer_key_id === addedKeyId)) {
230
+ return { ok: false, code: "VALIDATION_FAILED", detail: `writer_key_id ${addedKeyId} is already an active writer` };
231
+ }
232
+ if (predecessorState.writers.length >= MAX_VAULT_WRITERS) {
233
+ return { ok: false, code: "VALIDATION_FAILED", detail: `this vault already has ${MAX_VAULT_WRITERS} active writers, the protocol maximum (MAX_VAULT_WRITERS)` };
234
+ }
235
+ const expectedNextWriters = sortWriters([
236
+ ...predecessorState.writers,
237
+ { writer_key_id: addedKeyId, signing_pubkey: payload.added_writer.signing_pubkey },
238
+ ]);
239
+ const expectedNextVersion = nextVersionHex16(predecessorState.version);
240
+ const expectedNextSha = writerSetSha256(repoId, expectedNextVersion, expectedNextWriters);
241
+ if (payload.next_writer_set.version !== expectedNextVersion ||
242
+ payload.next_writer_set.sha256 !== expectedNextSha ||
243
+ !writersArraysEqual(payload.next_writer_set.writers, expectedNextWriters)) {
244
+ return { ok: false, code: "VALIDATION_FAILED", detail: "next_writer_set is not exactly base_writer_set plus added_writer" };
245
+ }
246
+ if (payload.authorization.kind === "writer") {
247
+ if (headWriterKeyId === addedKeyId) {
248
+ return { ok: false, code: "VALIDATION_FAILED", detail: 'authorization.kind:"writer" cannot be self-signed by the added key' };
249
+ }
250
+ if (!predecessorState.writers.some((w) => w.writer_key_id === headWriterKeyId)) {
251
+ return { ok: false, code: "GITVAULT_WRITER_NOT_ADMITTED", detail: "the carrying head's signer is not an active writer at the predecessor generation" };
252
+ }
253
+ return { ok: true };
254
+ }
255
+ // authorization.kind === "handoff"
256
+ if (headWriterKeyId !== addedKeyId) {
257
+ return { ok: false, code: "VALIDATION_FAILED", detail: "a handoff activation head must be signed by the added key itself" };
258
+ }
259
+ const { grant, acceptance } = payload.authorization;
260
+ const grantorKeyId = asStr(grant.grantor_writer_key_id);
261
+ const grantorEntry = grantorKeyId === null ? null : (predecessorState.writers.find((w) => w.writer_key_id === grantorKeyId) ?? null);
262
+ if (grantorEntry === null) {
263
+ return { ok: false, code: "HANDOFF_KEY_REVOKED", detail: "grantor_not_active" };
264
+ }
265
+ if (asStr(grant.object_kind) !== "writer_admission_grant") {
266
+ return { ok: false, code: "VALIDATION_FAILED", detail: "writer_admission_grant.object_kind is not writer_admission_grant" };
267
+ }
268
+ const grantSignature = asStr(grant.signature);
269
+ const grantPreimage = signaturePreimage("writer_admission_grant", gitvaultWithoutSignature(grant));
270
+ if (grantSignature === null || !verifyDetachedB64u(grantorEntry.signing_pubkey, grantSignature, grantPreimage)) {
271
+ return { ok: false, code: "VALIDATION_FAILED", detail: "writer_admission_grant signature does not verify under the grantor's writer key" };
272
+ }
273
+ const statementRaw = acceptance.statement;
274
+ if (typeof statementRaw !== "object" || statementRaw === null || Array.isArray(statementRaw)) {
275
+ return { ok: false, code: "VALIDATION_FAILED", detail: "writer_acceptance.statement is missing or malformed" };
276
+ }
277
+ const statement = statementRaw;
278
+ const statementPreimage = signaturePreimage("handoff-writer-accept/v1", statement);
279
+ const handoffAdmissionPubkey = asStr(grant.handoff_admission_pubkey);
280
+ const acceptanceSignature = asStr(acceptance.acceptance_signature);
281
+ if (handoffAdmissionPubkey === null ||
282
+ acceptanceSignature === null ||
283
+ !verifyDetachedB64u(handoffAdmissionPubkey, acceptanceSignature, statementPreimage)) {
284
+ return { ok: false, code: "VALIDATION_FAILED", detail: "writer_acceptance.acceptance_signature does not verify under the grant's admission key" };
285
+ }
286
+ const possessionSignature = asStr(acceptance.possession_signature);
287
+ if (possessionSignature === null ||
288
+ !verifyDetachedB64u(payload.added_writer.signing_pubkey, possessionSignature, statementPreimage)) {
289
+ return { ok: false, code: "VALIDATION_FAILED", detail: "writer_acceptance.possession_signature does not verify under the added writer's own key" };
290
+ }
291
+ const handoffId = asStr(grant.handoff_id);
292
+ if (handoffId === null || handoffId !== asStr(statement.handoff_id)) {
293
+ return { ok: false, code: "VALIDATION_FAILED", detail: "grant.handoff_id != acceptance.statement.handoff_id" };
294
+ }
295
+ if (asStr(grant.auth_hash) !== asStr(statement.auth_hash)) {
296
+ return { ok: false, code: "VALIDATION_FAILED", detail: "grant.auth_hash != acceptance.statement.auth_hash" };
297
+ }
298
+ if (asStr(statement.writer_key_id) !== addedKeyId || asStr(statement.signing_pubkey) !== payload.added_writer.signing_pubkey) {
299
+ return { ok: false, code: "VALIDATION_FAILED", detail: "acceptance.statement writer identity does not match added_writer" };
300
+ }
301
+ if (predecessorState.consumedHandoffIds.has(handoffId)) {
302
+ return { ok: false, code: "VALIDATION_FAILED", detail: `handoff_id ${handoffId} has already been consumed in this vault's writer history` };
303
+ }
304
+ return { ok: true };
305
+ }
306
+ /**
307
+ * The FULL cryptographic + structural validation of a `rotate_epoch`
308
+ * transition's `writer_set_update` field (protocol §4.18, D227), given the
309
+ * writer state at the PREDECESSOR generation, the carrying head's own
310
+ * `writer_key_id`, and the caller's own belief about which writer keys are
311
+ * due for removal (`gatewayBlockedWriterKeyIds` — for a client building a
312
+ * CANDIDATE rotation this is `state read (blocked set)`; the gateway's own
313
+ * fence-time re-check is the source of truth at admission). `allowEmptyResult`
314
+ * is true ONLY for the explicit, owner+step-up, sole remaining writer
315
+ * `writer_key_revoked` removal (D228); every other call site passes `false`.
316
+ */
317
+ export function validateWriterSetUpdate(repoId, predecessorState, update, headWriterKeyId, gatewayBlockedWriterKeyIds, allowEmptyResult) {
318
+ if (update.base_version !== predecessorState.version || update.base_sha256 !== predecessorState.sha256) {
319
+ return { ok: false, code: "VALIDATION_FAILED", detail: "writer_set_update base does not match the writer state at the predecessor generation" };
320
+ }
321
+ const removedIds = new Set(update.removed.map((r) => r.writer_key_id));
322
+ const blockedSorted = [...gatewayBlockedWriterKeyIds].sort();
323
+ const removedSorted = [...removedIds].sort();
324
+ const exactMatch = blockedSorted.length === removedSorted.length && blockedSorted.every((id, i) => id === removedSorted[i]);
325
+ if (!exactMatch) {
326
+ return { ok: false, code: "RECIPIENT_SET_MISMATCH", detail: "writer_set_update.removed does not equal exactly the blocked writer key set" };
327
+ }
328
+ const expectedWriters = sortWriters(predecessorState.writers.filter((w) => !removedIds.has(w.writer_key_id)));
329
+ if (!writersArraysEqual(update.writers, expectedWriters)) {
330
+ return { ok: false, code: "RECIPIENT_SET_MISMATCH", detail: "writer_set_update.writers is not base_writer_set minus exactly the removed set" };
331
+ }
332
+ if (expectedWriters.length === 0 && !allowEmptyResult) {
333
+ return { ok: false, code: "EPOCH_ROTATION_WOULD_LEAVE_VAULT_UNCOVERED", detail: "this rotation would leave the vault with zero writers" };
334
+ }
335
+ if (expectedWriters.length > 0 && !expectedWriters.some((w) => w.writer_key_id === headWriterKeyId)) {
336
+ return { ok: false, code: "VALIDATION_FAILED", detail: "the rotation head's own signer must remain in the next writer set" };
337
+ }
338
+ const expectedNextVersion = nextVersionHex16(predecessorState.version);
339
+ const expectedNextSha = writerSetSha256(repoId, expectedNextVersion, expectedWriters);
340
+ if (update.next_version !== expectedNextVersion || update.next_sha256 !== expectedNextSha) {
341
+ return { ok: false, code: "VALIDATION_FAILED", detail: "writer_set_update next commitment does not match the recomputed removal" };
342
+ }
343
+ return { ok: true };
344
+ }
345
+ export function replayWriterState(repoId, genesis, transitions) {
346
+ let state = initialWriterState(repoId, genesis);
347
+ for (const t of transitions) {
348
+ state =
349
+ t.kind === "add_writer_key"
350
+ ? applyAddWriterKey(repoId, state, t.addedWriter, t.consumedHandoffId)
351
+ : applyWriterSetUpdate(repoId, state, t.removedWriterKeyIds);
352
+ }
353
+ return state;
354
+ }
355
+ /** protocol §4.15: `MAX_VAULT_WRITERS`. A writer-changing transition producing more active writers than this is refused. */
356
+ export const MAX_VAULT_WRITERS = 64;
357
+ //# sourceMappingURL=gitvault-writer-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitvault-writer-state.js","sourceRoot":"","sources":["../../src/node/gitvault-writer-state.ts"],"names":[],"mappings":"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"}
@@ -126,6 +126,10 @@ export { GITVAULT_DEPLOY_OUTCOMES, SNAPSHOT_MOVED_DURING_DEPLOY, checkActivation
126
126
  export type { GitvaultCaptureReceiptMatch, GitvaultDeployError, GitvaultDeployLane, GitvaultDeployLaneCommitInput, GitvaultDeployLanePlan, GitvaultDeployLanePlanInput, GitvaultDeployNextAction, GitvaultDeployOptions, GitvaultDeployOutcome, GitvaultDeployResult, GitvaultOverrideDrainReport, GitvaultOverrideJournal, GitvaultSnapshotMovedDetails, } from "./gitvault-deploy.js";
127
127
  export { HANDOFF_ENVELOPE_KIND, HANDOFF_KEY_PREFIXES, assembleHandoffKey, assertHandoffNoteHasNoSecret, deriveHandoffSecrets, isKygitHandoffNote, openHandoffEnvelope, parseHandoffKey, scanHandoffNoteForSecrets, sealHandoffEnvelope, uuidToBytes, } from "./gitvault-handoff.js";
128
128
  export type { HandoffEnvelopePayload, HandoffKeyParts, HandoffKeyPrefixEntry, HandoffNoteSecretFinding, HandoffSecrets, KygitHandoffNote, KygitHandoffNoteCapture, } from "./gitvault-handoff.js";
129
+ export { HANDOFF_WRITER_ACCEPT_DOMAIN, buildWriterAcceptance, buildWriterAdmissionGrant, deriveWriterAdmissionSeed, verifyWriterAcceptance, verifyWriterAdmissionGrant, } from "./gitvault-handoff.js";
130
+ export type { BuildWriterAcceptanceInput, BuildWriterAdmissionGrantInput, GitvaultWriterMintedRole, HandoffWriterAcceptStatement, WriterAcceptance, WriterAdmissionGrant, } from "./gitvault-handoff.js";
131
+ export { MAX_VAULT_WRITERS, ORG_ROLE_RANK, WRITER_ELIGIBLE_ROLE_RANK, applyAddWriterKey, applyWriterSetUpdate, buildAddWriterKeyActivationPayload, buildWriterDoorAddWriterKeyPayload, initialWriterState, meetsWriterEligibleRole, predictMintedRole, replayWriterState, resolveActiveWriter, validateAddWriterKeyPayload, validateWriterSetUpdate, writerKeyIdOf, writerSetSha256, } from "./gitvault-writer-state.js";
132
+ export type { AddWriterKeyPayload, AdmittedWriterTransition, WriterChainState, WriterKeyEntry, WriterSetUpdatePayload, WriterStateRefusalCode, WriterStateVerdict, } from "./gitvault-writer-state.js";
129
133
  export { applyHandoffCheckpoint, cloneGitvaultRemote, readGitCommitMessage, resolveResumeTargetDir } from "./gitvault-restore.js";
130
134
  export type { GitvaultHandoffRestoreOptions, GitvaultHandoffRestoreResult } from "./gitvault-restore.js";
131
135
  export { openOrCreateGitvault } from "./gitvault-open-or-create.js";