@tinycloud/sdk-core 2.4.0-beta.1 → 2.4.0-beta.10

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/index.js CHANGED
@@ -970,6 +970,7 @@ var SpaceService = class {
970
970
  this._userDid = config.userDid;
971
971
  this.sharingService = config.sharingService;
972
972
  this.createDelegationFn = config.createDelegation;
973
+ this.onSpaceRegisteredFn = config.onSpaceRegistered;
973
974
  }
974
975
  /**
975
976
  * Update the service configuration.
@@ -985,6 +986,7 @@ var SpaceService = class {
985
986
  if (config.userDid !== void 0) this._userDid = config.userDid;
986
987
  if (config.sharingService) this.sharingService = config.sharingService;
987
988
  if (config.createDelegation) this.createDelegationFn = config.createDelegation;
989
+ if (config.onSpaceRegistered) this.onSpaceRegisteredFn = config.onSpaceRegistered;
988
990
  this.spaceCache.clear();
989
991
  this.infoCache.clear();
990
992
  }
@@ -1035,6 +1037,9 @@ var SpaceService = class {
1035
1037
  spaces.push(...delegatedSpaces);
1036
1038
  }
1037
1039
  const uniqueSpaces = this.deduplicateSpaces(spaces);
1040
+ for (const space of uniqueSpaces) {
1041
+ this.notifySpaceRegistered(space);
1042
+ }
1038
1043
  return ok(uniqueSpaces);
1039
1044
  } catch (error) {
1040
1045
  return err(
@@ -1232,6 +1237,7 @@ var SpaceService = class {
1232
1237
  permissions: ["*"]
1233
1238
  };
1234
1239
  this.infoCache.set(spaceInfo.id, { info: spaceInfo, cachedAt: Date.now() });
1240
+ this.notifySpaceRegistered(spaceInfo);
1235
1241
  return ok(spaceInfo);
1236
1242
  } catch (error) {
1237
1243
  return err(
@@ -1244,6 +1250,11 @@ var SpaceService = class {
1244
1250
  );
1245
1251
  }
1246
1252
  }
1253
+ notifySpaceRegistered(space) {
1254
+ if (!this.onSpaceRegisteredFn) return;
1255
+ void Promise.resolve(this.onSpaceRegisteredFn(space)).catch(() => {
1256
+ });
1257
+ }
1247
1258
  // ===========================================================================
1248
1259
  // Get Space
1249
1260
  // ===========================================================================
@@ -2151,1474 +2162,2326 @@ var TinyCloud = class _TinyCloud {
2151
2162
  }
2152
2163
  };
2153
2164
 
2154
- // src/index.ts
2165
+ // src/account/AccountService.ts
2155
2166
  import {
2156
- ServiceContext as ServiceContext2,
2157
- KVService as KVService2,
2158
- PrefixedKVService,
2159
- ok as ok4,
2160
- err as err4,
2161
- serviceError as serviceError4,
2162
- ErrorCodes as ErrorCodes2,
2163
- defaultRetryPolicy as defaultRetryPolicy2,
2164
- SQLService as SQLService2,
2165
- DatabaseHandle,
2166
- SQLAction,
2167
- DuckDbService as DuckDbService2,
2168
- DuckDbDatabaseHandle,
2169
- DuckDbAction,
2170
- HooksService as HooksService2,
2171
- DataVaultService,
2172
- VaultHeaders,
2173
- VaultPublicSpaceKVActions,
2174
- createVaultCrypto,
2175
- SecretsService,
2176
- SECRET_NAME_RE as SECRET_NAME_RE2,
2177
- canonicalizeSecretScope,
2178
- resolveSecretListPrefix,
2179
- resolveSecretPath as resolveSecretPath2,
2180
- EncryptionService,
2181
- parseNetworkId as parseNetworkId2,
2182
- buildNetworkId as buildNetworkId2,
2183
- isNetworkId,
2184
- networkDiscoveryKey,
2185
- NetworkIdError,
2186
- ENCRYPTION_NETWORK_URN_PREFIX,
2187
- NETWORK_NAME_PATTERN,
2188
- canonicalizeEncryptionJson,
2189
- canonicalHashHex,
2190
- hexEncode,
2191
- hexDecode,
2192
- base64Encode,
2193
- base64Decode,
2194
- utf8Encode,
2195
- utf8Decode,
2196
- encryptToNetwork,
2197
- decryptEnvelopeWithKey,
2198
- validateEnvelope,
2199
- generateRandomReceiverKey,
2200
- deriveSignedReceiverKey,
2201
- buildCanonicalDecryptRequest,
2202
- buildDecryptFacts,
2203
- buildDecryptAttenuation,
2204
- buildDecryptInvocation,
2205
- checkDecryptInvocationInput,
2206
- verifyDecryptResponse,
2207
- canonicalSignedResponse,
2208
- openWrappedKey,
2209
- discoverNetwork,
2210
- ensureNetworkUsableForDecrypt,
2211
- DEFAULT_ENCRYPTION_ALG,
2212
- ENVELOPE_VERSION,
2213
- DEFAULT_KEY_VERSION,
2214
- DECRYPT_FACT_TYPE,
2215
- DECRYPT_RESULT_TYPE,
2216
- DECRYPT_ACTION,
2217
- ENCRYPTION_SERVICE,
2218
- ENCRYPTION_SERVICE_SHORT,
2219
- encryptionError
2167
+ err as err3,
2168
+ ok as ok3,
2169
+ serviceError as serviceError3
2220
2170
  } from "@tinycloud/sdk-services";
2221
2171
 
2222
- // src/space.ts
2223
- async function fetchPeerId(host, spaceId) {
2224
- const res = await fetch(
2225
- `${host}/peer/generate/${encodeURIComponent(spaceId)}`
2226
- );
2227
- if (!res.ok) {
2228
- const error = await res.text().catch(() => res.statusText);
2229
- throw new Error(`Failed to get peer ID: ${res.status} - ${error}`);
2172
+ // src/manifest.ts
2173
+ import ms from "ms";
2174
+ import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
2175
+ var ManifestValidationError = class extends Error {
2176
+ constructor(message) {
2177
+ super(`Manifest validation failed: ${message}`);
2178
+ this.name = "ManifestValidationError";
2230
2179
  }
2231
- return res.text();
2180
+ };
2181
+ var DEFAULT_EXPIRY = "30d";
2182
+ var DEFAULT_DEFAULTS = true;
2183
+ var DEFAULT_MANIFEST_VERSION = 1;
2184
+ var DEFAULT_MANIFEST_SPACE = "applications";
2185
+ var ACCOUNT_REGISTRY_SPACE = "account";
2186
+ var ACCOUNT_REGISTRY_PATH = "applications/";
2187
+ var SECRETS_SPACE = "secrets";
2188
+ var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
2189
+ var SERVICE_SHORT_TO_LONG = Object.freeze({
2190
+ kv: "tinycloud.kv",
2191
+ sql: "tinycloud.sql",
2192
+ duckdb: "tinycloud.duckdb",
2193
+ capabilities: "tinycloud.capabilities",
2194
+ hooks: "tinycloud.hooks",
2195
+ encryption: "tinycloud.encryption"
2196
+ });
2197
+ var ENCRYPTION_PERMISSION_SERVICE = "tinycloud.encryption";
2198
+ var ENCRYPTION_MANIFEST_SPACE = "encryption";
2199
+ var SERVICE_LONG_TO_SHORT = Object.freeze(
2200
+ Object.fromEntries(
2201
+ Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
2202
+ )
2203
+ );
2204
+ var DEFAULT_STANDARD_ENTRIES = [
2205
+ {
2206
+ service: "tinycloud.kv",
2207
+ space: DEFAULT_MANIFEST_SPACE,
2208
+ path: "/",
2209
+ actions: ["get", "put", "del", "list", "metadata"]
2210
+ },
2211
+ {
2212
+ service: "tinycloud.sql",
2213
+ space: DEFAULT_MANIFEST_SPACE,
2214
+ path: "/",
2215
+ actions: ["read", "write"]
2216
+ }
2217
+ ];
2218
+ var DEFAULT_ADMIN_ENTRIES = [
2219
+ {
2220
+ service: "tinycloud.kv",
2221
+ space: DEFAULT_MANIFEST_SPACE,
2222
+ path: "/",
2223
+ actions: ["get", "put", "del", "list", "metadata"]
2224
+ },
2225
+ {
2226
+ service: "tinycloud.sql",
2227
+ space: DEFAULT_MANIFEST_SPACE,
2228
+ path: "/",
2229
+ actions: ["read", "write", "ddl"]
2230
+ }
2231
+ ];
2232
+ var DEFAULT_ALL_ENTRIES = [
2233
+ {
2234
+ service: "tinycloud.kv",
2235
+ space: DEFAULT_MANIFEST_SPACE,
2236
+ path: "/",
2237
+ actions: ["get", "put", "del", "list", "metadata"]
2238
+ },
2239
+ {
2240
+ service: "tinycloud.sql",
2241
+ space: DEFAULT_MANIFEST_SPACE,
2242
+ path: "/",
2243
+ actions: ["read", "write", "ddl"]
2244
+ },
2245
+ {
2246
+ service: "tinycloud.duckdb",
2247
+ space: DEFAULT_MANIFEST_SPACE,
2248
+ path: "/",
2249
+ actions: ["read", "write"]
2250
+ }
2251
+ ];
2252
+ function parseExpiry(duration) {
2253
+ if (typeof duration !== "string" || duration.length === 0) {
2254
+ throw new ManifestValidationError(
2255
+ `expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
2256
+ );
2257
+ }
2258
+ const parsed = ms(duration);
2259
+ if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
2260
+ throw new ManifestValidationError(
2261
+ `invalid expiry duration: ${JSON.stringify(duration)}`
2262
+ );
2263
+ }
2264
+ return parsed;
2232
2265
  }
2233
- async function submitHostDelegation(host, headers) {
2234
- const res = await fetch(`${host}/delegate`, {
2235
- method: "POST",
2236
- headers
2266
+ function expandActionShortNames(service, actions) {
2267
+ return actions.map((a) => {
2268
+ if (a.includes("/")) {
2269
+ return a;
2270
+ }
2271
+ return `${service}/${a}`;
2237
2272
  });
2238
- return {
2239
- success: res.ok,
2240
- status: res.status,
2241
- error: res.ok ? void 0 : await res.text().catch(() => res.statusText)
2242
- };
2243
2273
  }
2244
- async function activateSessionWithHost(host, delegationHeader) {
2245
- const res = await fetch(`${host}/delegate`, {
2246
- method: "POST",
2247
- headers: delegationHeader
2248
- });
2249
- if (res.ok) {
2250
- try {
2251
- const body = await res.json();
2252
- return {
2253
- success: true,
2254
- status: res.status,
2255
- activated: body.activated ?? [],
2256
- skipped: body.skipped ?? []
2257
- };
2258
- } catch {
2259
- return {
2260
- success: true,
2261
- status: res.status,
2262
- activated: [],
2263
- skipped: []
2264
- };
2265
- }
2274
+ function expandPermissionEntry(entry) {
2275
+ if (entry.service === ENCRYPTION_PERMISSION_SERVICE) {
2276
+ return expandEncryptionPermissionEntry(entry);
2266
2277
  }
2267
- return {
2268
- success: false,
2269
- status: res.status,
2270
- error: await res.text().catch(() => res.statusText)
2271
- };
2272
- }
2273
-
2274
- // src/delegations/DelegationManager.ts
2275
- var DelegationAction = {
2276
- CREATE: "tinycloud.delegation/create",
2277
- REVOKE: "tinycloud.delegation/revoke",
2278
- LIST: "tinycloud.delegation/list",
2279
- GET: "tinycloud.delegation/get",
2280
- CHECK: "tinycloud.delegation/check"
2281
- };
2282
- function createError(code, message, cause, meta) {
2283
- return {
2284
- code,
2285
- message,
2286
- service: "delegation",
2287
- cause,
2288
- meta
2289
- };
2278
+ if (entry.service !== VAULT_PERMISSION_SERVICE) {
2279
+ return [
2280
+ {
2281
+ ...entry,
2282
+ actions: expandActionShortNames(entry.service, entry.actions)
2283
+ }
2284
+ ];
2285
+ }
2286
+ return expandVaultPermissionEntry(entry);
2290
2287
  }
2291
- var DelegationManager = class {
2292
- /**
2293
- * Creates a new DelegationManager instance.
2294
- *
2295
- * @param config - Configuration including hosts, session, and invoke function
2296
- */
2297
- constructor(config) {
2298
- this.hosts = config.hosts;
2299
- this.session = config.session;
2300
- this.invoke = config.invoke;
2301
- this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
2288
+ function expandEncryptionPermissionEntry(entry) {
2289
+ if (typeof entry.path !== "string" || !entry.path.startsWith("urn:tinycloud:encryption:")) {
2290
+ throw new ManifestValidationError(
2291
+ `tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
2292
+ );
2302
2293
  }
2303
- /**
2304
- * Updates the session (e.g., after re-authentication).
2305
- *
2306
- * @param session - New session to use for operations
2307
- */
2308
- updateSession(session) {
2309
- this.session = session;
2310
- }
2311
- /**
2312
- * Gets the primary host URL.
2313
- */
2314
- get host() {
2315
- return this.hosts[0];
2316
- }
2317
- /**
2318
- * Executes an invoke operation against the delegation API.
2319
- */
2320
- async invokeOperation(path, action, body) {
2321
- const headers = this.invoke(this.session, "delegation", path, action);
2322
- return this.fetchFn(`${this.host}/invoke`, {
2323
- method: "POST",
2324
- headers,
2325
- body
2326
- });
2327
- }
2328
- /**
2329
- * Creates a new delegation.
2330
- *
2331
- * Delegates specific permissions to another DID for a given path.
2332
- * The delegatee can then use these permissions to access resources
2333
- * within the specified scope.
2334
- *
2335
- * @param params - Parameters for the delegation
2336
- * @returns Result containing the created Delegation or an error
2337
- *
2338
- * @example
2339
- * ```typescript
2340
- * const result = await manager.create({
2341
- * delegateDID: bob.did,
2342
- * path: "documents/shared/",
2343
- * actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
2344
- * expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
2345
- * });
2346
- * ```
2347
- */
2348
- async create(params) {
2349
- if (!params.delegateDID) {
2350
- return {
2351
- ok: false,
2352
- error: createError(
2353
- DelegationErrorCodes.INVALID_INPUT,
2354
- "delegateDID is required"
2355
- )
2356
- };
2294
+ const normalizedActions = [];
2295
+ for (const action of entry.actions) {
2296
+ if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
2297
+ normalizedActions.push("tinycloud.encryption/decrypt");
2298
+ continue;
2357
2299
  }
2358
- if (!params.path) {
2359
- return {
2360
- ok: false,
2361
- error: createError(
2362
- DelegationErrorCodes.INVALID_INPUT,
2363
- "path is required"
2364
- )
2365
- };
2300
+ if (action === "network.create" || action === "tinycloud.encryption/network.create") {
2301
+ normalizedActions.push("tinycloud.encryption/network.create");
2302
+ continue;
2366
2303
  }
2367
- if (!params.actions || params.actions.length === 0) {
2368
- return {
2369
- ok: false,
2370
- error: createError(
2371
- DelegationErrorCodes.INVALID_INPUT,
2372
- "at least one action is required"
2373
- )
2374
- };
2304
+ if (action === "network.revoke" || action === "tinycloud.encryption/network.revoke") {
2305
+ normalizedActions.push("tinycloud.encryption/network.revoke");
2306
+ continue;
2375
2307
  }
2376
- try {
2377
- const body = JSON.stringify({
2378
- delegateDID: params.delegateDID,
2379
- path: params.path,
2380
- actions: params.actions,
2381
- expiry: params.expiry?.toISOString(),
2382
- disableSubDelegation: params.disableSubDelegation ?? false,
2383
- statement: params.statement
2384
- });
2385
- const response = await this.invokeOperation(
2386
- params.path,
2387
- DelegationAction.CREATE,
2388
- body
2308
+ if (action.includes("/")) {
2309
+ throw new ManifestValidationError(
2310
+ `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
2389
2311
  );
2390
- if (!response.ok) {
2391
- const errorText = await response.text();
2392
- return {
2393
- ok: false,
2394
- error: createError(
2395
- DelegationErrorCodes.CREATION_FAILED,
2396
- `Failed to create delegation: ${response.status} - ${errorText}`,
2397
- void 0,
2398
- { status: response.status, path: params.path }
2399
- )
2400
- };
2401
- }
2402
- const apiResponse = await response.json();
2403
- const delegation = {
2404
- cid: apiResponse.cid ?? "",
2405
- delegateDID: params.delegateDID,
2406
- spaceId: this.session.spaceId,
2407
- path: params.path,
2408
- actions: params.actions,
2409
- expiry: params.expiry ?? new Date(Date.now() + EXPIRY.SHARE_MS),
2410
- isRevoked: false,
2411
- allowSubDelegation: !(params.disableSubDelegation ?? false),
2412
- createdAt: /* @__PURE__ */ new Date()
2413
- };
2414
- return { ok: true, data: delegation };
2415
- } catch (error) {
2416
- if (error instanceof Error && error.name === "AbortError") {
2417
- return {
2418
- ok: false,
2419
- error: createError(
2420
- DelegationErrorCodes.ABORTED,
2421
- "Request aborted",
2422
- error
2423
- )
2424
- };
2425
- }
2426
- return {
2427
- ok: false,
2428
- error: createError(
2429
- DelegationErrorCodes.NETWORK_ERROR,
2430
- `Network error during delegation creation: ${String(error)}`,
2431
- error instanceof Error ? error : void 0
2432
- )
2433
- };
2434
2312
  }
2313
+ throw new ManifestValidationError(
2314
+ `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
2315
+ );
2435
2316
  }
2436
- /**
2437
- * Revokes an existing delegation.
2438
- *
2439
- * Once revoked, the delegation can no longer be used to access resources.
2440
- * This also invalidates any sub-delegations derived from this delegation.
2441
- *
2442
- * @param cid - The CID of the delegation to revoke
2443
- * @returns Result indicating success or an error
2444
- *
2445
- * @example
2446
- * ```typescript
2447
- * const result = await manager.revoke("bafy...");
2448
- * if (result.ok) {
2449
- * console.log("Delegation revoked successfully");
2450
- * }
2451
- * ```
2452
- */
2453
- async revoke(cid) {
2454
- if (!cid) {
2455
- return {
2456
- ok: false,
2457
- error: createError(
2458
- DelegationErrorCodes.INVALID_INPUT,
2459
- "cid is required"
2460
- )
2461
- };
2317
+ const dedupedActions = [];
2318
+ const seen = /* @__PURE__ */ new Set();
2319
+ for (const a of normalizedActions) {
2320
+ if (!seen.has(a)) {
2321
+ dedupedActions.push(a);
2322
+ seen.add(a);
2462
2323
  }
2463
- try {
2464
- const body = JSON.stringify({ cid });
2465
- const response = await this.invokeOperation(
2466
- cid,
2467
- DelegationAction.REVOKE,
2468
- body
2469
- );
2470
- if (!response.ok) {
2471
- const errorText = await response.text();
2472
- if (response.status === 404) {
2473
- return {
2474
- ok: false,
2475
- error: createError(
2476
- DelegationErrorCodes.NOT_FOUND,
2477
- `Delegation not found: ${cid}`
2478
- )
2479
- };
2480
- }
2481
- return {
2482
- ok: false,
2483
- error: createError(
2484
- DelegationErrorCodes.REVOCATION_FAILED,
2485
- `Failed to revoke delegation: ${response.status} - ${errorText}`,
2486
- void 0,
2487
- { status: response.status, cid }
2488
- )
2489
- };
2490
- }
2491
- return { ok: true, data: void 0 };
2492
- } catch (error) {
2493
- if (error instanceof Error && error.name === "AbortError") {
2494
- return {
2495
- ok: false,
2496
- error: createError(
2497
- DelegationErrorCodes.ABORTED,
2498
- "Request aborted",
2499
- error
2500
- )
2501
- };
2502
- }
2503
- return {
2504
- ok: false,
2505
- error: createError(
2506
- DelegationErrorCodes.NETWORK_ERROR,
2507
- `Network error during delegation revocation: ${String(error)}`,
2508
- error instanceof Error ? error : void 0
2509
- )
2510
- };
2324
+ }
2325
+ return [
2326
+ {
2327
+ service: ENCRYPTION_PERMISSION_SERVICE,
2328
+ space: ENCRYPTION_MANIFEST_SPACE,
2329
+ path: entry.path,
2330
+ actions: dedupedActions,
2331
+ skipPrefix: true,
2332
+ ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
2333
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2511
2334
  }
2335
+ ];
2336
+ }
2337
+ function expandPermissionEntries(entries) {
2338
+ return entries.flatMap(expandPermissionEntry);
2339
+ }
2340
+ function applyPrefix(prefix, path, skipPrefix) {
2341
+ if (skipPrefix) {
2342
+ return path;
2512
2343
  }
2513
- /**
2514
- * Lists all delegations for the current session's space.
2515
- *
2516
- * Returns both delegations created by the current user (as delegator)
2517
- * and delegations granted to the current user (as delegatee).
2518
- *
2519
- * @returns Result containing an array of Delegations or an error
2520
- *
2521
- * @example
2522
- * ```typescript
2523
- * const result = await manager.list();
2524
- * if (result.ok) {
2525
- * for (const delegation of result.data) {
2526
- * console.log(`${delegation.cid}: ${delegation.path} -> ${delegation.delegateDID}`);
2527
- * }
2528
- * }
2529
- * ```
2530
- */
2531
- async list() {
2532
- try {
2533
- const response = await this.invokeOperation("", DelegationAction.LIST);
2534
- if (!response.ok) {
2535
- const errorText = await response.text();
2536
- return {
2537
- ok: false,
2538
- error: createError(
2539
- DelegationErrorCodes.NETWORK_ERROR,
2540
- `Failed to list delegations: ${response.status} - ${errorText}`,
2541
- void 0,
2542
- { status: response.status }
2543
- )
2544
- };
2545
- }
2546
- const data = await response.json();
2547
- const delegations = data.map((item) => ({
2548
- cid: item.cid,
2549
- delegateDID: item.delegateDID,
2550
- delegatorDID: item.delegatorDID,
2551
- spaceId: item.spaceId,
2552
- path: item.path,
2553
- actions: item.actions,
2554
- expiry: new Date(item.expiry),
2555
- isRevoked: item.isRevoked,
2556
- createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
2557
- parentCid: item.parentCid,
2558
- allowSubDelegation: item.allowSubDelegation
2559
- }));
2560
- return { ok: true, data: delegations };
2561
- } catch (error) {
2562
- if (error instanceof Error && error.name === "AbortError") {
2563
- return {
2564
- ok: false,
2565
- error: createError(
2566
- DelegationErrorCodes.ABORTED,
2567
- "Request aborted",
2568
- error
2569
- )
2570
- };
2571
- }
2572
- return {
2573
- ok: false,
2574
- error: createError(
2575
- DelegationErrorCodes.NETWORK_ERROR,
2576
- `Network error during delegation list: ${String(error)}`,
2577
- error instanceof Error ? error : void 0
2578
- )
2579
- };
2344
+ if (prefix === "") {
2345
+ return path;
2346
+ }
2347
+ if (path.startsWith("/")) {
2348
+ return `${prefix}${path}`;
2349
+ }
2350
+ return `${prefix}/${path}`;
2351
+ }
2352
+ async function loadManifest(url) {
2353
+ const fetchFn = globalThis.fetch;
2354
+ if (typeof fetchFn !== "function") {
2355
+ throw new ManifestValidationError(
2356
+ "loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
2357
+ );
2358
+ }
2359
+ const res = await fetchFn(url);
2360
+ if (!res.ok) {
2361
+ throw new ManifestValidationError(
2362
+ `failed to fetch manifest from ${url}: HTTP ${res.status}`
2363
+ );
2364
+ }
2365
+ const json = await res.json();
2366
+ return validateManifest(json);
2367
+ }
2368
+ function validateManifest(input) {
2369
+ if (input === null || typeof input !== "object") {
2370
+ throw new ManifestValidationError("manifest must be an object");
2371
+ }
2372
+ const m = input;
2373
+ if (m.manifest_version !== void 0 && m.manifest_version !== DEFAULT_MANIFEST_VERSION) {
2374
+ throw new ManifestValidationError(
2375
+ `manifest.manifest_version must be ${DEFAULT_MANIFEST_VERSION}`
2376
+ );
2377
+ }
2378
+ if (typeof m.app_id !== "string" || m.app_id.length === 0) {
2379
+ throw new ManifestValidationError(
2380
+ "manifest.app_id is required and must be a non-empty string"
2381
+ );
2382
+ }
2383
+ if (typeof m.name !== "string" || m.name.length === 0) {
2384
+ throw new ManifestValidationError(
2385
+ "manifest.name is required and must be a non-empty string"
2386
+ );
2387
+ }
2388
+ if (m.did !== void 0 && (typeof m.did !== "string" || m.did.length === 0)) {
2389
+ throw new ManifestValidationError(
2390
+ "manifest.did must be a non-empty DID string"
2391
+ );
2392
+ }
2393
+ if (m.space !== void 0 && (typeof m.space !== "string" || m.space.length === 0)) {
2394
+ throw new ManifestValidationError(
2395
+ "manifest.space must be a non-empty string"
2396
+ );
2397
+ }
2398
+ if (m.expiry !== void 0) {
2399
+ parseExpiry(m.expiry);
2400
+ }
2401
+ if (m.permissions !== void 0) {
2402
+ if (!Array.isArray(m.permissions)) {
2403
+ throw new ManifestValidationError(
2404
+ "manifest.permissions must be an array"
2405
+ );
2580
2406
  }
2407
+ m.permissions.forEach(
2408
+ (p, i) => validatePermissionEntry(p, `permissions[${i}]`)
2409
+ );
2581
2410
  }
2582
- /**
2583
- * Gets the full delegation chain for a given delegation.
2584
- *
2585
- * Returns the chain of delegations from the root (original delegator)
2586
- * to the specified delegation, including all intermediate sub-delegations.
2587
- *
2588
- * @param cid - The CID of the delegation to get the chain for
2589
- * @returns Result containing the DelegationChain or an error
2590
- *
2591
- * @example
2592
- * ```typescript
2593
- * const result = await manager.getChain("bafy...");
2594
- * if (result.ok) {
2595
- * console.log("Chain length:", result.data.length);
2596
- * for (const delegation of result.data) {
2597
- * console.log(`- ${delegation.delegatorDID} -> ${delegation.delegateDID}`);
2598
- * }
2599
- * }
2600
- * ```
2601
- */
2602
- async getChain(cid) {
2603
- if (!cid) {
2604
- return {
2605
- ok: false,
2606
- error: createError(
2607
- DelegationErrorCodes.INVALID_INPUT,
2608
- "cid is required"
2609
- )
2610
- };
2411
+ if (m.secrets !== void 0) {
2412
+ validateManifestSecrets(m.secrets);
2413
+ }
2414
+ return m;
2415
+ }
2416
+ function validateManifestSecrets(secrets) {
2417
+ if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
2418
+ throw new ManifestValidationError("manifest.secrets must be an object");
2419
+ }
2420
+ for (const [name, spec] of Object.entries(secrets)) {
2421
+ if (!SECRET_NAME_RE.test(name)) {
2422
+ throw new ManifestValidationError(
2423
+ `manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
2424
+ );
2611
2425
  }
2612
2426
  try {
2613
- const body = JSON.stringify({ cid, includeChain: true });
2614
- const response = await this.invokeOperation(
2615
- cid,
2616
- DelegationAction.GET,
2617
- body
2427
+ resolveSecretPath(
2428
+ secretNameFromSpec(name, spec),
2429
+ { scope: secretScopeFromSpec(spec) }
2618
2430
  );
2619
- if (!response.ok) {
2620
- const errorText = await response.text();
2621
- if (response.status === 404) {
2622
- return {
2623
- ok: false,
2624
- error: createError(
2625
- DelegationErrorCodes.NOT_FOUND,
2626
- `Delegation not found: ${cid}`
2627
- )
2628
- };
2629
- }
2630
- return {
2631
- ok: false,
2632
- error: createError(
2633
- DelegationErrorCodes.NETWORK_ERROR,
2634
- `Failed to get delegation chain: ${response.status} - ${errorText}`,
2635
- void 0,
2636
- { status: response.status, cid }
2637
- )
2638
- };
2639
- }
2640
- const data = await response.json();
2641
- const chain = data.chain.map((item) => ({
2642
- cid: item.cid,
2643
- delegateDID: item.delegateDID,
2644
- delegatorDID: item.delegatorDID,
2645
- spaceId: item.spaceId,
2646
- path: item.path,
2647
- actions: item.actions,
2648
- expiry: new Date(item.expiry),
2649
- isRevoked: item.isRevoked,
2650
- createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
2651
- parentCid: item.parentCid,
2652
- allowSubDelegation: item.allowSubDelegation
2653
- }));
2654
- return { ok: true, data: chain };
2655
2431
  } catch (error) {
2656
- if (error instanceof Error && error.name === "AbortError") {
2657
- return {
2658
- ok: false,
2659
- error: createError(
2660
- DelegationErrorCodes.ABORTED,
2661
- "Request aborted",
2662
- error
2663
- )
2664
- };
2432
+ throw new ManifestValidationError(
2433
+ `manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
2434
+ );
2435
+ }
2436
+ const actions = secretActionsFromSpec(name, spec);
2437
+ if (actions.length === 0) {
2438
+ throw new ManifestValidationError(
2439
+ `manifest.secrets.${name} actions must be non-empty`
2440
+ );
2441
+ }
2442
+ for (const action of actions) {
2443
+ if (typeof action !== "string" || action.length === 0) {
2444
+ throw new ManifestValidationError(
2445
+ `manifest.secrets.${name} actions must be non-empty strings`
2446
+ );
2665
2447
  }
2666
- return {
2667
- ok: false,
2668
- error: createError(
2669
- DelegationErrorCodes.NETWORK_ERROR,
2670
- `Network error during chain retrieval: ${String(error)}`,
2671
- error instanceof Error ? error : void 0
2672
- )
2673
- };
2448
+ }
2449
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
2450
+ parseExpiry(spec.expiry);
2674
2451
  }
2675
2452
  }
2676
- /**
2677
- * Checks if the current session has permission for a given path and action.
2678
- *
2679
- * This can be used to verify permissions before attempting an operation,
2680
- * or to implement custom access control logic.
2681
- *
2682
- * @param path - The resource path to check
2683
- * @param action - The action to check (e.g., "tinycloud.kv/get")
2684
- * @returns Result containing a boolean indicating permission or an error
2685
- *
2686
- * @example
2687
- * ```typescript
2688
- * const result = await manager.checkPermission("documents/private/", "tinycloud.kv/put");
2689
- * if (result.ok && result.data) {
2690
- * console.log("Permission granted");
2691
- * } else {
2692
- * console.log("Permission denied");
2693
- * }
2694
- * ```
2695
- */
2696
- async checkPermission(path, action) {
2697
- if (!path) {
2698
- return {
2699
- ok: false,
2700
- error: createError(
2701
- DelegationErrorCodes.INVALID_INPUT,
2702
- "path is required"
2703
- )
2704
- };
2705
- }
2706
- if (!action) {
2707
- return {
2708
- ok: false,
2709
- error: createError(
2710
- DelegationErrorCodes.INVALID_INPUT,
2711
- "action is required"
2712
- )
2713
- };
2714
- }
2715
- try {
2716
- const body = JSON.stringify({ path, action });
2717
- const response = await this.invokeOperation(
2718
- path,
2719
- DelegationAction.CHECK,
2720
- body
2721
- );
2722
- if (!response.ok) {
2723
- if (response.status === 403) {
2724
- return { ok: true, data: false };
2725
- }
2726
- const errorText = await response.text();
2727
- return {
2728
- ok: false,
2729
- error: createError(
2730
- DelegationErrorCodes.NETWORK_ERROR,
2731
- `Failed to check permission: ${response.status} - ${errorText}`,
2732
- void 0,
2733
- { status: response.status, path, action }
2734
- )
2735
- };
2736
- }
2737
- const data = await response.json();
2738
- return { ok: true, data: data.allowed };
2739
- } catch (error) {
2740
- if (error instanceof Error && error.name === "AbortError") {
2741
- return {
2742
- ok: false,
2743
- error: createError(
2744
- DelegationErrorCodes.ABORTED,
2745
- "Request aborted",
2746
- error
2747
- )
2748
- };
2749
- }
2750
- return {
2751
- ok: false,
2752
- error: createError(
2753
- DelegationErrorCodes.NETWORK_ERROR,
2754
- `Network error during permission check: ${String(error)}`,
2755
- error instanceof Error ? error : void 0
2756
- )
2757
- };
2758
- }
2759
- }
2760
- };
2761
-
2762
- // src/delegations/SharingService.schema.ts
2763
- import { z as z5 } from "zod";
2764
- var EncodedShareDataSchema = z5.object({
2765
- /** Private key in JWK format (must include d parameter) */
2766
- key: JWKSchema.refine(
2767
- (jwk) => typeof jwk.d === "string" && jwk.d.length > 0,
2768
- { message: "JWK must include private key (d parameter)" }
2769
- ),
2770
- /** DID of the key */
2771
- keyDid: z5.string().min(1, "keyDid is required"),
2772
- /** The delegation granting access */
2773
- delegation: DelegationSchema,
2774
- /** Resource path this link grants access to */
2775
- path: z5.string().min(1, "path is required"),
2776
- /** TinyCloud host URL */
2777
- host: z5.string().url("host must be a valid URL"),
2778
- /** Space ID */
2779
- spaceId: z5.string().min(1, "spaceId is required"),
2780
- /** Schema version (must be 1) */
2781
- version: z5.literal(1)
2782
- });
2783
- var ReceiveOptionsSchema = z5.object({
2784
- /**
2785
- * Whether to automatically create a sub-delegation to the current session key.
2786
- * Default: true
2787
- */
2788
- autoSubdelegate: z5.boolean().optional(),
2789
- /**
2790
- * Whether to use the current session key for operations (requires autoSubdelegate).
2791
- * Default: true
2792
- */
2793
- useSessionKey: z5.boolean().optional(),
2794
- /**
2795
- * Ingestion options passed to CapabilityKeyRegistry.
2796
- */
2797
- ingestOptions: IngestOptionsSchema.optional()
2798
- });
2799
- var SharingServiceConfigSchema = z5.object({
2800
- /** TinyCloud host URLs */
2801
- hosts: z5.array(z5.string().url()).min(1, "At least one host URL is required"),
2802
- /**
2803
- * Active session for authentication.
2804
- * Required for generate(), optional for receive().
2805
- */
2806
- session: z5.unknown().refine(
2807
- (val) => val === void 0 || val !== null && typeof val === "object",
2808
- { message: "Expected a ServiceSession object or undefined" }
2809
- ).optional(),
2810
- /** Platform-specific invoke function */
2811
- invoke: z5.unknown().refine((val) => typeof val === "function", {
2812
- message: "Expected an invoke function"
2813
- }),
2814
- /** Optional custom fetch implementation */
2815
- fetch: z5.unknown().refine(
2816
- (val) => val === void 0 || typeof val === "function",
2817
- { message: "Expected a fetch function or undefined" }
2818
- ).optional(),
2819
- /** Key provider for cryptographic operations */
2820
- keyProvider: KeyProviderSchema,
2821
- /** Capability key registry for key/delegation management */
2822
- registry: z5.unknown().refine(
2823
- (val) => val !== null && typeof val === "object",
2824
- { message: "Expected an ICapabilityKeyRegistry object" }
2825
- ),
2826
- /**
2827
- * Delegation manager for creating delegations.
2828
- * Required for generate(), optional for receive().
2829
- */
2830
- delegationManager: z5.unknown().refine(
2831
- (val) => val === void 0 || val !== null && typeof val === "object",
2832
- { message: "Expected a DelegationManager object or undefined" }
2833
- ).optional(),
2834
- /** Factory for creating KV service instances */
2835
- createKVService: z5.unknown().refine(
2836
- (val) => typeof val === "function",
2837
- { message: "Expected a createKVService factory function" }
2838
- ),
2839
- /** Base URL for sharing links (e.g., "https://share.myapp.com") */
2840
- baseUrl: z5.string().optional(),
2841
- /**
2842
- * Custom delegation creation function.
2843
- */
2844
- createDelegation: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
2845
- message: "Expected a createDelegation function or undefined"
2846
- }).optional(),
2847
- /**
2848
- * WASM function for client-side delegation creation.
2849
- */
2850
- createDelegationWasm: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
2851
- message: "Expected a createDelegationWasm function or undefined"
2852
- }).optional(),
2853
- /**
2854
- * Path prefix for KV operations.
2855
- */
2856
- pathPrefix: z5.string().optional(),
2857
- /**
2858
- * Session expiry time.
2859
- */
2860
- sessionExpiry: z5.date().optional(),
2861
- /**
2862
- * Callback to create a DIRECT delegation from wallet to share key.
2863
- * This is the preferred method for long-lived share links because it
2864
- * bypasses the session delegation chain entirely.
2865
- */
2866
- onRootDelegationNeeded: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
2867
- message: "Expected an onRootDelegationNeeded function or undefined"
2868
- }).optional()
2869
- });
2870
- function validateEncodedShareData(data) {
2871
- const result = EncodedShareDataSchema.safeParse(data);
2872
- if (!result.success) {
2873
- return {
2874
- ok: false,
2875
- error: {
2876
- code: DelegationErrorCodes.VALIDATION_ERROR,
2877
- message: `Invalid share data: ${result.error.message}`,
2878
- service: "delegation",
2879
- meta: { issues: result.error.issues }
2880
- }
2881
- };
2882
- }
2883
- return { ok: true, data: result.data };
2884
2453
  }
2885
-
2886
- // src/manifest.ts
2887
- import ms from "ms";
2888
- import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
2889
- var ManifestValidationError = class extends Error {
2890
- constructor(message) {
2891
- super(`Manifest validation failed: ${message}`);
2892
- this.name = "ManifestValidationError";
2893
- }
2894
- };
2895
- var DEFAULT_EXPIRY = "30d";
2896
- var DEFAULT_DEFAULTS = true;
2897
- var DEFAULT_MANIFEST_VERSION = 1;
2898
- var DEFAULT_MANIFEST_SPACE = "applications";
2899
- var ACCOUNT_REGISTRY_SPACE = "account";
2900
- var ACCOUNT_REGISTRY_PATH = "applications/";
2901
- var SECRETS_SPACE = "secrets";
2902
- var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
2903
- var SERVICE_SHORT_TO_LONG = Object.freeze({
2904
- kv: "tinycloud.kv",
2905
- sql: "tinycloud.sql",
2906
- duckdb: "tinycloud.duckdb",
2907
- capabilities: "tinycloud.capabilities",
2908
- hooks: "tinycloud.hooks",
2909
- encryption: "tinycloud.encryption"
2910
- });
2911
- var ENCRYPTION_PERMISSION_SERVICE = "tinycloud.encryption";
2912
- var ENCRYPTION_MANIFEST_SPACE = "encryption";
2913
- var SERVICE_LONG_TO_SHORT = Object.freeze(
2914
- Object.fromEntries(
2915
- Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
2916
- )
2917
- );
2918
- var DEFAULT_STANDARD_ENTRIES = [
2919
- {
2920
- service: "tinycloud.kv",
2921
- space: DEFAULT_MANIFEST_SPACE,
2922
- path: "/",
2923
- actions: ["get", "put", "del", "list", "metadata"]
2924
- },
2925
- {
2926
- service: "tinycloud.sql",
2927
- space: DEFAULT_MANIFEST_SPACE,
2928
- path: "/",
2929
- actions: ["read", "write"]
2454
+ function validatePermissionEntry(p, path) {
2455
+ if (p === null || typeof p !== "object") {
2456
+ throw new ManifestValidationError(`${path} must be an object`);
2930
2457
  }
2931
- ];
2932
- var DEFAULT_ADMIN_ENTRIES = [
2933
- {
2934
- service: "tinycloud.kv",
2935
- space: DEFAULT_MANIFEST_SPACE,
2936
- path: "/",
2937
- actions: ["get", "put", "del", "list", "metadata"]
2938
- },
2939
- {
2940
- service: "tinycloud.sql",
2941
- space: DEFAULT_MANIFEST_SPACE,
2942
- path: "/",
2943
- actions: ["read", "write", "ddl"]
2458
+ const entry = p;
2459
+ if (typeof entry.service !== "string" || entry.service.length === 0) {
2460
+ throw new ManifestValidationError(`${path}.service is required`);
2944
2461
  }
2945
- ];
2946
- var DEFAULT_ALL_ENTRIES = [
2947
- {
2948
- service: "tinycloud.kv",
2949
- space: DEFAULT_MANIFEST_SPACE,
2950
- path: "/",
2951
- actions: ["get", "put", "del", "list", "metadata"]
2952
- },
2953
- {
2954
- service: "tinycloud.sql",
2955
- space: DEFAULT_MANIFEST_SPACE,
2956
- path: "/",
2957
- actions: ["read", "write", "ddl"]
2958
- },
2959
- {
2960
- service: "tinycloud.duckdb",
2961
- space: DEFAULT_MANIFEST_SPACE,
2962
- path: "/",
2963
- actions: ["read", "write"]
2462
+ if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space.length === 0)) {
2463
+ throw new ManifestValidationError(
2464
+ `${path}.space must be a non-empty string`
2465
+ );
2964
2466
  }
2965
- ];
2966
- function parseExpiry(duration) {
2967
- if (typeof duration !== "string" || duration.length === 0) {
2467
+ if (typeof entry.path !== "string") {
2968
2468
  throw new ManifestValidationError(
2969
- `expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
2469
+ `${path}.path is required (use "" or "/" for root)`
2970
2470
  );
2971
2471
  }
2972
- const parsed = ms(duration);
2973
- if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
2472
+ if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
2974
2473
  throw new ManifestValidationError(
2975
- `invalid expiry duration: ${JSON.stringify(duration)}`
2474
+ `${path}.actions must be a non-empty array`
2976
2475
  );
2977
2476
  }
2978
- return parsed;
2979
- }
2980
- function expandActionShortNames(service, actions) {
2981
- return actions.map((a) => {
2982
- if (a.includes("/")) {
2983
- return a;
2477
+ for (const action of entry.actions) {
2478
+ if (typeof action !== "string" || action.length === 0) {
2479
+ throw new ManifestValidationError(
2480
+ `${path}.actions must contain non-empty strings`
2481
+ );
2984
2482
  }
2985
- return `${service}/${a}`;
2986
- });
2483
+ if (entry.service === VAULT_PERMISSION_SERVICE) {
2484
+ vaultActionExpansion(action);
2485
+ }
2486
+ }
2487
+ if (entry.expiry !== void 0) {
2488
+ parseExpiry(entry.expiry);
2489
+ }
2987
2490
  }
2988
- function expandPermissionEntry(entry) {
2989
- if (entry.service === ENCRYPTION_PERMISSION_SERVICE) {
2990
- return expandEncryptionPermissionEntry(entry);
2491
+ function normalizeDefaults(value) {
2492
+ if (value === void 0) {
2493
+ return DEFAULT_DEFAULTS;
2991
2494
  }
2992
- if (entry.service !== VAULT_PERMISSION_SERVICE) {
2993
- return [
2994
- {
2995
- ...entry,
2996
- actions: expandActionShortNames(entry.service, entry.actions)
2997
- }
2998
- ];
2495
+ if (typeof value === "boolean") {
2496
+ return value;
2999
2497
  }
3000
- return expandVaultPermissionEntry(entry);
2498
+ if (typeof value !== "string") {
2499
+ return true;
2500
+ }
2501
+ const normalized = value.trim().toLowerCase();
2502
+ if (normalized === "admin" || normalized === "all") {
2503
+ return normalized;
2504
+ }
2505
+ return true;
3001
2506
  }
3002
- function expandEncryptionPermissionEntry(entry) {
3003
- if (typeof entry.path !== "string" || !entry.path.startsWith("urn:tinycloud:encryption:")) {
3004
- throw new ManifestValidationError(
3005
- `tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
3006
- );
2507
+ function defaultEntriesForTier(tier) {
2508
+ if (tier === false) {
2509
+ return [];
3007
2510
  }
3008
- const normalizedActions = [];
3009
- for (const action of entry.actions) {
3010
- if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
3011
- normalizedActions.push("tinycloud.encryption/decrypt");
2511
+ const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
2512
+ return source.map((e) => ({
2513
+ service: e.service,
2514
+ space: e.space,
2515
+ path: e.path,
2516
+ actions: [...e.actions],
2517
+ ...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
2518
+ }));
2519
+ }
2520
+ function resolveManifest(input) {
2521
+ const manifest = validateManifest(input);
2522
+ const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.app_id;
2523
+ const space = manifest.space ?? DEFAULT_MANIFEST_SPACE;
2524
+ const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
2525
+ const includePublicSpace = manifest.includePublicSpace ?? true;
2526
+ const tier = normalizeDefaults(manifest.defaults);
2527
+ const defaultEntries = defaultEntriesForTier(tier);
2528
+ const explicitEntries = manifest.permissions ?? [];
2529
+ const secretEntries = secretEntriesForManifest(manifest.secrets);
2530
+ const allEntries = [
2531
+ ...defaultEntries,
2532
+ ...explicitEntries,
2533
+ ...secretEntries
2534
+ ];
2535
+ const resources = withCapabilitiesReadForSpaces(
2536
+ allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
2537
+ );
2538
+ const additionalDelegates = manifest.did === void 0 ? [] : [
2539
+ {
2540
+ did: manifest.did,
2541
+ name: manifest.name,
2542
+ expiryMs,
2543
+ permissions: resources.map(cloneResourceCapability)
2544
+ }
2545
+ ];
2546
+ return {
2547
+ app_id: manifest.app_id,
2548
+ ...manifest.did !== void 0 ? { did: manifest.did } : {},
2549
+ space,
2550
+ resources,
2551
+ expiryMs,
2552
+ includePublicSpace,
2553
+ additionalDelegates
2554
+ };
2555
+ }
2556
+ function normalizeSecretActions(actions) {
2557
+ const out = [];
2558
+ const seen = /* @__PURE__ */ new Set();
2559
+ const add = (action) => {
2560
+ if (!seen.has(action)) {
2561
+ out.push(action);
2562
+ seen.add(action);
2563
+ }
2564
+ };
2565
+ for (const action of actions) {
2566
+ if (action === "read") {
2567
+ add("get");
3012
2568
  continue;
3013
2569
  }
3014
- if (action === "network.create" || action === "tinycloud.encryption/network.create") {
3015
- normalizedActions.push("tinycloud.encryption/network.create");
2570
+ if (action === "write") {
2571
+ add("put");
3016
2572
  continue;
3017
2573
  }
3018
- if (action === "network.revoke" || action === "tinycloud.encryption/network.revoke") {
3019
- normalizedActions.push("tinycloud.encryption/network.revoke");
2574
+ if (action === "delete") {
2575
+ add("del");
3020
2576
  continue;
3021
2577
  }
3022
- if (action.includes("/")) {
3023
- throw new ManifestValidationError(
3024
- `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
3025
- );
2578
+ if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
2579
+ add(action);
2580
+ continue;
2581
+ }
2582
+ if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
2583
+ add(action);
2584
+ continue;
3026
2585
  }
3027
2586
  throw new ManifestValidationError(
3028
- `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
2587
+ `unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
3029
2588
  );
3030
2589
  }
3031
- const dedupedActions = [];
3032
- const seen = /* @__PURE__ */ new Set();
3033
- for (const a of normalizedActions) {
3034
- if (!seen.has(a)) {
3035
- dedupedActions.push(a);
3036
- seen.add(a);
3037
- }
3038
- }
3039
- return [
3040
- {
3041
- service: ENCRYPTION_PERMISSION_SERVICE,
3042
- space: ENCRYPTION_MANIFEST_SPACE,
3043
- path: entry.path,
3044
- actions: dedupedActions,
3045
- skipPrefix: true,
3046
- ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
3047
- ...entry.description !== void 0 ? { description: entry.description } : {}
3048
- }
3049
- ];
3050
- }
3051
- function expandPermissionEntries(entries) {
3052
- return entries.flatMap(expandPermissionEntry);
2590
+ return out;
3053
2591
  }
3054
- function applyPrefix(prefix, path, skipPrefix) {
3055
- if (skipPrefix) {
3056
- return path;
3057
- }
3058
- if (prefix === "") {
3059
- return path;
3060
- }
3061
- if (path.startsWith("/")) {
3062
- return `${prefix}${path}`;
2592
+ function secretNameFromSpec(fallbackName, spec) {
2593
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
2594
+ return spec.name ?? fallbackName;
3063
2595
  }
3064
- return `${prefix}/${path}`;
2596
+ return fallbackName;
3065
2597
  }
3066
- async function loadManifest(url) {
3067
- const fetchFn = globalThis.fetch;
3068
- if (typeof fetchFn !== "function") {
3069
- throw new ManifestValidationError(
3070
- "loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
3071
- );
3072
- }
3073
- const res = await fetchFn(url);
3074
- if (!res.ok) {
3075
- throw new ManifestValidationError(
3076
- `failed to fetch manifest from ${url}: HTTP ${res.status}`
3077
- );
2598
+ function secretScopeFromSpec(spec) {
2599
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
2600
+ return spec.scope;
3078
2601
  }
3079
- const json = await res.json();
3080
- return validateManifest(json);
2602
+ return void 0;
3081
2603
  }
3082
- function validateManifest(input) {
3083
- if (input === null || typeof input !== "object") {
3084
- throw new ManifestValidationError("manifest must be an object");
3085
- }
3086
- const m = input;
3087
- if (m.manifest_version !== void 0 && m.manifest_version !== DEFAULT_MANIFEST_VERSION) {
3088
- throw new ManifestValidationError(
3089
- `manifest.manifest_version must be ${DEFAULT_MANIFEST_VERSION}`
3090
- );
3091
- }
3092
- if (typeof m.app_id !== "string" || m.app_id.length === 0) {
3093
- throw new ManifestValidationError(
3094
- "manifest.app_id is required and must be a non-empty string"
3095
- );
2604
+ function secretActionsFromSpec(name, spec) {
2605
+ if (spec === true) {
2606
+ return ["read"];
3096
2607
  }
3097
- if (typeof m.name !== "string" || m.name.length === 0) {
3098
- throw new ManifestValidationError(
3099
- "manifest.name is required and must be a non-empty string"
3100
- );
2608
+ if (typeof spec === "string") {
2609
+ return [spec];
3101
2610
  }
3102
- if (m.did !== void 0 && (typeof m.did !== "string" || m.did.length === 0)) {
3103
- throw new ManifestValidationError(
3104
- "manifest.did must be a non-empty DID string"
3105
- );
2611
+ if (Array.isArray(spec)) {
2612
+ return spec;
3106
2613
  }
3107
- if (m.space !== void 0 && (typeof m.space !== "string" || m.space.length === 0)) {
2614
+ if (spec === null || typeof spec !== "object") {
3108
2615
  throw new ManifestValidationError(
3109
- "manifest.space must be a non-empty string"
2616
+ `manifest.secrets.${name} must be true, a string action, an actions array, or an object`
3110
2617
  );
3111
2618
  }
3112
- if (m.expiry !== void 0) {
3113
- parseExpiry(m.expiry);
2619
+ if (spec.actions === void 0) {
2620
+ return ["read"];
3114
2621
  }
3115
- if (m.permissions !== void 0) {
3116
- if (!Array.isArray(m.permissions)) {
3117
- throw new ManifestValidationError(
3118
- "manifest.permissions must be an array"
3119
- );
3120
- }
3121
- m.permissions.forEach(
3122
- (p, i) => validatePermissionEntry(p, `permissions[${i}]`)
3123
- );
2622
+ if (typeof spec.actions === "string") {
2623
+ return [spec.actions];
3124
2624
  }
3125
- if (m.secrets !== void 0) {
3126
- validateManifestSecrets(m.secrets);
2625
+ if (Array.isArray(spec.actions)) {
2626
+ return spec.actions;
3127
2627
  }
3128
- return m;
2628
+ throw new ManifestValidationError(
2629
+ `manifest.secrets.${name}.actions must be a string or array`
2630
+ );
3129
2631
  }
3130
- function validateManifestSecrets(secrets) {
3131
- if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
3132
- throw new ManifestValidationError("manifest.secrets must be an object");
2632
+ function secretEntriesForManifest(secrets) {
2633
+ if (secrets === void 0) {
2634
+ return [];
3133
2635
  }
2636
+ const entries = [];
3134
2637
  for (const [name, spec] of Object.entries(secrets)) {
3135
- if (!SECRET_NAME_RE.test(name)) {
3136
- throw new ManifestValidationError(
3137
- `manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
3138
- );
3139
- }
3140
- try {
3141
- resolveSecretPath(
3142
- secretNameFromSpec(name, spec),
3143
- { scope: secretScopeFromSpec(spec) }
3144
- );
3145
- } catch (error) {
3146
- throw new ManifestValidationError(
3147
- `manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
3148
- );
3149
- }
3150
2638
  const actions = secretActionsFromSpec(name, spec);
3151
- if (actions.length === 0) {
3152
- throw new ManifestValidationError(
3153
- `manifest.secrets.${name} actions must be non-empty`
3154
- );
3155
- }
3156
- for (const action of actions) {
3157
- if (typeof action !== "string" || action.length === 0) {
3158
- throw new ManifestValidationError(
3159
- `manifest.secrets.${name} actions must be non-empty strings`
3160
- );
2639
+ const secretPath = resolveSecretPath(
2640
+ secretNameFromSpec(name, spec),
2641
+ { scope: secretScopeFromSpec(spec) }
2642
+ );
2643
+ const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
2644
+ entries.push({
2645
+ service: VAULT_PERMISSION_SERVICE,
2646
+ space: SECRETS_SPACE,
2647
+ path: secretPath.vaultKey,
2648
+ actions: normalizeSecretActions(actions),
2649
+ skipPrefix: true,
2650
+ ...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
2651
+ ...extra.description !== void 0 ? { description: extra.description } : {}
2652
+ });
2653
+ }
2654
+ return entries;
2655
+ }
2656
+ function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
2657
+ const skipPrefixForEntry = entry.skipPrefix === true || entry.service === ENCRYPTION_PERMISSION_SERVICE;
2658
+ const resolvedPath = applyPrefix(prefix, entry.path, skipPrefixForEntry);
2659
+ const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
2660
+ return expandPermissionEntry({
2661
+ ...entry,
2662
+ space: entry.space ?? inheritedSpace,
2663
+ path: resolvedPath,
2664
+ skipPrefix: true
2665
+ }).map((expanded) => ({
2666
+ service: expanded.service,
2667
+ space: expanded.space ?? inheritedSpace,
2668
+ path: expanded.path,
2669
+ actions: expanded.actions,
2670
+ // Only populate `expiryMs` when the entry had its own expiry override.
2671
+ // When absent, callers use the parent (delegation or manifest) expiry
2672
+ // which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
2673
+ ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
2674
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2675
+ }));
2676
+ }
2677
+ function expandVaultPermissionEntry(entry) {
2678
+ const byBase = /* @__PURE__ */ new Map();
2679
+ for (const action of entry.actions) {
2680
+ const expansion = vaultActionExpansion(action);
2681
+ for (const base of expansion.bases) {
2682
+ const actions = byBase.get(base) ?? [];
2683
+ if (!actions.includes(expansion.action)) {
2684
+ actions.push(expansion.action);
3161
2685
  }
3162
- }
3163
- if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
3164
- parseExpiry(spec.expiry);
2686
+ byBase.set(base, actions);
3165
2687
  }
3166
2688
  }
2689
+ return [...byBase.entries()].map(([base, actions]) => ({
2690
+ ...entry,
2691
+ service: "tinycloud.kv",
2692
+ path: vaultKVPath(base, entry.path),
2693
+ actions,
2694
+ skipPrefix: true
2695
+ }));
3167
2696
  }
3168
- function validatePermissionEntry(p, path) {
3169
- if (p === null || typeof p !== "object") {
3170
- throw new ManifestValidationError(`${path} must be an object`);
3171
- }
3172
- const entry = p;
3173
- if (typeof entry.service !== "string" || entry.service.length === 0) {
3174
- throw new ManifestValidationError(`${path}.service is required`);
2697
+ function vaultActionExpansion(action) {
2698
+ const normalized = normalizeVaultAction(action);
2699
+ if (normalized === "read" || normalized === "get") {
2700
+ return { bases: ["vault"], action: "tinycloud.kv/get" };
3175
2701
  }
3176
- if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space.length === 0)) {
3177
- throw new ManifestValidationError(
3178
- `${path}.space must be a non-empty string`
3179
- );
2702
+ if (normalized === "write" || normalized === "put") {
2703
+ return { bases: ["vault"], action: "tinycloud.kv/put" };
3180
2704
  }
3181
- if (typeof entry.path !== "string") {
3182
- throw new ManifestValidationError(
3183
- `${path}.path is required (use "" or "/" for root)`
3184
- );
2705
+ if (normalized === "delete" || normalized === "del") {
2706
+ return { bases: ["vault"], action: "tinycloud.kv/del" };
3185
2707
  }
3186
- if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
3187
- throw new ManifestValidationError(
3188
- `${path}.actions must be a non-empty array`
3189
- );
2708
+ if (normalized === "list") {
2709
+ return { bases: ["vault"], action: "tinycloud.kv/list" };
3190
2710
  }
3191
- for (const action of entry.actions) {
3192
- if (typeof action !== "string" || action.length === 0) {
3193
- throw new ManifestValidationError(
3194
- `${path}.actions must contain non-empty strings`
3195
- );
3196
- }
3197
- if (entry.service === VAULT_PERMISSION_SERVICE) {
3198
- vaultActionExpansion(action);
3199
- }
2711
+ if (normalized === "head") {
2712
+ return { bases: ["vault"], action: "tinycloud.kv/get" };
3200
2713
  }
3201
- if (entry.expiry !== void 0) {
3202
- parseExpiry(entry.expiry);
2714
+ if (normalized === "metadata") {
2715
+ return { bases: ["vault"], action: "tinycloud.kv/metadata" };
3203
2716
  }
2717
+ throw new ManifestValidationError(
2718
+ `unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
2719
+ );
3204
2720
  }
3205
- function normalizeDefaults(value) {
3206
- if (value === void 0) {
3207
- return DEFAULT_DEFAULTS;
2721
+ function normalizeVaultAction(action) {
2722
+ if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
2723
+ return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
3208
2724
  }
3209
- if (typeof value === "boolean") {
3210
- return value;
2725
+ if (action.startsWith("tinycloud.kv/")) {
2726
+ return action.slice("tinycloud.kv/".length);
3211
2727
  }
3212
- if (typeof value !== "string") {
3213
- return true;
2728
+ if (action.includes("/")) {
2729
+ throw new ManifestValidationError(
2730
+ `unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
2731
+ );
3214
2732
  }
3215
- const normalized = value.trim().toLowerCase();
3216
- if (normalized === "admin" || normalized === "all") {
3217
- return normalized;
2733
+ return action;
2734
+ }
2735
+ function vaultKVPath(base, path) {
2736
+ const normalized = path.startsWith("/") ? path.slice(1) : path;
2737
+ return `${base}/${normalized}`;
2738
+ }
2739
+ function cloneResourceCapability(entry) {
2740
+ return {
2741
+ service: entry.service,
2742
+ space: entry.space,
2743
+ path: entry.path,
2744
+ actions: [...entry.actions],
2745
+ ...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
2746
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2747
+ };
2748
+ }
2749
+ function clonePermissionEntry(entry) {
2750
+ return {
2751
+ service: entry.service,
2752
+ ...entry.space !== void 0 ? { space: entry.space } : {},
2753
+ path: entry.path,
2754
+ actions: [...entry.actions],
2755
+ ...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
2756
+ ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
2757
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2758
+ };
2759
+ }
2760
+ function dedupeResources(resources) {
2761
+ const byKey = /* @__PURE__ */ new Map();
2762
+ for (const resource of resources) {
2763
+ const key = `${resource.service}\0${resource.space}\0${resource.path}\0${resource.expiryMs ?? ""}`;
2764
+ const existing = byKey.get(key);
2765
+ if (existing === void 0) {
2766
+ byKey.set(key, cloneResourceCapability(resource));
2767
+ continue;
2768
+ }
2769
+ const seen = new Set(existing.actions);
2770
+ for (const action of resource.actions) {
2771
+ if (!seen.has(action)) {
2772
+ existing.actions.push(action);
2773
+ seen.add(action);
2774
+ }
2775
+ }
2776
+ if (existing.description === void 0 && resource.description !== void 0) {
2777
+ existing.description = resource.description;
2778
+ }
3218
2779
  }
3219
- return true;
2780
+ return [...byKey.values()];
3220
2781
  }
3221
- function defaultEntriesForTier(tier) {
3222
- if (tier === false) {
2782
+ function capabilitiesReadPermission(space) {
2783
+ return {
2784
+ service: "tinycloud.capabilities",
2785
+ space,
2786
+ path: "",
2787
+ actions: ["tinycloud.capabilities/read"]
2788
+ };
2789
+ }
2790
+ function withCapabilitiesReadForSpaces(resources) {
2791
+ if (resources.length === 0) {
3223
2792
  return [];
3224
2793
  }
3225
- const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
3226
- return source.map((e) => ({
3227
- service: e.service,
3228
- space: e.space,
3229
- path: e.path,
3230
- actions: [...e.actions],
3231
- ...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
2794
+ const spaces = new Set(
2795
+ resources.filter((resource) => resource.service !== ENCRYPTION_PERMISSION_SERVICE).map((resource) => resource.space)
2796
+ );
2797
+ return dedupeResources([
2798
+ ...resources,
2799
+ ...[...spaces].map(capabilitiesReadPermission)
2800
+ ]);
2801
+ }
2802
+ function accountRegistryPermissions() {
2803
+ return [ACCOUNT_REGISTRY_PATH, "spaces/"].map((path) => ({
2804
+ service: "tinycloud.kv",
2805
+ space: ACCOUNT_REGISTRY_SPACE,
2806
+ path,
2807
+ actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
3232
2808
  }));
3233
2809
  }
3234
- function resolveManifest(input) {
3235
- const manifest = validateManifest(input);
3236
- const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.app_id;
3237
- const space = manifest.space ?? DEFAULT_MANIFEST_SPACE;
3238
- const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
3239
- const includePublicSpace = manifest.includePublicSpace ?? true;
3240
- const tier = normalizeDefaults(manifest.defaults);
3241
- const defaultEntries = defaultEntriesForTier(tier);
3242
- const explicitEntries = manifest.permissions ?? [];
3243
- const secretEntries = secretEntriesForManifest(manifest.secrets);
3244
- const allEntries = [
3245
- ...defaultEntries,
3246
- ...explicitEntries,
3247
- ...secretEntries
3248
- ];
3249
- const resources = withCapabilitiesReadForSpaces(
3250
- allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
3251
- );
3252
- const additionalDelegates = manifest.did === void 0 ? [] : [
3253
- {
3254
- did: manifest.did,
3255
- name: manifest.name,
3256
- expiryMs,
3257
- permissions: resources.map(cloneResourceCapability)
3258
- }
3259
- ];
2810
+ function accountRegistryIndexPermission() {
3260
2811
  return {
3261
- app_id: manifest.app_id,
3262
- ...manifest.did !== void 0 ? { did: manifest.did } : {},
3263
- space,
3264
- resources,
3265
- expiryMs,
3266
- includePublicSpace,
3267
- additionalDelegates
2812
+ service: "tinycloud.sql",
2813
+ space: ACCOUNT_REGISTRY_SPACE,
2814
+ path: "account",
2815
+ actions: ["tinycloud.sql/read", "tinycloud.sql/write", "tinycloud.sql/ddl"]
3268
2816
  };
3269
2817
  }
3270
- function normalizeSecretActions(actions) {
3271
- const out = [];
3272
- const seen = /* @__PURE__ */ new Set();
3273
- const add = (action) => {
3274
- if (!seen.has(action)) {
3275
- out.push(action);
3276
- seen.add(action);
2818
+ function composeManifestRequest(inputs, options = {}) {
2819
+ if (!Array.isArray(inputs) || inputs.length === 0) {
2820
+ throw new ManifestValidationError(
2821
+ "composeManifestRequest requires at least one manifest"
2822
+ );
2823
+ }
2824
+ const includeAccountRegistryPermissions = options.includeAccountRegistryPermissions ?? true;
2825
+ const manifests = inputs.map(validateManifest);
2826
+ const resolved = manifests.map(resolveManifest);
2827
+ const resources = resolved.flatMap((entry) => entry.resources);
2828
+ const delegationTargets = resolved.flatMap(
2829
+ (entry) => entry.additionalDelegates.map((delegate) => ({
2830
+ ...delegate,
2831
+ permissions: dedupeResources(delegate.permissions)
2832
+ }))
2833
+ );
2834
+ if (includeAccountRegistryPermissions) {
2835
+ resources.push(...accountRegistryPermissions());
2836
+ resources.push(accountRegistryIndexPermission());
2837
+ }
2838
+ const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
2839
+ const manifestsByAppId = /* @__PURE__ */ new Map();
2840
+ for (const manifest of manifests) {
2841
+ const current = manifestsByAppId.get(manifest.app_id);
2842
+ if (current === void 0) {
2843
+ manifestsByAppId.set(manifest.app_id, [manifest]);
2844
+ } else {
2845
+ current.push(manifest);
3277
2846
  }
2847
+ }
2848
+ const registryRecords = includeAccountRegistryPermissions ? [...manifestsByAppId.entries()].map(([app_id, appManifests]) => ({
2849
+ key: `${ACCOUNT_REGISTRY_PATH}${app_id}`,
2850
+ app_id,
2851
+ manifests: appManifests.map((manifest) => ({
2852
+ ...manifest,
2853
+ permissions: manifest.permissions?.map(clonePermissionEntry)
2854
+ }))
2855
+ })) : [];
2856
+ return {
2857
+ manifests,
2858
+ resources: resourcesWithImplicitCapabilities,
2859
+ delegationTargets,
2860
+ registryRecords,
2861
+ expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
2862
+ includePublicSpace: resolved.some((entry) => entry.includePublicSpace)
3278
2863
  };
3279
- for (const action of actions) {
3280
- if (action === "read") {
3281
- add("get");
3282
- continue;
3283
- }
3284
- if (action === "write") {
3285
- add("put");
3286
- continue;
3287
- }
3288
- if (action === "delete") {
3289
- add("del");
3290
- continue;
2864
+ }
2865
+ function resourceCapabilitiesToAbilitiesMap(resources) {
2866
+ const out = {};
2867
+ for (const r of resources) {
2868
+ const shortService = SERVICE_LONG_TO_SHORT[r.service];
2869
+ if (shortService === void 0) {
2870
+ throw new ManifestValidationError(
2871
+ `unknown service '${r.service}' \u2014 no short-form mapping. Known services: ${Object.keys(SERVICE_LONG_TO_SHORT).join(", ")}`
2872
+ );
3291
2873
  }
3292
- if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
3293
- add(action);
3294
- continue;
2874
+ if (out[shortService] === void 0) {
2875
+ out[shortService] = {};
3295
2876
  }
3296
- if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
3297
- add(action);
3298
- continue;
2877
+ const pathsMap = out[shortService];
2878
+ const existing = pathsMap[r.path];
2879
+ if (existing === void 0) {
2880
+ pathsMap[r.path] = [...r.actions];
2881
+ } else {
2882
+ const seen = new Set(existing);
2883
+ for (const action of r.actions) {
2884
+ if (!seen.has(action)) {
2885
+ existing.push(action);
2886
+ seen.add(action);
2887
+ }
2888
+ }
3299
2889
  }
3300
- throw new ManifestValidationError(
3301
- `unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
3302
- );
3303
2890
  }
3304
2891
  return out;
3305
2892
  }
3306
- function secretNameFromSpec(fallbackName, spec) {
3307
- if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
3308
- return spec.name ?? fallbackName;
2893
+ function resourceCapabilitiesToSpaceAbilitiesMap(resources) {
2894
+ const grouped = /* @__PURE__ */ new Map();
2895
+ for (const resource of resources) {
2896
+ const entries = grouped.get(resource.space);
2897
+ if (entries === void 0) {
2898
+ grouped.set(resource.space, [resource]);
2899
+ } else {
2900
+ entries.push(resource);
2901
+ }
3309
2902
  }
3310
- return fallbackName;
2903
+ const out = {};
2904
+ for (const [space, entries] of grouped.entries()) {
2905
+ out[space] = resourceCapabilitiesToAbilitiesMap(entries);
2906
+ }
2907
+ return out;
3311
2908
  }
3312
- function secretScopeFromSpec(spec) {
3313
- if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
3314
- return spec.scope;
2909
+ function manifestAbilitiesUnion(resolved) {
2910
+ const all = [...resolved.resources];
2911
+ for (const delegate of resolved.additionalDelegates) {
2912
+ for (const perm of delegate.permissions) {
2913
+ all.push(perm);
2914
+ }
3315
2915
  }
3316
- return void 0;
2916
+ return resourceCapabilitiesToAbilitiesMap(all);
3317
2917
  }
3318
- function secretActionsFromSpec(name, spec) {
3319
- if (spec === true) {
3320
- return ["read"];
2918
+
2919
+ // src/account/AccountService.ts
2920
+ var SERVICE_NAME2 = "account";
2921
+ var ACCOUNT_INDEX_DB = "account";
2922
+ var ACCOUNT_INDEX_NAMESPACE = "tinycloud.account.index";
2923
+ var ACCOUNT_SPACES_PATH = "spaces/";
2924
+ var AccountService = class {
2925
+ constructor(config) {
2926
+ this.config = config;
2927
+ this.applications = {
2928
+ list: async (options = {}) => {
2929
+ if (options.preferIndex) {
2930
+ const indexed = await this.index.applications.list();
2931
+ if (indexed.ok && indexed.data.length > 0) return indexed;
2932
+ if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
2933
+ const canonical = await this.applications.list();
2934
+ if (canonical.ok && options.refreshIndex !== false) {
2935
+ await this.replaceApplicationsIndexQuietly(canonical.data);
2936
+ }
2937
+ return canonical;
2938
+ }
2939
+ const kvResult = this.accountKV();
2940
+ if (!kvResult.ok) return kvResult;
2941
+ const listed = await kvResult.data.list({ prefix: ACCOUNT_REGISTRY_PATH });
2942
+ if (!listed.ok) return accountErr(listed.error);
2943
+ const applications = [];
2944
+ for (const key of listed.data.keys) {
2945
+ const loaded = await kvResult.data.get(key);
2946
+ if (!loaded.ok) return accountErr(loaded.error);
2947
+ applications.push(applicationFromRecord(key, loaded.data.data));
2948
+ }
2949
+ applications.sort((a, b) => a.appId.localeCompare(b.appId));
2950
+ return ok3(applications);
2951
+ },
2952
+ get: async (appId) => {
2953
+ const kvResult = this.accountKV();
2954
+ if (!kvResult.ok) return kvResult;
2955
+ const key = applicationKey(appId);
2956
+ const loaded = await kvResult.data.get(key);
2957
+ if (!loaded.ok) return accountErr(loaded.error);
2958
+ return ok3(applicationFromRecord(key, loaded.data.data));
2959
+ },
2960
+ register: async (manifest) => {
2961
+ const manifests = Array.isArray(manifest) ? manifest : [manifest];
2962
+ const request = composeManifestRequest(manifests);
2963
+ if (request.registryRecords.length === 0) {
2964
+ return err3(
2965
+ serviceError3(
2966
+ "INVALID_MANIFEST",
2967
+ "Manifest did not produce an account application registry record",
2968
+ SERVICE_NAME2
2969
+ )
2970
+ );
2971
+ }
2972
+ await this.config.ensureAccountSpaceHosted?.();
2973
+ const kvResult = this.accountKV();
2974
+ if (!kvResult.ok) return kvResult;
2975
+ let registered;
2976
+ for (const record of request.registryRecords) {
2977
+ const manifestHash = hashJson(record.manifests);
2978
+ if (await this.indexHasApplicationHash(record.app_id, manifestHash)) {
2979
+ registered = {
2980
+ appId: record.app_id,
2981
+ manifests: record.manifests,
2982
+ manifestHash,
2983
+ name: record.manifests[0]?.name,
2984
+ description: record.manifests[0]?.description
2985
+ };
2986
+ continue;
2987
+ }
2988
+ const stored = {
2989
+ app_id: record.app_id,
2990
+ manifests: record.manifests,
2991
+ manifest_hash: manifestHash,
2992
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
2993
+ };
2994
+ const written = await kvResult.data.put(record.key, stored);
2995
+ if (!written.ok) return accountErr(written.error);
2996
+ registered = applicationFromRecord(record.key, stored);
2997
+ await this.upsertApplicationIndexQuietly(registered);
2998
+ }
2999
+ return ok3(registered);
3000
+ },
3001
+ remove: async (appId) => {
3002
+ const kvResult = this.accountKV();
3003
+ if (!kvResult.ok) return kvResult;
3004
+ const removed = await kvResult.data.delete(applicationKey(appId));
3005
+ if (!removed.ok) return accountErr(removed.error);
3006
+ await this.deleteApplicationIndexQuietly(appId);
3007
+ return ok3(void 0);
3008
+ }
3009
+ };
3010
+ this.spaces = {
3011
+ list: async (options = {}) => {
3012
+ if (options.preferIndex) {
3013
+ const indexed = await this.index.spaces.list();
3014
+ if (indexed.ok && indexed.data.length > 0) return indexed;
3015
+ if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
3016
+ const canonical = await this.spaces.syncAccessible();
3017
+ if (canonical.ok && options.refreshIndex !== false) {
3018
+ await this.replaceSpacesIndexQuietly(canonical.data);
3019
+ }
3020
+ return canonical;
3021
+ }
3022
+ const kvResult = this.accountKV();
3023
+ if (!kvResult.ok) return kvResult;
3024
+ const listed = await kvResult.data.list({ prefix: ACCOUNT_SPACES_PATH });
3025
+ if (!listed.ok) return accountErr(listed.error);
3026
+ const spaces = [];
3027
+ for (const key of listed.data.keys) {
3028
+ const loaded = await kvResult.data.get(key);
3029
+ if (!loaded.ok) return accountErr(loaded.error);
3030
+ spaces.push(spaceFromRecord(key, loaded.data.data));
3031
+ }
3032
+ spaces.sort((a, b) => a.name.localeCompare(b.name) || a.spaceId.localeCompare(b.spaceId));
3033
+ return ok3(spaces);
3034
+ },
3035
+ get: async (spaceId) => {
3036
+ const kvResult = this.accountKV();
3037
+ if (!kvResult.ok) return kvResult;
3038
+ const loaded = await kvResult.data.get(spaceKey(spaceId));
3039
+ if (!loaded.ok) return accountErr(loaded.error);
3040
+ return ok3(spaceFromRecord(spaceKey(spaceId), loaded.data.data));
3041
+ },
3042
+ register: async (space) => {
3043
+ await this.config.ensureAccountSpaceHosted?.();
3044
+ const kvResult = this.accountKV();
3045
+ if (!kvResult.ok) return kvResult;
3046
+ const stored = spaceRecordFromInput(space);
3047
+ const written = await kvResult.data.put(spaceKey(stored.space_id), stored);
3048
+ if (!written.ok) return accountErr(written.error);
3049
+ const registered = spaceFromRecord(spaceKey(stored.space_id), stored);
3050
+ await this.upsertSpaceIndexQuietly(registered);
3051
+ return ok3(registered);
3052
+ },
3053
+ syncAccessible: async () => {
3054
+ const listed = await this.config.getSpaces().list();
3055
+ if (!listed.ok) return accountErr(listed.error);
3056
+ const registered = [];
3057
+ for (const space of listed.data) {
3058
+ const result = await this.spaces.register(space);
3059
+ if (!result.ok) return result;
3060
+ registered.push(result.data);
3061
+ }
3062
+ return ok3(registered);
3063
+ },
3064
+ remove: async (spaceId) => {
3065
+ const kvResult = this.accountKV();
3066
+ if (!kvResult.ok) return kvResult;
3067
+ const removed = await kvResult.data.delete(spaceKey(spaceId));
3068
+ if (!removed.ok) return accountErr(removed.error);
3069
+ await this.deleteSpaceIndexQuietly(spaceId);
3070
+ return ok3(void 0);
3071
+ }
3072
+ };
3073
+ this.delegations = {
3074
+ list: async (options = {}) => {
3075
+ if (options.preferIndex) {
3076
+ const indexed = await this.index.delegations.list(options);
3077
+ if (indexed.ok && indexed.data.length > 0) return indexed;
3078
+ if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
3079
+ const live = await this.delegations.list({
3080
+ direction: options.direction,
3081
+ space: options.space
3082
+ });
3083
+ if (live.ok && options.refreshIndex !== false) {
3084
+ await this.replaceDelegationsIndexQuietly(live.data);
3085
+ }
3086
+ return live;
3087
+ }
3088
+ const spaces = await this.config.getSpaces().list();
3089
+ if (!spaces.ok) return accountErr(spaces.error);
3090
+ const targetSpaces = options.space ? spaces.data.filter((space) => space.id === options.space || space.name === options.space) : spaces.data;
3091
+ const delegations = [];
3092
+ for (const space of targetSpaces) {
3093
+ const scoped = this.config.getSpaces().get(space.id).delegations;
3094
+ if (options.direction !== "received") {
3095
+ const granted = await scoped.list();
3096
+ if (!granted.ok) return accountErr(granted.error);
3097
+ delegations.push(...granted.data.map((d) => mapDelegation(d, space, "granted")));
3098
+ }
3099
+ if (options.direction !== "granted") {
3100
+ const received = await scoped.listReceived();
3101
+ if (!received.ok) return accountErr(received.error);
3102
+ delegations.push(...received.data.map((d) => mapDelegation(d, space, "received")));
3103
+ }
3104
+ }
3105
+ delegations.sort((a, b) => a.spaceId.localeCompare(b.spaceId) || a.cid.localeCompare(b.cid));
3106
+ return ok3(delegations);
3107
+ },
3108
+ revoke: async (options) => {
3109
+ const space = await this.resolveSpace(options.space);
3110
+ if (!space.ok) return space;
3111
+ const revoked = await this.config.getSpaces().get(space.data.id).delegations.revoke(options.cid);
3112
+ if (!revoked.ok) return accountErr(revoked.error);
3113
+ return ok3(void 0);
3114
+ }
3115
+ };
3116
+ this.index = {
3117
+ rebuild: async () => {
3118
+ const dbResult = this.accountDb();
3119
+ if (!dbResult.ok) return dbResult;
3120
+ const applications = await this.applications.list();
3121
+ if (!applications.ok) return applications;
3122
+ const spaces = await this.spaces.list();
3123
+ if (!spaces.ok) return spaces;
3124
+ const delegations = await this.delegations.list();
3125
+ if (!delegations.ok) return delegations;
3126
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3127
+ const schema = await this.ensureAccountIndex(dbResult.data);
3128
+ if (!schema.ok) return schema;
3129
+ const statements = [
3130
+ { sql: "DELETE FROM applications" },
3131
+ { sql: "DELETE FROM application_state" },
3132
+ { sql: "DELETE FROM spaces" },
3133
+ { sql: "DELETE FROM delegations" },
3134
+ { sql: "DELETE FROM sync_state" },
3135
+ ...applications.data.map((app) => ({
3136
+ sql: "INSERT INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
3137
+ params: [
3138
+ app.appId,
3139
+ app.name ?? null,
3140
+ app.description ?? null,
3141
+ app.updatedAt ?? syncedAt,
3142
+ JSON.stringify(app.manifests)
3143
+ ]
3144
+ })),
3145
+ ...applications.data.map((app) => ({
3146
+ sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
3147
+ params: [app.appId, app.manifestHash ?? hashJson(app.manifests), syncedAt]
3148
+ })),
3149
+ ...spaces.data.map((space) => ({
3150
+ sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
3151
+ params: [
3152
+ space.spaceId,
3153
+ space.name,
3154
+ space.ownerDid,
3155
+ space.type,
3156
+ JSON.stringify(space.permissions),
3157
+ space.status,
3158
+ space.registeredAt ?? syncedAt,
3159
+ space.updatedAt ?? syncedAt,
3160
+ space.expiresAt?.toISOString() ?? null
3161
+ ]
3162
+ })),
3163
+ ...delegations.data.map((delegation) => ({
3164
+ sql: "INSERT INTO delegations (cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
3165
+ params: [
3166
+ delegation.cid,
3167
+ delegation.direction,
3168
+ delegation.spaceId,
3169
+ delegation.spaceName ?? null,
3170
+ delegation.counterpartyDid,
3171
+ delegation.delegateDid,
3172
+ delegation.delegatorDid ?? null,
3173
+ delegation.path,
3174
+ JSON.stringify(delegation.actions),
3175
+ delegation.expiry.toISOString(),
3176
+ delegation.status,
3177
+ delegation.createdAt?.toISOString() ?? null,
3178
+ syncedAt
3179
+ ]
3180
+ })),
3181
+ {
3182
+ sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3183
+ params: ["applications", syncedAt, applications.data.length]
3184
+ },
3185
+ {
3186
+ sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3187
+ params: ["spaces", syncedAt, spaces.data.length]
3188
+ },
3189
+ {
3190
+ sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3191
+ params: ["delegations", syncedAt, delegations.data.length]
3192
+ }
3193
+ ];
3194
+ const rebuilt = await dbResult.data.batch(statements);
3195
+ if (!rebuilt.ok) return accountErr(rebuilt.error);
3196
+ return ok3({
3197
+ database: ACCOUNT_INDEX_DB,
3198
+ applications: applications.data.length,
3199
+ spaces: spaces.data.length,
3200
+ delegations: delegations.data.length,
3201
+ syncedAt
3202
+ });
3203
+ },
3204
+ applications: {
3205
+ list: async () => {
3206
+ const dbResult = this.accountDb();
3207
+ if (!dbResult.ok) return dbResult;
3208
+ const queried = await dbResult.data.query(
3209
+ "SELECT applications.app_id, name, description, updated_at, manifest_json, application_state.manifest_hash FROM applications LEFT JOIN application_state ON applications.app_id = application_state.app_id ORDER BY applications.app_id"
3210
+ );
3211
+ if (!queried.ok) return accountErr(queried.error);
3212
+ return ok3(queried.data.rows.map(indexedApplicationFromRow));
3213
+ }
3214
+ },
3215
+ spaces: {
3216
+ list: async () => {
3217
+ const dbResult = this.accountDb();
3218
+ if (!dbResult.ok) return dbResult;
3219
+ const queried = await dbResult.data.query(
3220
+ "SELECT space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at FROM spaces ORDER BY name, space_id"
3221
+ );
3222
+ if (!queried.ok) return accountErr(queried.error);
3223
+ return ok3(queried.data.rows.map(indexedSpaceFromRow));
3224
+ }
3225
+ },
3226
+ delegations: {
3227
+ list: async (options = {}) => {
3228
+ const dbResult = this.accountDb();
3229
+ if (!dbResult.ok) return dbResult;
3230
+ const where = [];
3231
+ const params = [];
3232
+ if (options.direction && options.direction !== "all") {
3233
+ where.push("direction = ?");
3234
+ params.push(options.direction);
3235
+ }
3236
+ if (options.space) {
3237
+ where.push("(space_id = ? OR space_name = ?)");
3238
+ params.push(options.space, options.space);
3239
+ }
3240
+ const queried = await dbResult.data.query(
3241
+ `SELECT cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at FROM delegations${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY space_id, cid`,
3242
+ params
3243
+ );
3244
+ if (!queried.ok) return accountErr(queried.error);
3245
+ return ok3(queried.data.rows.map(indexedDelegationFromRow));
3246
+ }
3247
+ },
3248
+ query: async (sql, params) => {
3249
+ const dbResult = this.accountDb();
3250
+ if (!dbResult.ok) return dbResult;
3251
+ const queried = await dbResult.data.query(sql, params);
3252
+ if (!queried.ok) return accountErr(queried.error);
3253
+ return ok3(queried.data);
3254
+ },
3255
+ status: async () => {
3256
+ const dbResult = this.accountDb();
3257
+ if (!dbResult.ok) return dbResult;
3258
+ const queried = await dbResult.data.query(
3259
+ "SELECT source, synced_at, count FROM sync_state ORDER BY source"
3260
+ );
3261
+ if (!queried.ok) {
3262
+ if (isMissingIndexError(queried.error)) {
3263
+ return ok3({ database: ACCOUNT_INDEX_DB, state: "missing", sources: [] });
3264
+ }
3265
+ return accountErr(queried.error);
3266
+ }
3267
+ return ok3({
3268
+ database: ACCOUNT_INDEX_DB,
3269
+ state: "ready",
3270
+ sources: queried.data.rows.map(([source, syncedAt, count]) => ({
3271
+ source,
3272
+ syncedAt,
3273
+ count
3274
+ }))
3275
+ });
3276
+ }
3277
+ };
3278
+ }
3279
+ async status() {
3280
+ const apps = await this.applications.list();
3281
+ if (!apps.ok) return apps;
3282
+ const delegations = await this.delegations.list();
3283
+ if (!delegations.ok) return delegations;
3284
+ const spaces = await this.spaces.list();
3285
+ if (!spaces.ok) return spaces;
3286
+ return ok3({
3287
+ did: this.config.getDid(),
3288
+ host: this.config.getHost(),
3289
+ primarySpaceId: this.config.getPrimarySpaceId(),
3290
+ accountSpaceId: this.config.getAccountSpaceId(),
3291
+ applications: apps.data.length,
3292
+ spaces: spaces.data.length,
3293
+ grantedDelegations: delegations.data.filter((d) => d.direction === "granted").length,
3294
+ receivedDelegations: delegations.data.filter((d) => d.direction === "received").length
3295
+ });
3296
+ }
3297
+ accountKV() {
3298
+ const accountSpaceId = this.config.getAccountSpaceId();
3299
+ if (!accountSpaceId) {
3300
+ return err3(
3301
+ serviceError3(
3302
+ "ACCOUNT_SPACE_UNAVAILABLE",
3303
+ "Account space is unavailable. Sign in with a wallet-backed profile first.",
3304
+ SERVICE_NAME2
3305
+ )
3306
+ );
3307
+ }
3308
+ return ok3(this.config.getSpaces().get(accountSpaceId).kv);
3309
+ }
3310
+ accountDb() {
3311
+ const db = this.config.getAccountDb?.();
3312
+ if (!db) {
3313
+ return err3(
3314
+ serviceError3(
3315
+ "ACCOUNT_INDEX_UNAVAILABLE",
3316
+ "Account index database is unavailable. Sign in with a wallet-backed profile first.",
3317
+ SERVICE_NAME2
3318
+ )
3319
+ );
3320
+ }
3321
+ return ok3(db);
3321
3322
  }
3322
- if (typeof spec === "string") {
3323
- return [spec];
3323
+ async indexHasApplicationHash(appId, manifestHash) {
3324
+ const dbResult = this.accountDb();
3325
+ if (!dbResult.ok) return false;
3326
+ const schema = await this.ensureAccountIndex(dbResult.data);
3327
+ if (!schema.ok) return false;
3328
+ const queried = await dbResult.data.query(
3329
+ "SELECT 1 FROM application_state WHERE app_id = ? AND manifest_hash = ? LIMIT 1",
3330
+ [appId, manifestHash]
3331
+ );
3332
+ return queried.ok && queried.data.rows.length > 0;
3333
+ }
3334
+ async upsertApplicationIndexQuietly(app) {
3335
+ await ignoreIndexFailure(() => this.upsertApplicationIndex(app));
3336
+ }
3337
+ async upsertApplicationIndex(app) {
3338
+ const dbResult = this.accountDb();
3339
+ if (!dbResult.ok) return ok3(void 0);
3340
+ const schema = await this.ensureAccountIndex(dbResult.data);
3341
+ if (!schema.ok) return schema;
3342
+ const updatedAt = app.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
3343
+ const manifestHash = app.manifestHash ?? hashJson(app.manifests);
3344
+ const written = await dbResult.data.batch([
3345
+ {
3346
+ sql: "INSERT OR REPLACE INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
3347
+ params: [
3348
+ app.appId,
3349
+ app.name ?? null,
3350
+ app.description ?? null,
3351
+ updatedAt,
3352
+ JSON.stringify(app.manifests)
3353
+ ]
3354
+ },
3355
+ {
3356
+ sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
3357
+ params: [app.appId, manifestHash, updatedAt]
3358
+ }
3359
+ ]);
3360
+ if (!written.ok) return accountErr(written.error);
3361
+ return ok3(void 0);
3324
3362
  }
3325
- if (Array.isArray(spec)) {
3326
- return spec;
3363
+ async deleteApplicationIndexQuietly(appId) {
3364
+ await ignoreIndexFailure(() => this.deleteApplicationIndex(appId));
3365
+ }
3366
+ async deleteApplicationIndex(appId) {
3367
+ const dbResult = this.accountDb();
3368
+ if (!dbResult.ok) return ok3(void 0);
3369
+ const schema = await this.ensureAccountIndex(dbResult.data);
3370
+ if (!schema.ok) return schema;
3371
+ const deleted = await dbResult.data.batch([
3372
+ { sql: "DELETE FROM applications WHERE app_id = ?", params: [appId] },
3373
+ { sql: "DELETE FROM application_state WHERE app_id = ?", params: [appId] }
3374
+ ]);
3375
+ if (!deleted.ok) return accountErr(deleted.error);
3376
+ return ok3(void 0);
3327
3377
  }
3328
- if (spec === null || typeof spec !== "object") {
3329
- throw new ManifestValidationError(
3330
- `manifest.secrets.${name} must be true, a string action, an actions array, or an object`
3331
- );
3378
+ async upsertSpaceIndexQuietly(space) {
3379
+ await ignoreIndexFailure(() => this.upsertSpaceIndex(space));
3332
3380
  }
3333
- if (spec.actions === void 0) {
3334
- return ["read"];
3381
+ async upsertSpaceIndex(space) {
3382
+ const dbResult = this.accountDb();
3383
+ if (!dbResult.ok) return ok3(void 0);
3384
+ const schema = await this.ensureAccountIndex(dbResult.data);
3385
+ if (!schema.ok) return schema;
3386
+ const updatedAt = space.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
3387
+ const written = await dbResult.data.batch([
3388
+ {
3389
+ sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
3390
+ params: [
3391
+ space.spaceId,
3392
+ space.name,
3393
+ space.ownerDid,
3394
+ space.type,
3395
+ JSON.stringify(space.permissions),
3396
+ space.status,
3397
+ space.registeredAt ?? updatedAt,
3398
+ updatedAt,
3399
+ space.expiresAt?.toISOString() ?? null
3400
+ ]
3401
+ }
3402
+ ]);
3403
+ if (!written.ok) return accountErr(written.error);
3404
+ return ok3(void 0);
3335
3405
  }
3336
- if (typeof spec.actions === "string") {
3337
- return [spec.actions];
3406
+ async deleteSpaceIndexQuietly(spaceId) {
3407
+ await ignoreIndexFailure(() => this.deleteSpaceIndex(spaceId));
3408
+ }
3409
+ async deleteSpaceIndex(spaceId) {
3410
+ const dbResult = this.accountDb();
3411
+ if (!dbResult.ok) return ok3(void 0);
3412
+ const schema = await this.ensureAccountIndex(dbResult.data);
3413
+ if (!schema.ok) return schema;
3414
+ const deleted = await dbResult.data.batch([
3415
+ { sql: "DELETE FROM spaces WHERE space_id = ?", params: [spaceId] }
3416
+ ]);
3417
+ if (!deleted.ok) return accountErr(deleted.error);
3418
+ return ok3(void 0);
3338
3419
  }
3339
- if (Array.isArray(spec.actions)) {
3340
- return spec.actions;
3420
+ async resolveSpace(space) {
3421
+ const listed = await this.config.getSpaces().list();
3422
+ if (!listed.ok) return accountErr(listed.error);
3423
+ const found = listed.data.find((candidate) => candidate.id === space || candidate.name === space);
3424
+ if (!found) {
3425
+ return err3(
3426
+ serviceError3("SPACE_NOT_FOUND", `No account space found for ${JSON.stringify(space)}`, SERVICE_NAME2)
3427
+ );
3428
+ }
3429
+ return ok3(found);
3430
+ }
3431
+ async replaceApplicationsIndexQuietly(applications) {
3432
+ await ignoreIndexFailure(async () => {
3433
+ const dbResult = this.accountDb();
3434
+ if (!dbResult.ok) return;
3435
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3436
+ const schema = await this.ensureAccountIndex(dbResult.data);
3437
+ if (!schema.ok) return;
3438
+ await dbResult.data.batch([
3439
+ { sql: "DELETE FROM applications" },
3440
+ { sql: "DELETE FROM application_state" },
3441
+ ...applications.map((app) => ({
3442
+ sql: "INSERT OR REPLACE INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
3443
+ params: [
3444
+ app.appId,
3445
+ app.name ?? null,
3446
+ app.description ?? null,
3447
+ app.updatedAt ?? syncedAt,
3448
+ JSON.stringify(app.manifests)
3449
+ ]
3450
+ })),
3451
+ ...applications.map((app) => ({
3452
+ sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
3453
+ params: [app.appId, app.manifestHash ?? hashJson(app.manifests), syncedAt]
3454
+ })),
3455
+ {
3456
+ sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3457
+ params: ["applications", syncedAt, applications.length]
3458
+ }
3459
+ ]);
3460
+ });
3341
3461
  }
3342
- throw new ManifestValidationError(
3343
- `manifest.secrets.${name}.actions must be a string or array`
3344
- );
3345
- }
3346
- function secretEntriesForManifest(secrets) {
3347
- if (secrets === void 0) {
3348
- return [];
3462
+ async replaceSpacesIndexQuietly(spaces) {
3463
+ await ignoreIndexFailure(async () => {
3464
+ const dbResult = this.accountDb();
3465
+ if (!dbResult.ok) return;
3466
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3467
+ const schema = await this.ensureAccountIndex(dbResult.data);
3468
+ if (!schema.ok) return;
3469
+ await dbResult.data.batch([
3470
+ { sql: "DELETE FROM spaces" },
3471
+ ...spaces.map((space) => ({
3472
+ sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
3473
+ params: [
3474
+ space.spaceId,
3475
+ space.name,
3476
+ space.ownerDid,
3477
+ space.type,
3478
+ JSON.stringify(space.permissions),
3479
+ space.status,
3480
+ space.registeredAt ?? syncedAt,
3481
+ space.updatedAt ?? syncedAt,
3482
+ space.expiresAt?.toISOString() ?? null
3483
+ ]
3484
+ })),
3485
+ {
3486
+ sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3487
+ params: ["spaces", syncedAt, spaces.length]
3488
+ }
3489
+ ]);
3490
+ });
3349
3491
  }
3350
- const entries = [];
3351
- for (const [name, spec] of Object.entries(secrets)) {
3352
- const actions = secretActionsFromSpec(name, spec);
3353
- const secretPath = resolveSecretPath(
3354
- secretNameFromSpec(name, spec),
3355
- { scope: secretScopeFromSpec(spec) }
3356
- );
3357
- const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
3358
- entries.push({
3359
- service: VAULT_PERMISSION_SERVICE,
3360
- space: SECRETS_SPACE,
3361
- path: secretPath.vaultKey,
3362
- actions: normalizeSecretActions(actions),
3363
- skipPrefix: true,
3364
- ...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
3365
- ...extra.description !== void 0 ? { description: extra.description } : {}
3492
+ async replaceDelegationsIndexQuietly(delegations) {
3493
+ await ignoreIndexFailure(async () => {
3494
+ const dbResult = this.accountDb();
3495
+ if (!dbResult.ok) return;
3496
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3497
+ const schema = await this.ensureAccountIndex(dbResult.data);
3498
+ if (!schema.ok) return;
3499
+ await dbResult.data.batch([
3500
+ { sql: "DELETE FROM delegations" },
3501
+ ...delegations.map((delegation) => ({
3502
+ sql: "INSERT OR REPLACE INTO delegations (cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
3503
+ params: [
3504
+ delegation.cid,
3505
+ delegation.direction,
3506
+ delegation.spaceId,
3507
+ delegation.spaceName ?? null,
3508
+ delegation.counterpartyDid,
3509
+ delegation.delegateDid,
3510
+ delegation.delegatorDid ?? null,
3511
+ delegation.path,
3512
+ JSON.stringify(delegation.actions),
3513
+ delegation.expiry.toISOString(),
3514
+ delegation.status,
3515
+ delegation.createdAt?.toISOString() ?? null,
3516
+ syncedAt
3517
+ ]
3518
+ })),
3519
+ {
3520
+ sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3521
+ params: ["delegations", syncedAt, delegations.length]
3522
+ }
3523
+ ]);
3366
3524
  });
3367
3525
  }
3368
- return entries;
3526
+ async ensureAccountIndex(db) {
3527
+ const migrated = await db.migrations.apply({
3528
+ namespace: ACCOUNT_INDEX_NAMESPACE,
3529
+ migrations: [
3530
+ {
3531
+ id: "001_initial",
3532
+ sql: ACCOUNT_INDEX_SCHEMA
3533
+ }
3534
+ ]
3535
+ });
3536
+ if (!migrated.ok) return accountErr(migrated.error);
3537
+ return ok3(void 0);
3538
+ }
3539
+ };
3540
+ var ACCOUNT_INDEX_SCHEMA = [
3541
+ `CREATE TABLE IF NOT EXISTS applications (
3542
+ app_id TEXT PRIMARY KEY,
3543
+ name TEXT,
3544
+ description TEXT,
3545
+ updated_at TEXT,
3546
+ manifest_json TEXT NOT NULL
3547
+ )`,
3548
+ `CREATE TABLE IF NOT EXISTS application_state (
3549
+ app_id TEXT PRIMARY KEY,
3550
+ manifest_hash TEXT NOT NULL,
3551
+ indexed_at TEXT NOT NULL
3552
+ )`,
3553
+ `CREATE TABLE IF NOT EXISTS spaces (
3554
+ space_id TEXT PRIMARY KEY,
3555
+ name TEXT NOT NULL,
3556
+ owner_did TEXT NOT NULL,
3557
+ type TEXT NOT NULL,
3558
+ permissions_json TEXT NOT NULL,
3559
+ status TEXT NOT NULL,
3560
+ registered_at TEXT,
3561
+ updated_at TEXT NOT NULL,
3562
+ expires_at TEXT
3563
+ )`,
3564
+ `CREATE TABLE IF NOT EXISTS delegations (
3565
+ cid TEXT PRIMARY KEY,
3566
+ direction TEXT NOT NULL,
3567
+ space_id TEXT NOT NULL,
3568
+ space_name TEXT,
3569
+ counterparty_did TEXT NOT NULL,
3570
+ delegate_did TEXT NOT NULL,
3571
+ delegator_did TEXT,
3572
+ path TEXT NOT NULL,
3573
+ actions_json TEXT NOT NULL,
3574
+ expiry TEXT NOT NULL,
3575
+ status TEXT NOT NULL,
3576
+ created_at TEXT,
3577
+ updated_at TEXT NOT NULL
3578
+ )`,
3579
+ `CREATE TABLE IF NOT EXISTS sync_state (
3580
+ source TEXT PRIMARY KEY,
3581
+ synced_at TEXT NOT NULL,
3582
+ count INTEGER NOT NULL
3583
+ )`,
3584
+ "CREATE INDEX IF NOT EXISTS idx_delegations_direction ON delegations(direction)",
3585
+ "CREATE INDEX IF NOT EXISTS idx_delegations_space ON delegations(space_id)",
3586
+ "CREATE INDEX IF NOT EXISTS idx_delegations_counterparty ON delegations(counterparty_did)",
3587
+ "CREATE INDEX IF NOT EXISTS idx_spaces_owner ON spaces(owner_did)",
3588
+ "CREATE INDEX IF NOT EXISTS idx_spaces_type ON spaces(type)"
3589
+ ];
3590
+ function applicationKey(appId) {
3591
+ return `${ACCOUNT_REGISTRY_PATH}${appId}`;
3369
3592
  }
3370
- function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
3371
- const skipPrefixForEntry = entry.skipPrefix === true || entry.service === ENCRYPTION_PERMISSION_SERVICE;
3372
- const resolvedPath = applyPrefix(prefix, entry.path, skipPrefixForEntry);
3373
- const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
3374
- return expandPermissionEntry({
3375
- ...entry,
3376
- space: entry.space ?? inheritedSpace,
3377
- path: resolvedPath,
3378
- skipPrefix: true
3379
- }).map((expanded) => ({
3380
- service: expanded.service,
3381
- space: expanded.space ?? inheritedSpace,
3382
- path: expanded.path,
3383
- actions: expanded.actions,
3384
- // Only populate `expiryMs` when the entry had its own expiry override.
3385
- // When absent, callers use the parent (delegation or manifest) expiry
3386
- // which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
3387
- ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
3388
- ...entry.description !== void 0 ? { description: entry.description } : {}
3389
- }));
3593
+ function appIdFromKey(key) {
3594
+ return key.startsWith(ACCOUNT_REGISTRY_PATH) ? key.slice(ACCOUNT_REGISTRY_PATH.length) : key;
3390
3595
  }
3391
- function expandVaultPermissionEntry(entry) {
3392
- const byBase = /* @__PURE__ */ new Map();
3393
- for (const action of entry.actions) {
3394
- const expansion = vaultActionExpansion(action);
3395
- for (const base of expansion.bases) {
3396
- const actions = byBase.get(base) ?? [];
3397
- if (!actions.includes(expansion.action)) {
3398
- actions.push(expansion.action);
3399
- }
3400
- byBase.set(base, actions);
3401
- }
3402
- }
3403
- return [...byBase.entries()].map(([base, actions]) => ({
3404
- ...entry,
3405
- service: "tinycloud.kv",
3406
- path: vaultKVPath(base, entry.path),
3407
- actions,
3408
- skipPrefix: true
3409
- }));
3596
+ function applicationFromRecord(key, record) {
3597
+ const manifests = Array.isArray(record.manifests) ? record.manifests : [];
3598
+ const first = manifests[0];
3599
+ return {
3600
+ appId: record.app_id ?? record.appId ?? first?.app_id ?? appIdFromKey(key),
3601
+ manifests,
3602
+ updatedAt: record.updated_at ?? record.updatedAt,
3603
+ name: first?.name,
3604
+ description: first?.description,
3605
+ manifestHash: record.manifest_hash ?? record.manifestHash ?? hashJson(manifests)
3606
+ };
3410
3607
  }
3411
- function vaultActionExpansion(action) {
3412
- const normalized = normalizeVaultAction(action);
3413
- if (normalized === "read" || normalized === "get") {
3414
- return { bases: ["vault"], action: "tinycloud.kv/get" };
3415
- }
3416
- if (normalized === "write" || normalized === "put") {
3417
- return { bases: ["vault"], action: "tinycloud.kv/put" };
3418
- }
3419
- if (normalized === "delete" || normalized === "del") {
3420
- return { bases: ["vault"], action: "tinycloud.kv/del" };
3421
- }
3422
- if (normalized === "list") {
3423
- return { bases: ["vault"], action: "tinycloud.kv/list" };
3424
- }
3425
- if (normalized === "head") {
3426
- return { bases: ["vault"], action: "tinycloud.kv/get" };
3608
+ function indexedApplicationFromRow(row) {
3609
+ const [appId, name, description, updatedAt, manifestJson, manifestHash] = row;
3610
+ return {
3611
+ appId,
3612
+ name: name ?? void 0,
3613
+ description: description ?? void 0,
3614
+ updatedAt: updatedAt ?? void 0,
3615
+ manifests: JSON.parse(manifestJson),
3616
+ manifestHash: manifestHash ?? void 0
3617
+ };
3618
+ }
3619
+ function spaceKey(spaceId) {
3620
+ return `${ACCOUNT_SPACES_PATH}${spaceId}`;
3621
+ }
3622
+ function spaceIdFromKey(key) {
3623
+ return key.startsWith(ACCOUNT_SPACES_PATH) ? key.slice(ACCOUNT_SPACES_PATH.length) : key;
3624
+ }
3625
+ function spaceRecordFromInput(space) {
3626
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3627
+ const accountSpace = "spaceId" in space ? space : {
3628
+ spaceId: space.id,
3629
+ name: space.name ?? space.id.split(":").pop() ?? space.id,
3630
+ ownerDid: space.owner ?? "",
3631
+ type: space.type ?? "discovered",
3632
+ permissions: space.permissions ?? [],
3633
+ status: "active",
3634
+ expiresAt: space.expiresAt
3635
+ };
3636
+ return {
3637
+ space_id: accountSpace.spaceId,
3638
+ name: accountSpace.name,
3639
+ owner_did: accountSpace.ownerDid,
3640
+ type: accountSpace.type,
3641
+ permissions: accountSpace.permissions,
3642
+ status: accountSpace.status,
3643
+ registered_at: accountSpace.registeredAt ?? now,
3644
+ updated_at: now,
3645
+ expires_at: accountSpace.expiresAt instanceof Date ? accountSpace.expiresAt.toISOString() : accountSpace.expiresAt
3646
+ };
3647
+ }
3648
+ function spaceFromRecord(key, record) {
3649
+ const expiresAt = record.expires_at ?? record.expiresAt;
3650
+ return {
3651
+ spaceId: record.space_id ?? record.spaceId ?? spaceIdFromKey(key),
3652
+ name: record.name ?? spaceIdFromKey(key).split(":").pop() ?? spaceIdFromKey(key),
3653
+ ownerDid: record.owner_did ?? record.ownerDid ?? record.owner ?? "",
3654
+ type: record.type ?? "discovered",
3655
+ permissions: Array.isArray(record.permissions) ? record.permissions : [],
3656
+ status: record.status ?? "active",
3657
+ registeredAt: record.registered_at ?? record.registeredAt,
3658
+ updatedAt: record.updated_at ?? record.updatedAt,
3659
+ expiresAt: expiresAt ? new Date(expiresAt) : void 0
3660
+ };
3661
+ }
3662
+ function indexedSpaceFromRow(row) {
3663
+ const [spaceId, name, ownerDid, type, permissionsJson, status, registeredAt, updatedAt, expiresAt] = row;
3664
+ return {
3665
+ spaceId,
3666
+ name,
3667
+ ownerDid,
3668
+ type,
3669
+ permissions: JSON.parse(permissionsJson),
3670
+ status,
3671
+ registeredAt: registeredAt ?? void 0,
3672
+ updatedAt,
3673
+ expiresAt: expiresAt ? new Date(expiresAt) : void 0
3674
+ };
3675
+ }
3676
+ function hashJson(value) {
3677
+ const input = stableJson(value);
3678
+ let hash = 0xcbf29ce484222325n;
3679
+ const prime = 0x100000001b3n;
3680
+ for (let index = 0; index < input.length; index += 1) {
3681
+ hash ^= BigInt(input.charCodeAt(index));
3682
+ hash = BigInt.asUintN(64, hash * prime);
3683
+ }
3684
+ return hash.toString(16).padStart(16, "0");
3685
+ }
3686
+ function stableJson(value) {
3687
+ if (value === null || typeof value !== "object") {
3688
+ return JSON.stringify(value);
3427
3689
  }
3428
- if (normalized === "metadata") {
3429
- return { bases: ["vault"], action: "tinycloud.kv/metadata" };
3690
+ if (Array.isArray(value)) {
3691
+ return `[${value.map(stableJson).join(",")}]`;
3430
3692
  }
3431
- throw new ManifestValidationError(
3432
- `unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
3433
- );
3693
+ const object = value;
3694
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
3434
3695
  }
3435
- function normalizeVaultAction(action) {
3436
- if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
3437
- return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
3696
+ function indexedDelegationFromRow(row) {
3697
+ const [
3698
+ cid,
3699
+ direction,
3700
+ spaceId,
3701
+ spaceName,
3702
+ counterpartyDid,
3703
+ delegateDid,
3704
+ delegatorDid,
3705
+ path,
3706
+ actionsJson,
3707
+ expiry,
3708
+ status,
3709
+ createdAt
3710
+ ] = row;
3711
+ return {
3712
+ cid,
3713
+ direction,
3714
+ spaceId,
3715
+ spaceName: spaceName ?? void 0,
3716
+ counterpartyDid,
3717
+ delegateDid,
3718
+ delegatorDid: delegatorDid ?? void 0,
3719
+ path,
3720
+ actions: JSON.parse(actionsJson),
3721
+ expiry: new Date(expiry),
3722
+ status,
3723
+ createdAt: createdAt ? new Date(createdAt) : void 0
3724
+ };
3725
+ }
3726
+ function mapDelegation(delegation, space, direction) {
3727
+ return {
3728
+ cid: delegation.cid,
3729
+ direction,
3730
+ spaceId: delegation.spaceId || space.id,
3731
+ spaceName: space.name,
3732
+ counterpartyDid: direction === "granted" ? delegation.delegateDID : delegation.delegatorDID ?? delegation.delegateDID,
3733
+ delegateDid: delegation.delegateDID,
3734
+ delegatorDid: delegation.delegatorDID,
3735
+ path: delegation.path,
3736
+ actions: delegation.actions,
3737
+ expiry: delegation.expiry,
3738
+ status: delegation.isRevoked ? "revoked" : delegation.expiry.getTime() <= Date.now() ? "expired" : "active",
3739
+ createdAt: delegation.createdAt
3740
+ };
3741
+ }
3742
+ function accountErr(error) {
3743
+ return err3(serviceError3(error.code, error.message, SERVICE_NAME2, { cause: error.cause, meta: error.meta }));
3744
+ }
3745
+ function isMissingIndexError(error) {
3746
+ return /no such table:/i.test(error.message);
3747
+ }
3748
+ async function ignoreIndexFailure(task) {
3749
+ try {
3750
+ await task();
3751
+ } catch {
3438
3752
  }
3439
- if (action.startsWith("tinycloud.kv/")) {
3440
- return action.slice("tinycloud.kv/".length);
3753
+ }
3754
+
3755
+ // src/index.ts
3756
+ import {
3757
+ ServiceContext as ServiceContext2,
3758
+ KVService as KVService2,
3759
+ PrefixedKVService,
3760
+ ok as ok5,
3761
+ err as err5,
3762
+ serviceError as serviceError5,
3763
+ ErrorCodes as ErrorCodes2,
3764
+ defaultRetryPolicy as defaultRetryPolicy2,
3765
+ SQLService as SQLService2,
3766
+ DatabaseHandle,
3767
+ SQLAction,
3768
+ DuckDbService as DuckDbService2,
3769
+ DuckDbDatabaseHandle,
3770
+ DuckDbAction,
3771
+ HooksService as HooksService2,
3772
+ DataVaultService,
3773
+ VaultHeaders,
3774
+ VaultPublicSpaceKVActions,
3775
+ createVaultCrypto,
3776
+ SecretsService,
3777
+ SECRET_NAME_RE as SECRET_NAME_RE2,
3778
+ canonicalizeSecretScope,
3779
+ resolveSecretListPrefix,
3780
+ resolveSecretPath as resolveSecretPath2,
3781
+ EncryptionService,
3782
+ parseNetworkId as parseNetworkId2,
3783
+ buildNetworkId as buildNetworkId2,
3784
+ isNetworkId,
3785
+ networkDiscoveryKey,
3786
+ NetworkIdError,
3787
+ ENCRYPTION_NETWORK_URN_PREFIX,
3788
+ NETWORK_NAME_PATTERN,
3789
+ canonicalizeEncryptionJson,
3790
+ canonicalHashHex,
3791
+ hexEncode,
3792
+ hexDecode,
3793
+ base64Encode,
3794
+ base64Decode,
3795
+ utf8Encode,
3796
+ utf8Decode,
3797
+ encryptToNetwork,
3798
+ decryptEnvelopeWithKey,
3799
+ validateEnvelope,
3800
+ generateRandomReceiverKey,
3801
+ deriveSignedReceiverKey,
3802
+ buildCanonicalDecryptRequest,
3803
+ buildDecryptFacts,
3804
+ buildDecryptAttenuation,
3805
+ buildDecryptInvocation,
3806
+ checkDecryptInvocationInput,
3807
+ verifyDecryptResponse,
3808
+ canonicalSignedResponse,
3809
+ openWrappedKey,
3810
+ discoverNetwork,
3811
+ ensureNetworkUsableForDecrypt,
3812
+ DEFAULT_ENCRYPTION_ALG,
3813
+ ENVELOPE_VERSION,
3814
+ DEFAULT_KEY_VERSION,
3815
+ DECRYPT_FACT_TYPE,
3816
+ DECRYPT_RESULT_TYPE,
3817
+ DECRYPT_ACTION,
3818
+ ENCRYPTION_SERVICE,
3819
+ ENCRYPTION_SERVICE_SHORT,
3820
+ encryptionError
3821
+ } from "@tinycloud/sdk-services";
3822
+
3823
+ // src/space.ts
3824
+ async function fetchPeerId(host, spaceId) {
3825
+ const res = await fetch(
3826
+ `${host}/peer/generate/${encodeURIComponent(spaceId)}`
3827
+ );
3828
+ if (!res.ok) {
3829
+ const error = await res.text().catch(() => res.statusText);
3830
+ throw new Error(`Failed to get peer ID: ${res.status} - ${error}`);
3441
3831
  }
3442
- if (action.includes("/")) {
3443
- throw new ManifestValidationError(
3444
- `unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
3445
- );
3832
+ return res.text();
3833
+ }
3834
+ async function submitHostDelegation(host, headers) {
3835
+ const res = await fetch(`${host}/delegate`, {
3836
+ method: "POST",
3837
+ headers
3838
+ });
3839
+ return {
3840
+ success: res.ok,
3841
+ status: res.status,
3842
+ error: res.ok ? void 0 : await res.text().catch(() => res.statusText)
3843
+ };
3844
+ }
3845
+ async function activateSessionWithHost(host, delegationHeader) {
3846
+ const res = await fetch(`${host}/delegate`, {
3847
+ method: "POST",
3848
+ headers: delegationHeader
3849
+ });
3850
+ if (res.ok) {
3851
+ try {
3852
+ const body = await res.json();
3853
+ return {
3854
+ success: true,
3855
+ status: res.status,
3856
+ activated: body.activated ?? [],
3857
+ skipped: body.skipped ?? []
3858
+ };
3859
+ } catch {
3860
+ return {
3861
+ success: true,
3862
+ status: res.status,
3863
+ activated: [],
3864
+ skipped: []
3865
+ };
3866
+ }
3446
3867
  }
3447
- return action;
3448
- }
3449
- function vaultKVPath(base, path) {
3450
- const normalized = path.startsWith("/") ? path.slice(1) : path;
3451
- return `${base}/${normalized}`;
3452
- }
3453
- function cloneResourceCapability(entry) {
3454
3868
  return {
3455
- service: entry.service,
3456
- space: entry.space,
3457
- path: entry.path,
3458
- actions: [...entry.actions],
3459
- ...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
3460
- ...entry.description !== void 0 ? { description: entry.description } : {}
3869
+ success: false,
3870
+ status: res.status,
3871
+ error: await res.text().catch(() => res.statusText)
3461
3872
  };
3462
3873
  }
3463
- function clonePermissionEntry(entry) {
3874
+
3875
+ // src/delegations/DelegationManager.ts
3876
+ var DelegationAction = {
3877
+ CREATE: "tinycloud.delegation/create",
3878
+ REVOKE: "tinycloud.delegation/revoke",
3879
+ LIST: "tinycloud.delegation/list",
3880
+ GET: "tinycloud.delegation/get",
3881
+ CHECK: "tinycloud.delegation/check"
3882
+ };
3883
+ function createError(code, message, cause, meta) {
3464
3884
  return {
3465
- service: entry.service,
3466
- ...entry.space !== void 0 ? { space: entry.space } : {},
3467
- path: entry.path,
3468
- actions: [...entry.actions],
3469
- ...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
3470
- ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
3471
- ...entry.description !== void 0 ? { description: entry.description } : {}
3885
+ code,
3886
+ message,
3887
+ service: "delegation",
3888
+ cause,
3889
+ meta
3472
3890
  };
3473
3891
  }
3474
- function dedupeResources(resources) {
3475
- const byKey = /* @__PURE__ */ new Map();
3476
- for (const resource of resources) {
3477
- const key = `${resource.service}\0${resource.space}\0${resource.path}\0${resource.expiryMs ?? ""}`;
3478
- const existing = byKey.get(key);
3479
- if (existing === void 0) {
3480
- byKey.set(key, cloneResourceCapability(resource));
3481
- continue;
3892
+ var DelegationManager = class {
3893
+ /**
3894
+ * Creates a new DelegationManager instance.
3895
+ *
3896
+ * @param config - Configuration including hosts, session, and invoke function
3897
+ */
3898
+ constructor(config) {
3899
+ this.hosts = config.hosts;
3900
+ this.session = config.session;
3901
+ this.invoke = config.invoke;
3902
+ this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
3903
+ }
3904
+ /**
3905
+ * Updates the session (e.g., after re-authentication).
3906
+ *
3907
+ * @param session - New session to use for operations
3908
+ */
3909
+ updateSession(session) {
3910
+ this.session = session;
3911
+ }
3912
+ /**
3913
+ * Gets the primary host URL.
3914
+ */
3915
+ get host() {
3916
+ return this.hosts[0];
3917
+ }
3918
+ /**
3919
+ * Executes an invoke operation against the delegation API.
3920
+ */
3921
+ async invokeOperation(path, action, body) {
3922
+ const headers = this.invoke(this.session, "delegation", path, action);
3923
+ return this.fetchFn(`${this.host}/invoke`, {
3924
+ method: "POST",
3925
+ headers,
3926
+ body
3927
+ });
3928
+ }
3929
+ /**
3930
+ * Creates a new delegation.
3931
+ *
3932
+ * Delegates specific permissions to another DID for a given path.
3933
+ * The delegatee can then use these permissions to access resources
3934
+ * within the specified scope.
3935
+ *
3936
+ * @param params - Parameters for the delegation
3937
+ * @returns Result containing the created Delegation or an error
3938
+ *
3939
+ * @example
3940
+ * ```typescript
3941
+ * const result = await manager.create({
3942
+ * delegateDID: bob.did,
3943
+ * path: "documents/shared/",
3944
+ * actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
3945
+ * expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
3946
+ * });
3947
+ * ```
3948
+ */
3949
+ async create(params) {
3950
+ if (!params.delegateDID) {
3951
+ return {
3952
+ ok: false,
3953
+ error: createError(
3954
+ DelegationErrorCodes.INVALID_INPUT,
3955
+ "delegateDID is required"
3956
+ )
3957
+ };
3482
3958
  }
3483
- const seen = new Set(existing.actions);
3484
- for (const action of resource.actions) {
3485
- if (!seen.has(action)) {
3486
- existing.actions.push(action);
3487
- seen.add(action);
3959
+ if (!params.path) {
3960
+ return {
3961
+ ok: false,
3962
+ error: createError(
3963
+ DelegationErrorCodes.INVALID_INPUT,
3964
+ "path is required"
3965
+ )
3966
+ };
3967
+ }
3968
+ if (!params.actions || params.actions.length === 0) {
3969
+ return {
3970
+ ok: false,
3971
+ error: createError(
3972
+ DelegationErrorCodes.INVALID_INPUT,
3973
+ "at least one action is required"
3974
+ )
3975
+ };
3976
+ }
3977
+ try {
3978
+ const body = JSON.stringify({
3979
+ delegateDID: params.delegateDID,
3980
+ path: params.path,
3981
+ actions: params.actions,
3982
+ expiry: params.expiry?.toISOString(),
3983
+ disableSubDelegation: params.disableSubDelegation ?? false,
3984
+ statement: params.statement
3985
+ });
3986
+ const response = await this.invokeOperation(
3987
+ params.path,
3988
+ DelegationAction.CREATE,
3989
+ body
3990
+ );
3991
+ if (!response.ok) {
3992
+ const errorText = await response.text();
3993
+ return {
3994
+ ok: false,
3995
+ error: createError(
3996
+ DelegationErrorCodes.CREATION_FAILED,
3997
+ `Failed to create delegation: ${response.status} - ${errorText}`,
3998
+ void 0,
3999
+ { status: response.status, path: params.path }
4000
+ )
4001
+ };
4002
+ }
4003
+ const apiResponse = await response.json();
4004
+ const delegation = {
4005
+ cid: apiResponse.cid ?? "",
4006
+ delegateDID: params.delegateDID,
4007
+ spaceId: this.session.spaceId,
4008
+ path: params.path,
4009
+ actions: params.actions,
4010
+ expiry: params.expiry ?? new Date(Date.now() + EXPIRY.SHARE_MS),
4011
+ isRevoked: false,
4012
+ allowSubDelegation: !(params.disableSubDelegation ?? false),
4013
+ createdAt: /* @__PURE__ */ new Date()
4014
+ };
4015
+ return { ok: true, data: delegation };
4016
+ } catch (error) {
4017
+ if (error instanceof Error && error.name === "AbortError") {
4018
+ return {
4019
+ ok: false,
4020
+ error: createError(
4021
+ DelegationErrorCodes.ABORTED,
4022
+ "Request aborted",
4023
+ error
4024
+ )
4025
+ };
4026
+ }
4027
+ return {
4028
+ ok: false,
4029
+ error: createError(
4030
+ DelegationErrorCodes.NETWORK_ERROR,
4031
+ `Network error during delegation creation: ${String(error)}`,
4032
+ error instanceof Error ? error : void 0
4033
+ )
4034
+ };
4035
+ }
4036
+ }
4037
+ /**
4038
+ * Revokes an existing delegation.
4039
+ *
4040
+ * Once revoked, the delegation can no longer be used to access resources.
4041
+ * This also invalidates any sub-delegations derived from this delegation.
4042
+ *
4043
+ * @param cid - The CID of the delegation to revoke
4044
+ * @returns Result indicating success or an error
4045
+ *
4046
+ * @example
4047
+ * ```typescript
4048
+ * const result = await manager.revoke("bafy...");
4049
+ * if (result.ok) {
4050
+ * console.log("Delegation revoked successfully");
4051
+ * }
4052
+ * ```
4053
+ */
4054
+ async revoke(cid) {
4055
+ if (!cid) {
4056
+ return {
4057
+ ok: false,
4058
+ error: createError(
4059
+ DelegationErrorCodes.INVALID_INPUT,
4060
+ "cid is required"
4061
+ )
4062
+ };
4063
+ }
4064
+ try {
4065
+ const body = JSON.stringify({ cid });
4066
+ const response = await this.invokeOperation(
4067
+ cid,
4068
+ DelegationAction.REVOKE,
4069
+ body
4070
+ );
4071
+ if (!response.ok) {
4072
+ const errorText = await response.text();
4073
+ if (response.status === 404) {
4074
+ return {
4075
+ ok: false,
4076
+ error: createError(
4077
+ DelegationErrorCodes.NOT_FOUND,
4078
+ `Delegation not found: ${cid}`
4079
+ )
4080
+ };
4081
+ }
4082
+ return {
4083
+ ok: false,
4084
+ error: createError(
4085
+ DelegationErrorCodes.REVOCATION_FAILED,
4086
+ `Failed to revoke delegation: ${response.status} - ${errorText}`,
4087
+ void 0,
4088
+ { status: response.status, cid }
4089
+ )
4090
+ };
3488
4091
  }
4092
+ return { ok: true, data: void 0 };
4093
+ } catch (error) {
4094
+ if (error instanceof Error && error.name === "AbortError") {
4095
+ return {
4096
+ ok: false,
4097
+ error: createError(
4098
+ DelegationErrorCodes.ABORTED,
4099
+ "Request aborted",
4100
+ error
4101
+ )
4102
+ };
4103
+ }
4104
+ return {
4105
+ ok: false,
4106
+ error: createError(
4107
+ DelegationErrorCodes.NETWORK_ERROR,
4108
+ `Network error during delegation revocation: ${String(error)}`,
4109
+ error instanceof Error ? error : void 0
4110
+ )
4111
+ };
3489
4112
  }
3490
- if (existing.description === void 0 && resource.description !== void 0) {
3491
- existing.description = resource.description;
3492
- }
3493
- }
3494
- return [...byKey.values()];
3495
- }
3496
- function capabilitiesReadPermission(space) {
3497
- return {
3498
- service: "tinycloud.capabilities",
3499
- space,
3500
- path: "",
3501
- actions: ["tinycloud.capabilities/read"]
3502
- };
3503
- }
3504
- function withCapabilitiesReadForSpaces(resources) {
3505
- if (resources.length === 0) {
3506
- return [];
3507
- }
3508
- const spaces = new Set(
3509
- resources.filter((resource) => resource.service !== ENCRYPTION_PERMISSION_SERVICE).map((resource) => resource.space)
3510
- );
3511
- return dedupeResources([
3512
- ...resources,
3513
- ...[...spaces].map(capabilitiesReadPermission)
3514
- ]);
3515
- }
3516
- function accountRegistryPermission() {
3517
- return {
3518
- service: "tinycloud.kv",
3519
- space: ACCOUNT_REGISTRY_SPACE,
3520
- path: ACCOUNT_REGISTRY_PATH,
3521
- actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
3522
- };
3523
- }
3524
- function composeManifestRequest(inputs, options = {}) {
3525
- if (!Array.isArray(inputs) || inputs.length === 0) {
3526
- throw new ManifestValidationError(
3527
- "composeManifestRequest requires at least one manifest"
3528
- );
3529
- }
3530
- const includeAccountRegistryPermissions = options.includeAccountRegistryPermissions ?? true;
3531
- const manifests = inputs.map(validateManifest);
3532
- const resolved = manifests.map(resolveManifest);
3533
- const resources = resolved.flatMap((entry) => entry.resources);
3534
- const delegationTargets = resolved.flatMap(
3535
- (entry) => entry.additionalDelegates.map((delegate) => ({
3536
- ...delegate,
3537
- permissions: dedupeResources(delegate.permissions)
3538
- }))
3539
- );
3540
- if (includeAccountRegistryPermissions) {
3541
- resources.push(accountRegistryPermission());
3542
4113
  }
3543
- const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
3544
- const manifestsByAppId = /* @__PURE__ */ new Map();
3545
- for (const manifest of manifests) {
3546
- const current = manifestsByAppId.get(manifest.app_id);
3547
- if (current === void 0) {
3548
- manifestsByAppId.set(manifest.app_id, [manifest]);
3549
- } else {
3550
- current.push(manifest);
4114
+ /**
4115
+ * Lists all delegations for the current session's space.
4116
+ *
4117
+ * Returns both delegations created by the current user (as delegator)
4118
+ * and delegations granted to the current user (as delegatee).
4119
+ *
4120
+ * @returns Result containing an array of Delegations or an error
4121
+ *
4122
+ * @example
4123
+ * ```typescript
4124
+ * const result = await manager.list();
4125
+ * if (result.ok) {
4126
+ * for (const delegation of result.data) {
4127
+ * console.log(`${delegation.cid}: ${delegation.path} -> ${delegation.delegateDID}`);
4128
+ * }
4129
+ * }
4130
+ * ```
4131
+ */
4132
+ async list() {
4133
+ try {
4134
+ const response = await this.invokeOperation("", DelegationAction.LIST);
4135
+ if (!response.ok) {
4136
+ const errorText = await response.text();
4137
+ return {
4138
+ ok: false,
4139
+ error: createError(
4140
+ DelegationErrorCodes.NETWORK_ERROR,
4141
+ `Failed to list delegations: ${response.status} - ${errorText}`,
4142
+ void 0,
4143
+ { status: response.status }
4144
+ )
4145
+ };
4146
+ }
4147
+ const data = await response.json();
4148
+ const delegations = data.map((item) => ({
4149
+ cid: item.cid,
4150
+ delegateDID: item.delegateDID,
4151
+ delegatorDID: item.delegatorDID,
4152
+ spaceId: item.spaceId,
4153
+ path: item.path,
4154
+ actions: item.actions,
4155
+ expiry: new Date(item.expiry),
4156
+ isRevoked: item.isRevoked,
4157
+ createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
4158
+ parentCid: item.parentCid,
4159
+ allowSubDelegation: item.allowSubDelegation
4160
+ }));
4161
+ return { ok: true, data: delegations };
4162
+ } catch (error) {
4163
+ if (error instanceof Error && error.name === "AbortError") {
4164
+ return {
4165
+ ok: false,
4166
+ error: createError(
4167
+ DelegationErrorCodes.ABORTED,
4168
+ "Request aborted",
4169
+ error
4170
+ )
4171
+ };
4172
+ }
4173
+ return {
4174
+ ok: false,
4175
+ error: createError(
4176
+ DelegationErrorCodes.NETWORK_ERROR,
4177
+ `Network error during delegation list: ${String(error)}`,
4178
+ error instanceof Error ? error : void 0
4179
+ )
4180
+ };
3551
4181
  }
3552
4182
  }
3553
- const registryRecords = includeAccountRegistryPermissions ? [...manifestsByAppId.entries()].map(([app_id, appManifests]) => ({
3554
- key: `${ACCOUNT_REGISTRY_PATH}${app_id}`,
3555
- app_id,
3556
- manifests: appManifests.map((manifest) => ({
3557
- ...manifest,
3558
- permissions: manifest.permissions?.map(clonePermissionEntry)
3559
- }))
3560
- })) : [];
3561
- return {
3562
- manifests,
3563
- resources: resourcesWithImplicitCapabilities,
3564
- delegationTargets,
3565
- registryRecords,
3566
- expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
3567
- includePublicSpace: resolved.some((entry) => entry.includePublicSpace)
3568
- };
3569
- }
3570
- function resourceCapabilitiesToAbilitiesMap(resources) {
3571
- const out = {};
3572
- for (const r of resources) {
3573
- const shortService = SERVICE_LONG_TO_SHORT[r.service];
3574
- if (shortService === void 0) {
3575
- throw new ManifestValidationError(
3576
- `unknown service '${r.service}' \u2014 no short-form mapping. Known services: ${Object.keys(SERVICE_LONG_TO_SHORT).join(", ")}`
4183
+ /**
4184
+ * Gets the full delegation chain for a given delegation.
4185
+ *
4186
+ * Returns the chain of delegations from the root (original delegator)
4187
+ * to the specified delegation, including all intermediate sub-delegations.
4188
+ *
4189
+ * @param cid - The CID of the delegation to get the chain for
4190
+ * @returns Result containing the DelegationChain or an error
4191
+ *
4192
+ * @example
4193
+ * ```typescript
4194
+ * const result = await manager.getChain("bafy...");
4195
+ * if (result.ok) {
4196
+ * console.log("Chain length:", result.data.length);
4197
+ * for (const delegation of result.data) {
4198
+ * console.log(`- ${delegation.delegatorDID} -> ${delegation.delegateDID}`);
4199
+ * }
4200
+ * }
4201
+ * ```
4202
+ */
4203
+ async getChain(cid) {
4204
+ if (!cid) {
4205
+ return {
4206
+ ok: false,
4207
+ error: createError(
4208
+ DelegationErrorCodes.INVALID_INPUT,
4209
+ "cid is required"
4210
+ )
4211
+ };
4212
+ }
4213
+ try {
4214
+ const body = JSON.stringify({ cid, includeChain: true });
4215
+ const response = await this.invokeOperation(
4216
+ cid,
4217
+ DelegationAction.GET,
4218
+ body
3577
4219
  );
4220
+ if (!response.ok) {
4221
+ const errorText = await response.text();
4222
+ if (response.status === 404) {
4223
+ return {
4224
+ ok: false,
4225
+ error: createError(
4226
+ DelegationErrorCodes.NOT_FOUND,
4227
+ `Delegation not found: ${cid}`
4228
+ )
4229
+ };
4230
+ }
4231
+ return {
4232
+ ok: false,
4233
+ error: createError(
4234
+ DelegationErrorCodes.NETWORK_ERROR,
4235
+ `Failed to get delegation chain: ${response.status} - ${errorText}`,
4236
+ void 0,
4237
+ { status: response.status, cid }
4238
+ )
4239
+ };
4240
+ }
4241
+ const data = await response.json();
4242
+ const chain = data.chain.map((item) => ({
4243
+ cid: item.cid,
4244
+ delegateDID: item.delegateDID,
4245
+ delegatorDID: item.delegatorDID,
4246
+ spaceId: item.spaceId,
4247
+ path: item.path,
4248
+ actions: item.actions,
4249
+ expiry: new Date(item.expiry),
4250
+ isRevoked: item.isRevoked,
4251
+ createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
4252
+ parentCid: item.parentCid,
4253
+ allowSubDelegation: item.allowSubDelegation
4254
+ }));
4255
+ return { ok: true, data: chain };
4256
+ } catch (error) {
4257
+ if (error instanceof Error && error.name === "AbortError") {
4258
+ return {
4259
+ ok: false,
4260
+ error: createError(
4261
+ DelegationErrorCodes.ABORTED,
4262
+ "Request aborted",
4263
+ error
4264
+ )
4265
+ };
4266
+ }
4267
+ return {
4268
+ ok: false,
4269
+ error: createError(
4270
+ DelegationErrorCodes.NETWORK_ERROR,
4271
+ `Network error during chain retrieval: ${String(error)}`,
4272
+ error instanceof Error ? error : void 0
4273
+ )
4274
+ };
3578
4275
  }
3579
- if (out[shortService] === void 0) {
3580
- out[shortService] = {};
4276
+ }
4277
+ /**
4278
+ * Checks if the current session has permission for a given path and action.
4279
+ *
4280
+ * This can be used to verify permissions before attempting an operation,
4281
+ * or to implement custom access control logic.
4282
+ *
4283
+ * @param path - The resource path to check
4284
+ * @param action - The action to check (e.g., "tinycloud.kv/get")
4285
+ * @returns Result containing a boolean indicating permission or an error
4286
+ *
4287
+ * @example
4288
+ * ```typescript
4289
+ * const result = await manager.checkPermission("documents/private/", "tinycloud.kv/put");
4290
+ * if (result.ok && result.data) {
4291
+ * console.log("Permission granted");
4292
+ * } else {
4293
+ * console.log("Permission denied");
4294
+ * }
4295
+ * ```
4296
+ */
4297
+ async checkPermission(path, action) {
4298
+ if (!path) {
4299
+ return {
4300
+ ok: false,
4301
+ error: createError(
4302
+ DelegationErrorCodes.INVALID_INPUT,
4303
+ "path is required"
4304
+ )
4305
+ };
3581
4306
  }
3582
- const pathsMap = out[shortService];
3583
- const existing = pathsMap[r.path];
3584
- if (existing === void 0) {
3585
- pathsMap[r.path] = [...r.actions];
3586
- } else {
3587
- const seen = new Set(existing);
3588
- for (const action of r.actions) {
3589
- if (!seen.has(action)) {
3590
- existing.push(action);
3591
- seen.add(action);
4307
+ if (!action) {
4308
+ return {
4309
+ ok: false,
4310
+ error: createError(
4311
+ DelegationErrorCodes.INVALID_INPUT,
4312
+ "action is required"
4313
+ )
4314
+ };
4315
+ }
4316
+ try {
4317
+ const body = JSON.stringify({ path, action });
4318
+ const response = await this.invokeOperation(
4319
+ path,
4320
+ DelegationAction.CHECK,
4321
+ body
4322
+ );
4323
+ if (!response.ok) {
4324
+ if (response.status === 403) {
4325
+ return { ok: true, data: false };
3592
4326
  }
4327
+ const errorText = await response.text();
4328
+ return {
4329
+ ok: false,
4330
+ error: createError(
4331
+ DelegationErrorCodes.NETWORK_ERROR,
4332
+ `Failed to check permission: ${response.status} - ${errorText}`,
4333
+ void 0,
4334
+ { status: response.status, path, action }
4335
+ )
4336
+ };
3593
4337
  }
4338
+ const data = await response.json();
4339
+ return { ok: true, data: data.allowed };
4340
+ } catch (error) {
4341
+ if (error instanceof Error && error.name === "AbortError") {
4342
+ return {
4343
+ ok: false,
4344
+ error: createError(
4345
+ DelegationErrorCodes.ABORTED,
4346
+ "Request aborted",
4347
+ error
4348
+ )
4349
+ };
4350
+ }
4351
+ return {
4352
+ ok: false,
4353
+ error: createError(
4354
+ DelegationErrorCodes.NETWORK_ERROR,
4355
+ `Network error during permission check: ${String(error)}`,
4356
+ error instanceof Error ? error : void 0
4357
+ )
4358
+ };
3594
4359
  }
3595
4360
  }
3596
- return out;
3597
- }
3598
- function resourceCapabilitiesToSpaceAbilitiesMap(resources) {
3599
- const grouped = /* @__PURE__ */ new Map();
3600
- for (const resource of resources) {
3601
- const entries = grouped.get(resource.space);
3602
- if (entries === void 0) {
3603
- grouped.set(resource.space, [resource]);
3604
- } else {
3605
- entries.push(resource);
3606
- }
3607
- }
3608
- const out = {};
3609
- for (const [space, entries] of grouped.entries()) {
3610
- out[space] = resourceCapabilitiesToAbilitiesMap(entries);
3611
- }
3612
- return out;
3613
- }
3614
- function manifestAbilitiesUnion(resolved) {
3615
- const all = [...resolved.resources];
3616
- for (const delegate of resolved.additionalDelegates) {
3617
- for (const perm of delegate.permissions) {
3618
- all.push(perm);
3619
- }
4361
+ };
4362
+
4363
+ // src/delegations/SharingService.schema.ts
4364
+ import { z as z5 } from "zod";
4365
+ var EncodedShareDataSchema = z5.object({
4366
+ /** Private key in JWK format (must include d parameter) */
4367
+ key: JWKSchema.refine(
4368
+ (jwk) => typeof jwk.d === "string" && jwk.d.length > 0,
4369
+ { message: "JWK must include private key (d parameter)" }
4370
+ ),
4371
+ /** DID of the key */
4372
+ keyDid: z5.string().min(1, "keyDid is required"),
4373
+ /** The delegation granting access */
4374
+ delegation: DelegationSchema,
4375
+ /** Resource path this link grants access to */
4376
+ path: z5.string().min(1, "path is required"),
4377
+ /** TinyCloud host URL */
4378
+ host: z5.string().url("host must be a valid URL"),
4379
+ /** Space ID */
4380
+ spaceId: z5.string().min(1, "spaceId is required"),
4381
+ /** Schema version (must be 1) */
4382
+ version: z5.literal(1)
4383
+ });
4384
+ var ReceiveOptionsSchema = z5.object({
4385
+ /**
4386
+ * Whether to automatically create a sub-delegation to the current session key.
4387
+ * Default: true
4388
+ */
4389
+ autoSubdelegate: z5.boolean().optional(),
4390
+ /**
4391
+ * Whether to use the current session key for operations (requires autoSubdelegate).
4392
+ * Default: true
4393
+ */
4394
+ useSessionKey: z5.boolean().optional(),
4395
+ /**
4396
+ * Ingestion options passed to CapabilityKeyRegistry.
4397
+ */
4398
+ ingestOptions: IngestOptionsSchema.optional()
4399
+ });
4400
+ var SharingServiceConfigSchema = z5.object({
4401
+ /** TinyCloud host URLs */
4402
+ hosts: z5.array(z5.string().url()).min(1, "At least one host URL is required"),
4403
+ /**
4404
+ * Active session for authentication.
4405
+ * Required for generate(), optional for receive().
4406
+ */
4407
+ session: z5.unknown().refine(
4408
+ (val) => val === void 0 || val !== null && typeof val === "object",
4409
+ { message: "Expected a ServiceSession object or undefined" }
4410
+ ).optional(),
4411
+ /** Platform-specific invoke function */
4412
+ invoke: z5.unknown().refine((val) => typeof val === "function", {
4413
+ message: "Expected an invoke function"
4414
+ }),
4415
+ /** Optional custom fetch implementation */
4416
+ fetch: z5.unknown().refine(
4417
+ (val) => val === void 0 || typeof val === "function",
4418
+ { message: "Expected a fetch function or undefined" }
4419
+ ).optional(),
4420
+ /** Key provider for cryptographic operations */
4421
+ keyProvider: KeyProviderSchema,
4422
+ /** Capability key registry for key/delegation management */
4423
+ registry: z5.unknown().refine(
4424
+ (val) => val !== null && typeof val === "object",
4425
+ { message: "Expected an ICapabilityKeyRegistry object" }
4426
+ ),
4427
+ /**
4428
+ * Delegation manager for creating delegations.
4429
+ * Required for generate(), optional for receive().
4430
+ */
4431
+ delegationManager: z5.unknown().refine(
4432
+ (val) => val === void 0 || val !== null && typeof val === "object",
4433
+ { message: "Expected a DelegationManager object or undefined" }
4434
+ ).optional(),
4435
+ /** Factory for creating KV service instances */
4436
+ createKVService: z5.unknown().refine(
4437
+ (val) => typeof val === "function",
4438
+ { message: "Expected a createKVService factory function" }
4439
+ ),
4440
+ /** Base URL for sharing links (e.g., "https://share.myapp.com") */
4441
+ baseUrl: z5.string().optional(),
4442
+ /**
4443
+ * Custom delegation creation function.
4444
+ */
4445
+ createDelegation: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
4446
+ message: "Expected a createDelegation function or undefined"
4447
+ }).optional(),
4448
+ /**
4449
+ * WASM function for client-side delegation creation.
4450
+ */
4451
+ createDelegationWasm: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
4452
+ message: "Expected a createDelegationWasm function or undefined"
4453
+ }).optional(),
4454
+ /**
4455
+ * Path prefix for KV operations.
4456
+ */
4457
+ pathPrefix: z5.string().optional(),
4458
+ /**
4459
+ * Session expiry time.
4460
+ */
4461
+ sessionExpiry: z5.date().optional(),
4462
+ /**
4463
+ * Callback to create a DIRECT delegation from wallet to share key.
4464
+ * This is the preferred method for long-lived share links because it
4465
+ * bypasses the session delegation chain entirely.
4466
+ */
4467
+ onRootDelegationNeeded: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
4468
+ message: "Expected an onRootDelegationNeeded function or undefined"
4469
+ }).optional()
4470
+ });
4471
+ function validateEncodedShareData(data) {
4472
+ const result = EncodedShareDataSchema.safeParse(data);
4473
+ if (!result.success) {
4474
+ return {
4475
+ ok: false,
4476
+ error: {
4477
+ code: DelegationErrorCodes.VALIDATION_ERROR,
4478
+ message: `Invalid share data: ${result.error.message}`,
4479
+ service: "delegation",
4480
+ meta: { issues: result.error.issues }
4481
+ }
4482
+ };
3620
4483
  }
3621
- return resourceCapabilitiesToAbilitiesMap(all);
4484
+ return { ok: true, data: result.data };
3622
4485
  }
3623
4486
 
3624
4487
  // src/delegations/SharingService.ts
@@ -3799,13 +4662,13 @@ var SharingService = class {
3799
4662
  )
3800
4663
  };
3801
4664
  }
3802
- } catch (err5) {
4665
+ } catch (err6) {
3803
4666
  return {
3804
4667
  ok: false,
3805
4668
  error: createError2(
3806
4669
  DelegationErrorCodes.CREATION_FAILED,
3807
- `Failed to generate session key for share: ${err5 instanceof Error ? err5.message : String(err5)}`,
3808
- err5 instanceof Error ? err5 : void 0
4670
+ `Failed to generate session key for share: ${err6 instanceof Error ? err6.message : String(err6)}`,
4671
+ err6 instanceof Error ? err6 : void 0
3809
4672
  )
3810
4673
  };
3811
4674
  }
@@ -3851,7 +4714,7 @@ var SharingService = class {
3851
4714
  }
3852
4715
  delegation = parsed;
3853
4716
  }
3854
- } catch (err5) {
4717
+ } catch (err6) {
3855
4718
  const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
3856
4719
  expiry = fallbackResult.expiry;
3857
4720
  const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
@@ -4049,13 +4912,13 @@ var SharingService = class {
4049
4912
  allowSubDelegation: true,
4050
4913
  createdAt: /* @__PURE__ */ new Date()
4051
4914
  };
4052
- } catch (err5) {
4915
+ } catch (err6) {
4053
4916
  return {
4054
4917
  ok: false,
4055
4918
  error: createError2(
4056
4919
  DelegationErrorCodes.CREATION_FAILED,
4057
- `Failed to create delegation via WASM: ${err5 instanceof Error ? err5.message : String(err5)}`,
4058
- err5 instanceof Error ? err5 : void 0
4920
+ `Failed to create delegation via WASM: ${err6 instanceof Error ? err6.message : String(err6)}`,
4921
+ err6 instanceof Error ? err6 : void 0
4059
4922
  )
4060
4923
  };
4061
4924
  }
@@ -4136,8 +4999,8 @@ var SharingService = class {
4136
4999
  let activeKey = keyInfo;
4137
5000
  if (autoSubdelegate && useSessionKey && this.session) {
4138
5001
  try {
4139
- } catch (err5) {
4140
- console.warn("Auto-subdelegation failed, using ingested key directly:", err5);
5002
+ } catch (err6) {
5003
+ console.warn("Auto-subdelegation failed, using ingested key directly:", err6);
4141
5004
  }
4142
5005
  }
4143
5006
  const authHeader = shareData.delegation.authHeader ?? `Bearer ${shareData.delegation.cid}`;
@@ -4235,26 +5098,26 @@ var SharingService = class {
4235
5098
  let jsonString;
4236
5099
  try {
4237
5100
  jsonString = base64UrlDecode(base64Data);
4238
- } catch (err5) {
5101
+ } catch (err6) {
4239
5102
  return {
4240
5103
  ok: false,
4241
5104
  error: createError2(
4242
5105
  DelegationErrorCodes.INVALID_TOKEN,
4243
- `Failed to decode base64 data: ${err5 instanceof Error ? err5.message : String(err5)}`,
4244
- err5 instanceof Error ? err5 : void 0
5106
+ `Failed to decode base64 data: ${err6 instanceof Error ? err6.message : String(err6)}`,
5107
+ err6 instanceof Error ? err6 : void 0
4245
5108
  )
4246
5109
  };
4247
5110
  }
4248
5111
  let parsed;
4249
5112
  try {
4250
5113
  parsed = JSON.parse(jsonString);
4251
- } catch (err5) {
5114
+ } catch (err6) {
4252
5115
  return {
4253
5116
  ok: false,
4254
5117
  error: createError2(
4255
5118
  DelegationErrorCodes.INVALID_TOKEN,
4256
- `Failed to parse share data JSON: ${err5 instanceof Error ? err5.message : String(err5)}`,
4257
- err5 instanceof Error ? err5 : void 0
5119
+ `Failed to parse share data JSON: ${err6 instanceof Error ? err6.message : String(err6)}`,
5120
+ err6 instanceof Error ? err6 : void 0
4258
5121
  )
4259
5122
  };
4260
5123
  }
@@ -4281,8 +5144,8 @@ function createSharingService(config) {
4281
5144
  }
4282
5145
 
4283
5146
  // src/authorization/CapabilityKeyRegistry.ts
4284
- import { ok as ok3, err as err3, serviceError as serviceError3 } from "@tinycloud/sdk-services";
4285
- var SERVICE_NAME2 = "capability-key-registry";
5147
+ import { ok as ok4, err as err4, serviceError as serviceError4 } from "@tinycloud/sdk-services";
5148
+ var SERVICE_NAME3 = "capability-key-registry";
4286
5149
  var CapabilityKeyRegistryErrorCodes = {
4287
5150
  /** Key not found in registry */
4288
5151
  KEY_NOT_FOUND: "KEY_NOT_FOUND",
@@ -4499,11 +5362,11 @@ var CapabilityKeyRegistry = class {
4499
5362
  revokeDelegation(cid) {
4500
5363
  const stored = this.store.byCid.get(cid);
4501
5364
  if (!stored) {
4502
- return err3(
4503
- serviceError3(
5365
+ return err4(
5366
+ serviceError4(
4504
5367
  CapabilityKeyRegistryErrorCodes.KEY_NOT_FOUND,
4505
5368
  `Delegation not found: ${cid}`,
4506
- SERVICE_NAME2
5369
+ SERVICE_NAME3
4507
5370
  )
4508
5371
  );
4509
5372
  }
@@ -4522,7 +5385,7 @@ var CapabilityKeyRegistry = class {
4522
5385
  }
4523
5386
  }
4524
5387
  }
4525
- return ok3(void 0);
5388
+ return ok4(void 0);
4526
5389
  }
4527
5390
  // ===========================================================================
4528
5391
  // Search
@@ -4751,8 +5614,8 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
4751
5614
  response = await fetchFn(`${host}/info`, {
4752
5615
  signal: AbortSignal.timeout(5e3)
4753
5616
  });
4754
- } catch (err5) {
4755
- throw new VersionCheckError(host, err5);
5617
+ } catch (err6) {
5618
+ throw new VersionCheckError(host, err6);
4756
5619
  }
4757
5620
  if (!response.ok) {
4758
5621
  throw new VersionCheckError(host);
@@ -5284,6 +6147,7 @@ function parseRecapCapabilities(parseWasm, siwe) {
5284
6147
  export {
5285
6148
  ACCOUNT_REGISTRY_PATH,
5286
6149
  ACCOUNT_REGISTRY_SPACE,
6150
+ AccountService,
5287
6151
  AutoApproveSpaceCreationHandler,
5288
6152
  CapabilityKeyRegistry,
5289
6153
  CapabilityKeyRegistryErrorCodes,
@@ -5389,7 +6253,7 @@ export {
5389
6253
  utf8Decode as encryptionUtf8Decode,
5390
6254
  utf8Encode as encryptionUtf8Encode,
5391
6255
  ensureNetworkUsableForDecrypt,
5392
- err4 as err,
6256
+ err5 as err,
5393
6257
  expandActionShortNames,
5394
6258
  expandPermissionEntries,
5395
6259
  expandPermissionEntry,
@@ -5410,7 +6274,7 @@ export {
5410
6274
  multiaddrToHttpUrl,
5411
6275
  networkDiscoveryKey,
5412
6276
  normalizeDefaults,
5413
- ok4 as ok,
6277
+ ok5 as ok,
5414
6278
  openWrappedKey,
5415
6279
  parseCanonicalNetworkId,
5416
6280
  parseExpiry,
@@ -5428,7 +6292,7 @@ export {
5428
6292
  resolveTinyCloudHosts,
5429
6293
  resourceCapabilitiesToAbilitiesMap,
5430
6294
  resourceCapabilitiesToSpaceAbilitiesMap,
5431
- serviceError4 as serviceError,
6295
+ serviceError5 as serviceError,
5432
6296
  signLocationRecord,
5433
6297
  submitHostDelegation,
5434
6298
  validateClientSession,