@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,183 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // Tests for the delegate signing path on sdk.compute.invokeBedrock.
4
+ // Until alpha.9 the method ALWAYS required an owner — a session that
5
+ // only held a mandate (no owner signers) failed with sdk_no_owner
6
+ // before any network call. From alpha.9 onward, when no owner is
7
+ // loaded but a delegate matches the requested mandate id, the SDK
8
+ // signs the envelope with the delegate's keypair and attaches the
9
+ // SignedMandate.
10
+ import { strict as assert } from "node:assert";
11
+ import { afterEach, beforeEach, describe, it } from "node:test";
12
+ import { createBrowserIdentity } from "@aithos/protocol-client";
13
+ import { AithosAuth, AithosSDKError, ComputeNamespace, memoryKeyStore, noopStore, } from "../src/index.js";
14
+ import { DEFAULT_SDK_ENDPOINTS } from "../src/endpoints.js";
15
+ /* -------------------------------------------------------------------------- */
16
+ /* Test plumbing */
17
+ /* -------------------------------------------------------------------------- */
18
+ let savedFetch;
19
+ let lastRequestBody = null;
20
+ function installFetchMock(response) {
21
+ savedFetch = globalThis.fetch;
22
+ lastRequestBody = null;
23
+ globalThis.fetch = (async (_input, init) => {
24
+ lastRequestBody = JSON.parse(init?.body);
25
+ return new Response(JSON.stringify(response.json), {
26
+ status: response.status ?? 200,
27
+ headers: { "content-type": "application/json" },
28
+ });
29
+ });
30
+ }
31
+ function uninstallFetchMock() {
32
+ if (savedFetch)
33
+ globalThis.fetch = savedFetch;
34
+ savedFetch = undefined;
35
+ lastRequestBody = null;
36
+ }
37
+ const okResponse = {
38
+ json: {
39
+ jsonrpc: "2.0",
40
+ id: "x",
41
+ result: {
42
+ content: "ok",
43
+ stopReason: "end_turn",
44
+ usage: { inputTokens: 1, outputTokens: 1 },
45
+ creditsCharged: 1,
46
+ walletBalance: 999,
47
+ auditId: "audit-test",
48
+ },
49
+ },
50
+ };
51
+ function freshAuth() {
52
+ return new AithosAuth({
53
+ sessionStore: noopStore(),
54
+ keyStore: memoryKeyStore(),
55
+ });
56
+ }
57
+ function freshCompute(auth) {
58
+ return new ComputeNamespace({
59
+ auth,
60
+ appDid: "did:aithos:app:example-placeholder",
61
+ endpoints: DEFAULT_SDK_ENDPOINTS,
62
+ fetch: globalThis.fetch.bind(globalThis),
63
+ });
64
+ }
65
+ /**
66
+ * Build a delegate bundle JSON that matches the wire shape importMandate
67
+ * accepts. Uses a real SignedMandate-like object minted from a fresh
68
+ * issuer so signature verification on import succeeds.
69
+ */
70
+ function makeDelegateBundleText(args) {
71
+ const issuer = createBrowserIdentity("alice", "Alice");
72
+ return JSON.stringify({
73
+ aithos_delegate_version: "0.1.0",
74
+ mandate: {
75
+ "aithos-mandate": "0.4.0",
76
+ id: args.mandateId,
77
+ issuer: issuer.did,
78
+ issued_by_key: `${issuer.did}#self`,
79
+ grantee: {
80
+ id: args.granteeId ?? "urn:aithos:agent:test",
81
+ pubkey: "z6MkqGenericPubKey",
82
+ },
83
+ actor_sphere: "self",
84
+ scopes: args.scopes,
85
+ not_before: "2026-05-10T00:00:00Z",
86
+ not_after: "2026-05-11T00:00:00Z",
87
+ issued_at: "2026-05-10T00:00:00Z",
88
+ nonce: "abc",
89
+ signature: { alg: "ed25519", key: `${issuer.did}#self`, value: "..." },
90
+ },
91
+ delegate_seed_hex: "11".repeat(32),
92
+ });
93
+ }
94
+ /* -------------------------------------------------------------------------- */
95
+ /* Tests */
96
+ /* -------------------------------------------------------------------------- */
97
+ describe("ComputeNamespace.invokeBedrock — delegate path", () => {
98
+ beforeEach(() => {
99
+ installFetchMock(okResponse);
100
+ });
101
+ afterEach(() => {
102
+ uninstallFetchMock();
103
+ });
104
+ it("rejects with sdk_no_delegate_for_mandate when delegate-only and mandateId doesn't match", async () => {
105
+ const auth = freshAuth();
106
+ const compute = freshCompute(auth);
107
+ // Import a delegate with a DIFFERENT mandate id than the one the call
108
+ // requests. Should error with the new code (NOT sdk_no_owner anymore).
109
+ await auth.importMandate({
110
+ bundle: makeDelegateBundleText({
111
+ mandateId: "mandate:held",
112
+ scopes: ["compute.invoke"],
113
+ }),
114
+ });
115
+ await assert.rejects(() => compute.invokeBedrock({
116
+ mandateId: "mandate:other-not-held",
117
+ model: "claude-haiku-4-5",
118
+ messages: [{ role: "user", content: "Hi" }],
119
+ maxTokens: 1,
120
+ }), (e) => e instanceof AithosSDKError &&
121
+ e.code === "sdk_no_delegate_for_mandate");
122
+ });
123
+ it("signs with the delegate keypair + attaches the mandate when delegate-only matches", async () => {
124
+ const auth = freshAuth();
125
+ const compute = freshCompute(auth);
126
+ const mandateId = "mandate:matching";
127
+ await auth.importMandate({
128
+ bundle: makeDelegateBundleText({
129
+ mandateId,
130
+ scopes: ["compute.invoke"],
131
+ }),
132
+ });
133
+ await compute.invokeBedrock({
134
+ mandateId,
135
+ model: "claude-haiku-4-5",
136
+ messages: [{ role: "user", content: "Hi" }],
137
+ maxTokens: 1,
138
+ });
139
+ const env = lastRequestBody?.params?._envelope;
140
+ assert.ok(env, "envelope must be present");
141
+ // verificationMethod must be the delegate's bare multibase pubkey
142
+ // (NOT a `#sphere` DID URL). And the SignedMandate must be inside.
143
+ assert.match(env.proof.verificationMethod, /^z[1-9A-HJ-NP-Za-km-z]+$/, `expected multibase pubkey, got ${env.proof.verificationMethod}`);
144
+ assert.ok(env.mandate, "delegate envelopes must attach the SignedMandate");
145
+ assert.equal(env.mandate.id, mandateId);
146
+ });
147
+ it("still works the owner path when an owner is signed in", async () => {
148
+ const auth = freshAuth();
149
+ const compute = freshCompute(auth);
150
+ // Owner sign-in via recovery — picks up four sphere keys from the
151
+ // recovery file format. Build one inline.
152
+ const issuer = createBrowserIdentity("owner", "Owner");
153
+ const seedHex = (b) => Array.from(b).map((x) => x.toString(16).padStart(2, "0")).join("");
154
+ const recoveryText = JSON.stringify({
155
+ aithos_recovery_version: "0.1.0-plaintext",
156
+ handle: issuer.handle,
157
+ display_name: issuer.displayName,
158
+ did: issuer.did,
159
+ created_at: new Date().toISOString(),
160
+ seeds_hex: {
161
+ root: seedHex(issuer.root.seed),
162
+ public: seedHex(issuer.public.seed),
163
+ circle: seedHex(issuer.circle.seed),
164
+ self: seedHex(issuer.self.seed),
165
+ },
166
+ });
167
+ await auth.signInWithRecovery({ file: recoveryText });
168
+ await compute.invokeBedrock({
169
+ mandateId: "mandate:owner-managed",
170
+ model: "claude-haiku-4-5",
171
+ messages: [{ role: "user", content: "Hi" }],
172
+ maxTokens: 1,
173
+ });
174
+ const env = lastRequestBody?.params?._envelope;
175
+ assert.ok(env, "envelope must be present");
176
+ // Owner path: verificationMethod is a `#public` DID URL, and the
177
+ // envelope MUST NOT carry a mandate (server resolves from
178
+ // params.mandate_id).
179
+ assert.match(env.proof.verificationMethod, /#public$/);
180
+ assert.equal(env.mandate, undefined, "owner-signed envelopes should not attach a mandate");
181
+ });
182
+ });
183
+ //# sourceMappingURL=compute-delegate-path.test.js.map
@@ -187,4 +187,8 @@ describe("compute.invokeBedrock — abort", () => {
187
187
  assert.equal(receivedSignal, ac.signal);
188
188
  });
189
189
  });
190
+ // `compute.invokeUrlFetch` was removed in alpha.24 (BREAKING). The
191
+ // Anthropic API-direct + web_fetch tool path is replaced by the
192
+ // `sdk.web.extract` namespace which routes through the deterministic
193
+ // web-extractor Lambda. No tests here anymore.
190
194
  //# sourceMappingURL=compute.test.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=converse.test.d.ts.map
@@ -0,0 +1,162 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // Unit tests for sdk.compute.runConversation with a mock fetch.
4
+ //
5
+ // Mirrors compute.test.ts: a real BrowserIdentity drives the actual
6
+ // envelope-signing path, and we assert on the JSON-RPC body posted to
7
+ // the compute proxy (method name + camelCase→snake_case param mapping).
8
+ import { strict as assert } from "node:assert";
9
+ import { describe, it } from "node:test";
10
+ import { createBrowserIdentity } from "@aithos/protocol-client";
11
+ import { AithosAuth, AithosSDK, AithosSDKError, memoryKeyStore, noopStore, } from "../src/index.js";
12
+ import { serializeRecoveryFile } from "../src/internal/recovery-file.js";
13
+ const APP_DID = "did:aithos:app:test";
14
+ async function makeSdk(fetchImpl) {
15
+ const id = createBrowserIdentity("test-handle", "Test User");
16
+ const auth = new AithosAuth({
17
+ authBaseUrl: "https://auth.test",
18
+ fetch: (() => {
19
+ throw new Error("auth not used in converse tests");
20
+ }),
21
+ sessionStore: noopStore(),
22
+ keyStore: memoryKeyStore(),
23
+ });
24
+ const { text } = serializeRecoveryFile(id);
25
+ await auth.signInWithRecovery({ file: text });
26
+ return new AithosSDK({
27
+ auth,
28
+ appDid: APP_DID,
29
+ endpoints: { compute: "https://compute.example.test" },
30
+ fetch: fetchImpl,
31
+ });
32
+ }
33
+ const HAPPY_RESULT = {
34
+ content: "Voici le résumé.",
35
+ stopReason: "end_turn",
36
+ iterations: 2,
37
+ usage: { inputTokens: 420, outputTokens: 95 },
38
+ toolCalls: [{ name: "ethos_read_section", ok: true, turn: 1 }],
39
+ creditsCharged: 130,
40
+ walletBalance: 99_870,
41
+ auditId: "audit-cv-1",
42
+ fundedBy: "purchase",
43
+ };
44
+ describe("compute.runConversation — happy path + param mapping", () => {
45
+ it("posts aithos.compute_converse with mapped params and parses the result", async () => {
46
+ let capturedUrl;
47
+ let capturedInit;
48
+ const fakeFetch = async (input, init) => {
49
+ capturedUrl = typeof input === "string" ? input : input.toString();
50
+ capturedInit = init;
51
+ return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
52
+ status: 200,
53
+ headers: { "content-type": "application/json" },
54
+ });
55
+ };
56
+ const sdk = await makeSdk(fakeFetch);
57
+ const out = await sdk.compute.runConversation({
58
+ mandateId: "mandate:abc",
59
+ model: "claude-sonnet-4-6",
60
+ system: "Tu agis dans la voix de l'utilisateur.",
61
+ messages: [{ role: "user", content: "Résume mon ethos." }],
62
+ mcp: { server: "aithos", tools: ["ethos_list_sections", "ethos_read_section"] },
63
+ workingSet: {
64
+ ethos: { public: [{ id: "p1", title: "Bio", body: "..." }] },
65
+ },
66
+ maxIterations: 5,
67
+ maxTokens: 800,
68
+ temperature: 0.4,
69
+ });
70
+ assert.deepEqual(out, HAPPY_RESULT);
71
+ assert.equal(capturedUrl, "https://compute.example.test/v1/invoke");
72
+ assert.equal(capturedInit?.method, "POST");
73
+ const body = JSON.parse(capturedInit?.body);
74
+ assert.equal(body.jsonrpc, "2.0");
75
+ assert.equal(body.method, "aithos.compute_converse");
76
+ assert.equal(body.params.app_did, APP_DID);
77
+ assert.equal(body.params.mandate_id, "mandate:abc");
78
+ assert.equal(body.params.model, "claude-sonnet-4-6");
79
+ assert.equal(body.params.system, "Tu agis dans la voix de l'utilisateur.");
80
+ // camelCase → snake_case mapping on the wire.
81
+ assert.equal(body.params.max_iterations, 5);
82
+ assert.equal(body.params.max_tokens, 800);
83
+ assert.equal(body.params.temperature, 0.4);
84
+ assert.ok(body.params.working_set, "working_set must be on the wire");
85
+ assert.deepEqual(body.params.mcp, {
86
+ server: "aithos",
87
+ tools: ["ethos_list_sections", "ethos_read_section"],
88
+ });
89
+ assert.match(body.params.idempotency_key, /^[0-9a-f]{32}$/);
90
+ assert.ok(body.params._envelope, "request must carry a signed envelope");
91
+ // SDK-only camelCase keys must NOT leak onto the wire.
92
+ assert.equal(body.params.maxIterations, undefined);
93
+ assert.equal(body.params.workingSet, undefined);
94
+ });
95
+ it("omits optional fields when not provided", async () => {
96
+ let capturedInit;
97
+ const fakeFetch = async (_input, init) => {
98
+ capturedInit = init;
99
+ return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
100
+ status: 200,
101
+ headers: { "content-type": "application/json" },
102
+ });
103
+ };
104
+ const sdk = await makeSdk(fakeFetch);
105
+ await sdk.compute.runConversation({
106
+ mandateId: "mandate:abc",
107
+ model: "claude-haiku-4-5",
108
+ messages: [{ role: "user", content: "Salut" }],
109
+ });
110
+ const body = JSON.parse(capturedInit?.body);
111
+ assert.equal(body.params.system, undefined);
112
+ assert.equal(body.params.mcp, undefined);
113
+ assert.equal(body.params.working_set, undefined);
114
+ assert.equal(body.params.max_iterations, undefined);
115
+ assert.equal(body.params.max_tokens, undefined);
116
+ });
117
+ });
118
+ describe("compute.runConversation — errors", () => {
119
+ it("maps a JSON-RPC error to AithosSDKError with the proxy code", async () => {
120
+ const fakeFetch = async () => new Response(JSON.stringify({
121
+ error: { code: -32071, message: "insufficient credits" },
122
+ }), { status: 200, headers: { "content-type": "application/json" } });
123
+ const sdk = await makeSdk(fakeFetch);
124
+ await assert.rejects(() => sdk.compute.runConversation({
125
+ mandateId: "mandate:abc",
126
+ model: "claude-sonnet-4-6",
127
+ messages: [{ role: "user", content: "Hi" }],
128
+ }), (err) => {
129
+ assert.ok(err instanceof AithosSDKError);
130
+ assert.equal(err.code, "-32071");
131
+ return true;
132
+ });
133
+ });
134
+ it("throws sdk_no_signer when no owner and no mandate", async () => {
135
+ // Build an SDK with no signed-in owner.
136
+ const auth = new AithosAuth({
137
+ authBaseUrl: "https://auth.test",
138
+ fetch: (() => {
139
+ throw new Error("unused");
140
+ }),
141
+ sessionStore: noopStore(),
142
+ keyStore: memoryKeyStore(),
143
+ });
144
+ const sdk = new AithosSDK({
145
+ auth,
146
+ appDid: APP_DID,
147
+ endpoints: { compute: "https://compute.example.test" },
148
+ fetch: (() => {
149
+ throw new Error("fetch must not be reached");
150
+ }),
151
+ });
152
+ await assert.rejects(() => sdk.compute.runConversation({
153
+ model: "claude-sonnet-4-6",
154
+ messages: [{ role: "user", content: "Hi" }],
155
+ }), (err) => {
156
+ assert.ok(err instanceof AithosSDKError);
157
+ assert.equal(err.code, "sdk_no_signer");
158
+ return true;
159
+ });
160
+ });
161
+ });
162
+ //# sourceMappingURL=converse.test.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data-sphere.test.d.ts.map
@@ -0,0 +1,57 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // Tests for the optional #data sphere through the SDK's owner key material:
4
+ // the recovery file and OwnerSigners (vault re-encryption projection).
5
+ // Mirrors the protocol-core / protocol-client data-sphere tests.
6
+ import { strict as assert } from "node:assert";
7
+ import { describe, it } from "node:test";
8
+ import { createBrowserIdentity } from "@aithos/protocol-client";
9
+ import { OwnerSigners } from "../src/internal/owner-signers.js";
10
+ import { serializeRecoveryFile, parseRecoveryFile, } from "../src/internal/recovery-file.js";
11
+ describe("#data sphere — recovery file", () => {
12
+ it("round-trips the #data seed for a fresh (eager) identity", () => {
13
+ const id = createBrowserIdentity("alice", "Alice");
14
+ assert.ok(id.data, "createBrowserIdentity should be eager about #data");
15
+ const { text } = serializeRecoveryFile(id);
16
+ const parsed = parseRecoveryFile(text);
17
+ assert.equal(typeof parsed.seedsHex.data, "string");
18
+ assert.equal(parsed.seedsHex.data.length, 64);
19
+ });
20
+ it("legacy identity without #data serializes/parses without a data seed", () => {
21
+ const id = { ...createBrowserIdentity("dave", "Dave"), data: undefined };
22
+ const { text } = serializeRecoveryFile(id);
23
+ const parsed = parseRecoveryFile(text);
24
+ assert.equal(parsed.seedsHex.data, undefined);
25
+ });
26
+ });
27
+ describe("#data sphere — OwnerSigners", () => {
28
+ it("preserves the #data seed on the vault re-encryption projection", () => {
29
+ const id = createBrowserIdentity("bob", "Bob");
30
+ const signers = OwnerSigners.fromBrowserIdentity(id);
31
+ try {
32
+ // _unsafeStoredIdentity() is what the vault re-encryption path reads —
33
+ // dropping #data here would silently lose the sphere on delegate-add.
34
+ const projected = signers._unsafeStoredIdentity();
35
+ assert.ok(projected.seeds.data, "#data seed must survive projection");
36
+ // signerForSphere exposes the #data signer.
37
+ assert.ok(signers.data, "OwnerSigners.data signer should be present");
38
+ assert.equal(signers.signerForSphere("data"), signers.data);
39
+ }
40
+ finally {
41
+ signers.destroy();
42
+ }
43
+ });
44
+ it("legacy owner (no #data) → no data signer, signerForSphere('data') throws", () => {
45
+ const id = { ...createBrowserIdentity("erin", "Erin"), data: undefined };
46
+ const signers = OwnerSigners.fromBrowserIdentity(id);
47
+ try {
48
+ assert.equal(signers.data, undefined);
49
+ assert.equal(signers._unsafeStoredIdentity().seeds.data, undefined);
50
+ assert.throws(() => signers.signerForSphere("data"), /no #data sphere/);
51
+ }
52
+ finally {
53
+ signers.destroy();
54
+ }
55
+ });
56
+ });
57
+ //# sourceMappingURL=data-sphere.test.js.map
@@ -4,7 +4,7 @@
4
4
  import { strict as assert } from "node:assert";
5
5
  import { describe, it } from "node:test";
6
6
  import { DEFAULT_SDK_ENDPOINTS } from "../src/index.js";
7
- import { computeInvokeUrl, resolveEndpoints, walletTopupCheckoutUrl, } from "../src/endpoints.js";
7
+ import { computeInvokeUrl, resolveEndpoints, walletTopupCheckoutUrl, webInvokeUrl, } from "../src/endpoints.js";
8
8
  describe("resolveEndpoints", () => {
9
9
  it("returns a fresh copy of the defaults when no override is given", () => {
10
10
  const a = resolveEndpoints();
@@ -23,12 +23,22 @@ describe("computeInvokeUrl", () => {
23
23
  assert.equal(computeInvokeUrl({
24
24
  compute: "https://compute.aithos.be",
25
25
  wallet: "https://wallet.aithos.be",
26
+ web: "https://extract.aithos.be",
27
+ pds: "https://pds.aithos.be",
28
+ assets: "https://assets.aithos.be",
29
+ api: "https://api.aithos.be",
30
+ cdn: "https://cdn.aithos.be",
26
31
  }), "https://compute.aithos.be/v1/invoke");
27
32
  });
28
33
  it("trims a trailing slash on the compute base", () => {
29
34
  assert.equal(computeInvokeUrl({
30
35
  compute: "https://compute.aithos.be/",
31
36
  wallet: "https://wallet.aithos.be",
37
+ web: "https://extract.aithos.be",
38
+ pds: "https://pds.aithos.be",
39
+ assets: "https://assets.aithos.be",
40
+ api: "https://api.aithos.be",
41
+ cdn: "https://cdn.aithos.be",
32
42
  }), "https://compute.aithos.be/v1/invoke");
33
43
  });
34
44
  });
@@ -37,7 +47,36 @@ describe("walletTopupCheckoutUrl", () => {
37
47
  assert.equal(walletTopupCheckoutUrl({
38
48
  compute: "https://compute.aithos.be",
39
49
  wallet: "https://wallet.aithos.be",
50
+ web: "https://extract.aithos.be",
51
+ pds: "https://pds.aithos.be",
52
+ assets: "https://assets.aithos.be",
53
+ api: "https://api.aithos.be",
54
+ cdn: "https://cdn.aithos.be",
40
55
  }), "https://wallet.aithos.be/v1/wallet/topup/checkout");
41
56
  });
42
57
  });
58
+ describe("webInvokeUrl", () => {
59
+ it("appends /v1/invoke to the web base", () => {
60
+ assert.equal(webInvokeUrl({
61
+ compute: "https://compute.aithos.be",
62
+ wallet: "https://wallet.aithos.be",
63
+ web: "https://extract.aithos.be",
64
+ pds: "https://pds.aithos.be",
65
+ assets: "https://assets.aithos.be",
66
+ api: "https://api.aithos.be",
67
+ cdn: "https://cdn.aithos.be",
68
+ }), "https://extract.aithos.be/v1/invoke");
69
+ });
70
+ it("trims a trailing slash on the web base", () => {
71
+ assert.equal(webInvokeUrl({
72
+ compute: "https://compute.aithos.be",
73
+ wallet: "https://wallet.aithos.be",
74
+ web: "https://extract.aithos.be/",
75
+ pds: "https://pds.aithos.be",
76
+ assets: "https://assets.aithos.be",
77
+ api: "https://api.aithos.be",
78
+ cdn: "https://cdn.aithos.be",
79
+ }), "https://extract.aithos.be/v1/invoke");
80
+ });
81
+ });
43
82
  //# sourceMappingURL=endpoints.test.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=envelope-core-conformance.test.d.ts.map
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ /**
4
+ * Conformance for the envelope-signing refactor.
5
+ *
6
+ * `signOwnerEnvelope` now delegates to `@aithos/protocol-core`'s
7
+ * `signEnvelopeWith` (pluggable async signer) instead of carrying a private
8
+ * JCS + signing implementation. These tests assert the migrated path is
9
+ * correct and stable:
10
+ *
11
+ * 1. the produced envelope's `proof.proofValue` verifies against the signer's
12
+ * public key over core's canonical signing bytes (i.e. the server will
13
+ * accept it);
14
+ * 2. `params_hash` equals core's `envelopeParamsHash(params)`;
15
+ * 3. with a fixed clock + nonce the output is deterministic (snapshot), which
16
+ * is what guarantees the wire format did not shift under the refactor.
17
+ *
18
+ * Note: this suite needs the SDK to resolve a build of `@aithos/protocol-core`
19
+ * that exposes `signEnvelopeWith` (>= 0.6.3). Run via `npm test`.
20
+ */
21
+ import { describe, test } from "node:test";
22
+ import { strict as assert } from "node:assert";
23
+ import * as ed from "@noble/ed25519";
24
+ import { sha512 } from "@noble/hashes/sha512";
25
+ import { envelopeParamsHash, envelopeSigningBytes, } from "@aithos/protocol-core/envelope";
26
+ import { signOwnerEnvelope } from "../src/internal/envelope.js";
27
+ ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
28
+ const SEED = new Uint8Array(32).fill(11);
29
+ const PUB = ed.getPublicKey(SEED);
30
+ const NOW = new Date("2026-05-31T12:00:00.000Z");
31
+ const NONCE = "01J0ENVELOPECONFORMANCE0000";
32
+ const ISS = "did:aithos:z6MkEnvelopeConformance";
33
+ const VM = `${ISS}#public`;
34
+ const base = {
35
+ iss: ISS,
36
+ aud: "https://api.aithos.be/mcp/primitives/write",
37
+ method: "aithos.data.insert_record",
38
+ params: { b: 2, a: 1, nested: { y: [3, 1, 2], x: "z" } },
39
+ verificationMethod: VM,
40
+ signer: { sign: async (bytes) => ed.sign(bytes, SEED) },
41
+ ttlSeconds: 60,
42
+ now: NOW,
43
+ nonce: NONCE,
44
+ };
45
+ describe("signOwnerEnvelope — core delegation conformance", () => {
46
+ test("proofValue verifies against signing bytes (server will accept)", async () => {
47
+ const env = await signOwnerEnvelope({ ...base });
48
+ const sig = b64urlDecode(env.proof.proofValue);
49
+ assert.equal(ed.verify(sig, envelopeSigningBytes(env), PUB), true);
50
+ });
51
+ test("params_hash matches core.envelopeParamsHash", async () => {
52
+ const env = await signOwnerEnvelope({ ...base });
53
+ assert.equal(env.params_hash, envelopeParamsHash(base.params));
54
+ });
55
+ test("deterministic under fixed clock + nonce (wire-format snapshot)", async () => {
56
+ const a = await signOwnerEnvelope({ ...base });
57
+ const b = await signOwnerEnvelope({ ...base });
58
+ assert.equal(JSON.stringify(a), JSON.stringify(b));
59
+ assert.equal(a["aithos-envelope"], "0.1.0");
60
+ assert.equal(a.iat, 1780228800);
61
+ assert.equal(a.exp, 1780228860);
62
+ assert.equal(a.nonce, NONCE);
63
+ assert.equal(a.proof.verificationMethod, VM);
64
+ });
65
+ });
66
+ function b64urlDecode(s) {
67
+ const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - (s.length % 4));
68
+ const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
69
+ const bin = atob(b64);
70
+ const out = new Uint8Array(bin.length);
71
+ for (let i = 0; i < bin.length; i++)
72
+ out[i] = bin.charCodeAt(i);
73
+ return out;
74
+ }
75
+ //# sourceMappingURL=envelope-core-conformance.test.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=envelope.test.d.ts.map