@aithos/sdk 0.1.0-alpha.26 → 0.1.0-alpha.27

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.
@@ -71,6 +71,44 @@ export declare class EthosClient {
71
71
  * so the next read picks up the fresh edition.
72
72
  */
73
73
  publish(): Promise<PublishResult>;
74
+ /**
75
+ * Idempotently ensure the subject's Ethos has at least one published
76
+ * edition. Required because a **delegate** cannot bootstrap a first
77
+ * edition (the first edition's manifest is signed with the owner's
78
+ * public-sphere key, which delegates do not have). Without an initial
79
+ * owner-published edition, subsequent delegate writes via
80
+ * {@link publish} fail with `not found: edition for did:…`.
81
+ *
82
+ * Semantics:
83
+ * - If an edition already exists (owner OR delegate OR anonymous mode),
84
+ * this is a NO-OP and returns `{ alreadyInitialized: true }`.
85
+ * - If no edition exists AND the actor is the owner, this stages and
86
+ * publishes a height=1 edition containing a single sentinel section
87
+ * `aithos-init` in the `public` zone, then returns
88
+ * `{ alreadyInitialized: false, editionHeight: 1 }`. Any previously
89
+ * staged mutations on this client are preserved and NOT auto-flushed.
90
+ * - If no edition exists AND the actor is NOT the owner (delegate or
91
+ * anonymous), throws `ethos_bootstrap_not_owner` — only the owner
92
+ * can sign a first edition.
93
+ *
94
+ * Call site: typically the owner's dashboard, right after sign-in and
95
+ * before any delegate-mode write (e.g. before triggering a backend
96
+ * worker that holds a mandate). Idempotent ⇒ safe to call on every
97
+ * mount.
98
+ *
99
+ * Implementation note: this routes through {@link #publishFirstEditionOwner}
100
+ * which the SDK already uses internally when {@link publish} detects a
101
+ * fresh Ethos with staged owner mutations. ensureInitialized() exposes
102
+ * the same code path as an explicit primitive, so the caller doesn't
103
+ * need to stage a mutation just to trigger first-edition logic.
104
+ */
105
+ ensureInitialized(): Promise<{
106
+ alreadyInitialized: true;
107
+ } | {
108
+ alreadyInitialized: false;
109
+ editionHeight: number;
110
+ manifestHash: string;
111
+ }>;
74
112
  _readZone(zone: ZoneName): Promise<readonly Section[]>;
75
113
  _stageAdd(zone: ZoneName, input: AddSectionInput): void;
76
114
  _stageUpdate(zone: ZoneName, sectionId: string, patch: UpdateSectionPatch): void;
package/dist/src/ethos.js CHANGED
@@ -203,6 +203,98 @@ export class EthosClient {
203
203
  throw new AithosSDKError("ethos_invalid_actor", "unsupported actor for publish()");
204
204
  }
205
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
+ /* ------------------------------------------------------------------------ */
206
298
  /* Internal — snapshot management */
207
299
  /* ------------------------------------------------------------------------ */
208
300
  async _readZone(zone) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aithos/sdk",
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.27",
4
4
  "description": "Aithos SDK \u2014 high-level TypeScript developer kit for building agentic apps on the Aithos protocol. Wraps @aithos/protocol-client and exposes the Aithos compute proxy and wallet (Stripe top-up) endpoints.",
5
5
  "keywords": [
6
6
  "aithos",