@aithos/sdk 0.1.0-alpha.54 → 0.1.0-alpha.56

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.
package/README.md CHANGED
@@ -169,9 +169,11 @@ const { session, passwordMustChange } = await sdk.auth.signInCustodial({
169
169
  email: "alice@example.com",
170
170
  password: "MyTempPass32chars",
171
171
  });
172
- // Local KeyStore is now hydrated with the 4 Ed25519 sphere seeds
173
- // the user can publish ethos editions, mint mandates, invoke compute,
174
- // exactly as if they had signed in via a recovery file or Google SSO.
172
+ // Local KeyStore is now hydrated with the 5 Ed25519 sphere seeds
173
+ // (root, public, circle, self, #data) — the user can publish ethos
174
+ // editions, mint mandates, invoke compute, and own PDS data/asset
175
+ // collections (signed under the dedicated #data sphere), exactly as if
176
+ // they had signed in via a recovery file or Google SSO.
175
177
  if (passwordMustChange) {
176
178
  // Optional: nudge the user to set their own password via the
177
179
  // standard reset flow.
@@ -39,11 +39,27 @@ export interface CreateAssetsClientArgs {
39
39
  * production vanity domain — SEPARATE from the data PDS) when omitted.
40
40
  * Override for self-hosting/staging. */
41
41
  readonly pdsUrl?: string;
42
- /** Subject DID. */
42
+ /**
43
+ * Subject DID that owns the assets. The canonical owner is a `did:aithos:…`
44
+ * account signing under its dedicated `#data` sphere; a `did:key:…` is a
45
+ * throwaway identity for demos/tests only.
46
+ */
43
47
  readonly did: string;
44
- /** Ed25519 sphere seed (32 bytes). */
48
+ /**
49
+ * Ed25519 sphere seed (32 bytes) that signs every assets-PDS envelope. For a
50
+ * `did:aithos` account this MUST be the subject's dedicated **`#data`** sphere
51
+ * seed (root stays cold). For a `did:key` it is the single embedded key.
52
+ *
53
+ * Note: this is the SIGNING key. Per-asset AMKs for private uploads are
54
+ * wrapped to the attaching context's X25519 key (`#data-kex` / `#circle-kex`
55
+ * / `#self-kex`) by the RecipientResolver — a separate mechanism.
56
+ */
45
57
  readonly sphereSeed: Uint8Array;
46
- /** Verification method URL within the DID document (e.g. `<did>#<multibase>` for did:key). */
58
+ /**
59
+ * Verification method URL within the DID document used to sign envelopes.
60
+ * For a `did:aithos` account this is **`<did>#data`**; for a `did:key` it is
61
+ * `<did>#<multibase>`.
62
+ */
47
63
  readonly verificationMethod: string;
48
64
  /** Optional fetch implementation. Defaults to globalThis.fetch. */
49
65
  readonly fetch?: typeof fetch;
package/dist/src/auth.js CHANGED
@@ -976,33 +976,15 @@ export class AithosAuth {
976
976
  }
977
977
  // Magic-link sign-in path. Mirror `signInCustodial` to materialise
978
978
  // the 4 sphere seeds in the keystore.
979
- if (resp.seed.byteLength !== 128) {
980
- zeroize(resp.seed);
981
- zeroize(resp.encKey);
982
- throw new AithosSDKError("auth_custodial_seed_format", `verifyEmail: expected 128-byte seed bundle, got ${resp.seed.byteLength}`);
983
- }
984
- const seedRoot = resp.seed.slice(0, 32);
985
- const seedPublic = resp.seed.slice(32, 64);
986
- const seedCircle = resp.seed.slice(64, 96);
987
- const seedSelf = resp.seed.slice(96, 128);
988
979
  const stored = {
989
980
  version: "0.1.0-hex",
990
981
  did: resp.did,
991
982
  handle: resp.handle,
992
983
  displayName: resp.displayName,
993
- seedsHex: {
994
- root: bytesToHex(seedRoot),
995
- public: bytesToHex(seedPublic),
996
- circle: bytesToHex(seedCircle),
997
- self: bytesToHex(seedSelf),
998
- },
984
+ // Accepts 128 (legacy) or 160 (with #data); zeroizes resp.seed.
985
+ seedsHex: custodialSeedsHex(resp.seed),
999
986
  savedAt: new Date().toISOString(),
1000
987
  };
1001
- zeroize(resp.seed);
1002
- zeroize(seedRoot);
1003
- zeroize(seedPublic);
1004
- zeroize(seedCircle);
1005
- zeroize(seedSelf);
1006
988
  zeroize(resp.encKey);
1007
989
  // Bootstrap the Ethos on api.aithos.be (cf. notes in signInCustodial).
1008
990
  // The magic-link flow is the FIRST time the user actually has
@@ -1106,33 +1088,15 @@ export class AithosAuth {
1106
1088
  });
1107
1089
  // Materialise the 4 sphere seeds + session — same shape as the verifyEmail
1108
1090
  // magic-link path. Kept inline (additive; verifyEmail is left untouched).
1109
- if (resp.seed.byteLength !== 128) {
1110
- zeroize(resp.seed);
1111
- zeroize(resp.encKey);
1112
- throw new AithosSDKError("auth_custodial_seed_format", `acceptInvite: expected 128-byte seed bundle, got ${resp.seed.byteLength}`);
1113
- }
1114
- const seedRoot = resp.seed.slice(0, 32);
1115
- const seedPublic = resp.seed.slice(32, 64);
1116
- const seedCircle = resp.seed.slice(64, 96);
1117
- const seedSelf = resp.seed.slice(96, 128);
1118
1091
  const stored = {
1119
1092
  version: "0.1.0-hex",
1120
1093
  did: resp.did,
1121
1094
  handle: resp.handle,
1122
1095
  displayName: resp.displayName,
1123
- seedsHex: {
1124
- root: bytesToHex(seedRoot),
1125
- public: bytesToHex(seedPublic),
1126
- circle: bytesToHex(seedCircle),
1127
- self: bytesToHex(seedSelf),
1128
- },
1096
+ // Accepts 128 (legacy) or 160 (with #data); zeroizes resp.seed.
1097
+ seedsHex: custodialSeedsHex(resp.seed),
1129
1098
  savedAt: new Date().toISOString(),
1130
1099
  };
1131
- zeroize(resp.seed);
1132
- zeroize(seedRoot);
1133
- zeroize(seedPublic);
1134
- zeroize(seedCircle);
1135
- zeroize(seedSelf);
1136
1100
  zeroize(resp.encKey);
1137
1101
  const identity = browserIdentityFromStored({
1138
1102
  handle: stored.handle,
@@ -1212,45 +1176,19 @@ export class AithosAuth {
1212
1176
  throw new AithosSDKError("auth_invalid_input", "signInCustodial: email and password are required");
1213
1177
  }
1214
1178
  const resp = await custodialSignIn({ fetchImpl: this.#fetchImpl, authBaseUrl: this.authBaseUrl }, input);
1215
- // Split the 128-byte seed bundle into the four sphere seeds. The
1216
- // backend lays them out in the canonical order
1217
- // [root || public || circle || self] (cf. seed-wrapper.ts).
1218
- if (resp.seed.byteLength !== 128) {
1219
- // Legacy 32-byte rows shouldn't happen in production (we wiped the
1220
- // single test row before redeploying with the 4-seed bundle), but
1221
- // we surface a clear error rather than silently corrupting the
1222
- // identity.
1223
- zeroize(resp.seed);
1224
- zeroize(resp.encKey);
1225
- throw new AithosSDKError("auth_custodial_seed_format", `signInCustodial: expected 128-byte seed bundle, got ${resp.seed.byteLength}`);
1226
- }
1227
- const seedRoot = resp.seed.slice(0, 32);
1228
- const seedPublic = resp.seed.slice(32, 64);
1229
- const seedCircle = resp.seed.slice(64, 96);
1230
- const seedSelf = resp.seed.slice(96, 128);
1231
- // Stored shape uses hex strings; round-trip through bytesToHex
1232
- // so the keyStore record is identical to what signUp(zk) writes.
1179
+ // Split the custodial seed bundle into the sphere seeds. The backend
1180
+ // lays them out in the canonical order [root || public || circle || self]
1181
+ // (128 bytes), plus the dedicated #data sphere when migrated (160 bytes).
1182
+ // See seed-wrapper.ts / custodialSeedsHex.
1233
1183
  const stored = {
1234
1184
  version: "0.1.0-hex",
1235
1185
  did: resp.did,
1236
1186
  handle: resp.handle,
1237
1187
  displayName: resp.displayName,
1238
- seedsHex: {
1239
- root: bytesToHex(seedRoot),
1240
- public: bytesToHex(seedPublic),
1241
- circle: bytesToHex(seedCircle),
1242
- self: bytesToHex(seedSelf),
1243
- },
1188
+ // Accepts 128 (legacy, no #data) or 160 (with #data); zeroizes resp.seed.
1189
+ seedsHex: custodialSeedsHex(resp.seed),
1244
1190
  savedAt: new Date().toISOString(),
1245
1191
  };
1246
- // Zeroize the raw bundle + the split copies now that they've been
1247
- // serialised into the keyStore record (hex strings live in the
1248
- // record; the original bytes can go).
1249
- zeroize(resp.seed);
1250
- zeroize(seedRoot);
1251
- zeroize(seedPublic);
1252
- zeroize(seedCircle);
1253
- zeroize(seedSelf);
1254
1192
  // The enc_key is informational here — the custodial blob is empty
1255
1193
  // at first login. We still don't keep it in memory.
1256
1194
  zeroize(resp.encKey);
@@ -1507,6 +1445,46 @@ function bytesToHex(b) {
1507
1445
  out += b[i].toString(16).padStart(2, "0");
1508
1446
  return out;
1509
1447
  }
1448
+ /**
1449
+ * Split a custodial seed bundle into the keystore's hex seeds, zeroizing the
1450
+ * raw bytes (the bundle and every slice) before returning.
1451
+ *
1452
+ * Two bundle shapes are accepted, in canonical sphere order:
1453
+ * - 128 bytes = root || public || circle || self (legacy, no #data)
1454
+ * - 160 bytes = root || public || circle || self || data (V2.2+, with #data)
1455
+ *
1456
+ * A 160-byte bundle yields a `data` seed so the custodial owner carries the
1457
+ * dedicated `#data` sphere — identical to a self-custody / SSO account. A
1458
+ * 128-byte bundle (an account the backend hasn't migrated yet) yields no
1459
+ * `data`; the backend upgrades it to 160 on the user's next sign-in.
1460
+ */
1461
+ function custodialSeedsHex(seed) {
1462
+ const len = seed.byteLength;
1463
+ if (len !== 128 && len !== 160) {
1464
+ zeroize(seed);
1465
+ throw new AithosSDKError("auth_custodial_seed_format", `expected a 128- or 160-byte custodial seed bundle, got ${len}`);
1466
+ }
1467
+ const root = seed.slice(0, 32);
1468
+ const pub = seed.slice(32, 64);
1469
+ const circle = seed.slice(64, 96);
1470
+ const self = seed.slice(96, 128);
1471
+ const data = len === 160 ? seed.slice(128, 160) : undefined;
1472
+ const hex = {
1473
+ root: bytesToHex(root),
1474
+ public: bytesToHex(pub),
1475
+ circle: bytesToHex(circle),
1476
+ self: bytesToHex(self),
1477
+ ...(data ? { data: bytesToHex(data) } : {}),
1478
+ };
1479
+ zeroize(root);
1480
+ zeroize(pub);
1481
+ zeroize(circle);
1482
+ zeroize(self);
1483
+ if (data)
1484
+ zeroize(data);
1485
+ zeroize(seed);
1486
+ return hex;
1487
+ }
1510
1488
  /**
1511
1489
  * Project a delegate as it appears in a `BlobPlaintext` (extension-kit
1512
1490
  * `StoredDelegate` shape) onto the SDK's own {@link StoredDelegateKeys}.
@@ -13,18 +13,25 @@ export interface CreateDataClientArgs {
13
13
  * staging (e.g. a raw `execute-api` URL).
14
14
  */
15
15
  readonly pdsUrl?: string;
16
- /** Subject DID — typically did:key:… in dev, did:aithos:… in prod. */
16
+ /**
17
+ * Subject DID that owns the data. The canonical owner is a `did:aithos:…`
18
+ * account signing under its dedicated `#data` sphere (see below). A
19
+ * `did:key:…` is a throwaway identity for quick demos/tests only — it has no
20
+ * sphere separation (every sphere collapses to the single embedded key).
21
+ */
17
22
  readonly did: string;
18
23
  /**
19
- * Ed25519 sphere seed (32 bytes). For did:key this is the same as
20
- * the key embedded in the DID itself. For did:aithos this is the
21
- * subject's `#data` (or `#public`) sphere key.
24
+ * Ed25519 sphere seed (32 bytes) that signs every PDS envelope. For a
25
+ * `did:aithos` account this MUST be the subject's dedicated **`#data`** sphere
26
+ * seed never the root key nor an Ethos sphere — so the root stays cold and
27
+ * data operations are isolated to their own key. For a `did:key` it is the
28
+ * single key embedded in the DID.
22
29
  */
23
30
  readonly sphereSeed: Uint8Array;
24
31
  /**
25
- * The verification method URL within the DID document used to sign
26
- * envelopes. For did:key this is `<did>#<multibase>`. For did:aithos
27
- * this is `<did>#data` (or `#public`).
32
+ * The verification method URL within the DID document used to sign PDS
33
+ * envelopes. For a `did:aithos` account this is **`<did>#data`**. For a
34
+ * `did:key` it is `<did>#<multibase>`.
28
35
  */
29
36
  readonly verificationMethod: string;
30
37
  /** Optional fetch implementation. Defaults to globalThis.fetch. */
@@ -339,4 +346,18 @@ export interface CreateAppendDataClientArgs {
339
346
  * (insert allowed; get/list/update/delete refused).
340
347
  */
341
348
  export declare function createAppendDataClient(args: CreateAppendDataClientArgs): AppendOnlyDataClient;
349
+ /**
350
+ * Derive an {@link AithosSchemaLite} from a PUBLISHED JSON Schema document (the
351
+ * shape `aithos.data.get_schema` / `registerSchema` round-trip). The field
352
+ * split is read from the per-property annotations:
353
+ * - `aithos:indexable: true` → indexable (server-visible, filter/sort)
354
+ * - `aithos:auto: …` → auto (server-populated, e.g. created_at)
355
+ * - anything else → encrypted (AEAD'd client-side)
356
+ *
357
+ * These annotations are authoritative — by convention they mirror the writer's
358
+ * own lite — so a reader that never bundled the schema can still split records
359
+ * correctly. `defaults` is left empty (it only matters for inserts; the writer
360
+ * supplies its own).
361
+ */
362
+ export declare function liteFromPublishedSchema(doc: object): AithosSchemaLite;
342
363
  //# sourceMappingURL=data.d.ts.map
package/dist/src/data.js CHANGED
@@ -370,8 +370,16 @@ class DataClientImpl {
370
370
  const ourRecipient = this.#delegate
371
371
  ? delegateRecipientDidUrl(this.#delegate.granteePubkeyMultibase)
372
372
  : `${this.#did}#data-kex`;
373
- const wrap = meta.cmk_envelope.wraps.find((w) => w.recipient === ourRecipient);
374
- if (!wrap) {
373
+ // A collection may carry MORE THAN ONE wrap under our recipient label
374
+ // e.g. after the #data-sphere dual-read migration an owner collection holds
375
+ // both the legacy-sphere-sealed wrap (kept so the old app keeps reading) and
376
+ // a #data-sealed wrap (both labelled `${did}#data-kex`). The label alone
377
+ // can't tell them apart (it's fixed regardless of the sealing key), so we
378
+ // try EVERY matching wrap with our key and keep the one that actually
379
+ // decrypts. For the common single-wrap case this is identical to the old
380
+ // `find` + unwrap behaviour.
381
+ const matching = meta.cmk_envelope.wraps.filter((w) => w.recipient === ourRecipient);
382
+ if (matching.length === 0) {
375
383
  throw new Error(this.#delegate
376
384
  ? `sdk.data: no CMK wrap for ${ourRecipient} in collection "${name}". ` +
377
385
  `The owner has not authorized this delegate on the collection yet — ` +
@@ -379,20 +387,46 @@ class DataClientImpl {
379
387
  : `sdk.data: no CMK wrap for ${ourRecipient} in collection ${name}. The collection was either created with a different recipient, or this client is not the owner.`);
380
388
  }
381
389
  const unwrapSeed = this.#delegate ? this.#delegate.delegateSeed : this.#seed;
382
- const cmk = this.#unwrapCmk({
383
- wrap,
384
- collectionUrn: meta.urn,
385
- privateKey: ed25519SeedToX25519PrivateKey(unwrapSeed),
386
- });
390
+ const privateKey = ed25519SeedToX25519PrivateKey(unwrapSeed);
391
+ let cmk;
392
+ for (const wrap of matching) {
393
+ try {
394
+ cmk = this.#unwrapCmk({ wrap, collectionUrn: meta.urn, privateKey });
395
+ break;
396
+ }
397
+ catch {
398
+ // Wrong wrap for this key (e.g. the legacy-sphere wrap when we hold the
399
+ // #data key, or vice-versa) — try the next same-label wrap.
400
+ }
401
+ }
402
+ if (!cmk) {
403
+ throw new Error(`sdk.data: found ${matching.length} CMK wrap(s) for ${ourRecipient} in collection ${name}, ` +
404
+ `but none could be unwrapped with this client's key. The collection may be sealed to a ` +
405
+ `different sphere/key than the one this client was constructed with.`);
406
+ }
387
407
  this.#cmkCache.set(name, cmk);
388
- const schema = this.#resolveSchema(meta.schema);
408
+ let schema = this.#resolveSchema(meta.schema);
389
409
  if (!schema) {
390
- throw new Error(`sdk.data: schema "${meta.schema}" not known to the SDK. ` +
391
- `Pass it via createDataClient({ schemas: [...] }) if it's an ` +
392
- `app-defined (vendor) schema, or upgrade the SDK if it's a core ` +
393
- `schema added in a later release.`);
410
+ // Auto-resolve a PUBLISHED vendor schema from the PDS. This lets any
411
+ // up-to-date client read ANY collection whose owner registered its schema
412
+ // (via registerSchema) without the reading app having to bundle the lite
413
+ // locally the published JSON Schema's `aithos:indexable` / `aithos:auto`
414
+ // annotations are the authoritative field split (they mirror the writer's
415
+ // lite by convention). Falls through to the error if nothing is published.
416
+ try {
417
+ const published = await this.getSchema(meta.schema, { subjectDid: this.#did });
418
+ if (published)
419
+ schema = liteFromPublishedSchema(published);
420
+ }
421
+ catch {
422
+ // network / not-found — leave `schema` undefined (reads still work)
423
+ }
394
424
  }
395
- const state = { name, urn: meta.urn, schema };
425
+ // Do NOT throw when the schema is unknown: reads decrypt from the CMK +
426
+ // metadata/payload alone (the schema is only needed to SPLIT on write).
427
+ // Leaving `schema` undefined keeps the collection fully readable; an
428
+ // insert/update will surface a precise "schema required to write" error.
429
+ const state = { name, urn: meta.urn, ...(schema ? { schema } : {}), schemaId: meta.schema };
396
430
  this.#colCache.set(name, state);
397
431
  return state;
398
432
  }
@@ -401,7 +435,7 @@ class DataClientImpl {
401
435
  const cmk = this.#cmkCache.get(state.name);
402
436
  if (!cmk)
403
437
  throw new Error("CMK not loaded");
404
- const { metadata, payload } = splitRecord(record, state.schema);
438
+ const { metadata, payload } = splitRecord(record, requireSchema(state));
405
439
  const recordId = `record_${makeUlid()}`;
406
440
  const encrypted = this.#encryptPayload({
407
441
  collectionName: state.name,
@@ -470,7 +504,7 @@ class DataClientImpl {
470
504
  const cmk = this.#cmkCache.get(state.name);
471
505
  if (!cmk)
472
506
  throw new Error("CMK not loaded");
473
- const { metadata, payload } = splitRecord(record, state.schema);
507
+ const { metadata, payload } = splitRecord(record, requireSchema(state));
474
508
  const encrypted = this.#encryptPayload({
475
509
  collectionName: state.name,
476
510
  recordId,
@@ -721,7 +755,7 @@ class DataClientImpl {
721
755
  if (!this.#deposit) {
722
756
  throw new Error("sdk.data: _insertDeposit called without a deposit session");
723
757
  }
724
- const { metadata, payload } = splitRecord(record, state.schema);
758
+ const { metadata, payload } = splitRecord(record, requireSchema(state));
725
759
  const recordId = `record_${makeUlid()}`;
726
760
  const encrypted = this.#encryptPayloadForOwner({
727
761
  collectionName: state.name,
@@ -741,7 +775,7 @@ class DataClientImpl {
741
775
  /** Build a local collection state for the deposit path (no server fetch:
742
776
  * append clients are not authorized to read collection metadata). */
743
777
  _depositCollectionState(name, schema) {
744
- return { name, urn: this.#collectionUrn(name), schema };
778
+ return { name, urn: this.#collectionUrn(name), schema, schemaId: schema.schema };
745
779
  }
746
780
  }
747
781
  class DataCollectionImpl {
@@ -775,6 +809,58 @@ class DataCollectionImpl {
775
809
  /* -------------------------------------------------------------------------- */
776
810
  /* Record split (metadata vs payload) */
777
811
  /* -------------------------------------------------------------------------- */
812
+ /**
813
+ * Derive an {@link AithosSchemaLite} from a PUBLISHED JSON Schema document (the
814
+ * shape `aithos.data.get_schema` / `registerSchema` round-trip). The field
815
+ * split is read from the per-property annotations:
816
+ * - `aithos:indexable: true` → indexable (server-visible, filter/sort)
817
+ * - `aithos:auto: …` → auto (server-populated, e.g. created_at)
818
+ * - anything else → encrypted (AEAD'd client-side)
819
+ *
820
+ * These annotations are authoritative — by convention they mirror the writer's
821
+ * own lite — so a reader that never bundled the schema can still split records
822
+ * correctly. `defaults` is left empty (it only matters for inserts; the writer
823
+ * supplies its own).
824
+ */
825
+ export function liteFromPublishedSchema(doc) {
826
+ const d = doc;
827
+ const props = d.properties ?? {};
828
+ const indexable = new Set();
829
+ const encrypted = new Set();
830
+ const auto = new Set();
831
+ for (const [k, v] of Object.entries(props)) {
832
+ const hasAuto = v?.["aithos:auto"] !== undefined;
833
+ const isIndexable = v?.["aithos:indexable"] === true;
834
+ if (hasAuto)
835
+ auto.add(k);
836
+ if (isIndexable)
837
+ indexable.add(k);
838
+ if (!isIndexable && !hasAuto)
839
+ encrypted.add(k);
840
+ }
841
+ return {
842
+ schema: d["aithos:schema"] ?? "",
843
+ indexable,
844
+ encrypted,
845
+ auto,
846
+ defaults: {},
847
+ };
848
+ }
849
+ /**
850
+ * Return the collection's write schema or throw a precise error. Writes need
851
+ * the schema to split a record into indexable metadata vs encrypted payload (and
852
+ * so the PDS can validate); reads never call this.
853
+ */
854
+ function requireSchema(state) {
855
+ if (!state.schema) {
856
+ const e = new Error(`sdk.data: writing to "${state.name}" needs its schema "${state.schemaId}", which is ` +
857
+ `neither bundled in this client (createDataClient({ schemas: [...] })) nor published on ` +
858
+ `the PDS (registerSchema). Reads work without it — only inserts/updates require it.`);
859
+ e.code = -32602;
860
+ throw e;
861
+ }
862
+ return state.schema;
863
+ }
778
864
  function splitRecord(record, schema) {
779
865
  const metadata = {};
780
866
  const payload = {};
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.1.0-alpha.44";
1
+ export declare const VERSION = "0.1.0-alpha.56";
2
2
  export { AithosSDK } from "./sdk.js";
3
3
  export type { AithosSDKConfig } from "./types.js";
4
4
  export { AithosSDKError } from "./types.js";
@@ -27,6 +27,8 @@ export type { AudienceSet, AppCreditPackId, CreateAppTopupSessionArgs, CreateApp
27
27
  export * as onboarding from "./onboarding.js";
28
28
  export { createBrowserIdentity, browserIdentityFromStored, type BrowserIdentity, } from "@aithos/protocol-client";
29
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";
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, liteFromPublishedSchema, } from "./data.js";
31
+ export { ensureDataSphere, rekeyLegacyCollections, addDataSphereWrap, migrateLegacyEthosToDataSphere, type MigrateOptions, type MigrateProgress, type EnsureDataSphereResult, type RekeyReport, type CollectionRekeyEntry, type CollectionRekeyStatus, type FullMigrationResult, } from "./migrate.js";
32
+ export { buildSignedRotatedDidDocument, rotateEthos, type RotationSeeds, type DidDocumentLike, type RotateEthosOptions, type RotateEthosResult, } from "./rotate.js";
31
33
  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";
32
34
  //# sourceMappingURL=index.d.ts.map
package/dist/src/index.js CHANGED
@@ -17,7 +17,7 @@
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.44";
20
+ export const VERSION = "0.1.0-alpha.56";
21
21
  export { AithosSDK } from "./sdk.js";
22
22
  export { AithosSDKError } from "./types.js";
23
23
  // Re-export protocol-client's JSON-RPC error type so consumers can
@@ -68,7 +68,20 @@ export { createBrowserIdentity, browserIdentityFromStored, } from "@aithos/proto
68
68
  // `sdk.data` namespace — Aithos data sub-protocol PDS client. Manages
69
69
  // the lifecycle of subject-owned, encrypted, schema-validated records.
70
70
  // See spec/data/ in the aithos-protocol repo.
71
- export { createDataClient, createDelegateDataClient, createAppendDataClient, } from "./data.js";
71
+ export { createDataClient, createDelegateDataClient, createAppendDataClient,
72
+ // Derive an AithosSchemaLite from a published JSON Schema (vendor schemas the
73
+ // client didn't bundle). The SDK uses this internally to auto-resolve unknown
74
+ // collection schemas from the PDS; exported for apps that want it directly.
75
+ liteFromPublishedSchema, } from "./data.js";
76
+ // Legacy `#data` sphere migration — add the dedicated #data sphere to a
77
+ // pre-#data identity and re-wrap its collections' CMK from the legacy sphere
78
+ // to #data. Idempotent, owner-only, dry-run capable. See ./migrate.ts.
79
+ export { ensureDataSphere, rekeyLegacyCollections, addDataSphereWrap, migrateLegacyEthosToDataSphere, } from "./migrate.js";
80
+ // Ethos rotation building blocks — buildSignedRotatedDidDocument produces a
81
+ // root-signed, rotated did.json (all spheres + #data, root unchanged) ready for
82
+ // aithos.rotate_sphere_key. The full rotateEthos orchestration (incl. zone
83
+ // recopy) builds on this. See ./rotate.ts.
84
+ export { buildSignedRotatedDidDocument, rotateEthos, } from "./rotate.js";
72
85
  // `sdk.assets` — Aithos assets sub-protocol PDS client. Upload,
73
86
  // fetch, list, ref/unref binary content (images, PDFs, audio, video)
74
87
  // owned by a subject. AEAD-encrypted per-asset under AMKs wrapped for
@@ -0,0 +1,41 @@
1
+ export interface CmkWrap {
2
+ readonly recipient: string;
3
+ readonly alg: "x25519-hkdf-sha256-aead";
4
+ readonly ephemeral_public: string;
5
+ readonly wrap_nonce: string;
6
+ readonly wrapped_key: string;
7
+ }
8
+ /** The CMK envelope stored on a collection. */
9
+ export interface CmkEnvelope {
10
+ readonly alg: string;
11
+ readonly wraps: CmkWrap[];
12
+ }
13
+ /**
14
+ * Derive a 32-byte X25519 private key from an Ed25519 seed via SHA-512
15
+ * truncation + clamping (libsodium crypto_sign_ed25519_sk_to_curve25519).
16
+ */
17
+ export declare function ed25519SeedToX25519PrivateKey(seed: Uint8Array): Uint8Array;
18
+ export declare function ed25519SeedToX25519PublicKey(seed: Uint8Array): Uint8Array;
19
+ export declare function wrapCmkForRecipient(args: {
20
+ cmk: Uint8Array;
21
+ recipientPublicKey: Uint8Array;
22
+ recipientDidUrl: string;
23
+ collectionUrn: string;
24
+ }): CmkWrap;
25
+ /**
26
+ * Try to unwrap the CMK from `wrap` with `privateKey`. Returns the CMK bytes
27
+ * on success, or `null` when the AEAD tag fails (wrong key / AAD mismatch) —
28
+ * the migration relies on this null-return to AUTO-DISCOVER which legacy sphere
29
+ * seed sealed the wrap (try each, the right one verifies).
30
+ */
31
+ export declare function tryUnwrapCmk(args: {
32
+ wrap: {
33
+ recipient: string;
34
+ ephemeral_public: string;
35
+ wrap_nonce: string;
36
+ wrapped_key: string;
37
+ };
38
+ collectionUrn: string;
39
+ privateKey: Uint8Array;
40
+ }): Uint8Array | null;
41
+ //# sourceMappingURL=cmk-wrap.d.ts.map