@aithos/sdk 0.1.0-alpha.35 → 0.1.0-alpha.37
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/dist/src/auth.js +18 -0
- package/dist/src/data.d.ts +17 -0
- package/dist/src/data.js +32 -6
- package/dist/test/signup-bootstrap.test.js +89 -0
- package/package.json +2 -2
package/dist/src/auth.js
CHANGED
|
@@ -1294,6 +1294,24 @@ export class AithosAuth {
|
|
|
1294
1294
|
}
|
|
1295
1295
|
const json = (await res.json());
|
|
1296
1296
|
if (json.error) {
|
|
1297
|
+
// Backward-compat shim for backends without the semantic-equality
|
|
1298
|
+
// fix on publish-identity (alpha.33+ regression): the server may
|
|
1299
|
+
// reject a republish with -32022 because the client regenerates
|
|
1300
|
+
// `aithos.created_at` (and `proof.created`) on every
|
|
1301
|
+
// `signedDidDocument()` call, breaking the strict byte-equal
|
|
1302
|
+
// idempotence the server enforces. For an honest signer (same
|
|
1303
|
+
// root key, same DID) the only way to hit this code path is the
|
|
1304
|
+
// timestamp-drift case — which is semantically a no-op. Treat as
|
|
1305
|
+
// success.
|
|
1306
|
+
//
|
|
1307
|
+
// Server-side fix (publish-identity.ts switched to semantic
|
|
1308
|
+
// equality on cryptographic fields only) makes this branch dead
|
|
1309
|
+
// code on upgraded backends. Kept here as defense-in-depth for
|
|
1310
|
+
// SDK consumers pointing at older deployments.
|
|
1311
|
+
if (json.error.code === -32022 &&
|
|
1312
|
+
/different did\.json already published/i.test(json.error.message)) {
|
|
1313
|
+
return; // already published with same crypto material — no-op
|
|
1314
|
+
}
|
|
1297
1315
|
// JSON-RPC error: don't retry — these are deterministic
|
|
1298
1316
|
// (validation, permission, identity-already-tombstoned, …).
|
|
1299
1317
|
throw new AithosSDKError("ethos_bootstrap_failed", `publish_identity rejected: ${json.error.message}`, {
|
package/dist/src/data.d.ts
CHANGED
|
@@ -24,6 +24,23 @@ export interface CreateDataClientArgs {
|
|
|
24
24
|
readonly verificationMethod: string;
|
|
25
25
|
/** Optional fetch implementation. Defaults to globalThis.fetch. */
|
|
26
26
|
readonly fetch?: typeof fetch;
|
|
27
|
+
/**
|
|
28
|
+
* Optional list of app-defined schema definitions, in addition to
|
|
29
|
+
* the SDK-bundled core schemas (currently `aithos.contacts.v1`).
|
|
30
|
+
*
|
|
31
|
+
* Apps in the vendor namespace (`aithos.x.<vendor>.<name>.v<N>`) or
|
|
32
|
+
* non-`aithos.*` namespaces MUST supply their schemas here so the
|
|
33
|
+
* SDK can split records into indexable metadata vs encrypted payload.
|
|
34
|
+
*
|
|
35
|
+
* When the same schema id appears in both the bundled core registry
|
|
36
|
+
* and this list, the app-supplied definition wins (intentional —
|
|
37
|
+
* allows local overrides for testing, though immutability per spec
|
|
38
|
+
* §3.5 means a published schema id should never change shape).
|
|
39
|
+
*
|
|
40
|
+
* Schemas are scoped to the {@link DataClient} instance — they don't
|
|
41
|
+
* leak to other clients in the same process.
|
|
42
|
+
*/
|
|
43
|
+
readonly schemas?: readonly AithosSchemaLite[];
|
|
27
44
|
}
|
|
28
45
|
export interface DataClient {
|
|
29
46
|
/** Get / create a collection handle. */
|
package/dist/src/data.js
CHANGED
|
@@ -19,10 +19,17 @@
|
|
|
19
19
|
* encrypted payload (server-blind), JSON-RPC dispatch.
|
|
20
20
|
*
|
|
21
21
|
* It does not (yet) handle: mandate-delegate authentication on the
|
|
22
|
-
* caller side (the SDK only signs as owner in v0.1), schema
|
|
23
|
-
* publication (
|
|
24
|
-
* forward-secrecy
|
|
25
|
-
* Sub-jalons.
|
|
22
|
+
* caller side (the SDK only signs as owner in v0.1), full schema
|
|
23
|
+
* publication (no `registerSchema` RPC yet — that lands with A2b, cf.
|
|
24
|
+
* aithos-protocol/PLAN-A2b-schema-self-registration.md), forward-secrecy
|
|
25
|
+
* CMK rotation primitives. Those land in later Sub-jalons.
|
|
26
|
+
*
|
|
27
|
+
* Apps that need to use a vendor schema (`aithos.x.<vendor>.<name>.v<N>`,
|
|
28
|
+
* or any non-`aithos.*` namespace) pass their schema definitions to
|
|
29
|
+
* `createDataClient({ schemas: [...] })`. The PDS accepts these at face
|
|
30
|
+
* value (no server-side metadata validation pending A2b); the SDK uses
|
|
31
|
+
* the supplied schema definitions to split records into indexable
|
|
32
|
+
* metadata and encrypted payload.
|
|
26
33
|
*
|
|
27
34
|
* Spec ref: spec/data/01..10 of the aithos-protocol repo.
|
|
28
35
|
*/
|
|
@@ -59,6 +66,13 @@ class DataClientImpl {
|
|
|
59
66
|
#seed;
|
|
60
67
|
#vm;
|
|
61
68
|
#fetch;
|
|
69
|
+
/**
|
|
70
|
+
* Per-client schema overrides, populated from `args.schemas` at
|
|
71
|
+
* construction. Looked up BEFORE the bundled SCHEMAS map (so an app
|
|
72
|
+
* can override a core schema locally if it really wants to, though
|
|
73
|
+
* the immutability rule of spec §3.5 strongly discourages this).
|
|
74
|
+
*/
|
|
75
|
+
#localSchemas;
|
|
62
76
|
// Per-collection CMK cache: cleared on reset()
|
|
63
77
|
#cmkCache = new Map();
|
|
64
78
|
#colCache = new Map();
|
|
@@ -68,6 +82,15 @@ class DataClientImpl {
|
|
|
68
82
|
this.#seed = args.sphereSeed;
|
|
69
83
|
this.#vm = args.verificationMethod;
|
|
70
84
|
this.#fetch = args.fetch ?? globalThis.fetch.bind(globalThis);
|
|
85
|
+
this.#localSchemas = new Map((args.schemas ?? []).map((s) => [s.schema, s]));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve a schema id to its lite definition. App-supplied schemas
|
|
89
|
+
* (via `createDataClient({ schemas })`) take precedence over the
|
|
90
|
+
* SDK-bundled core registry.
|
|
91
|
+
*/
|
|
92
|
+
#resolveSchema(schemaId) {
|
|
93
|
+
return this.#localSchemas.get(schemaId) ?? SCHEMAS.get(schemaId) ?? null;
|
|
71
94
|
}
|
|
72
95
|
collection(name) {
|
|
73
96
|
return new DataCollectionImpl(this, name);
|
|
@@ -168,9 +191,12 @@ class DataClientImpl {
|
|
|
168
191
|
privateKey: ed25519SeedToX25519PrivateKey(this.#seed),
|
|
169
192
|
});
|
|
170
193
|
this.#cmkCache.set(name, cmk);
|
|
171
|
-
const schema =
|
|
194
|
+
const schema = this.#resolveSchema(meta.schema);
|
|
172
195
|
if (!schema) {
|
|
173
|
-
throw new Error(`sdk.data: schema "${meta.schema}" not known to the SDK`
|
|
196
|
+
throw new Error(`sdk.data: schema "${meta.schema}" not known to the SDK. ` +
|
|
197
|
+
`Pass it via createDataClient({ schemas: [...] }) if it's an ` +
|
|
198
|
+
`app-defined (vendor) schema, or upgrade the SDK if it's a core ` +
|
|
199
|
+
`schema added in a later release.`);
|
|
174
200
|
}
|
|
175
201
|
const state = { name, urn: meta.urn, schema };
|
|
176
202
|
this.#colCache.set(name, state);
|
|
@@ -218,5 +218,94 @@ describe("AithosAuth.signUp — Ethos bootstrap", () => {
|
|
|
218
218
|
assert.equal(publishCalls, 3);
|
|
219
219
|
assert.equal(auth.canSignAsOwner(), false);
|
|
220
220
|
});
|
|
221
|
+
/* ------------------------------------------------------------------------ */
|
|
222
|
+
/* alpha.36 — defense-in-depth for legacy backends without semantic-equal */
|
|
223
|
+
/* ------------------------------------------------------------------------ */
|
|
224
|
+
it("treats -32022 'different did.json already published' as a no-op success", async () => {
|
|
225
|
+
// This is the regression introduced in alpha.33: republishing the same
|
|
226
|
+
// identity returns -32022 because `signedDidDocument()` regenerates
|
|
227
|
+
// `aithos.created_at` on every call. For an honest signer, this is
|
|
228
|
+
// semantically a no-op — the Ethos is published, crypto material matches.
|
|
229
|
+
// The SDK swallows this specific case so chatty publish_identity callers
|
|
230
|
+
// (signInCustodial, verifyEmail) don't break on every subsequent sign-in.
|
|
231
|
+
const { fetch: f, calls } = makeMockFetch([
|
|
232
|
+
{ url: "/auth/register", method: "POST", respond: fakeRegisterOk },
|
|
233
|
+
{
|
|
234
|
+
url: "/mcp/primitives/write",
|
|
235
|
+
method: "POST",
|
|
236
|
+
respond: () => ({
|
|
237
|
+
json: {
|
|
238
|
+
jsonrpc: "2.0",
|
|
239
|
+
id: "publish_identity",
|
|
240
|
+
error: {
|
|
241
|
+
code: -32022,
|
|
242
|
+
message: "different did.json already published for this DID",
|
|
243
|
+
data: { existing_doc_url: "https://cdn.aithos.be/did.json" },
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
const auth = makeAuth(f);
|
|
250
|
+
// Must succeed, not throw — the republish-conflict is masked.
|
|
251
|
+
await auth.signUp(validInput);
|
|
252
|
+
// Hydrate worked: owner is loaded.
|
|
253
|
+
assert.equal(auth.canSignAsOwner(), true);
|
|
254
|
+
// No retry on -32022 (deterministic) — exactly 1 publish call.
|
|
255
|
+
assert.equal(calls.filter((c) => c.url.includes("/mcp/primitives/write")).length, 1, "publish_identity must not retry on -32022");
|
|
256
|
+
});
|
|
257
|
+
it("still throws ethos_bootstrap_failed on -32022 with a DIFFERENT message", async () => {
|
|
258
|
+
// The shim is narrow: it ONLY swallows the specific
|
|
259
|
+
// "different did.json already published" message. Other -32022 cases
|
|
260
|
+
// (server might use the same code for different semantics) must still
|
|
261
|
+
// bubble up as ethos_bootstrap_failed.
|
|
262
|
+
const { fetch: f } = makeMockFetch([
|
|
263
|
+
{ url: "/auth/register", method: "POST", respond: fakeRegisterOk },
|
|
264
|
+
{
|
|
265
|
+
url: "/mcp/primitives/write",
|
|
266
|
+
method: "POST",
|
|
267
|
+
respond: () => ({
|
|
268
|
+
json: {
|
|
269
|
+
jsonrpc: "2.0",
|
|
270
|
+
id: "publish_identity",
|
|
271
|
+
error: {
|
|
272
|
+
code: -32022,
|
|
273
|
+
message: "subject identity is tombstoned",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
}),
|
|
277
|
+
},
|
|
278
|
+
]);
|
|
279
|
+
const auth = makeAuth(f);
|
|
280
|
+
await assert.rejects(() => auth.signUp(validInput), (e) => e instanceof AithosSDKError &&
|
|
281
|
+
e.code === "ethos_bootstrap_failed" &&
|
|
282
|
+
/tombstoned/i.test(e.message));
|
|
283
|
+
assert.equal(auth.canSignAsOwner(), false);
|
|
284
|
+
});
|
|
285
|
+
it("still throws ethos_bootstrap_failed on the conflict message under a different code", async () => {
|
|
286
|
+
// Conversely, an error matching the message but with a code OTHER than
|
|
287
|
+
// -32022 is not swallowed — we anchor on both (code, message) to keep
|
|
288
|
+
// the shim narrow.
|
|
289
|
+
const { fetch: f } = makeMockFetch([
|
|
290
|
+
{ url: "/auth/register", method: "POST", respond: fakeRegisterOk },
|
|
291
|
+
{
|
|
292
|
+
url: "/mcp/primitives/write",
|
|
293
|
+
method: "POST",
|
|
294
|
+
respond: () => ({
|
|
295
|
+
json: {
|
|
296
|
+
jsonrpc: "2.0",
|
|
297
|
+
id: "publish_identity",
|
|
298
|
+
error: {
|
|
299
|
+
code: -32000,
|
|
300
|
+
message: "different did.json already published for this DID",
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
},
|
|
305
|
+
]);
|
|
306
|
+
const auth = makeAuth(f);
|
|
307
|
+
await assert.rejects(() => auth.signUp(validInput), (e) => e instanceof AithosSDKError && e.code === "ethos_bootstrap_failed");
|
|
308
|
+
assert.equal(auth.canSignAsOwner(), false);
|
|
309
|
+
});
|
|
221
310
|
});
|
|
222
311
|
//# sourceMappingURL=signup-bootstrap.test.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aithos/sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "Aithos SDK
|
|
3
|
+
"version": "0.1.0-alpha.37",
|
|
4
|
+
"description": "Aithos SDK — 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",
|
|
7
7
|
"sdk",
|