@cef-ai/vault-sdk 3.3.0 → 4.1.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 (43) hide show
  1. package/dist/client.d.ts +26 -2
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +40 -3
  4. package/dist/client.js.map +1 -1
  5. package/dist/core/canonical.d.ts +17 -1
  6. package/dist/core/canonical.d.ts.map +1 -1
  7. package/dist/core/canonical.js +26 -1
  8. package/dist/core/canonical.js.map +1 -1
  9. package/dist/core/endpoints.d.ts +5 -4
  10. package/dist/core/endpoints.d.ts.map +1 -1
  11. package/dist/core/endpoints.js +13 -4
  12. package/dist/core/endpoints.js.map +1 -1
  13. package/dist/core/grant-doc.d.ts +477 -0
  14. package/dist/core/grant-doc.d.ts.map +1 -0
  15. package/dist/core/grant-doc.js +768 -0
  16. package/dist/core/grant-doc.js.map +1 -0
  17. package/dist/core/types.d.ts +181 -49
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/fluent/vault.d.ts +6 -0
  20. package/dist/fluent/vault.d.ts.map +1 -1
  21. package/dist/fluent/vault.js +7 -0
  22. package/dist/fluent/vault.js.map +1 -1
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +5 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/internal/memberships.d.ts +360 -0
  28. package/dist/internal/memberships.d.ts.map +1 -0
  29. package/dist/internal/memberships.js +403 -0
  30. package/dist/internal/memberships.js.map +1 -0
  31. package/dist/internal/vaults.d.ts +22 -0
  32. package/dist/internal/vaults.d.ts.map +1 -1
  33. package/dist/internal/vaults.js +11 -0
  34. package/dist/internal/vaults.js.map +1 -1
  35. package/dist/internal-exports.d.ts +2 -1
  36. package/dist/internal-exports.d.ts.map +1 -1
  37. package/dist/internal-exports.js +1 -1
  38. package/dist/internal-exports.js.map +1 -1
  39. package/package.json +2 -2
  40. package/dist/internal/orgs.d.ts +0 -59
  41. package/dist/internal/orgs.d.ts.map +0 -1
  42. package/dist/internal/orgs.js +0 -80
  43. package/dist/internal/orgs.js.map +0 -1
@@ -0,0 +1,477 @@
1
+ import type { SignedGrant, SignedRoster } from "./types.js";
2
+ /**
3
+ * The schema string a CEILING-LESS grant carries inside its signed bytes
4
+ * (`authz.GrantSchemaV1`). Still emitted, still the common case: a grant that
5
+ * sets no privacy ceiling is encoded at v1, byte for byte as it always was,
6
+ * so every signature minted before v2 existed goes on verifying.
7
+ */
8
+ export declare const GRANT_SCHEMA = "cere.vault.grant.v1";
9
+ /**
10
+ * The schema string a ceiling-carrying grant carries (`authz.GrantSchemaV2`).
11
+ *
12
+ * A document is v2 if and only if it carries a privacy ceiling. There is no
13
+ * v2 spelling of "no ceiling" — that is v1, and giving it two spellings is
14
+ * the ambiguity `NO_GRANT_EXPIRY`'s comment spends a paragraph refusing.
15
+ *
16
+ * The version is a FUNCTION of the ceiling rather than an unconditional bump:
17
+ * bumping it always would change the bytes of every grant, including the
18
+ * overwhelming majority that sets no ceiling, which is a flag day on every
19
+ * stored signature. See `canonicalGrantBytesWithCeiling`.
20
+ */
21
+ export declare const GRANT_SCHEMA_V2 = "cere.vault.grant.v2";
22
+ /**
23
+ * The most sensitive class of fact a membership may see, when the grant
24
+ * carries one — Go's `vault.PrivacyCeiling`, reproduced.
25
+ *
26
+ * # IT IS NOT A SCOPE LEVEL, AND THE TWO MUST NEVER BE COMPARED
27
+ *
28
+ * Level (audit|reader|member) is WHAT a member may do in a place. Privacy is
29
+ * HOW SENSITIVE a thing is, independent of where it lives. The two enums
30
+ * share no value, which is what makes a mix-up a refusal rather than a silent
31
+ * widening: `isValidPrivacyCeiling("member")` is false.
32
+ *
33
+ * A BRANDED string for the same reason `GrantExpiry` is one: it sits next to
34
+ * `expiresAt` in the encoder's parameter list, and two adjacent parameters of
35
+ * the same underlying kind are the slip that compiles and signs valid bytes
36
+ * for the wrong document. Use {@link privacyCeiling} to make one.
37
+ */
38
+ export type PrivacyCeiling = string & {
39
+ readonly __privacyCeiling: unique symbol;
40
+ };
41
+ /**
42
+ * The ONE spelling of "no privacy ceiling was ever signed for this
43
+ * membership" — and it is NOT a synonym for `"public"`.
44
+ *
45
+ * It means NOTHING VISIBLE, and that is a decision on the record: a caller
46
+ * whose ceiling is this sees no memory-graph row at all, because a ceiling
47
+ * nobody signed is not a ceiling. Picking `"internal"` as the default would
48
+ * invent an authorisation the vault owner never attested on the axis whose
49
+ * whole job is disclosure — the same ruling `NO_GRANT_EXPIRY` makes one field
50
+ * earlier, except that there the failure direction WIDENS an unsigned grant
51
+ * and here it NARROWS to zero, which is why failing closed is free.
52
+ *
53
+ * Selects {@link GRANT_SCHEMA} in the encoder: a grant carrying this is a v1
54
+ * document, byte for byte.
55
+ */
56
+ export declare const NO_PRIVACY_CEILING: PrivacyCeiling;
57
+ /**
58
+ * Every ceiling a grant may carry, least to most sensitive — Go's
59
+ * `validPrivacyCeilings`. {@link NO_PRIVACY_CEILING} is deliberately ABSENT:
60
+ * it is a legal decoded state (the document is v1) but never a value a caller
61
+ * may SET, and admitting `""` at the writer would give the state two
62
+ * spellings.
63
+ */
64
+ export declare const PRIVACY_CEILINGS: readonly ["public", "internal", "private", "restricted"];
65
+ /** The literal union of {@link PRIVACY_CEILINGS} — what `privacyCeiling` accepts. */
66
+ export type PrivacyCeilingName = (typeof PRIVACY_CEILINGS)[number];
67
+ /** Is `c` a ceiling a v2 grant may carry? The WRITER-side guard. */
68
+ export declare function isValidPrivacyCeiling(c: PrivacyCeiling): boolean;
69
+ /** Tags a string as a privacy ceiling. The parameter type is the literal
70
+ * union, so a misspelling is a compile error and an as-yet-unknown ceiling
71
+ * from a newer peer has to be cast deliberately. */
72
+ export declare function privacyCeiling(c: PrivacyCeilingName): PrivacyCeiling;
73
+ /**
74
+ * The header a membership write carries the vault owner's grant signature in
75
+ * (`authz.GrantSignatureHeader`), hex, with or without an `0x` prefix.
76
+ *
77
+ * A HEADER OF ITS OWN, not the wallet auth's `X-Signature`, because the two
78
+ * cover different bytes: `X-Signature` covers the request BODY and
79
+ * authenticates the CALLER (who may be an admin acting for the owner), while
80
+ * this covers the canonical grant document and attests the OWNER's
81
+ * authorisation of that exact grant. The body of `PUT .../members` is
82
+ * `{"role":…,"timestamp":…}` — the vault id and the member's key are URL
83
+ * parameters and never enter the signed bytes — so one owner-signed
84
+ * `{"role":"admin",…}` would otherwise verify identically for every member of
85
+ * every vault that owner holds.
86
+ */
87
+ export declare const GRANT_SIGNATURE_HEADER = "X-Grant-Signature";
88
+ /**
89
+ * The header carrying the `issuedAt` the signed document was minted with
90
+ * (`pkgvault.GrantIssuedAtHeader`).
91
+ *
92
+ * vault-api does not RECEIVE the grant document, it REBUILDS it — from the URL
93
+ * parameters, the body, and the other half read back from its store — and then
94
+ * checks your signature over what it built. Every field must therefore be
95
+ * something the server can reproduce, and a mint time is the one thing it
96
+ * cannot: a server-chosen `now` would never equal yours. So it travels beside
97
+ * the signature.
98
+ *
99
+ * NOT the body's `timestamp`, even though every mutation body already carries
100
+ * one and the wallet middleware already bounds its skew. The signer and the
101
+ * sender are different parties: the route admits an admin, but the document
102
+ * must be signed by the VAULT's wallet, so an admin presents a grant their
103
+ * owner signed out of band. Binding the mint time to the request body would
104
+ * make the owner predict the admin's exact timestamp string, and the skew
105
+ * window would expire their pre-signed grant in five minutes.
106
+ */
107
+ export declare const GRANT_ISSUED_AT_HEADER = "X-Grant-Issued-At";
108
+ /**
109
+ * `auth.CanonicalPubKeyHex`, reproduced: `0x`-prefixed lowercase hex, or the
110
+ * input UNCHANGED when it is not hex at all.
111
+ *
112
+ * THE SERVER APPLIES THIS TO THE URL PARAMETER BEFORE IT BUILDS THE DOCUMENT
113
+ * (`memberPubkeyParam`), so a signer that skips it produces a perfectly valid
114
+ * signature over bytes the server never derives — the failure that presents
115
+ * as a key problem rather than a spelling one. Callers have always been
116
+ * allowed to pass a bare or upper-case key (the store canonicalises on the
117
+ * way in), so signing their exact spelling would break the writes that work
118
+ * today.
119
+ *
120
+ * A non-hex value passes through untouched, exactly as Go's does: a malformed
121
+ * key must reach the server as the caller wrote it and be refused there,
122
+ * rather than be silently rewritten into a different key here.
123
+ */
124
+ export declare function canonicalPubkeyHex(s: string): string;
125
+ /**
126
+ * The vault id a wallet key owns — `pkgvault.IDFor`, reproduced.
127
+ *
128
+ * `"v-"` + blake2b-128 of the key's CANONICAL HEX SPELLING, as 32 lowercase
129
+ * hex characters.
130
+ *
131
+ * # This is a one-way function, and that is the whole point
132
+ *
133
+ * It runs pubkey -> id. It does NOT run backwards, and no amount of holding
134
+ * an id recovers the key that made it: blake2b-128 is a hash. What a caller
135
+ * holding only a vault id CAN do is the inverse QUESTION rather than the
136
+ * inverse function — take a candidate key and ask whether it derives that id.
137
+ * That single check is what turns `verifySignedRoster` from a consistency
138
+ * test into a verification, because the id came from outside the response
139
+ * and the key inside it now has to match something the attacker cannot
140
+ * choose.
141
+ *
142
+ * # Two ways to get this wrong, both of which produce a plausible id
143
+ *
144
+ * 1. The key is hashed as its hex TEXT (Go writes `[]byte(walletPubkeyHex)`),
145
+ * never as the 32 bytes that text denotes. `@polkadot`'s `blake2AsHex`
146
+ * decodes a `0x`-prefixed STRING as hex, so it must be handed encoded
147
+ * bytes — passing the spelling straight in silently hashes the wrong
148
+ * input.
149
+ * 2. Because the text is the input, the SPELLING is part of it: `deadbeef`,
150
+ * `0xdeadbeef` and `0xDEADBEEF` are three different vault ids in Go.
151
+ * Callers have always been allowed any of those spellings, so the
152
+ * canonical form is applied first — the same rule the server derives with.
153
+ *
154
+ * Both are pinned by golden vectors taken from the running Go function.
155
+ */
156
+ export declare function vaultIdFor(walletPubkeyHex: string): string;
157
+ /**
158
+ * A grant document's signed expiry: the moment the membership stops standing,
159
+ * as the opaque string its signer wrote — or {@link NO_GRANT_EXPIRY} when it
160
+ * never stops.
161
+ *
162
+ * A BRANDED string rather than a bare one, mirroring Go's `GrantExpiry`
163
+ * defined type and for exactly the same reason. `canonicalGrantBytes` takes
164
+ * `issuedAt` and `expiresAt` as its last two parameters; they are two
165
+ * timestamps of the same shape, side by side, so transposing them is the most
166
+ * likely slip at a call site — and with two plain `string`s that transposition
167
+ * COMPILES and signs valid bytes for the wrong document, a defect that
168
+ * presents as a key problem.
169
+ *
170
+ * Go gets that property from the scopes map for its other three strings; there
171
+ * is no such separator for this pair, so a distinct type is the whole defence
172
+ * on both sides. Use {@link grantExpiry} to make one.
173
+ */
174
+ export type GrantExpiry = string & {
175
+ readonly __grantExpiry: unique symbol;
176
+ };
177
+ /**
178
+ * The ONE spelling of "this membership does not expire": the empty string,
179
+ * always emitted, never an absent key and never `null`.
180
+ *
181
+ * A FIXED SENTINEL so the canonical document has exactly one shape. Two shapes
182
+ * is how a signature comes to verify against bytes the other side never
183
+ * builds, and here conditional omission would be worse than ambiguous: a
184
+ * document that DROPPED `expiresAt` when there is no expiry is byte-identical
185
+ * to a document minted before the field existed, so every signature already
186
+ * stored would verify as a brand-new never-expires grant. Emitting `""` makes
187
+ * those older bytes unbuildable, which is the version gate this change would
188
+ * otherwise have to spend a new {@link GRANT_SCHEMA} string on.
189
+ *
190
+ * The empty string also cannot be mistaken for a moment — no RFC 3339 spelling
191
+ * is empty — so, unlike a far-future sentinel instant, nothing downstream can
192
+ * parse it into a date and silently act on the year 9999.
193
+ */
194
+ export declare const NO_GRANT_EXPIRY: GrantExpiry;
195
+ /**
196
+ * Tags a timestamp string as the grant's expiry.
197
+ *
198
+ * It does NOT validate: the string is carried into the signed bytes verbatim
199
+ * and neither side parses or re-formats it, so a check here would be a second
200
+ * opinion about a value the server rebuilds from what you sent. vault-api DOES
201
+ * validate — `PUT …/members` 400s an `expiresAt` that is not RFC 3339, because
202
+ * that value also has to reach a `timestamptz` column — so a malformed expiry
203
+ * is refused at the one place that can refuse it usefully.
204
+ *
205
+ * Pass the SAME string you put in the request body. Formatting a `Date` twice
206
+ * is how the two copies drift by a trimmed fractional zero.
207
+ */
208
+ export declare function grantExpiry(rfc3339: string): GrantExpiry;
209
+ /**
210
+ * A canonical grant document, read back apart.
211
+ *
212
+ * `role` and `scopes`'s values are `string`, NOT `MemberRole` / `ScopeLevel`,
213
+ * and that is deliberate in both directions:
214
+ *
215
+ * - The signed document is the authority. A level this SDK version does not
216
+ * know is a document this SDK should not silently rewrite or narrow — the
217
+ * server passes it through unvalidated for the same reason. Refusing it is
218
+ * the consumer's decision to make explicitly.
219
+ * - Go's `authz.Grant` is `Role string` / `Scopes map[string]string`. This is
220
+ * its mirror, not a richer restatement of it.
221
+ */
222
+ export interface Grant {
223
+ schema: string;
224
+ vaultId: string;
225
+ memberPubkey: string;
226
+ role: string;
227
+ scopes: Record<string, string>;
228
+ /**
229
+ * When the SIGNER minted this document, as the string it was signed as —
230
+ * by convention RFC3339 UTC, but UNPARSED here, so
231
+ * `canonicalGrantBytes(…, scopes, issuedAt)` reproduces the bytes exactly.
232
+ * Parse it yourself if you want the instant.
233
+ *
234
+ * Holding it is not permission to enforce on it. Nothing in this SDK or in
235
+ * vault-api checks an expiry against it, and a recent `issuedAt` on a
236
+ * PRESENTED document still says nothing about revocation — see the header.
237
+ */
238
+ issuedAt: string;
239
+ /**
240
+ * When the membership this document describes stops standing, as the string
241
+ * it was signed as — or `""` ({@link NO_GRANT_EXPIRY}) when it never does.
242
+ * Unparsed, like `issuedAt`, so re-encoding reproduces the bytes exactly.
243
+ *
244
+ * THIS IS A CLAIM, NOT AN ENFORCEMENT. Nothing here compares it to a clock.
245
+ * A roster you FETCHED already excludes expired memberships — vault-api
246
+ * filters them in SQL — which is the signal that actually carries expiry. On
247
+ * a document somebody HANDED you it is worth no more than `issuedAt` is:
248
+ * see the header's rule.
249
+ */
250
+ expiresAt: GrantExpiry;
251
+ /**
252
+ * The most sensitive class of fact this membership may see, or
253
+ * {@link NO_PRIVACY_CEILING} when the document is v1 and no ceiling was
254
+ * signed.
255
+ *
256
+ * NO_PRIVACY_CEILING HERE MEANS NOTHING VISIBLE, not "public". Read that
257
+ * as "this membership was signed before the memory surface existed and
258
+ * attests no disclosure at all" — the vault owner opts a member in by
259
+ * signing a v2 grant, which is a ceremony, not a default.
260
+ *
261
+ * Mirrors Go's `Grant.PrivacyCeiling`, which decodes v1 documents to
262
+ * `NoPrivacyCeiling` for the same reason.
263
+ */
264
+ privacyCeiling: PrivacyCeiling;
265
+ }
266
+ /**
267
+ * A signed grant that could not be trusted: the signature does not verify,
268
+ * the document does not decode, its schema is unknown, or it describes a
269
+ * different membership from the one it was served under.
270
+ *
271
+ * THROWN by {@link verifyGrant}, where one grant IS the whole question and a
272
+ * failure must stop the caller. REPORTED (and the entry skipped) by
273
+ * {@link verifySignedRoster}, where the same failure on one entry used to
274
+ * fail the whole roster for every other member — see that function's doc
275
+ * comment for the outage that ruling turned out to be. `memberPubkey` names
276
+ * the entry so an operator can find it.
277
+ */
278
+ export declare class GrantVerificationError extends Error {
279
+ /**
280
+ * The member the failure is about, or `""` when it is about the ENVELOPE
281
+ * — a missing anchor, a roster for the wrong vault, a malformed shape —
282
+ * which belongs to no single entry.
283
+ */
284
+ readonly memberPubkey: string;
285
+ constructor(message: string,
286
+ /**
287
+ * The member the failure is about, or `""` when it is about the ENVELOPE
288
+ * — a missing anchor, a roster for the wrong vault, a malformed shape —
289
+ * which belongs to no single entry.
290
+ */
291
+ memberPubkey?: string);
292
+ }
293
+ /**
294
+ * The exact bytes a CEILING-LESS grant signature covers — v1, byte for byte
295
+ * as `authz.CanonicalGrantBytes` has always emitted them. Delegates to
296
+ * {@link canonicalGrantBytesWithCeiling}; see that function for the full
297
+ * contract, and this file's header for why the version is a function of the
298
+ * ceiling rather than an unconditional bump.
299
+ */
300
+ export declare function canonicalGrantBytes(vaultId: string, memberPubkey: string, role: string, scopes: Record<string, string>, issuedAt: string, expiresAt: GrantExpiry): Uint8Array;
301
+ /**
302
+ * The encoder, mirroring `authz.CanonicalGrantBytesWithCeiling`. Emits
303
+ * {@link GRANT_SCHEMA} when the grant sets no privacy ceiling and
304
+ * {@link GRANT_SCHEMA_V2} when it sets one.
305
+ *
306
+ * Every bound value is inside the output: a document proves ONE member's
307
+ * standing on ONE vault and nothing else. `scopes` is emitted as an ARRAY
308
+ * sorted by name — not as an object — because the ordering is then this
309
+ * function's own step and can be tested, where a JSON object would have the
310
+ * encoder sort the keys and a stability test would pass whatever this code
311
+ * did.
312
+ *
313
+ * An incomplete grant throws rather than producing shorter bytes: a document
314
+ * naming no vault or no member would verify perfectly and authorise nobody in
315
+ * particular. `issuedAt` is required on the same grounds — an empty one is a
316
+ * document with no mint time that verifies exactly as well as one with a mint
317
+ * time.
318
+ *
319
+ * `privacyCeiling` is emitted LAST, after `expiresAt`, and ONLY when set —
320
+ * `omitempty` in Go, an append here — so a ceiling-less grant is
321
+ * byte-identical to a v1 document and every signature already stored goes on
322
+ * matching what this encoder rebuilds.
323
+ *
324
+ * The ceiling is REFUSED, never coerced: an unrecognised value throws, because
325
+ * a document that verifies perfectly and confers a ceiling nobody named is,
326
+ * on the disclosure axis, the one output this function must never hand back.
327
+ * {@link NO_PRIVACY_CEILING} is the single exception and is not a value: it
328
+ * selects v1.
329
+ *
330
+ * `issuedAt` has NO DEFAULT on purpose: a `new Date().toISOString()` fallback
331
+ * would be a hidden clock inside the encoder, and a caller who forgot to pass
332
+ * one would silently sign a different moment from the one they sent. `scopes`
333
+ * loses its `= {}` default with it — a default that can never be used, because
334
+ * a required parameter follows, reads as an option and is not one. `expiresAt`
335
+ * has none for the sharper version of the same reason: its default would be
336
+ * "never expires", the one wrong answer that looks right.
337
+ *
338
+ * `expiresAt` is the ONE parameter with no emptiness check, because `""` is
339
+ * {@link NO_GRANT_EXPIRY} — a legal, common, deliberate value rather than a
340
+ * missing one. `ceiling` shares the exemption for the same reason.
341
+ */
342
+ export declare function canonicalGrantBytesWithCeiling(vaultId: string, memberPubkey: string, role: string, scopes: Record<string, string>, issuedAt: string, expiresAt: GrantExpiry, privacyCeiling: PrivacyCeiling): Uint8Array;
343
+ /**
344
+ * Reads a canonical grant document back into its parts, mirroring
345
+ * `authz.DecodeGrantDoc`. Never re-encodes; the bytes stay the authority.
346
+ *
347
+ * Accepts BOTH current schemas — {@link GRANT_SCHEMA} and
348
+ * {@link GRANT_SCHEMA_V2} — with the field that distinguishes them checked in
349
+ * both directions (a v1 document carrying a `privacyCeiling` is a forgery
350
+ * shape; a v2 document without a recognised one has given "no ceiling" a
351
+ * second spelling). Throws on any other schema and on the same three required
352
+ * fields the encoder refuses to omit.
353
+ */
354
+ export declare function decodeGrantDoc(doc: Uint8Array): Grant;
355
+ /**
356
+ * Is `sigHex` `orgWalletPubkeyHex`'s ed25519 signature over `doc`?
357
+ *
358
+ * A boolean, not a throw, because nothing here can be undecided: both inputs
359
+ * are in hand and the check is arithmetic. Malformed inputs (a key or
360
+ * signature of the wrong length, non-hex) are `false` rather than an
361
+ * exception, mirroring `authz.VerifyGrantSig` — the caller that needs to tell
362
+ * "nothing was sent" from "it does not verify" looks at the inputs itself.
363
+ *
364
+ * Hex is accepted with or without an `0x` prefix, in either case, because
365
+ * wallets and SDKs disagree about both.
366
+ */
367
+ export declare function verifyGrantSig(orgWalletPubkeyHex: string, doc: Uint8Array, sigHex: string): Promise<boolean>;
368
+ /**
369
+ * Options for {@link verifySignedRoster}.
370
+ */
371
+ export interface RosterVerifyOptions {
372
+ /**
373
+ * Called once for EVERY entry the roster serves that could not be verified,
374
+ * with the failure. The entry is DEGRADED — omitted from the returned
375
+ * grants — and the roster as a whole still verifies; this callback is the
376
+ * only place the degradation is visible, so a caller that enforces on the
377
+ * result should treat every reported member as holding NOTHING PROVEN
378
+ * rather than as absent from the vault.
379
+ *
380
+ * A v2 grant minted by a server newer than this SDK decodes fine and is
381
+ * NOT reported here; what lands in this callback is an entry whose bytes
382
+ * could not be trusted at all — a future schema, a corrupt row, a
383
+ * signature that does not verify.
384
+ */
385
+ onUnverifiableGrant?: (err: GrantVerificationError) => void;
386
+ }
387
+ /**
388
+ * Verifies EVERY grant in a signed roster against the vault owner's key and
389
+ * returns the grants decoded from the verified bytes.
390
+ *
391
+ * This is the whole point of the signed roster: after this call you hold
392
+ * values that came out of bytes the vault owner signed, and you can throw the
393
+ * response away. Enforce on the returned `Grant`s — never on
394
+ * `SignedGrant.role` / `SignedGrant.scopes`, which are vault-api's rendering
395
+ * of the same document and are not covered by anything you checked.
396
+ *
397
+ * Three bindings are checked per entry, and the last two matter as much as
398
+ * the signature: the document must carry a schema this SDK understands, it
399
+ * must name the vault the roster is FOR, and it must name the member it was
400
+ * served UNDER. A document that verifies but describes a different membership
401
+ * is a valid signature over somebody else's grant — the substitution the
402
+ * canonical bytes bind all four values to prevent.
403
+ *
404
+ * # One unverifiable entry degrades the ENTRY, not the roster
405
+ *
406
+ * An entry that fails any of its checks is skipped and reported to
407
+ * {@link RosterVerifyOptions.onUnverifiableGrant} (when given) rather than
408
+ * thrown, and the remaining entries are returned. This is deliberate, and it
409
+ * is the second half of the condition ddc-node's `allowV2GrantMint` hold
410
+ * names for lifting: the loop this replaces threw on the FIRST bad entry, so
411
+ * one grant a consumer could not read — a ceiling-carrying v2 under 4.0.0, a
412
+ * future v3, a corrupt row — made the WHOLE roster fail verification for
413
+ * every member of that vault. An outage with a bystander list that long is
414
+ * not fail-closed, it is fail-everyone-else's.
415
+ *
416
+ * The envelope is still fail-closed: a roster naming the wrong vault, a
417
+ * signing key that does not derive the vault, or a malformed roster shape
418
+ * throws `GrantVerificationError` and NOTHING is returned — until the anchor
419
+ * holds, every entry is attacker-supplied, and there is no per-entry question
420
+ * to degrade to.
421
+ *
422
+ * SILENT SHORTENING IS THE RESIDUAL RISK, and the callback is the mitigation:
423
+ * a caller that ignores `onUnverifiableGrant` sees a shorter roster that
424
+ * looks complete. Treat a reported member as UNPROVEN — absence from the
425
+ * returned list means "no verified standing", which for authorization means
426
+ * deny, not "not in the vault".
427
+ *
428
+ * # `expectedVaultId` is the trust anchor, and it is why this is a
429
+ * # verification rather than a consistency check
430
+ *
431
+ * PASS THE ID YOU ASKED FOR — the same one handed to
432
+ * `sdk.memberships.signedRoster()`. It is required, and the requirement is
433
+ * the fix for the hole it closes:
434
+ *
435
+ * Every other value used here arrives INSIDE the response. Given only a
436
+ * roster, "verify it" can mean no more than "does this agree with itself?" —
437
+ * and it always does, because whoever composed it chose every field. Anyone
438
+ * able to answer on this address could sign a roster with a key of their own,
439
+ * name that key as `orgWalletPubkey`, and watch it verify.
440
+ *
441
+ * One externally-held value breaks the circle. `vaultIdFor` is a one-way
442
+ * derivation, so a key that derives `expectedVaultId` cannot be picked; it
443
+ * can only be the vault's actual owner. The two checks are therefore:
444
+ *
445
+ * 1. the roster must be FOR the vault asked about (`roster.vaultId`), which
446
+ * is the binding the two separate calls otherwise leave open — a
447
+ * response about somebody else's vault used to verify perfectly; and
448
+ * 2. `orgWalletPubkey` must DERIVE that vault, which pins the signing key
449
+ * to an identity the response did not get to choose.
450
+ *
451
+ * Both run BEFORE any signature is checked, mirroring
452
+ * `RosterAuthority.verifyRoster` in ddc-node: until the anchor holds, every
453
+ * byte of the envelope is attacker-supplied, including the key the
454
+ * signatures would be checked against.
455
+ *
456
+ * A caller who holds the OWNER KEY rather than the id needs nothing extra —
457
+ * `vaultIdFor(ownerKey)` is the id. That direction works; the reverse does
458
+ * not exist.
459
+ *
460
+ * This anchors the roster to a vault, which is all a hash can do. It does not
461
+ * tell you the id itself is the vault you meant: if `expectedVaultId` was
462
+ * itself learned from an untrusted party, the result is trust in THAT party's
463
+ * choice of vault. Anchor it to something you already knew.
464
+ */
465
+ export declare function verifySignedRoster(roster: SignedRoster, expectedVaultId: string, options?: RosterVerifyOptions): Promise<Grant[]>;
466
+ /**
467
+ * One entry of `verifySignedRoster`, exposed for a caller holding a single
468
+ * grant (a member proving their own standing, say) rather than a whole
469
+ * roster. Same bindings, same fail-closed throw.
470
+ *
471
+ * `vaultId` and `orgWalletPubkey` must CORRESPOND — the same anchor
472
+ * `verifySignedRoster` applies, checked here too because both arguments are
473
+ * the caller's and the natural way to fill them is straight off a response,
474
+ * which is exactly the unanchored call this check refuses.
475
+ */
476
+ export declare function verifyGrant(vaultId: string, orgWalletPubkey: string, grant: SignedGrant): Promise<Grant>;
477
+ //# sourceMappingURL=grant-doc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant-doc.d.ts","sourceRoot":"","sources":["../../src/core/grant-doc.ts"],"names":[],"mappings":"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"}