@aithos/sdk 0.1.0-alpha.40 → 0.1.0-alpha.42

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 (40) hide show
  1. package/dist/src/apps.d.ts +224 -0
  2. package/dist/src/apps.js +443 -0
  3. package/dist/src/compute.d.ts +30 -0
  4. package/dist/src/index.d.ts +3 -1
  5. package/dist/src/index.js +7 -1
  6. package/dist/src/sdk.d.ts +7 -0
  7. package/dist/src/sdk.js +13 -0
  8. package/dist/test/auth-j3.test.d.ts +2 -0
  9. package/dist/test/auth-j3.test.js +391 -0
  10. package/dist/test/auth.test.d.ts +2 -0
  11. package/dist/test/auth.test.js +175 -0
  12. package/dist/test/compute-delegate-path.test.d.ts +2 -0
  13. package/dist/test/compute-delegate-path.test.js +183 -0
  14. package/dist/test/compute.test.d.ts +2 -0
  15. package/dist/test/compute.test.js +194 -0
  16. package/dist/test/endpoints.test.d.ts +2 -0
  17. package/dist/test/endpoints.test.js +62 -0
  18. package/dist/test/envelope.test.d.ts +2 -0
  19. package/dist/test/envelope.test.js +318 -0
  20. package/dist/test/ethos-first-edition.test.d.ts +2 -0
  21. package/dist/test/ethos-first-edition.test.js +248 -0
  22. package/dist/test/ethos.test.d.ts +2 -0
  23. package/dist/test/ethos.test.js +219 -0
  24. package/dist/test/key-store.test.d.ts +2 -0
  25. package/dist/test/key-store.test.js +161 -0
  26. package/dist/test/mandates-compute.test.d.ts +2 -0
  27. package/dist/test/mandates-compute.test.js +256 -0
  28. package/dist/test/mandates.test.d.ts +2 -0
  29. package/dist/test/mandates.test.js +93 -0
  30. package/dist/test/sdk.test.d.ts +2 -0
  31. package/dist/test/sdk.test.js +126 -0
  32. package/dist/test/signer.test.d.ts +2 -0
  33. package/dist/test/signer.test.js +117 -0
  34. package/dist/test/signup-bootstrap.test.d.ts +2 -0
  35. package/dist/test/signup-bootstrap.test.js +311 -0
  36. package/dist/test/wallet.test.d.ts +2 -0
  37. package/dist/test/wallet.test.js +121 -0
  38. package/dist/test/web.test.d.ts +2 -0
  39. package/dist/test/web.test.js +270 -0
  40. package/package.json +10 -11
@@ -58,6 +58,36 @@ export interface InvokeBedrockResult {
58
58
  readonly walletBalance: number;
59
59
  /** Audit log id for traceability. */
60
60
  readonly auditId: string;
61
+ /**
62
+ * Which wallet was actually debited (draft §13.8, V0.1 sponsorship).
63
+ * - `"sponsored"` — the app developer's wallet (free trial / promo).
64
+ * - `"grant"` — the user's grant bucket (Aithos-donated credits).
65
+ * - `"purchase"` — the user's own paid credits.
66
+ * Absent on legacy server responses (pre-2026-05-27).
67
+ */
68
+ readonly fundedBy?: "sponsored" | "grant" | "purchase";
69
+ /**
70
+ * If `fundedBy === "sponsored"`, the sponsor's DID (= the developer
71
+ * who pre-paid the pool). Absent otherwise.
72
+ */
73
+ readonly sponsoredBy?: string;
74
+ /**
75
+ * If sponsored, the signed `ConsumptionReceipt` id (`rcpt_…`) the
76
+ * authority issued for this debit. The receipt itself can be fetched
77
+ * later via the receipts API (V0.2). Present on every signed call
78
+ * when the authority is configured, regardless of `fundedBy`.
79
+ */
80
+ readonly receiptId?: string;
81
+ /**
82
+ * If sponsored, remaining microcredits for THIS consumer in this
83
+ * sponsorship pool. Useful for displaying "X free calls remaining"
84
+ * without an extra round-trip. May be `0` even when the user can
85
+ * still consume — when the authority can't compute the remainder
86
+ * cheaply (e.g. windowed caps), the SDK gets a conservative 0 and
87
+ * the SPA should fall back to calling `sdk.apps.getSponsorshipStatusForUser`
88
+ * (V0.2).
89
+ */
90
+ readonly sponsoredRemainingForUser?: number;
61
91
  }
62
92
  /**
63
93
  * Stable cross-provider image model ids supported by the Aithos compute
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.1.0-alpha.39";
1
+ export declare const VERSION = "0.1.0-alpha.42";
2
2
  export { AithosSDK } from "./sdk.js";
3
3
  export type { AithosSDKConfig } from "./types.js";
4
4
  export { AithosSDKError } from "./types.js";
@@ -20,6 +20,8 @@ export { EthosClient, EthosNamespace, EthosZone, ZONE_NAMES, } from "./ethos.js"
20
20
  export type { AddSectionInput, PublishResult, StagedChange, UpdateSectionPatch, ZoneName, } from "./ethos.js";
21
21
  export { COMPUTE_INVOKE_SCOPE, MandatesNamespace } from "./mandates.js";
22
22
  export type { ActorSphere, CreateMandateComputeInput, CreateMandateInput, MintedMandate, OwnedMandate, Scope, } from "./mandates.js";
23
+ export { AppsNamespace } from "./apps.js";
24
+ export type { AudienceSet, AppCreditPackId, CreateAppTopupSessionArgs, CreateAppTopupSessionResult, CreateSponsorshipMandateArgs, SignedSponsorshipMandate, SignedSponsorshipRevocation, SponsorshipAccountingAuthorityInput, SponsorshipAudienceInput, SponsorshipBudgetInput, } from "./apps.js";
23
25
  export * as onboarding from "./onboarding.js";
24
26
  export { createBrowserIdentity, browserIdentityFromStored, type BrowserIdentity, } from "@aithos/protocol-client";
25
27
  export type { Section } from "@aithos/protocol-client";
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.39";
20
+ export const VERSION = "0.1.0-alpha.42";
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
@@ -52,6 +52,12 @@ export { DEFAULT_KEYSTORE_DB_NAME, defaultKeyStore, indexedDbKeyStore, memoryKey
52
52
  export { EthosClient, EthosNamespace, EthosZone, ZONE_NAMES, } from "./ethos.js";
53
53
  // `sdk.mandates` namespace — owner-side mandate lifecycle.
54
54
  export { COMPUTE_INVOKE_SCOPE, MandatesNamespace } from "./mandates.js";
55
+ // `sdk.apps` namespace — sponsorship mandates + app-credit top-ups
56
+ // (draft §13, V0.1). Pre-pay a pool that funds your users' compute
57
+ // calls within explicit caps. Per-user caps + per-day caps + lifetime
58
+ // pool cap enforced server-side. Fallback to user wallet when caps
59
+ // are reached or pool is empty.
60
+ export { AppsNamespace } from "./apps.js";
55
61
  // Onboarding re-exports kept for advanced callers — the curated API
56
62
  // for first-run flows is `auth.signUp` (already shipped in J3).
57
63
  export * as onboarding from "./onboarding.js";
package/dist/src/sdk.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AithosAuth } from "./auth.js";
2
+ import { AppsNamespace } from "./apps.js";
2
3
  import { ComputeNamespace } from "./compute.js";
3
4
  import { type AithosSdkEndpoints } from "./endpoints.js";
4
5
  import { EthosNamespace } from "./ethos.js";
@@ -47,6 +48,12 @@ export declare class AithosSDK {
47
48
  readonly mandates: MandatesNamespace;
48
49
  /** Web extraction namespace — aithos.web_extract through the web extractor proxy. */
49
50
  readonly web: WebNamespace;
51
+ /**
52
+ * App lifecycle namespace — sponsorship mandates + app-credit top-ups.
53
+ * V0.1 (2026-05-27 — draft §13). Lets a developer pre-pay a pool that
54
+ * funds compute calls from their app's users within explicit caps.
55
+ */
56
+ readonly apps: AppsNamespace;
50
57
  constructor(config: AithosSDKConfig);
51
58
  /** DID of the currently signed-in owner, or null if no owner is loaded. */
52
59
  get userDid(): string | null;
package/dist/src/sdk.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright 2026 Mathieu Colla
3
+ import { AppsNamespace } from "./apps.js";
3
4
  import { ComputeNamespace } from "./compute.js";
4
5
  import { resolveEndpoints } from "./endpoints.js";
5
6
  import { EthosNamespace } from "./ethos.js";
@@ -23,6 +24,12 @@ export class AithosSDK {
23
24
  mandates;
24
25
  /** Web extraction namespace — aithos.web_extract through the web extractor proxy. */
25
26
  web;
27
+ /**
28
+ * App lifecycle namespace — sponsorship mandates + app-credit top-ups.
29
+ * V0.1 (2026-05-27 — draft §13). Lets a developer pre-pay a pool that
30
+ * funds compute calls from their app's users within explicit caps.
31
+ */
32
+ apps;
26
33
  constructor(config) {
27
34
  if (!config.auth) {
28
35
  throw new TypeError("AithosSDK: config.auth is required");
@@ -62,6 +69,12 @@ export class AithosSDK {
62
69
  endpoints: this.endpoints,
63
70
  fetch: fetchImpl,
64
71
  });
72
+ this.apps = new AppsNamespace({
73
+ auth: config.auth,
74
+ appDid: config.appDid,
75
+ endpoints: this.endpoints,
76
+ fetch: fetchImpl,
77
+ });
65
78
  }
66
79
  /** DID of the currently signed-in owner, or null if no owner is loaded. */
67
80
  get userDid() {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-j3.test.d.ts.map
@@ -0,0 +1,391 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // Tests for J3 — KeyStore integration, signInWithRecovery,
4
+ // importMandate, resume(), signOut(), state accessors.
5
+ import { strict as assert } from "node:assert";
6
+ import { describe, it } from "node:test";
7
+ import { createBrowserIdentity } from "@aithos/protocol-client";
8
+ import { AithosAuth, AithosSDKError, memoryKeyStore, noopStore, } from "../src/index.js";
9
+ import { parseDelegateBundle, readDelegateBundleText, } from "../src/internal/delegate-bundle.js";
10
+ import { parseRecoveryFile, serializeRecoveryFile, } from "../src/internal/recovery-file.js";
11
+ /* -------------------------------------------------------------------------- */
12
+ /* Test helpers */
13
+ /* -------------------------------------------------------------------------- */
14
+ function makeAuth(opts = {}) {
15
+ return new AithosAuth({
16
+ authBaseUrl: "https://auth.test",
17
+ fetch: (() => {
18
+ throw new Error("network not expected in this test");
19
+ }),
20
+ sessionStore: opts.sessionStore ?? noopStore(),
21
+ keyStore: opts.keyStore ?? memoryKeyStore(),
22
+ });
23
+ }
24
+ /** Build a recovery-file JSON string from a fresh BrowserIdentity. */
25
+ function recoveryTextFor(handle, displayName) {
26
+ const id = createBrowserIdentity(handle, displayName);
27
+ const { text } = serializeRecoveryFile(id);
28
+ return { text, did: id.did };
29
+ }
30
+ function delegateBundleText(args) {
31
+ return JSON.stringify({
32
+ aithos_delegate_version: "0.1.0",
33
+ mandate: {
34
+ id: args.mandateId,
35
+ subject_did: args.subjectDid,
36
+ grantee: {
37
+ id: args.granteeId,
38
+ pubkey: args.granteePubkeyMultibase ?? "z6MkqGenericPubKey",
39
+ },
40
+ scopes: args.scopes ?? ["ethos.read.public"],
41
+ },
42
+ delegate_seed_hex: "11".repeat(32),
43
+ });
44
+ }
45
+ /* -------------------------------------------------------------------------- */
46
+ /* parseRecoveryFile / serializeRecoveryFile */
47
+ /* -------------------------------------------------------------------------- */
48
+ describe("recovery file: parse + serialize", () => {
49
+ it("round-trips a fresh identity", () => {
50
+ const id = createBrowserIdentity("alice", "Alice");
51
+ const { text } = serializeRecoveryFile(id);
52
+ const parsed = parseRecoveryFile(text);
53
+ assert.equal(parsed.did, id.did);
54
+ assert.equal(parsed.handle, id.handle);
55
+ assert.equal(parsed.displayName, id.displayName);
56
+ assert.equal(parsed.seedsHex.root.length, 64);
57
+ });
58
+ it("accepts the runOnboarding shape (warning + created_at)", () => {
59
+ const id = createBrowserIdentity("bob", "Bob");
60
+ const text = JSON.stringify({
61
+ aithos_recovery_version: "0.1.0-plaintext",
62
+ warning: "this is plaintext, store offline",
63
+ handle: id.handle,
64
+ display_name: id.displayName,
65
+ did: id.did,
66
+ created_at: new Date().toISOString(),
67
+ seeds_hex: {
68
+ root: bytesToHex(id.root.seed),
69
+ public: bytesToHex(id.public.seed),
70
+ circle: bytesToHex(id.circle.seed),
71
+ self: bytesToHex(id.self.seed),
72
+ },
73
+ public_keys_multibase: {},
74
+ });
75
+ const parsed = parseRecoveryFile(text);
76
+ assert.equal(parsed.did, id.did);
77
+ });
78
+ it("rejects unsupported versions", () => {
79
+ assert.throws(() => parseRecoveryFile(JSON.stringify({ aithos_recovery_version: "9.9.9" })), (e) => e instanceof AithosSDKError && e.code === "auth_invalid_recovery_file");
80
+ });
81
+ it("rejects malformed seeds", () => {
82
+ const id = createBrowserIdentity("alice", "Alice");
83
+ const { text } = serializeRecoveryFile(id);
84
+ const obj = JSON.parse(text);
85
+ obj.seeds_hex.root = "not hex";
86
+ assert.throws(() => parseRecoveryFile(JSON.stringify(obj)), AithosSDKError);
87
+ });
88
+ });
89
+ /* -------------------------------------------------------------------------- */
90
+ /* parseDelegateBundle */
91
+ /* -------------------------------------------------------------------------- */
92
+ describe("delegate bundle: parse", () => {
93
+ it("parses a well-formed bundle (legacy subject_did field)", () => {
94
+ const text = delegateBundleText({
95
+ mandateId: "mandate:01H8XYZ",
96
+ subjectDid: "did:aithos:zCarol",
97
+ granteeId: "urn:aithos:agent:bob1",
98
+ scopes: ["ethos.read.public", "ethos.write.public"],
99
+ });
100
+ const parsed = parseDelegateBundle(text);
101
+ assert.equal(parsed.mandateId, "mandate:01H8XYZ");
102
+ assert.equal(parsed.subjectDid, "did:aithos:zCarol");
103
+ assert.equal(parsed.granteeId, "urn:aithos:agent:bob1");
104
+ assert.equal(parsed.delegateSeedHex.length, 64);
105
+ });
106
+ it("parses a bundle minted by mintDelegateBundle (issuer field)", () => {
107
+ // Real wire shape emitted by `mintDelegateBundle` in protocol-client:
108
+ // SignedMandate carries the subject's DID under `issuer`, NOT
109
+ // `subject_did`. Regression test for the import flow that broke
110
+ // every freshly-minted mandate before this fix.
111
+ const text = JSON.stringify({
112
+ aithos_delegate_version: "0.1.0",
113
+ mandate: {
114
+ "aithos-mandate": "0.1",
115
+ id: "mandate:01H8ISSUER",
116
+ issuer: "did:aithos:zCarol",
117
+ issued_by_key: "did:aithos:zCarol#root",
118
+ grantee: {
119
+ id: "urn:aithos:agent:bob1",
120
+ pubkey: "z6MkqGenericPubKey",
121
+ },
122
+ actor_sphere: "self",
123
+ scopes: ["ethos.read.public", "ethos.write.public"],
124
+ not_before: "2026-05-10T00:00:00Z",
125
+ not_after: "2026-05-11T00:00:00Z",
126
+ issued_at: "2026-05-10T00:00:00Z",
127
+ nonce: "abc",
128
+ signature: { alg: "ed25519", key: "...", value: "..." },
129
+ },
130
+ delegate_seed_hex: "11".repeat(32),
131
+ });
132
+ const parsed = parseDelegateBundle(text);
133
+ assert.equal(parsed.subjectDid, "did:aithos:zCarol");
134
+ assert.equal(parsed.mandateId, "mandate:01H8ISSUER");
135
+ assert.equal(parsed.granteeId, "urn:aithos:agent:bob1");
136
+ });
137
+ it("readDelegateBundleText accepts string passthrough", async () => {
138
+ const text = delegateBundleText({
139
+ mandateId: "m",
140
+ subjectDid: "did:aithos:z",
141
+ granteeId: "urn:x",
142
+ });
143
+ assert.equal(await readDelegateBundleText(text), text);
144
+ });
145
+ it("rejects missing mandate", () => {
146
+ assert.throws(() => parseDelegateBundle(JSON.stringify({
147
+ aithos_delegate_version: "0.1.0",
148
+ delegate_seed_hex: "11".repeat(32),
149
+ })), AithosSDKError);
150
+ });
151
+ it("rejects malformed delegate seed", () => {
152
+ assert.throws(() => parseDelegateBundle(JSON.stringify({
153
+ aithos_delegate_version: "0.1.0",
154
+ mandate: {
155
+ id: "m",
156
+ subject_did: "did:aithos:z",
157
+ grantee: { id: "u", pubkey: "z6MkXYZ" },
158
+ },
159
+ delegate_seed_hex: "not hex",
160
+ })), AithosSDKError);
161
+ });
162
+ });
163
+ /* -------------------------------------------------------------------------- */
164
+ /* AithosAuth — recovery sign-in */
165
+ /* -------------------------------------------------------------------------- */
166
+ describe("AithosAuth.signInWithRecovery", () => {
167
+ it("hydrates owner signers + persists to keyStore (no JWT)", async () => {
168
+ const keyStore = memoryKeyStore();
169
+ const sessionStore = noopStore();
170
+ const auth = makeAuth({ sessionStore, keyStore });
171
+ assert.equal(auth.canSignAsOwner(), false);
172
+ assert.equal(auth.getOwnerInfo(), null);
173
+ const { text } = recoveryTextFor("alice", "Alice");
174
+ const info = await auth.signInWithRecovery({ file: text });
175
+ assert.equal(info.handle, "alice");
176
+ assert.equal(auth.canSignAsOwner(), true);
177
+ assert.equal(auth.getOwnerInfo()?.did, info.did);
178
+ assert.equal(auth.getCurrentSession(), null, "no JWT for recovery flow");
179
+ const persisted = await keyStore.loadOwner();
180
+ assert.equal(persisted?.did, info.did);
181
+ });
182
+ it("rejects loading a different owner without signOut first", async () => {
183
+ const auth = makeAuth();
184
+ const a = recoveryTextFor("alice", "Alice");
185
+ await auth.signInWithRecovery({ file: a.text });
186
+ const b = recoveryTextFor("bob", "Bob");
187
+ await assert.rejects(() => auth.signInWithRecovery({ file: b.text }), (e) => e instanceof AithosSDKError && e.code === "auth_owner_already_loaded");
188
+ });
189
+ it("clears any stale JWT to keep stores in sync", async () => {
190
+ const keyStore = memoryKeyStore();
191
+ let stored = {
192
+ session: "stale-jwt",
193
+ exp: Math.floor(Date.now() / 1000) + 3600,
194
+ did: "did:aithos:zStale",
195
+ handle: "stale",
196
+ blob_b64: "",
197
+ blob_nonce_b64: "",
198
+ blob_version: 0,
199
+ enc_key_b64: "",
200
+ is_first_login: false,
201
+ };
202
+ const sessionStore = {
203
+ get: () => stored,
204
+ set: (s) => {
205
+ stored = s;
206
+ },
207
+ clear: () => {
208
+ stored = null;
209
+ },
210
+ };
211
+ const auth = makeAuth({ sessionStore, keyStore });
212
+ const { text } = recoveryTextFor("alice", "Alice");
213
+ await auth.signInWithRecovery({ file: text });
214
+ assert.equal(stored, null, "stale JWT must be wiped");
215
+ });
216
+ });
217
+ /* -------------------------------------------------------------------------- */
218
+ /* AithosAuth — importMandate */
219
+ /* -------------------------------------------------------------------------- */
220
+ describe("AithosAuth.importMandate", () => {
221
+ it("registers a delegate, lists it, removes it", async () => {
222
+ const keyStore = memoryKeyStore();
223
+ const auth = makeAuth({ keyStore });
224
+ const text = delegateBundleText({
225
+ mandateId: "mandate:A",
226
+ subjectDid: "did:aithos:zCarol",
227
+ granteeId: "urn:aithos:agent:bob1",
228
+ scopes: ["ethos.read.circle"],
229
+ });
230
+ const info = await auth.importMandate({ bundle: text });
231
+ assert.equal(info.mandateId, "mandate:A");
232
+ assert.equal(info.subjectDid, "did:aithos:zCarol");
233
+ assert.deepEqual(info.scopes, ["ethos.read.circle"]);
234
+ const list = auth.getDelegates();
235
+ assert.equal(list.length, 1);
236
+ assert.equal(list[0]?.mandateId, "mandate:A");
237
+ assert.equal(auth.canSignAsDelegateFor("did:aithos:zCarol"), true);
238
+ assert.equal(auth.canSignAsDelegateFor("did:aithos:zNobody"), false);
239
+ await auth.removeMandate("mandate:A");
240
+ assert.equal(auth.getDelegates().length, 0);
241
+ assert.equal((await keyStore.listDelegates()).length, 0);
242
+ });
243
+ it("works without an owner loaded (delegate-only session)", async () => {
244
+ const auth = makeAuth();
245
+ assert.equal(auth.canSignAsOwner(), false);
246
+ const text = delegateBundleText({
247
+ mandateId: "mandate:Solo",
248
+ subjectDid: "did:aithos:zCarol",
249
+ granteeId: "urn:aithos:agent:solo1",
250
+ });
251
+ await auth.importMandate({ bundle: text });
252
+ assert.equal(auth.canSignAsOwner(), false);
253
+ assert.equal(auth.canSignAsDelegateFor("did:aithos:zCarol"), true);
254
+ });
255
+ it("re-importing the same mandate replaces the prior actor", async () => {
256
+ const auth = makeAuth();
257
+ const text = delegateBundleText({
258
+ mandateId: "mandate:R",
259
+ subjectDid: "did:aithos:zCarol",
260
+ granteeId: "urn:aithos:agent:r",
261
+ });
262
+ await auth.importMandate({ bundle: text });
263
+ await auth.importMandate({ bundle: text });
264
+ assert.equal(auth.getDelegates().length, 1);
265
+ });
266
+ });
267
+ /* -------------------------------------------------------------------------- */
268
+ /* AithosAuth — resume() */
269
+ /* -------------------------------------------------------------------------- */
270
+ describe("AithosAuth.resume", () => {
271
+ it("rehydrates owner + delegates from keyStore on a fresh instance", async () => {
272
+ const keyStore = memoryKeyStore();
273
+ const sessionStore = noopStore();
274
+ // Seed the stores via a first auth instance.
275
+ {
276
+ const auth1 = makeAuth({ keyStore, sessionStore });
277
+ const { text: rt } = recoveryTextFor("alice", "Alice");
278
+ await auth1.signInWithRecovery({ file: rt });
279
+ const dt = delegateBundleText({
280
+ mandateId: "mandate:M1",
281
+ subjectDid: "did:aithos:zCarol",
282
+ granteeId: "urn:x",
283
+ });
284
+ await auth1.importMandate({ bundle: dt });
285
+ }
286
+ // Fresh instance, same stores → resume() must reload everything.
287
+ const auth2 = makeAuth({ keyStore, sessionStore });
288
+ assert.equal(auth2.canSignAsOwner(), false, "before resume, in-memory only");
289
+ await auth2.resume();
290
+ assert.equal(auth2.canSignAsOwner(), true);
291
+ assert.equal(auth2.getOwnerInfo()?.handle, "alice");
292
+ assert.equal(auth2.getDelegates().length, 1);
293
+ assert.equal(auth2.canSignAsDelegateFor("did:aithos:zCarol"), true);
294
+ });
295
+ it("strict mode: JWT in sessionStore but no owner in keyStore → wipes JWT", async () => {
296
+ let jwt = {
297
+ session: "j",
298
+ exp: Math.floor(Date.now() / 1000) + 3600,
299
+ did: "did:aithos:zGhost",
300
+ handle: "ghost",
301
+ blob_b64: "",
302
+ blob_nonce_b64: "",
303
+ blob_version: 0,
304
+ enc_key_b64: "",
305
+ is_first_login: false,
306
+ };
307
+ const sessionStore = {
308
+ get: () => jwt,
309
+ set: (s) => {
310
+ jwt = s;
311
+ },
312
+ clear: () => {
313
+ jwt = null;
314
+ },
315
+ };
316
+ const keyStore = memoryKeyStore();
317
+ const auth = makeAuth({ keyStore, sessionStore });
318
+ await auth.resume();
319
+ assert.equal(jwt, null, "out-of-sync JWT must be cleared");
320
+ assert.equal(auth.canSignAsOwner(), false);
321
+ });
322
+ it("strict mode: JWT and owner disagree on DID → wipes JWT only", async () => {
323
+ const keyStore = memoryKeyStore();
324
+ const { text } = recoveryTextFor("alice", "Alice");
325
+ // Seed the keystore with alice via one instance.
326
+ {
327
+ const auth1 = makeAuth({ keyStore });
328
+ await auth1.signInWithRecovery({ file: text });
329
+ }
330
+ // Now plant a JWT for a DIFFERENT DID in the session store.
331
+ let jwt = {
332
+ session: "j",
333
+ exp: Math.floor(Date.now() / 1000) + 3600,
334
+ did: "did:aithos:zSomeoneElse",
335
+ handle: "someone",
336
+ blob_b64: "",
337
+ blob_nonce_b64: "",
338
+ blob_version: 0,
339
+ enc_key_b64: "",
340
+ is_first_login: false,
341
+ };
342
+ const sessionStore = {
343
+ get: () => jwt,
344
+ set: (s) => {
345
+ jwt = s;
346
+ },
347
+ clear: () => {
348
+ jwt = null;
349
+ },
350
+ };
351
+ const auth2 = makeAuth({ keyStore, sessionStore });
352
+ await auth2.resume();
353
+ assert.equal(jwt, null, "mismatched JWT must be wiped");
354
+ // Owner is preserved (it's the source of truth in strict mode).
355
+ assert.equal(auth2.canSignAsOwner(), true);
356
+ assert.equal(auth2.getOwnerInfo()?.handle, "alice");
357
+ });
358
+ });
359
+ /* -------------------------------------------------------------------------- */
360
+ /* AithosAuth — signOut */
361
+ /* -------------------------------------------------------------------------- */
362
+ describe("AithosAuth.signOut", () => {
363
+ it("wipes both stores and in-memory state", async () => {
364
+ const keyStore = memoryKeyStore();
365
+ const auth = makeAuth({ keyStore });
366
+ const { text } = recoveryTextFor("alice", "Alice");
367
+ await auth.signInWithRecovery({ file: text });
368
+ const dt = delegateBundleText({
369
+ mandateId: "mandate:X",
370
+ subjectDid: "did:aithos:zCarol",
371
+ granteeId: "urn:x",
372
+ });
373
+ await auth.importMandate({ bundle: dt });
374
+ await auth.signOut();
375
+ assert.equal(auth.canSignAsOwner(), false);
376
+ assert.equal(auth.getOwnerInfo(), null);
377
+ assert.equal(auth.getDelegates().length, 0);
378
+ assert.equal(await keyStore.loadOwner(), null);
379
+ assert.equal((await keyStore.listDelegates()).length, 0);
380
+ });
381
+ });
382
+ /* -------------------------------------------------------------------------- */
383
+ /* Helpers */
384
+ /* -------------------------------------------------------------------------- */
385
+ function bytesToHex(b) {
386
+ let out = "";
387
+ for (let i = 0; i < b.length; i++)
388
+ out += b[i].toString(16).padStart(2, "0");
389
+ return out;
390
+ }
391
+ //# sourceMappingURL=auth-j3.test.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map