@aithos/sdk 0.1.0-alpha.6 → 0.1.0-alpha.60

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 (105) hide show
  1. package/README.md +202 -7
  2. package/dist/src/agent-dispatch.d.ts +18 -0
  3. package/dist/src/agent-dispatch.js +178 -0
  4. package/dist/src/agent-loop.d.ts +94 -0
  5. package/dist/src/agent-loop.js +95 -0
  6. package/dist/src/agent-tools.d.ts +24 -0
  7. package/dist/src/agent-tools.js +147 -0
  8. package/dist/src/apps.d.ts +224 -0
  9. package/dist/src/apps.js +432 -0
  10. package/dist/src/assets.d.ts +225 -0
  11. package/dist/src/assets.js +534 -0
  12. package/dist/src/auth-api.d.ts +219 -0
  13. package/dist/src/auth-api.js +248 -0
  14. package/dist/src/auth.d.ts +591 -0
  15. package/dist/src/auth.js +947 -31
  16. package/dist/src/compute.d.ts +674 -6
  17. package/dist/src/compute.js +968 -20
  18. package/dist/src/data-schema-contacts-v1.d.ts +14 -0
  19. package/dist/src/data-schema-contacts-v1.js +28 -0
  20. package/dist/src/data.d.ts +368 -0
  21. package/dist/src/data.js +1124 -0
  22. package/dist/src/endpoints.d.ts +43 -0
  23. package/dist/src/endpoints.js +23 -0
  24. package/dist/src/ethos.d.ts +85 -0
  25. package/dist/src/ethos.js +463 -7
  26. package/dist/src/index.d.ts +22 -4
  27. package/dist/src/index.js +47 -2
  28. package/dist/src/internal/cmk-wrap.d.ts +41 -0
  29. package/dist/src/internal/cmk-wrap.js +132 -0
  30. package/dist/src/internal/delegate-bundle.js +7 -2
  31. package/dist/src/internal/envelope.d.ts +93 -0
  32. package/dist/src/internal/envelope.js +59 -0
  33. package/dist/src/internal/owner-signers.d.ts +5 -2
  34. package/dist/src/internal/owner-signers.js +22 -1
  35. package/dist/src/internal/recovery-file.d.ts +2 -0
  36. package/dist/src/internal/recovery-file.js +7 -0
  37. package/dist/src/key-store.d.ts +10 -0
  38. package/dist/src/key-store.js +6 -0
  39. package/dist/src/mandates.d.ts +58 -1
  40. package/dist/src/mandates.js +46 -3
  41. package/dist/src/migrate.d.ts +105 -0
  42. package/dist/src/migrate.js +367 -0
  43. package/dist/src/react/AithosAsset.d.ts +66 -0
  44. package/dist/src/react/AithosAsset.js +67 -0
  45. package/dist/src/react/context.d.ts +29 -0
  46. package/dist/src/react/context.js +31 -0
  47. package/dist/src/react/index.d.ts +29 -0
  48. package/dist/src/react/index.js +31 -0
  49. package/dist/src/react/use-aithos-asset.d.ts +39 -0
  50. package/dist/src/react/use-aithos-asset.js +118 -0
  51. package/dist/src/react/use-transcribe-pending.d.ts +21 -0
  52. package/dist/src/react/use-transcribe-pending.js +47 -0
  53. package/dist/src/rotate.d.ts +94 -0
  54. package/dist/src/rotate.js +298 -0
  55. package/dist/src/sdk.d.ts +36 -2
  56. package/dist/src/sdk.js +72 -1
  57. package/dist/src/transcribe-resilience.d.ts +57 -0
  58. package/dist/src/transcribe-resilience.js +203 -0
  59. package/dist/src/web.d.ts +279 -0
  60. package/dist/src/web.js +186 -0
  61. package/dist/test/agent-dispatch.test.d.ts +2 -0
  62. package/dist/test/agent-dispatch.test.js +222 -0
  63. package/dist/test/agent-loop.test.d.ts +2 -0
  64. package/dist/test/agent-loop.test.js +117 -0
  65. package/dist/test/agent-tools.test.d.ts +2 -0
  66. package/dist/test/agent-tools.test.js +50 -0
  67. package/dist/test/auth-j3.test.js +32 -1
  68. package/dist/test/canonical-conformance.test.d.ts +2 -0
  69. package/dist/test/canonical-conformance.test.js +86 -0
  70. package/dist/test/compute-delegate-path.test.d.ts +2 -0
  71. package/dist/test/compute-delegate-path.test.js +183 -0
  72. package/dist/test/compute.test.js +4 -0
  73. package/dist/test/converse.test.d.ts +2 -0
  74. package/dist/test/converse.test.js +162 -0
  75. package/dist/test/data-sphere.test.d.ts +2 -0
  76. package/dist/test/data-sphere.test.js +57 -0
  77. package/dist/test/endpoints.test.js +40 -1
  78. package/dist/test/envelope-core-conformance.test.d.ts +2 -0
  79. package/dist/test/envelope-core-conformance.test.js +75 -0
  80. package/dist/test/envelope.test.d.ts +2 -0
  81. package/dist/test/envelope.test.js +318 -0
  82. package/dist/test/ethos-first-edition.test.d.ts +2 -0
  83. package/dist/test/ethos-first-edition.test.js +371 -0
  84. package/dist/test/invoke-turn-sdk.test.d.ts +2 -0
  85. package/dist/test/invoke-turn-sdk.test.js +177 -0
  86. package/dist/test/migrate.test.d.ts +2 -0
  87. package/dist/test/migrate.test.js +340 -0
  88. package/dist/test/owner-data-client.test.d.ts +2 -0
  89. package/dist/test/owner-data-client.test.js +88 -0
  90. package/dist/test/rotate-ethos.test.d.ts +2 -0
  91. package/dist/test/rotate-ethos.test.js +151 -0
  92. package/dist/test/rotate.test.d.ts +2 -0
  93. package/dist/test/rotate.test.js +63 -0
  94. package/dist/test/schema-autoresolve.test.d.ts +2 -0
  95. package/dist/test/schema-autoresolve.test.js +146 -0
  96. package/dist/test/sdk.test.js +11 -2
  97. package/dist/test/signup-bootstrap.test.d.ts +2 -0
  98. package/dist/test/signup-bootstrap.test.js +311 -0
  99. package/dist/test/transcribe-invoke.test.d.ts +2 -0
  100. package/dist/test/transcribe-invoke.test.js +204 -0
  101. package/dist/test/transcribe.test.d.ts +2 -0
  102. package/dist/test/transcribe.test.js +186 -0
  103. package/dist/test/web.test.d.ts +2 -0
  104. package/dist/test/web.test.js +270 -0
  105. package/package.json +20 -3
@@ -0,0 +1,534 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ /**
4
+ * `sdk.assets` — high-level API for the Aithos assets sub-protocol PDS.
5
+ *
6
+ * Stores binary content (images, PDFs, audio, video) owned by a
7
+ * subject, encrypted client-side under per-asset AMKs (Asset Master
8
+ * Keys), accessible to authorized apps via signed mandates (v0.2).
9
+ *
10
+ * const assets = sdk.assets;
11
+ * const avatar = await assets.upload({
12
+ * bytes: pngBuffer,
13
+ * mediaType: "image/png",
14
+ * attachTo: { ethos: { zone: "public", sectionId: "sec_identity" } },
15
+ * });
16
+ * // avatar.url is a stable CloudFront URL (public asset)
17
+ *
18
+ * const cv = await assets.upload({
19
+ * bytes: pdfBuffer,
20
+ * mediaType: "application/pdf",
21
+ * attachTo: { ethos: { zone: "circle", sectionId: "sec_career_docs" } },
22
+ * });
23
+ * // cv is private: stored encrypted, fetched via short-lived presigned URL.
24
+ *
25
+ * const bytes = await assets.fetch(cv.urn);
26
+ * // decrypted plaintext returned to the caller
27
+ *
28
+ * The module wires:
29
+ * - AMK generation + wrap (via @aithos/assets-crypto)
30
+ * - Bytes encryption with the canonical nonce-prefix on-disk layout
31
+ * - RecipientResolver (v0.2-ethos: maps {zone} → recipient set)
32
+ * - Direct S3 PUT against the presigned URL returned by init_upload
33
+ * - In-memory AMK cache (per asset URN) for sub-second re-fetches
34
+ * - Signed envelope JSON-RPC dispatch to /mcp/primitives/{read,write}
35
+ *
36
+ * Spec ref: spec/assets/ in the aithos-protocol repo.
37
+ */
38
+ import { generateAMK, wrapAMKForRecipient, unwrapAMK, encryptAssetBytes, decryptAssetBytes, plaintextSha256Hex, } from "@aithos/assets-crypto";
39
+ import { x25519 } from "@noble/curves/ed25519.js";
40
+ import { sha512 } from "@noble/hashes/sha2.js";
41
+ import { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
42
+ import { signOwnerEnvelope } from "./internal/envelope.js";
43
+ /* -------------------------------------------------------------------------- */
44
+ /* Public factory */
45
+ /* -------------------------------------------------------------------------- */
46
+ export function createAssetsClient(args) {
47
+ return new AssetsClient(args);
48
+ }
49
+ /* -------------------------------------------------------------------------- */
50
+ /* AssetsClient implementation */
51
+ /* -------------------------------------------------------------------------- */
52
+ export class AssetsClient {
53
+ #pdsUrl;
54
+ #did;
55
+ #seed;
56
+ #verificationMethod;
57
+ #fetch;
58
+ #resolver;
59
+ /** Cache: URN → AMK (recovered on first fetch, reused thereafter). */
60
+ #amkCache = new Map();
61
+ constructor(args) {
62
+ this.#pdsUrl = (args.pdsUrl ?? DEFAULT_SDK_ENDPOINTS.assets).replace(/\/$/, "");
63
+ this.#did = args.did;
64
+ this.#seed = args.sphereSeed;
65
+ this.#verificationMethod = args.verificationMethod;
66
+ this.#fetch = args.fetch ?? globalThis.fetch.bind(globalThis);
67
+ this.#resolver = args.recipientResolver ?? defaultSelfResolver(args);
68
+ }
69
+ /* ------------------------------------------------------------------ */
70
+ /* upload */
71
+ /* ------------------------------------------------------------------ */
72
+ async upload(input) {
73
+ const regime = resolveRegime(input);
74
+ const sha = plaintextSha256Hex(input.bytes);
75
+ // For private regime, generate AMK + wraps eagerly so the
76
+ // init_upload call already carries the envelope (the server
77
+ // records it as part of the pending-upload metadata).
78
+ let amk;
79
+ let amkEnvelope;
80
+ let recipientSet;
81
+ if (regime === "private") {
82
+ recipientSet = await this.#resolver.resolve({
83
+ subjectDid: this.#did,
84
+ context: input.attachTo,
85
+ });
86
+ if (recipientSet.recipients.length === 0) {
87
+ throw new Error("sdk.assets.upload: private regime requires at least one recipient — check RecipientResolver");
88
+ }
89
+ amk = generateAMK();
90
+ // The bytes encryption nonce is computed inside encryptAssetBytes;
91
+ // the wrap list will be filled below once we know the final URN
92
+ // (it's bound into the AAD via assetUrn).
93
+ // We pre-compose a placeholder envelope to satisfy server
94
+ // validation, then re-emit with the real URN at init+complete.
95
+ amkEnvelope = {
96
+ alg: "xchacha20poly1305-ietf",
97
+ nonce: "",
98
+ wraps: [],
99
+ };
100
+ }
101
+ // init_upload
102
+ const initResp = (await this.#callWrite("aithos.assets.init_upload", {
103
+ subject_did: this.#did,
104
+ media_type: input.mediaType,
105
+ size_bytes: input.bytes.length,
106
+ sha256_of_plaintext: sha,
107
+ ...(input.attachTo ? { attached_context: serializeContext(input.attachTo) } : {}),
108
+ regime,
109
+ ...(input.forwardSecrecy ? { forward_secrecy: input.forwardSecrecy } : {}),
110
+ ...(amkEnvelope ? { amk_envelope: amkEnvelope } : {}),
111
+ }));
112
+ if (initResp.result === "dedup_hit") {
113
+ // Asset already exists — return existing URN; no PUT needed.
114
+ const asset = initResp.asset;
115
+ if (!asset) {
116
+ throw new Error("sdk.assets.upload: dedup_hit response is missing the asset metadata");
117
+ }
118
+ return {
119
+ urn: initResp.urn,
120
+ assetId: initResp.asset_id,
121
+ mediaType: asset.media_type,
122
+ sizeBytes: asset.size_bytes,
123
+ sha256OfPlaintext: asset.sha256_of_plaintext,
124
+ encrypted: asset.encrypted,
125
+ url: !asset.encrypted ? composePublicUrl(asset) : undefined,
126
+ dedupHit: true,
127
+ };
128
+ }
129
+ const finalUrn = initResp.urn;
130
+ // Build the real wraps using the final URN.
131
+ let blob;
132
+ let nonceB64;
133
+ let finalWraps = [];
134
+ if (regime === "private") {
135
+ finalWraps = recipientSet.recipients.map((r) => wrapAMKForRecipient({
136
+ amk: amk,
137
+ recipientPublicKey: r.x25519PublicKey,
138
+ recipientDidUrl: r.didUrl,
139
+ assetUrn: finalUrn,
140
+ }));
141
+ const enc = encryptAssetBytes({
142
+ amk: amk,
143
+ assetUrn: finalUrn,
144
+ plaintext: input.bytes,
145
+ });
146
+ blob = enc.blob;
147
+ nonceB64 = enc.nonce_b64;
148
+ }
149
+ else {
150
+ blob = input.bytes;
151
+ }
152
+ // PUT bytes to S3. TS lib.dom typings expect BodyInit; a plain
153
+ // Uint8Array is accepted at runtime by all modern fetch
154
+ // implementations (browser, Node 18+, undici), but the lib.dom
155
+ // signature requires a cast to a BodyInit-compatible view.
156
+ const putResp = await this.#fetch(initResp.upload_url, {
157
+ method: "PUT",
158
+ headers: {
159
+ "content-type": regime === "private" ? "application/octet-stream" : input.mediaType,
160
+ },
161
+ body: blob,
162
+ });
163
+ if (!putResp.ok) {
164
+ throw new Error(`sdk.assets.upload: S3 PUT failed with status ${putResp.status}`);
165
+ }
166
+ // complete_upload (with real AMK envelope for private uploads)
167
+ const completePayload = {
168
+ upload_session: initResp.upload_session,
169
+ observed_sha256_of_plaintext: sha,
170
+ };
171
+ if (regime === "private") {
172
+ // The server stores the amk_envelope from init_upload — for v0.1
173
+ // we don't have a separate "update_envelope_on_complete" hook,
174
+ // so we recompose it here client-side and call authorize_grantee-
175
+ // style updates if we need to push real wraps. The simpler v0.1
176
+ // workaround: include the envelope in init_upload from the start
177
+ // by precomputing the URN client-side — but the URN is allocated
178
+ // by the server. For now we ship the envelope on complete via a
179
+ // backwards-compatible extension field, and the backend stores
180
+ // it on the asset doc.
181
+ completePayload.amk_envelope = {
182
+ alg: "xchacha20poly1305-ietf",
183
+ nonce: nonceB64,
184
+ wraps: finalWraps,
185
+ };
186
+ }
187
+ const completeResp = (await this.#callWrite("aithos.assets.complete_upload", completePayload));
188
+ // Cache the AMK locally for the lifetime of this session.
189
+ if (amk) {
190
+ this.#amkCache.set(finalUrn, amk);
191
+ }
192
+ return {
193
+ urn: completeResp.urn,
194
+ assetId: initResp.asset_id,
195
+ mediaType: input.mediaType,
196
+ sizeBytes: input.bytes.length,
197
+ sha256OfPlaintext: sha,
198
+ encrypted: regime === "private",
199
+ url: regime === "public"
200
+ ? composePublicUrl(completeResp.asset)
201
+ : undefined,
202
+ dedupHit: false,
203
+ };
204
+ }
205
+ /* ------------------------------------------------------------------ */
206
+ /* uploadWithThumbnails */
207
+ /* ------------------------------------------------------------------ */
208
+ /**
209
+ * Upload a primary asset plus one or more thumbnails (downscaled
210
+ * client-side). Convenience method for the Deep avatar use case and
211
+ * any UI that displays the same asset at multiple resolutions.
212
+ *
213
+ * The thumbnails are attached to the same context as the primary
214
+ * and uploaded in parallel. They carry the {@link AssetReference}
215
+ * role `"thumbnail"` when referenced from a section (see
216
+ * spec/assets/03-asset-descriptors.md §3.2.3).
217
+ */
218
+ async uploadWithThumbnails(input) {
219
+ const primary = await this.upload(input);
220
+ const thumbnails = await Promise.all(input.sizes.map(async (size) => {
221
+ const scaledBytes = await input.downscale(input.bytes, size);
222
+ const thumb = await this.upload({
223
+ ...input,
224
+ bytes: scaledBytes,
225
+ // Thumbnails inherit the primary's attachTo so they live in
226
+ // the same recipient set.
227
+ });
228
+ return { size, result: thumb };
229
+ }));
230
+ return { primary, thumbnails };
231
+ }
232
+ /* ------------------------------------------------------------------ */
233
+ /* fetch */
234
+ /* ------------------------------------------------------------------ */
235
+ async fetch(urn) {
236
+ const resp = (await this.#callRead("aithos.assets.get_asset", {
237
+ urn,
238
+ }));
239
+ const asset = resp.asset;
240
+ const r = await this.#fetch(resp.fetch_url);
241
+ if (!r.ok) {
242
+ throw new Error(`sdk.assets.fetch: byte fetch failed with status ${r.status}`);
243
+ }
244
+ const blob = new Uint8Array(await r.arrayBuffer());
245
+ let bytes;
246
+ if (asset.encrypted) {
247
+ const amk = this.#amkCache.get(urn) ??
248
+ this.#unwrapAmkForSelf(urn, asset.amk_envelope);
249
+ this.#amkCache.set(urn, amk);
250
+ bytes = decryptAssetBytes({
251
+ amk,
252
+ assetUrn: urn,
253
+ blob,
254
+ expectedSha256Hex: asset.sha256_of_plaintext,
255
+ });
256
+ }
257
+ else {
258
+ bytes = blob;
259
+ // SHA verification still applies for public assets.
260
+ if (plaintextSha256Hex(bytes) !== asset.sha256_of_plaintext) {
261
+ throw new Error("sdk.assets.fetch: plaintext SHA-256 mismatch on public asset");
262
+ }
263
+ }
264
+ return {
265
+ urn,
266
+ mediaType: asset.media_type,
267
+ sizeBytes: asset.size_bytes,
268
+ bytes,
269
+ sha256OfPlaintext: asset.sha256_of_plaintext,
270
+ };
271
+ }
272
+ /* ------------------------------------------------------------------ */
273
+ /* head */
274
+ /* ------------------------------------------------------------------ */
275
+ async head(urn) {
276
+ const r = (await this.#callRead("aithos.assets.head_asset", {
277
+ urn,
278
+ }));
279
+ return r.asset;
280
+ }
281
+ /* ------------------------------------------------------------------ */
282
+ /* list */
283
+ /* ------------------------------------------------------------------ */
284
+ async list(opts = {}) {
285
+ const params = {
286
+ subject_did: this.#did,
287
+ };
288
+ if (opts.limit !== undefined)
289
+ params.limit = opts.limit;
290
+ if (opts.cursor)
291
+ params.cursor = opts.cursor;
292
+ if (opts.order)
293
+ params.order = opts.order;
294
+ if (opts.includeOrphaned)
295
+ params.include_orphaned = true;
296
+ if (opts.includeTombstoned)
297
+ params.include_tombstoned = true;
298
+ if (opts.filter) {
299
+ const f = {};
300
+ if (opts.filter.mediaTypePrefix)
301
+ f.media_type_prefix = opts.filter.mediaTypePrefix;
302
+ if (opts.filter.sizeBytes)
303
+ f.size_bytes = opts.filter.sizeBytes;
304
+ if (opts.filter.createdAfter)
305
+ f.created_after = opts.filter.createdAfter;
306
+ if (opts.filter.createdBefore)
307
+ f.created_before = opts.filter.createdBefore;
308
+ params.filter = f;
309
+ }
310
+ const r = (await this.#callRead("aithos.assets.list_assets", params));
311
+ return {
312
+ items: r.items.map((it) => ({
313
+ urn: it.urn,
314
+ assetId: it.asset_id,
315
+ mediaType: it.media_type,
316
+ sizeBytes: it.size_bytes,
317
+ sha256OfPlaintext: it.sha256_of_plaintext,
318
+ encrypted: it.encrypted,
319
+ state: it.state,
320
+ referenceCount: it.reference_count,
321
+ createdAt: it.created_at,
322
+ modifiedAt: it.modified_at,
323
+ })),
324
+ ...(r.next_cursor ? { nextCursor: r.next_cursor } : {}),
325
+ };
326
+ }
327
+ /* ------------------------------------------------------------------ */
328
+ /* ref / unref / listReferences */
329
+ /* ------------------------------------------------------------------ */
330
+ async ref(urn, reference) {
331
+ const r = (await this.#callWrite("aithos.assets.ref_asset", {
332
+ urn,
333
+ reference,
334
+ }));
335
+ return { referenceCount: r.reference_count, gammaRef: r.gamma_ref };
336
+ }
337
+ async unref(urn, reference) {
338
+ const r = (await this.#callWrite("aithos.assets.unref_asset", {
339
+ urn,
340
+ reference,
341
+ }));
342
+ return { referenceCount: r.reference_count, gammaRef: r.gamma_ref };
343
+ }
344
+ async listReferences(urn) {
345
+ const r = (await this.#callRead("aithos.assets.list_references", {
346
+ urn,
347
+ }));
348
+ return r.items;
349
+ }
350
+ /* ------------------------------------------------------------------ */
351
+ /* delete */
352
+ /* ------------------------------------------------------------------ */
353
+ async delete(urn) {
354
+ const r = (await this.#callWrite("aithos.assets.delete_asset", {
355
+ urn,
356
+ }));
357
+ this.#amkCache.delete(urn);
358
+ return { tombstonedAt: r.tombstoned_at, gammaRef: r.gamma_ref };
359
+ }
360
+ /* ------------------------------------------------------------------ */
361
+ /* reset cache */
362
+ /* ------------------------------------------------------------------ */
363
+ /** Zero in-memory AMK cache. Useful at user logout. */
364
+ reset() {
365
+ for (const k of this.#amkCache.values())
366
+ k.fill(0);
367
+ this.#amkCache.clear();
368
+ }
369
+ /* ------------------------------------------------------------------ */
370
+ /* Internals — envelope dispatch */
371
+ /* ------------------------------------------------------------------ */
372
+ async #callRead(method, params) {
373
+ return this.#dispatch("/mcp/primitives/read", method, params);
374
+ }
375
+ async #callWrite(method, params) {
376
+ return this.#dispatch("/mcp/primitives/write", method, params);
377
+ }
378
+ async #dispatch(path, method, params) {
379
+ const aud = `${this.#pdsUrl}${path}`;
380
+ const envelope = await this.#signEnvelope({ aud, method, params });
381
+ const body = {
382
+ jsonrpc: "2.0",
383
+ id: makeUlidLite(),
384
+ method,
385
+ params: { ...params, _envelope: envelope },
386
+ };
387
+ const r = await this.#fetch(aud, {
388
+ method: "POST",
389
+ headers: { "content-type": "application/json" },
390
+ body: JSON.stringify(body),
391
+ });
392
+ const json = (await r.json());
393
+ if (json.error) {
394
+ const err = new Error(json.error.message);
395
+ err.code = json.error.code;
396
+ err.data = json.error.data;
397
+ throw err;
398
+ }
399
+ return json.result ?? {};
400
+ }
401
+ async #signEnvelope(args) {
402
+ return signOwnerEnvelope({
403
+ iss: this.#did,
404
+ aud: args.aud,
405
+ method: args.method,
406
+ params: args.params,
407
+ verificationMethod: this.#verificationMethod,
408
+ signer: {
409
+ sign: async (msg) => {
410
+ // We delegate to @noble/ed25519 directly to avoid pulling the
411
+ // SDK's Signer abstraction surface into this module. The
412
+ // dependency is already present transitively via assets-crypto.
413
+ const { signAsync } = await import("@noble/ed25519");
414
+ return signAsync(msg, this.#seed);
415
+ },
416
+ },
417
+ });
418
+ }
419
+ /* ------------------------------------------------------------------ */
420
+ /* Internals — AMK unwrap for self */
421
+ /* ------------------------------------------------------------------ */
422
+ #unwrapAmkForSelf(urn, envelope) {
423
+ const ourPub = ed25519SeedToX25519PublicKey(this.#seed);
424
+ void ourPub;
425
+ const ourPriv = ed25519SeedToX25519PrivateKey(this.#seed);
426
+ // Try every wrap that names a recipient under our DID. The DID
427
+ // URL fragment is unconstrained — `#kex`, `#circle-kex`, `#self-kex`
428
+ // are all candidates. We attempt each wrap whose `recipient`
429
+ // starts with our DID; AAD binding makes wrong attempts fail
430
+ // cleanly.
431
+ const ours = envelope.wraps.filter((w) => w.recipient.startsWith(this.#did));
432
+ for (const wrap of ours) {
433
+ try {
434
+ return unwrapAMK({
435
+ wrap,
436
+ recipientPrivateKey: ourPriv,
437
+ assetUrn: urn,
438
+ });
439
+ }
440
+ catch {
441
+ // Wrong fragment / wrong key — try the next wrap.
442
+ }
443
+ }
444
+ throw new Error(`sdk.assets.fetch: no AMK wrap for ${this.#did} found in asset ${urn}`);
445
+ }
446
+ }
447
+ /* -------------------------------------------------------------------------- */
448
+ /* Default RecipientResolver — self-only */
449
+ /* -------------------------------------------------------------------------- */
450
+ function defaultSelfResolver(args) {
451
+ return {
452
+ async resolve(input) {
453
+ // For the default self-only resolver we always wrap to the
454
+ // subject's own X25519 sphere key. Other recipients (grantees)
455
+ // can be added explicitly later via authorize_grantee in v0.2.
456
+ const pub = ed25519SeedToX25519PublicKey(args.sphereSeed);
457
+ const didUrl = recipientDidUrlForContext(input.subjectDid, input.context);
458
+ return {
459
+ recipients: [{ didUrl, x25519PublicKey: pub }],
460
+ };
461
+ },
462
+ };
463
+ }
464
+ /** Pick a DID URL fragment matching the attaching zone. */
465
+ function recipientDidUrlForContext(subjectDid, context) {
466
+ if (context?.ethos?.zone === "circle")
467
+ return `${subjectDid}#circle-kex`;
468
+ if (context?.ethos?.zone === "self")
469
+ return `${subjectDid}#self-kex`;
470
+ if (context?.data)
471
+ return `${subjectDid}#data-kex`;
472
+ return `${subjectDid}#kex`;
473
+ }
474
+ function resolveRegime(input) {
475
+ if (input.regime === "public")
476
+ return "public";
477
+ if (input.regime === "private")
478
+ return "private";
479
+ if (input.attachTo?.ethos?.zone === "public")
480
+ return "public";
481
+ return "private";
482
+ }
483
+ function serializeContext(ctx) {
484
+ if (ctx.ethos) {
485
+ return {
486
+ kind: "ethos",
487
+ zone: ctx.ethos.zone,
488
+ ...(ctx.ethos.sectionId ? { section_id: ctx.ethos.sectionId } : {}),
489
+ };
490
+ }
491
+ if (ctx.data) {
492
+ return {
493
+ kind: "data",
494
+ collection_urn: ctx.data.collectionUrn,
495
+ ...(ctx.data.recordId ? { record_id: ctx.data.recordId } : {}),
496
+ };
497
+ }
498
+ return {};
499
+ }
500
+ function composePublicUrl(asset) {
501
+ return asset.public_url;
502
+ }
503
+ /**
504
+ * Derive a 32-byte X25519 private key from an Ed25519 seed via
505
+ * SHA-512 truncation + clamping. Same construction libsodium uses for
506
+ * `crypto_sign_ed25519_sk_to_curve25519`.
507
+ */
508
+ function ed25519SeedToX25519PrivateKey(seed) {
509
+ if (seed.length !== 32)
510
+ throw new Error("Ed25519 seed must be 32 bytes");
511
+ const h = sha512(seed);
512
+ const sk = new Uint8Array(h.slice(0, 32));
513
+ // Clamp per X25519 spec
514
+ sk[0] = sk[0] & 248;
515
+ sk[31] = sk[31] & 127;
516
+ sk[31] = sk[31] | 64;
517
+ return sk;
518
+ }
519
+ function ed25519SeedToX25519PublicKey(seed) {
520
+ const sk = ed25519SeedToX25519PrivateKey(seed);
521
+ try {
522
+ return x25519.getPublicKey(sk);
523
+ }
524
+ finally {
525
+ sk.fill(0);
526
+ }
527
+ }
528
+ function makeUlidLite() {
529
+ // Lightweight monotonic-ish id for JSON-RPC `id` field — not
530
+ // crypto-grade. Production code uses ulid().
531
+ return (Date.now().toString(36) +
532
+ Math.random().toString(36).substring(2, 10));
533
+ }
534
+ //# sourceMappingURL=assets.js.map