@aithos/sdk 0.1.0-alpha.5 → 0.1.0-alpha.50

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 (67) hide show
  1. package/README.md +245 -7
  2. package/dist/src/apps.d.ts +224 -0
  3. package/dist/src/apps.js +432 -0
  4. package/dist/src/assets.d.ts +208 -0
  5. package/dist/src/assets.js +534 -0
  6. package/dist/src/auth-api.d.ts +219 -0
  7. package/dist/src/auth-api.js +248 -0
  8. package/dist/src/auth.d.ts +543 -0
  9. package/dist/src/auth.js +937 -31
  10. package/dist/src/compute.d.ts +464 -6
  11. package/dist/src/compute.js +746 -20
  12. package/dist/src/data-schema-contacts-v1.d.ts +14 -0
  13. package/dist/src/data-schema-contacts-v1.js +28 -0
  14. package/dist/src/data.d.ts +342 -0
  15. package/dist/src/data.js +1002 -0
  16. package/dist/src/endpoints.d.ts +18 -0
  17. package/dist/src/endpoints.js +6 -0
  18. package/dist/src/ethos.d.ts +85 -0
  19. package/dist/src/ethos.js +463 -7
  20. package/dist/src/index.d.ts +17 -6
  21. package/dist/src/index.js +25 -3
  22. package/dist/src/internal/delegate-bundle.js +7 -2
  23. package/dist/src/internal/envelope.d.ts +93 -0
  24. package/dist/src/internal/envelope.js +59 -0
  25. package/dist/src/mandates.d.ts +111 -2
  26. package/dist/src/mandates.js +150 -7
  27. package/dist/src/react/AithosAsset.d.ts +66 -0
  28. package/dist/src/react/AithosAsset.js +67 -0
  29. package/dist/src/react/context.d.ts +29 -0
  30. package/dist/src/react/context.js +31 -0
  31. package/dist/src/react/index.d.ts +29 -0
  32. package/dist/src/react/index.js +31 -0
  33. package/dist/src/react/use-aithos-asset.d.ts +39 -0
  34. package/dist/src/react/use-aithos-asset.js +118 -0
  35. package/dist/src/react/use-transcribe-pending.d.ts +21 -0
  36. package/dist/src/react/use-transcribe-pending.js +47 -0
  37. package/dist/src/sdk.d.ts +10 -0
  38. package/dist/src/sdk.js +22 -0
  39. package/dist/src/transcribe-resilience.d.ts +57 -0
  40. package/dist/src/transcribe-resilience.js +203 -0
  41. package/dist/src/web.d.ts +279 -0
  42. package/dist/src/web.js +186 -0
  43. package/dist/test/auth-j3.test.js +32 -1
  44. package/dist/test/canonical-conformance.test.d.ts +2 -0
  45. package/dist/test/canonical-conformance.test.js +86 -0
  46. package/dist/test/compute-delegate-path.test.d.ts +2 -0
  47. package/dist/test/compute-delegate-path.test.js +183 -0
  48. package/dist/test/compute.test.js +4 -0
  49. package/dist/test/endpoints.test.js +25 -1
  50. package/dist/test/envelope-core-conformance.test.d.ts +2 -0
  51. package/dist/test/envelope-core-conformance.test.js +75 -0
  52. package/dist/test/envelope.test.d.ts +2 -0
  53. package/dist/test/envelope.test.js +318 -0
  54. package/dist/test/ethos-first-edition.test.d.ts +2 -0
  55. package/dist/test/ethos-first-edition.test.js +371 -0
  56. package/dist/test/mandates-compute.test.d.ts +2 -0
  57. package/dist/test/mandates-compute.test.js +256 -0
  58. package/dist/test/sdk.test.js +10 -2
  59. package/dist/test/signup-bootstrap.test.d.ts +2 -0
  60. package/dist/test/signup-bootstrap.test.js +311 -0
  61. package/dist/test/transcribe-invoke.test.d.ts +2 -0
  62. package/dist/test/transcribe-invoke.test.js +204 -0
  63. package/dist/test/transcribe.test.d.ts +2 -0
  64. package/dist/test/transcribe.test.js +186 -0
  65. package/dist/test/web.test.d.ts +2 -0
  66. package/dist/test/web.test.js +270 -0
  67. package/package.json +20 -3
package/dist/src/ethos.js CHANGED
@@ -25,7 +25,7 @@
25
25
  // actor and forwards all real work into protocol-client's
26
26
  // `loadEditSnapshot` / `publishZoneEdit` / `publishPublicZoneAsDelegate`
27
27
  // / `publishPrivateZoneAsDelegate`.
28
- import { addSectionToList, deleteSectionFromList, loadEditSnapshot, modifySectionInList, publishPrivateZoneAsDelegate, publishPublicZoneAsDelegate, publishZoneEdit, } from "@aithos/protocol-client";
28
+ import { addSectionToList, AithosRpcError, browserIdentityFromStored, buildSignedEnvelope, buildSignedFirstEditionFromSections, deleteSectionFromList, loadEditSnapshot, modifySectionInList, publishPrivateZoneAsDelegate, publishPublicZoneAsDelegate, publishZoneEdit, signedDidDocument, writeEndpoint, } from "@aithos/protocol-client";
29
29
  import { delegateKeyPair } from "./internal/protocol-client-bridge.js";
30
30
  import { AithosSDKError } from "./types.js";
31
31
  export const ZONE_NAMES = ["public", "circle", "self"];
@@ -38,6 +38,15 @@ export class EthosClient {
38
38
  #actor;
39
39
  #snapshots = new Map();
40
40
  #mutations = [];
41
+ /**
42
+ * Set to `true` when the server reports that no edition has been
43
+ * published yet for this subject (JSON-RPC code -32020 with the message
44
+ * `not found: edition for <did>`). Toggled lazily on the first read /
45
+ * publish attempt and cleared again after a successful first-edition
46
+ * publish so subsequent operations take the regular `publishZoneEdit`
47
+ * path.
48
+ */
49
+ #ethosHasNoEditionYet = false;
41
50
  constructor(actor) {
42
51
  this.#actor = actor;
43
52
  this.subjectDid = actor.subjectDid;
@@ -80,8 +89,15 @@ export class EthosClient {
80
89
  // have staged mutations for. Build everything we need then call
81
90
  // publishZoneEdit once.
82
91
  if (this.#actor.kind === "owner") {
83
- // Need the snapshot regardless we read base sections + zoneBytes.
84
- const snap = await this.#ensureSnapshotOwner();
92
+ // First-edition path: no prior edition exists. Detected when the
93
+ // tolerant snapshot wrapper returned null on a previous read OR
94
+ // when this is the first network touch and the server responds
95
+ // with "not found: edition". Skip the snapshot fetch entirely and
96
+ // build a height=1 manifest from the staged mutations.
97
+ const snap = await this.#tryEnsureSnapshotOwner();
98
+ if (snap === null) {
99
+ return this.#publishFirstEditionOwner();
100
+ }
85
101
  const newPublic = touched.has("public")
86
102
  ? this.#applyMutations("public", snap.publicSections)
87
103
  : undefined;
@@ -142,6 +158,25 @@ export class EthosClient {
142
158
  for (const zone of ["circle", "self"]) {
143
159
  if (!touched.has(zone))
144
160
  continue;
161
+ // CRITICAL — refuse to publish a private zone the delegate
162
+ // can't decrypt. publishPrivateZoneAsDelegate treats
163
+ // `newSections` as the COMPLETE new content of the zone (the
164
+ // server can't merge encrypted bytes it can't read), so if we
165
+ // forwarded `[]` here the new edition would silently wipe
166
+ // every section the owner had. This data-loss landed in alpha.9
167
+ // — alpha.10 turns it into a clean error so the caller knows
168
+ // the mandate isn't usable until the owner has bootstrapped a
169
+ // wrap for this delegate (a single owner-side publish on the
170
+ // zone after mint is enough).
171
+ if (snap.zoneDecryptErrors?.[zone]) {
172
+ throw new AithosSDKError("ethos_delegate_cannot_overwrite_unreadable", `delegate cannot publish to "${zone}": the existing edition is not readable for this delegate (${snap.zoneDecryptErrors[zone]}). Ask the owner to publish ${zone} once so this delegate's wrap is included; only then can the delegate write — otherwise the new edition would replace content this delegate cannot see.`, {
173
+ data: {
174
+ zone,
175
+ mandateId: actor.mandateId,
176
+ decryptError: snap.zoneDecryptErrors[zone],
177
+ },
178
+ });
179
+ }
145
180
  const base = (zone === "circle"
146
181
  ? snap.circleSections
147
182
  : snap.selfSections) ?? [];
@@ -168,6 +203,98 @@ export class EthosClient {
168
203
  throw new AithosSDKError("ethos_invalid_actor", "unsupported actor for publish()");
169
204
  }
170
205
  /* ------------------------------------------------------------------------ */
206
+ /* Bootstrap — first-edition init for fresh Ethos */
207
+ /* ------------------------------------------------------------------------ */
208
+ /**
209
+ * Idempotently ensure the subject's Ethos has at least one published
210
+ * edition. Required because a **delegate** cannot bootstrap a first
211
+ * edition (the first edition's manifest is signed with the owner's
212
+ * public-sphere key, which delegates do not have). Without an initial
213
+ * owner-published edition, subsequent delegate writes via
214
+ * {@link publish} fail with `not found: edition for did:…`.
215
+ *
216
+ * Semantics:
217
+ * - If an edition already exists (owner OR delegate OR anonymous mode),
218
+ * this is a NO-OP and returns `{ alreadyInitialized: true }`.
219
+ * - If no edition exists AND the actor is the owner, this stages and
220
+ * publishes a height=1 edition containing a single sentinel section
221
+ * `aithos-init` in the `public` zone, then returns
222
+ * `{ alreadyInitialized: false, editionHeight: 1 }`. Any previously
223
+ * staged mutations on this client are preserved and NOT auto-flushed.
224
+ * - If no edition exists AND the actor is NOT the owner (delegate or
225
+ * anonymous), throws `ethos_bootstrap_not_owner` — only the owner
226
+ * can sign a first edition.
227
+ *
228
+ * Call site: typically the owner's dashboard, right after sign-in and
229
+ * before any delegate-mode write (e.g. before triggering a backend
230
+ * worker that holds a mandate). Idempotent ⇒ safe to call on every
231
+ * mount.
232
+ *
233
+ * Implementation note: this routes through {@link #publishFirstEditionOwner}
234
+ * which the SDK already uses internally when {@link publish} detects a
235
+ * fresh Ethos with staged owner mutations. ensureInitialized() exposes
236
+ * the same code path as an explicit primitive, so the caller doesn't
237
+ * need to stage a mutation just to trigger first-edition logic.
238
+ */
239
+ async ensureInitialized() {
240
+ // Read attempt — tolerant (returns null if no edition exists). Routes
241
+ // through the right snapshot method based on actor kind so the
242
+ // detection is correct in every mode.
243
+ let snap;
244
+ if (this.#actor.kind === "anonymous") {
245
+ snap = await this.#tryEnsureSnapshotAnonymous();
246
+ }
247
+ else if (this.#actor.kind === "owner") {
248
+ snap = await this.#tryEnsureSnapshotOwner();
249
+ }
250
+ else {
251
+ snap = await this.#tryEnsureSnapshotDelegate();
252
+ }
253
+ if (snap !== null) {
254
+ return { alreadyInitialized: true };
255
+ }
256
+ // Bootstrap path — owner only.
257
+ if (this.#actor.kind !== "owner") {
258
+ throw new AithosSDKError("ethos_bootstrap_not_owner", `subject ${this.subjectDid} has no published edition yet, and only the owner can sign a first edition. Have the owner call ensureInitialized() once (typically from their dashboard at sign-in time).`, { data: { subjectDid: this.subjectDid, actorKind: this.#actor.kind } });
259
+ }
260
+ // Stage the sentinel init section. We don't disturb any pre-existing
261
+ // staged mutations — they'll be picked up by the same first-edition
262
+ // publish that runs underneath.
263
+ const prevMutations = this.#mutations.slice();
264
+ this.#mutations.push({
265
+ kind: "add",
266
+ zone: "public",
267
+ section: {
268
+ id: "sec_" + randomHex(12),
269
+ title: "aithos-init",
270
+ body: "Ethos initialized.\n\n" +
271
+ "This section is a sentinel created by `EthosClient.ensureInitialized()` " +
272
+ "to materialise the subject's first edition. It is safe to delete or " +
273
+ "edit later — its only purpose was to satisfy the protocol's " +
274
+ "requirement that an edition contain at least one section.",
275
+ gamma_ref: "gamma_none_" + randomHex(24),
276
+ },
277
+ });
278
+ try {
279
+ const result = await this.#publishFirstEditionOwner();
280
+ // Re-stage the caller's prior mutations (if any) so a subsequent
281
+ // publish() in the same call sequence picks them up. The init
282
+ // section we added has been consumed by the publish above.
283
+ this.#mutations = prevMutations;
284
+ return {
285
+ alreadyInitialized: false,
286
+ editionHeight: result.editionHeight,
287
+ manifestHash: result.manifestHash,
288
+ };
289
+ }
290
+ catch (e) {
291
+ // On failure, restore the caller's mutations as they were before
292
+ // we touched the buffer so the client is left in a consistent state.
293
+ this.#mutations = prevMutations;
294
+ throw e;
295
+ }
296
+ }
297
+ /* ------------------------------------------------------------------------ */
171
298
  /* Internal — snapshot management */
172
299
  /* ------------------------------------------------------------------------ */
173
300
  async _readZone(zone) {
@@ -175,12 +302,18 @@ export class EthosClient {
175
302
  if (zone !== "public") {
176
303
  throw new AithosSDKError("ethos_anonymous_private_zone", `anonymous reader cannot access the "${zone}" zone`);
177
304
  }
178
- const snap = await this.#ensureSnapshotAnonymous();
179
- const base = snap.publicSections;
305
+ const snap = await this.#tryEnsureSnapshotAnonymous();
306
+ // No edition yet → no base sections; only staged mutations contribute.
307
+ const base = snap === null ? [] : snap.publicSections;
180
308
  return this.#applyMutations(zone, base);
181
309
  }
182
310
  if (this.#actor.kind === "owner") {
183
- const snap = await this.#ensureSnapshotOwner();
311
+ const snap = await this.#tryEnsureSnapshotOwner();
312
+ if (snap === null) {
313
+ // No edition yet for this owner — base sections are empty in every
314
+ // zone. Staged mutations apply on top of an empty list.
315
+ return this.#applyMutations(zone, []);
316
+ }
184
317
  const base = baseSectionsFromSnapshot(snap, zone);
185
318
  // Surface decryption errors clearly (if reading a private zone we
186
319
  // can't unwrap, the snapshot has it in zoneDecryptErrors).
@@ -190,7 +323,10 @@ export class EthosClient {
190
323
  return this.#applyMutations(zone, base);
191
324
  }
192
325
  // Delegate
193
- const snap = await this.#ensureSnapshotDelegate();
326
+ const snap = await this.#tryEnsureSnapshotDelegate();
327
+ if (snap === null) {
328
+ return this.#applyMutations(zone, []);
329
+ }
194
330
  const base = baseSectionsFromSnapshot(snap, zone);
195
331
  if (zone !== "public" && snap.zoneDecryptErrors?.[zone]) {
196
332
  throw new AithosSDKError("ethos_zone_unreadable", `cannot read ${zone}: ${snap.zoneDecryptErrors[zone]}`);
@@ -283,6 +419,57 @@ export class EthosClient {
283
419
  this.#cacheSnapshotAllZones(snap);
284
420
  return snap;
285
421
  }
422
+ /**
423
+ * Wrap {@link #ensureSnapshotOwner} so the "no edition published yet"
424
+ * server response (-32020 with message `not found: edition for <did>`)
425
+ * is converted into `null`. Once converted, {@link #ethosHasNoEditionYet}
426
+ * is set to short-circuit subsequent reads without re-hitting the network.
427
+ *
428
+ * Returns `null` to mean "subject has an identity but no editions yet —
429
+ * treat all zones as empty". Any other error is re-thrown.
430
+ */
431
+ async #tryEnsureSnapshotOwner() {
432
+ if (this.#ethosHasNoEditionYet)
433
+ return null;
434
+ try {
435
+ return await this.#ensureSnapshotOwner();
436
+ }
437
+ catch (e) {
438
+ if (isNoEditionYetError(e)) {
439
+ this.#ethosHasNoEditionYet = true;
440
+ return null;
441
+ }
442
+ throw e;
443
+ }
444
+ }
445
+ async #tryEnsureSnapshotDelegate() {
446
+ if (this.#ethosHasNoEditionYet)
447
+ return null;
448
+ try {
449
+ return await this.#ensureSnapshotDelegate();
450
+ }
451
+ catch (e) {
452
+ if (isNoEditionYetError(e)) {
453
+ this.#ethosHasNoEditionYet = true;
454
+ return null;
455
+ }
456
+ throw e;
457
+ }
458
+ }
459
+ async #tryEnsureSnapshotAnonymous() {
460
+ if (this.#ethosHasNoEditionYet)
461
+ return null;
462
+ try {
463
+ return await this.#ensureSnapshotAnonymous();
464
+ }
465
+ catch (e) {
466
+ if (isNoEditionYetError(e)) {
467
+ this.#ethosHasNoEditionYet = true;
468
+ return null;
469
+ }
470
+ throw e;
471
+ }
472
+ }
286
473
  #cacheSnapshotAllZones(snap) {
287
474
  for (const z of ZONE_NAMES)
288
475
  this.#snapshots.set(z, snap);
@@ -291,6 +478,162 @@ export class EthosClient {
291
478
  this.#mutations = [];
292
479
  this.#snapshots.clear();
293
480
  }
481
+ /* ------------------------------------------------------------------------ */
482
+ /* First-edition publish (owner) */
483
+ /* ------------------------------------------------------------------------ */
484
+ /**
485
+ * Publish height=1 for an owner whose Ethos identity exists on
486
+ * `api.aithos.be` (provisioned by `auth.signUp()` in alpha.6+) but who
487
+ * has no editions yet. Builds the manifest from the staged ADD
488
+ * mutations and POSTs `aithos.publish_ethos_edition`.
489
+ *
490
+ * Behaviour:
491
+ * - All three zones (public / circle / self) are supported at
492
+ * height=1 since `@aithos/protocol-client@>=0.1.0-alpha.14`. Circle
493
+ * and self sections are sealed via the same DEK + HKDF wrap
494
+ * machinery that the height>=2 path uses, so they're verifiable by
495
+ * the existing reader path with no special-casing.
496
+ * - If the caller staged only circle / self mutations (typical for
497
+ * an app like Linkedone that writes a personality section straight
498
+ * to `circle`), an `aithos-init` sentinel is auto-injected into
499
+ * the public zone. This preserves the invariant that every Ethos
500
+ * has a non-empty public zone at height=1 — which all resolution
501
+ * flows (handle lookup, public crawl) depend on.
502
+ * - First-edition publishes don't accept update/delete mutations
503
+ * (there's nothing to update or delete yet) — those are rejected
504
+ * with `ethos_first_edition_invalid_op`.
505
+ * - The `ethos_first_edition_public_only` error code is no longer
506
+ * emitted; the bucket-and-auto-inject path replaces it.
507
+ */
508
+ async #publishFirstEditionOwner() {
509
+ if (this.#actor.kind !== "owner") {
510
+ // Defensive — caller already checked this branch.
511
+ throw new AithosSDKError("ethos_invalid_actor", "expected owner actor");
512
+ }
513
+ // Validate the staged operation set and bucket by zone. First
514
+ // edition = ADD mutations only; update / delete are rejected because
515
+ // there's no prior state to mutate.
516
+ const publicAdds = [];
517
+ const circleAdds = [];
518
+ const selfAdds = [];
519
+ for (const m of this.#mutations) {
520
+ if (m.kind !== "add") {
521
+ throw new AithosSDKError("ethos_first_edition_invalid_op", `first edition: cannot ${m.kind} a section before any edition exists; only addSection is supported on a fresh Ethos`, { data: { mutation: m } });
522
+ }
523
+ if (m.zone === "public")
524
+ publicAdds.push(m.section);
525
+ else if (m.zone === "circle")
526
+ circleAdds.push(m.section);
527
+ else if (m.zone === "self")
528
+ selfAdds.push(m.section);
529
+ }
530
+ if (publicAdds.length === 0 &&
531
+ circleAdds.length === 0 &&
532
+ selfAdds.length === 0) {
533
+ // Should never reach here — publish() short-circuits on empty
534
+ // mutations. Belt-and-braces in case the contract drifts.
535
+ throw new AithosSDKError("ethos_first_edition_empty", "first edition: stage at least one section before publishing");
536
+ }
537
+ // Public zone invariant: every Ethos has a non-empty public zone at
538
+ // height=1. If the caller only staged encrypted-zone mutations
539
+ // (typical for apps like Linkedone that write straight to circle),
540
+ // auto-inject an `aithos-init` sentinel — identical in shape to
541
+ // what `ensureInitialized()` would produce if called explicitly.
542
+ if (publicAdds.length === 0) {
543
+ publicAdds.push({
544
+ id: "sec_" + randomHex(12),
545
+ title: "aithos-init",
546
+ body: "Ethos initialized.\n\n" +
547
+ "This section is a sentinel created by `EthosClient.publish()` " +
548
+ "to materialise the subject's first edition alongside the " +
549
+ "encrypted-zone content the caller staged. It is safe to delete " +
550
+ "or edit later — its only purpose is to satisfy the invariant " +
551
+ "that every Ethos has a non-empty public zone at height=1.",
552
+ gamma_ref: "gamma_none_" + randomHex(24),
553
+ });
554
+ }
555
+ const identity = this.#actor.signers._unsafeStoredIdentity();
556
+ const browserId = browserIdentityFromStored(identity);
557
+ const signedDoc = signedDidDocument(browserId);
558
+ const built = buildSignedFirstEditionFromSections({
559
+ identity: browserId,
560
+ signedDidDoc: signedDoc,
561
+ publicSections: publicAdds,
562
+ ...(circleAdds.length > 0 ? { circleSections: circleAdds } : {}),
563
+ ...(selfAdds.length > 0 ? { selfSections: selfAdds } : {}),
564
+ });
565
+ const url = writeEndpoint();
566
+ const zonesPayload = {
567
+ public: { bytes_base64: bytesToBase64Padded(built.publicMarkdownBytes) },
568
+ };
569
+ if (built.circleBytes) {
570
+ zonesPayload.circle = {
571
+ bytes_base64: bytesToBase64Padded(built.circleBytes),
572
+ };
573
+ }
574
+ if (built.selfBytes) {
575
+ zonesPayload.self = {
576
+ bytes_base64: bytesToBase64Padded(built.selfBytes),
577
+ };
578
+ }
579
+ const params = {
580
+ manifest: built.manifest,
581
+ zones: zonesPayload,
582
+ };
583
+ const envelope = buildSignedEnvelope({
584
+ iss: browserId.did,
585
+ aud: url,
586
+ method: "aithos.publish_ethos_edition",
587
+ verificationMethod: `${browserId.did}#public`,
588
+ params,
589
+ signer: browserId.public,
590
+ });
591
+ const body = JSON.stringify({
592
+ jsonrpc: "2.0",
593
+ id: "publish_ethos_edition",
594
+ method: "aithos.publish_ethos_edition",
595
+ params: { ...params, _envelope: envelope },
596
+ });
597
+ let res;
598
+ try {
599
+ res = await fetch(url, {
600
+ method: "POST",
601
+ headers: { "content-type": "application/json" },
602
+ body,
603
+ });
604
+ }
605
+ catch (e) {
606
+ throw new AithosSDKError("ethos_publish_network", `publish_ethos_edition (first edition): network error: ${e.message ?? "unknown"}`);
607
+ }
608
+ let json;
609
+ try {
610
+ json = (await res.json());
611
+ }
612
+ catch {
613
+ throw new AithosSDKError("ethos_publish_invalid_response", `publish_ethos_edition (first edition): server returned non-JSON (HTTP ${res.status})`);
614
+ }
615
+ if (json.error) {
616
+ throw new AithosSDKError("ethos_first_edition_rejected", `publish_ethos_edition (first edition) rejected: ${json.error.message}`, {
617
+ status: res.status,
618
+ data: { rpc_code: json.error.code, ...(json.error.data ?? {}) },
619
+ });
620
+ }
621
+ // Success: clear the no-edition flag so subsequent reads/publishes
622
+ // take the regular next-edition path.
623
+ this.#ethosHasNoEditionYet = false;
624
+ this.#afterPublish();
625
+ const zonesPublished = ["public"];
626
+ if (built.circleBytes)
627
+ zonesPublished.push("circle");
628
+ if (built.selfBytes)
629
+ zonesPublished.push("self");
630
+ return {
631
+ editionHeight: 1,
632
+ manifestHash: "", // protocol-client surfaces this on later editions; not on first
633
+ subjectDid: browserId.did,
634
+ zonesPublished,
635
+ };
636
+ }
294
637
  }
295
638
  /* -------------------------------------------------------------------------- */
296
639
  /* EthosZone — per-zone proxy */
@@ -318,6 +661,89 @@ export class EthosZone {
318
661
  deleteSection(sectionId) {
319
662
  this.#parent._stageDelete(this.#name, sectionId);
320
663
  }
664
+ /* ------------------------------------------------------------------------ */
665
+ /* By-title helpers */
666
+ /* */
667
+ /* The Aithos protocol does not (yet) treat section titles as a queryable */
668
+ /* index — only `section.id` is a normative anchor. These three methods */
669
+ /* are an ergonomic SDK-side affordance for the common LLM-driven case */
670
+ /* "act on the section called X". They resolve titles client-side by */
671
+ /* loading the full zone (`sections()`) and exact-match filtering on */
672
+ /* `Section.title`. */
673
+ /* */
674
+ /* When a future revision of `aithos.get_ethos_zone` (or a new */
675
+ /* `aithos.find_sections` primitive) supports server-side title */
676
+ /* filtering, the implementation behind these three methods can swap to */
677
+ /* the server call without changing the public signatures here. The */
678
+ /* contract — async, exact case-sensitive match, plural semantics — is */
679
+ /* intentionally aligned with what such an API would return. */
680
+ /* ------------------------------------------------------------------------ */
681
+ /**
682
+ * Return every section in this zone whose `title` is exactly `title`.
683
+ *
684
+ * Match is exact and case-sensitive. The result is always an array — it
685
+ * may be empty (no match), have one element (the typical case), or have
686
+ * more than one element when the author has happened to publish two
687
+ * sections with the same title. Section titles are not required by the
688
+ * protocol to be unique within a zone.
689
+ *
690
+ * The order of returned sections is the zone's authored order
691
+ * (`sections()` ordering, spec §2.5.2).
692
+ *
693
+ * @param title Section title to look up — exact, case-sensitive.
694
+ */
695
+ async findSectionsByTitle(title) {
696
+ const all = await this.#parent._readZone(this.#name);
697
+ return all.filter((s) => s.title === title);
698
+ }
699
+ /**
700
+ * Stage an update for **every** section in this zone whose `title`
701
+ * matches `title` exactly. Returns the list of section IDs that were
702
+ * staged — empty when nothing matched.
703
+ *
704
+ * Apply with `client.publish()` like any other staged mutation. The
705
+ * staged entries are identical to what `updateSection(id, patch)` would
706
+ * produce, one per matched section, so `pendingChanges()` /
707
+ * `discard()` behave normally.
708
+ *
709
+ * Note: this method does NOT throw when there is no match — it returns
710
+ * `[]`. That's intentional: callers driven by an LLM frequently want to
711
+ * upsert (try update, then fall back to add) and shouldn't have to
712
+ * catch.
713
+ *
714
+ * @param title Section title to look up — exact, case-sensitive.
715
+ * @param patch Same patch shape accepted by `updateSection`.
716
+ * @returns Array of `section.id` strings whose updates were staged.
717
+ */
718
+ async updateSectionsByTitle(title, patch) {
719
+ const matches = await this.findSectionsByTitle(title);
720
+ const ids = [];
721
+ for (const s of matches) {
722
+ this.#parent._stageUpdate(this.#name, s.id, patch);
723
+ ids.push(s.id);
724
+ }
725
+ return ids;
726
+ }
727
+ /**
728
+ * Stage a delete for **every** section in this zone whose `title`
729
+ * matches `title` exactly. Returns the list of section IDs that were
730
+ * staged — empty when nothing matched.
731
+ *
732
+ * Same semantics as {@link updateSectionsByTitle}: silent on no-match,
733
+ * apply with `client.publish()`.
734
+ *
735
+ * @param title Section title to look up — exact, case-sensitive.
736
+ * @returns Array of `section.id` strings whose deletes were staged.
737
+ */
738
+ async deleteSectionsByTitle(title) {
739
+ const matches = await this.findSectionsByTitle(title);
740
+ const ids = [];
741
+ for (const s of matches) {
742
+ this.#parent._stageDelete(this.#name, s.id);
743
+ ids.push(s.id);
744
+ }
745
+ return ids;
746
+ }
321
747
  }
322
748
  export class EthosNamespace {
323
749
  #deps;
@@ -410,6 +836,36 @@ function projectPublishResult(manifest, subjectDid, zones) {
410
836
  zonesPublished: zones,
411
837
  };
412
838
  }
839
+ /**
840
+ * Detect the server-side "no edition published yet" response. Matches the
841
+ * exact error shape emitted by primitives-read's `notFound("edition for
842
+ * <did>")` helper: JSON-RPC code -32020 + message starting with `not
843
+ * found: edition for `. Other -32020 cases (e.g. "not found: manifest
844
+ * <did>@<height>" raised when the index advertises an edition the S3
845
+ * bucket can't serve) deliberately fall through — they're symptoms, not
846
+ * the "fresh subject" case we're trying to swallow.
847
+ */
848
+ function isNoEditionYetError(e) {
849
+ if (!(e instanceof AithosRpcError))
850
+ return false;
851
+ if (e.code !== -32020)
852
+ return false;
853
+ return typeof e.message === "string"
854
+ && e.message.startsWith("not found: edition for ");
855
+ }
856
+ /**
857
+ * Standard base64 with `=` padding — matches what protocol-client's
858
+ * publishZoneEdit uses for `zones.<zone>.bytes_base64`. The server is
859
+ * tolerant of either padded or unpadded variants per the API contract,
860
+ * but we mirror the existing wire to keep payloads byte-identical for
861
+ * easy diffing in dev tools.
862
+ */
863
+ function bytesToBase64Padded(bytes) {
864
+ let bin = "";
865
+ for (let i = 0; i < bytes.length; i++)
866
+ bin += String.fromCharCode(bytes[i]);
867
+ return btoa(bin);
868
+ }
413
869
  function randomHex(n) {
414
870
  const bytes = new Uint8Array(Math.ceil(n / 2));
415
871
  crypto.getRandomValues(bytes);
@@ -1,21 +1,32 @@
1
- export declare const VERSION = "0.1.0-alpha.5";
1
+ export declare const VERSION = "0.1.0-alpha.44";
2
2
  export { AithosSDK } from "./sdk.js";
3
3
  export type { AithosSDKConfig } from "./types.js";
4
4
  export { AithosSDKError } from "./types.js";
5
+ export { AithosRpcError } from "@aithos/protocol-client";
5
6
  export type { AithosSdkEndpoints } from "./endpoints.js";
6
7
  export { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
7
- export type { ComputeMessage, InvokeBedrockArgs, InvokeBedrockResult, StopReason, } from "./compute.js";
8
+ export type { ComputeMessage, ImageAspectRatio, ImageModelId, InvokeBedrockArgs, InvokeBedrockResult, InvokeBedrockVisionArgs, InvokeBedrockVisionResult, InvokeImageArgs, InvokeImageImage, InvokeImageResult, InvokeSegmentationArgs, InvokeSegmentationResult, SegmentPolygon, StopReason, TranscribeModelId, TranscribeProgressState, TranscribeSegment, TranscribeWord, InvokeTranscribeArgs, InvokeTranscribeResult, PrepareTranscribeArgs, PrepareTranscribeResult, StartTranscribeArgs, StartTranscribeResult, TranscribeStatusResult, TranscribeJobSummary, } from "./compute.js";
8
9
  export { ComputeNamespace } from "./compute.js";
10
+ export type { LocalPendingEntry, LocalPendingStatus, TranscribeDraftMeta, TranscribeDraftRecord, } from "./transcribe-resilience.js";
11
+ export { LocalPendingTranscribeTracker, TranscribeDraftStore, TranscribeDraftUnavailableError, } from "./transcribe-resilience.js";
9
12
  export type { CreditPackId, CreateTopupSessionArgs, CreateTopupSessionResult, GetBalanceArgs, GetBalanceResult, } from "./wallet.js";
10
13
  export { WalletNamespace } from "./wallet.js";
11
- export { AithosAuth, DEFAULT_AUTH_BASE_URL } from "./auth.js";
12
- export type { AithosAuthConfig, AithosSession, DelegateInfo, ImportMandateInput, OwnerInfo, SignInInput, SignInWithGoogleOptions, SignInWithRecoveryInput, SignUpInput, SignUpResult, } from "./auth.js";
14
+ export type { ComponentStyle, ExtractArgs, ExtractContent, ExtractData, ExtractForm, ExtractFormField, ExtractHeading, ExtractIconDeclaration, ExtractImage, ExtractLink, ExtractLogo, ExtractMeta, ExtractResult, ExtractSection, ExtractStructure, ExtractStyles, FetchAssetArgs, FetchAssetResult, PaletteEntry, VisualSignature, WebNamespaceDeps, } from "./web.js";
15
+ export { WebNamespace, WEB_EXTRACT_SCOPE } from "./web.js";
16
+ export { AithosAuth, DEFAULT_API_BASE_URL, DEFAULT_AUTH_BASE_URL, } from "./auth.js";
17
+ export type { AcceptInviteInput, AcceptInviteResult, AithosAuthConfig, AithosSession, ApplyPasswordResetInput, ApplyPasswordResetResult, CompleteSsoFirstLoginInput, CompleteSsoFirstLoginResult, CustodialSignInInput, CustodialSignInResult, CustodialSignUpInput, CustodialSignUpResult, DelegateInfo, ImportMandateInput, InviteCustodialInput, InviteCustodialResult, OwnerInfo, RequestPasswordResetInput, ResendVerificationInput, SignInInput, SignInWithGoogleOptions, SignInWithRecoveryInput, SignUpInput, SignUpResult, VerifyEmailInput, VerifyEmailResult, } from "./auth.js";
18
+ export type { SignedEnvelope } from "./internal/envelope.js";
13
19
  export { DEFAULT_SESSION_STORAGE_KEY, defaultSessionStore, localStorageStore, noopStore, sessionStorageStore, type AithosSessionStore, } from "./session-store.js";
14
20
  export { DEFAULT_KEYSTORE_DB_NAME, defaultKeyStore, indexedDbKeyStore, memoryKeyStore, type AithosKeyStore, type StoredDelegateKeys, type StoredOwnerKeys, } from "./key-store.js";
15
21
  export { EthosClient, EthosNamespace, EthosZone, ZONE_NAMES, } from "./ethos.js";
16
22
  export type { AddSectionInput, PublishResult, StagedChange, UpdateSectionPatch, ZoneName, } from "./ethos.js";
17
- export { MandatesNamespace } from "./mandates.js";
18
- export type { ActorSphere, CreateMandateInput, MintedMandate, OwnedMandate, Scope, } from "./mandates.js";
23
+ export { COMPUTE_INVOKE_SCOPE, MandatesNamespace } from "./mandates.js";
24
+ export type { ActorSphere, CreateMandateComputeInput, CreateMandateInput, MintedMandate, OwnedMandate, Scope, } from "./mandates.js";
25
+ export { AppsNamespace } from "./apps.js";
26
+ export type { AudienceSet, AppCreditPackId, CreateAppTopupSessionArgs, CreateAppTopupSessionResult, CreateSponsorshipMandateArgs, SignedSponsorshipMandate, SignedSponsorshipRevocation, SponsorshipAccountingAuthorityInput, SponsorshipAudienceInput, SponsorshipBudgetInput, } from "./apps.js";
19
27
  export * as onboarding from "./onboarding.js";
20
28
  export { createBrowserIdentity, browserIdentityFromStored, type BrowserIdentity, } from "@aithos/protocol-client";
29
+ export type { Section } from "@aithos/protocol-client";
30
+ export { createDataClient, createDelegateDataClient, createAppendDataClient, type CreateDataClientArgs, type CreateDelegateDataClientArgs, type CreateAppendDataClientArgs, type DataClient, type DataCollection, type ReadonlyDataClient, type ReadonlyDataCollection, type AppendOnlyDataClient, type AppendOnlyDataCollection, type ListOpts, type AithosSchemaLite, } from "./data.js";
31
+ export { createAssetsClient, AssetsClient, type CreateAssetsClientArgs, type AttachedContext, type AssetUploadInput, type AssetUploadResult, type AssetFetchResult, type AssetBrief, type ListAssetsOpts, type ThumbnailUploadInput, type ThumbnailUploadResult, type RecipientResolver, type RecipientSet, } from "./assets.js";
21
32
  //# sourceMappingURL=index.d.ts.map
package/dist/src/index.js CHANGED
@@ -17,18 +17,24 @@
17
17
  // Public types specific to the SDK (`AithosSDKConfig`, `AithosSDKError`)
18
18
  // are exported from here. Endpoint config (`AithosSdkEndpoints`,
19
19
  // `DEFAULT_SDK_ENDPOINTS`) likewise.
20
- export const VERSION = "0.1.0-alpha.5";
20
+ export const VERSION = "0.1.0-alpha.44";
21
21
  export { AithosSDK } from "./sdk.js";
22
22
  export { AithosSDKError } from "./types.js";
23
+ // Re-export protocol-client's JSON-RPC error type so consumers can
24
+ // `instanceof`-check server-side errors and inspect the JSON-RPC code
25
+ // without taking a direct dependency on @aithos/protocol-client.
26
+ export { AithosRpcError } from "@aithos/protocol-client";
23
27
  export { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
24
28
  export { ComputeNamespace } from "./compute.js";
29
+ export { LocalPendingTranscribeTracker, TranscribeDraftStore, TranscribeDraftUnavailableError, } from "./transcribe-resilience.js";
25
30
  export { WalletNamespace } from "./wallet.js";
31
+ export { WebNamespace, WEB_EXTRACT_SCOPE } from "./web.js";
26
32
  // Sign-up, sign-in, sign-in-with-Google. Lives outside the AithosSDK
27
33
  // class because the auth flow runs *before* the user has a
28
34
  // BrowserIdentity (sign-up creates one, sign-in restores it from the
29
35
  // server). The class also owns the session store — see
30
36
  // ./session-store.ts for pluggable persistence.
31
- export { AithosAuth, DEFAULT_AUTH_BASE_URL } from "./auth.js";
37
+ export { AithosAuth, DEFAULT_API_BASE_URL, DEFAULT_AUTH_BASE_URL, } from "./auth.js";
32
38
  // Session storage backends used by AithosAuth. `sessionStorageStore` is
33
39
  // the default in browser environments ; pass another store at construction
34
40
  // time if you need different persistence.
@@ -46,11 +52,27 @@ export { DEFAULT_KEYSTORE_DB_NAME, defaultKeyStore, indexedDbKeyStore, memoryKey
46
52
  // the entry points.
47
53
  export { EthosClient, EthosNamespace, EthosZone, ZONE_NAMES, } from "./ethos.js";
48
54
  // `sdk.mandates` namespace — owner-side mandate lifecycle.
49
- export { MandatesNamespace } from "./mandates.js";
55
+ export { COMPUTE_INVOKE_SCOPE, MandatesNamespace } from "./mandates.js";
56
+ // `sdk.apps` namespace — sponsorship mandates + app-credit top-ups
57
+ // (draft §13, V0.1). Pre-pay a pool that funds your users' compute
58
+ // calls within explicit caps. Per-user caps + per-day caps + lifetime
59
+ // pool cap enforced server-side. Fallback to user wallet when caps
60
+ // are reached or pool is empty.
61
+ export { AppsNamespace } from "./apps.js";
50
62
  // Onboarding re-exports kept for advanced callers — the curated API
51
63
  // for first-run flows is `auth.signUp` (already shipped in J3).
52
64
  export * as onboarding from "./onboarding.js";
53
65
  // Convenience direct re-exports of the most-used identity primitives so the
54
66
  // quick-start example doesn't need a namespace import.
55
67
  export { createBrowserIdentity, browserIdentityFromStored, } from "@aithos/protocol-client";
68
+ // `sdk.data` namespace — Aithos data sub-protocol PDS client. Manages
69
+ // the lifecycle of subject-owned, encrypted, schema-validated records.
70
+ // See spec/data/ in the aithos-protocol repo.
71
+ export { createDataClient, createDelegateDataClient, createAppendDataClient, } from "./data.js";
72
+ // `sdk.assets` — Aithos assets sub-protocol PDS client. Upload,
73
+ // fetch, list, ref/unref binary content (images, PDFs, audio, video)
74
+ // owned by a subject. AEAD-encrypted per-asset under AMKs wrapped for
75
+ // the subject's X25519 sphere keys (and, in v0.2, mandate grantees).
76
+ // See spec/assets/ in the aithos-protocol repo.
77
+ export { createAssetsClient, AssetsClient, } from "./assets.js";
56
78
  //# sourceMappingURL=index.js.map
@@ -50,13 +50,18 @@ export function parseDelegateBundle(text) {
50
50
  }
51
51
  const m = mandate;
52
52
  const mandateId = m["id"];
53
- const subjectDid = m["subject_did"] ?? m["subjectDid"];
53
+ // The mandate subject's DID is carried by `issuer` in the wire format
54
+ // emitted by `mintDelegateBundle()` (cf. SignedMandate.issuer in
55
+ // protocol-client). We accept the legacy `subject_did` / camelCase
56
+ // variants too so older test fixtures and any externally-minted
57
+ // bundles using the older shape keep working.
58
+ const subjectDid = m["issuer"] ?? m["subject_did"] ?? m["subjectDid"];
54
59
  const grantee = m["grantee"];
55
60
  if (typeof mandateId !== "string" || !mandateId) {
56
61
  throw bad("mandate.id missing");
57
62
  }
58
63
  if (typeof subjectDid !== "string" || !subjectDid.startsWith("did:")) {
59
- throw bad("mandate.subject_did missing or malformed");
64
+ throw bad("mandate.issuer missing or malformed (expected a `did:` URL)");
60
65
  }
61
66
  if (typeof grantee !== "object" || grantee === null) {
62
67
  throw bad("mandate.grantee missing");