@tinycloud/node-sdk 2.4.0-beta.1 → 2.4.0-beta.11

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
@@ -17186,7 +17186,9 @@ import {
17186
17186
  verifyDidKeyEd25519Signature,
17187
17187
  canonicalizeAddress as canonicalizeAddress2,
17188
17188
  pkhDid as pkhDid2,
17189
- principalDidEquals
17189
+ resolveTinyCloudHosts as resolveTinyCloudHosts2,
17190
+ principalDidEquals as principalDidEquals2,
17191
+ parseNetworkId as parseNetworkId2
17190
17192
  } from "@tinycloud/sdk-core";
17191
17193
 
17192
17194
  // src/authorization/NodeUserAuthorization.ts
@@ -17199,6 +17201,8 @@ import {
17199
17201
  DEFAULT_MANIFEST_SPACE,
17200
17202
  ENCRYPTION_PERMISSION_SERVICE,
17201
17203
  composeManifestRequest,
17204
+ parseNetworkId,
17205
+ principalDidEquals,
17202
17206
  resourceCapabilitiesToAbilitiesMap,
17203
17207
  resourceCapabilitiesToSpaceAbilitiesMap,
17204
17208
  resolveTinyCloudHosts,
@@ -17283,6 +17287,25 @@ var MemorySessionStorage = class {
17283
17287
  };
17284
17288
 
17285
17289
  // src/authorization/NodeUserAuthorization.ts
17290
+ var DECRYPT_ACTION = "tinycloud.encryption/decrypt";
17291
+ var NETWORK_CREATE_ACTION = "tinycloud.encryption/network.create";
17292
+ function didPrincipalMatches(actual, expected) {
17293
+ try {
17294
+ return principalDidEquals(actual, expected);
17295
+ } catch {
17296
+ return actual === expected;
17297
+ }
17298
+ }
17299
+ function addRawAbility(rawAbilities, resource, action) {
17300
+ const actions = rawAbilities[resource];
17301
+ if (actions === void 0) {
17302
+ rawAbilities[resource] = [action];
17303
+ return;
17304
+ }
17305
+ if (!actions.includes(action)) {
17306
+ actions.push(action);
17307
+ }
17308
+ }
17286
17309
  var NodeUserAuthorization = class {
17287
17310
  constructor(config) {
17288
17311
  this.extensions = [];
@@ -17309,6 +17332,7 @@ var NodeUserAuthorization = class {
17309
17332
  "": [
17310
17333
  "tinycloud.sql/read",
17311
17334
  "tinycloud.sql/write",
17335
+ "tinycloud.sql/ddl",
17312
17336
  "tinycloud.sql/admin",
17313
17337
  "tinycloud.sql/export"
17314
17338
  ]
@@ -17400,12 +17424,48 @@ var NodeUserAuthorization = class {
17400
17424
  * `siwe` is the load-bearing one because `extractSiweExpiration` returns
17401
17425
  * undefined for missing SIWEs and the SDK then treats the session as
17402
17426
  * expired-at-epoch-zero.
17427
+ *
17428
+ * @param hosts - The TinyCloud hosts this session was created against,
17429
+ * as persisted in {@link PersistedSessionData.tinycloudHosts}. When
17430
+ * present (and non-empty) they are adopted directly so the restored
17431
+ * session resolves to the same node as the original sign-in without
17432
+ * re-running registry/fallback resolution. When absent (old session)
17433
+ * hosts are resolved lazily on the first host-needing call via
17434
+ * {@link ensureTinyCloudHosts}.
17403
17435
  */
17404
- setRestoredTinyCloudSession(session) {
17436
+ setRestoredTinyCloudSession(session, hosts) {
17405
17437
  const address = canonicalizeAddress(session.address);
17406
17438
  this._tinyCloudSession = { ...session, address };
17407
17439
  this._address = address;
17408
17440
  this._chainId = session.chainId;
17441
+ if ((!this.tinycloudHosts || this.tinycloudHosts.length === 0) && hosts && hosts.length > 0) {
17442
+ this.tinycloudHosts = [...hosts];
17443
+ }
17444
+ }
17445
+ /**
17446
+ * Ensure `tinycloudHosts` are resolved before a host-needing call.
17447
+ *
17448
+ * Fresh sign-in resolves hosts up front; a restored session may not have
17449
+ * (old persisted sessions predate {@link PersistedSessionData.tinycloudHosts}).
17450
+ * This guard makes a restored session resolve the node exactly like a fresh
17451
+ * sign-in — persisted hosts are preferred (already set by
17452
+ * {@link setRestoredTinyCloudSession}), otherwise the registry/fallback
17453
+ * resolution runs lazily here. Idempotent: {@link resolveTinyCloudHostsForSignIn}
17454
+ * early-returns when hosts are already set.
17455
+ *
17456
+ * Throws if hosts are unset and the restored session has no address/chainId
17457
+ * to resolve from — a real failure that must surface, not be masked.
17458
+ */
17459
+ async ensureTinyCloudHosts() {
17460
+ if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
17461
+ return;
17462
+ }
17463
+ if (this._address === void 0 || this._chainId === void 0) {
17464
+ throw new Error(
17465
+ "Cannot resolve TinyCloud hosts: no address/chainId available. Sign in or restore a session first."
17466
+ );
17467
+ }
17468
+ await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId);
17409
17469
  }
17410
17470
  async resolveTinyCloudHostsForSignIn(address, chainId) {
17411
17471
  if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
@@ -17512,20 +17572,18 @@ var NodeUserAuthorization = class {
17512
17572
  };
17513
17573
  }
17514
17574
  const rawAbilities = {};
17575
+ const currentDid = pkhDid(address, chainId);
17515
17576
  const spaceResources = request.resources.filter((entry) => {
17516
17577
  if (entry.service !== ENCRYPTION_PERMISSION_SERVICE) {
17517
17578
  return true;
17518
17579
  }
17519
- const existing = rawAbilities[entry.path];
17520
- if (existing === void 0) {
17521
- rawAbilities[entry.path] = [...entry.actions];
17522
- } else {
17523
- const seen = new Set(existing);
17524
- for (const action of entry.actions) {
17525
- if (!seen.has(action)) {
17526
- existing.push(action);
17527
- seen.add(action);
17528
- }
17580
+ for (const action of entry.actions) {
17581
+ addRawAbility(rawAbilities, entry.path, action);
17582
+ }
17583
+ if (entry.actions.includes(DECRYPT_ACTION)) {
17584
+ const parsed = parseNetworkId(entry.path);
17585
+ if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
17586
+ addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
17529
17587
  }
17530
17588
  }
17531
17589
  return false;
@@ -17604,6 +17662,7 @@ var NodeUserAuthorization = class {
17604
17662
  if (!this._tinyCloudSession || !this._address || !this._chainId) {
17605
17663
  throw new Error("Must be signed in to host space");
17606
17664
  }
17665
+ await this.ensureTinyCloudHosts();
17607
17666
  const host = this.primaryTinyCloudHost;
17608
17667
  const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
17609
17668
  const peerId = await fetchPeerId(host, spaceId);
@@ -17646,6 +17705,7 @@ var NodeUserAuthorization = class {
17646
17705
  if (!this._tinyCloudSession) {
17647
17706
  throw new Error("Must be signed in to ensure space exists");
17648
17707
  }
17708
+ await this.ensureTinyCloudHosts();
17649
17709
  const host = this.primaryTinyCloudHost;
17650
17710
  const primarySpaceId = this._tinyCloudSession.spaceId;
17651
17711
  const result = await activateSessionWithHost(
@@ -17827,7 +17887,8 @@ var NodeUserAuthorization = class {
17827
17887
  },
17828
17888
  expiresAt: expirationTime.toISOString(),
17829
17889
  createdAt: now.toISOString(),
17830
- version: "1.0"
17890
+ version: "1.0",
17891
+ tinycloudHosts: this.tinycloudHosts
17831
17892
  };
17832
17893
  await this.sessionStorage.save(address, persistedData);
17833
17894
  this._session = clientSession;
@@ -17994,7 +18055,8 @@ var NodeUserAuthorization = class {
17994
18055
  },
17995
18056
  expiresAt,
17996
18057
  createdAt,
17997
- version: "1.0"
18058
+ version: "1.0",
18059
+ tinycloudHosts: this.tinycloudHosts
17998
18060
  };
17999
18061
  await this.sessionStorage.save(address, persistedData);
18000
18062
  this._session = clientSession;
@@ -18084,6 +18146,9 @@ var NodeUserAuthorization = class {
18084
18146
  }
18085
18147
  };
18086
18148
 
18149
+ // src/account/AccountService.ts
18150
+ import { AccountService } from "@tinycloud/sdk-core";
18151
+
18087
18152
  // src/DelegatedAccess.ts
18088
18153
  import {
18089
18154
  KVService,
@@ -18501,13 +18566,13 @@ var NodeSecretsService = class {
18501
18566
  // src/TinyCloudNode.ts
18502
18567
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
18503
18568
  var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
18504
- var NETWORK_CREATE_ACTION = "tinycloud.encryption/network.create";
18505
- var DECRYPT_ACTION = "tinycloud.encryption/decrypt";
18569
+ var NETWORK_CREATE_ACTION2 = "tinycloud.encryption/network.create";
18570
+ var DECRYPT_ACTION2 = "tinycloud.encryption/decrypt";
18506
18571
  var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
18507
18572
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
18508
- function didPrincipalMatches(actual, expected) {
18573
+ function didPrincipalMatches2(actual, expected) {
18509
18574
  try {
18510
- return principalDidEquals(actual, expected);
18575
+ return principalDidEquals2(actual, expected);
18511
18576
  } catch {
18512
18577
  return actual === expected;
18513
18578
  }
@@ -18844,6 +18909,37 @@ var _TinyCloudNode = class _TinyCloudNode {
18844
18909
  get spaceId() {
18845
18910
  return this.auth?.tinyCloudSession?.spaceId;
18846
18911
  }
18912
+ /**
18913
+ * Get the account space ID for this wallet identity.
18914
+ * Available after wallet-backed sign-in or a restored session with address metadata.
18915
+ */
18916
+ get accountSpaceId() {
18917
+ if (!this._address) {
18918
+ return void 0;
18919
+ }
18920
+ return this.wasmBindings.makeSpaceId(this._address, this._chainId, ACCOUNT_REGISTRY_SPACE);
18921
+ }
18922
+ /**
18923
+ * Account-level application and delegation helpers.
18924
+ */
18925
+ get account() {
18926
+ if (!this._account) {
18927
+ this._account = new AccountService({
18928
+ getDid: () => this.did,
18929
+ getHost: () => this.hosts[0] ?? this.config.host,
18930
+ getPrimarySpaceId: () => this.spaceId,
18931
+ getAccountSpaceId: () => this.accountSpaceId,
18932
+ getSpaces: () => this.spaces,
18933
+ getAccountDb: () => this.accountSpaceId ? this.sqlForSpace(this.accountSpaceId).db("account") : void 0,
18934
+ ensureAccountSpaceHosted: async () => {
18935
+ if (this.accountSpaceId && this.auth) {
18936
+ await this.ensureOwnedSpaceHostedById(this.accountSpaceId);
18937
+ }
18938
+ }
18939
+ });
18940
+ }
18941
+ return this._account;
18942
+ }
18847
18943
  /**
18848
18944
  * Get the current TinyCloud session.
18849
18945
  * Available after signIn().
@@ -18881,10 +18977,11 @@ var _TinyCloudNode = class _TinyCloudNode {
18881
18977
  await this.tc.signIn(options);
18882
18978
  this.syncResolvedHostFromAuth();
18883
18979
  this.initializeServices();
18980
+ await this.ensureRequestedEncryptionNetworks();
18884
18981
  if (this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
18885
- await this.ensureOwnedSpaceHosted(this.ownedSpaceId("secrets"));
18982
+ await this.ensureOwnedSpaceHostedById(this.ownedSpaceId("secrets"));
18886
18983
  }
18887
- await this.writeManifestRegistryRecords();
18984
+ this.scheduleAccountRegistrySync();
18888
18985
  this.notificationHandler.success("Successfully signed in");
18889
18986
  }
18890
18987
  ownedSpaceId(name) {
@@ -18902,22 +18999,68 @@ var _TinyCloudNode = class _TinyCloudNode {
18902
18999
  throw new Error("Manifest registry write requires wallet mode");
18903
19000
  }
18904
19001
  const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE);
18905
- await this.ensureOwnedSpaceHosted(accountSpaceId);
18906
- const accountKV = this.spaces.get(accountSpaceId).kv;
18907
- for (const record of request.registryRecords) {
18908
- const result = await accountKV.put(record.key, {
18909
- app_id: record.app_id,
18910
- manifests: record.manifests,
18911
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
18912
- });
18913
- if (!result.ok) {
18914
- throw new Error(
18915
- `Failed to write manifest registry record ${record.key}: ${result.error.message}`
18916
- );
19002
+ await this.ensureOwnedSpaceHostedById(accountSpaceId);
19003
+ const result = await this.account.applications.register(request.manifests);
19004
+ if (!result.ok) {
19005
+ throw new Error(
19006
+ `Failed to write manifest registry records: ${result.error.message}`
19007
+ );
19008
+ }
19009
+ }
19010
+ scheduleAccountRegistrySync() {
19011
+ void this.withAccountRegistryRetry(async () => {
19012
+ await this.writeManifestRegistryRecords();
19013
+ const spaces = await this.account.spaces.syncAccessible();
19014
+ if (!spaces.ok) {
19015
+ throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
19016
+ }
19017
+ });
19018
+ }
19019
+ async withAccountRegistryRetry(task) {
19020
+ const delays = [250, 1e3, 3e3];
19021
+ let lastError;
19022
+ for (let attempt = 0; attempt < delays.length; attempt += 1) {
19023
+ try {
19024
+ await task();
19025
+ return;
19026
+ } catch (error) {
19027
+ lastError = error;
19028
+ if (attempt < delays.length - 1) {
19029
+ await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
19030
+ }
19031
+ }
19032
+ }
19033
+ console.warn(
19034
+ "TinyCloud account registry sync failed after retries",
19035
+ lastError
19036
+ );
19037
+ }
19038
+ requestedEncryptionNetworkIds() {
19039
+ const request = this.capabilityRequest;
19040
+ if (!request) {
19041
+ return [];
19042
+ }
19043
+ const networkIds = /* @__PURE__ */ new Set();
19044
+ for (const resource of request.resources) {
19045
+ if (resource.service === ENCRYPTION_PERMISSION_SERVICE2 && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
19046
+ networkIds.add(resource.path);
18917
19047
  }
18918
19048
  }
19049
+ return [...networkIds];
18919
19050
  }
18920
- async ensureOwnedSpaceHosted(spaceId) {
19051
+ async ensureRequestedEncryptionNetworks() {
19052
+ if (!this.signer || !this.auth) {
19053
+ return;
19054
+ }
19055
+ for (const networkId of this.requestedEncryptionNetworkIds()) {
19056
+ const parsed = parseNetworkId2(networkId);
19057
+ if (!didPrincipalMatches2(parsed.ownerDid, this.did)) {
19058
+ continue;
19059
+ }
19060
+ await this.ensureEncryptionNetwork(networkId);
19061
+ }
19062
+ }
19063
+ async ensureOwnedSpaceHostedById(spaceId) {
18921
19064
  if (!this.auth) {
18922
19065
  throw new Error("Owned space hosting requires wallet mode");
18923
19066
  }
@@ -18960,7 +19103,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18960
19103
  * caller is the root authority of their own owned spaces, so no additional
18961
19104
  * delegation is required.
18962
19105
  *
18963
- * Unlike {@link ensureOwnedSpaceHosted}, this always submits the host
19106
+ * Unlike {@link ensureOwnedSpaceHostedById}, this always submits the host
18964
19107
  * delegation rather than inferring hosting from session activation: a space
18965
19108
  * the current session has never referenced is reported neither as
18966
19109
  * `activated` nor `skipped`, so activation-based detection would wrongly
@@ -18994,8 +19137,40 @@ var _TinyCloudNode = class _TinyCloudNode {
18994
19137
  `Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
18995
19138
  );
18996
19139
  }
19140
+ void this.account.spaces.register({
19141
+ spaceId,
19142
+ name,
19143
+ ownerDid: this.did,
19144
+ type: "owned",
19145
+ permissions: ["*"],
19146
+ status: "active"
19147
+ }).catch(() => {
19148
+ });
18997
19149
  return spaceId;
18998
19150
  }
19151
+ /**
19152
+ * Ensure one of this user's owned spaces (e.g. `"secrets"`) is hosted on the
19153
+ * server.
19154
+ *
19155
+ * At sign-in, a full-authority session auto-hosts the owner's `secrets`
19156
+ * space, but a session created with a manifest / capabilityRequest does not.
19157
+ * Such a session can therefore hold valid `tinycloud.kv/*` capabilities for
19158
+ * the owned `secrets` space yet still fail its first scoped
19159
+ * `secrets.put(...)` with `404 Space not found`, because the space was never
19160
+ * registered on the node.
19161
+ *
19162
+ * Calling this resolves `name` to the owner's owned-space URI
19163
+ * (`tinycloud:pkh:eip155:<chain>:<addr>:<name>`) and hosts it via the
19164
+ * host-SIWE delegation flow. The host SIWE is idempotent server-side, so it
19165
+ * is safe to call whether or not the space already exists; do not gate it on
19166
+ * a prior existence check. Must be called after {@link signIn}.
19167
+ *
19168
+ * @param name - The owned space name (e.g. `"secrets"`).
19169
+ * @returns The hosted owned-space URI.
19170
+ */
19171
+ async ensureOwnedSpaceHosted(name) {
19172
+ return this.hostOwnedSpace(name);
19173
+ }
18999
19174
  /**
19000
19175
  * Restore a previously established session from stored delegation data.
19001
19176
  *
@@ -19025,6 +19200,14 @@ var _TinyCloudNode = class _TinyCloudNode {
19025
19200
  if (sessionData.chainId) {
19026
19201
  this._chainId = sessionData.chainId;
19027
19202
  }
19203
+ const resolvedHost = await this.resolveRestoredHost(
19204
+ sessionData.tinycloudHosts,
19205
+ restoredAddress,
19206
+ sessionData.chainId
19207
+ );
19208
+ if (resolvedHost) {
19209
+ this.config.host = resolvedHost;
19210
+ }
19028
19211
  this._serviceContext = new ServiceContext2({
19029
19212
  invoke: this.invokeWithRuntimePermissions,
19030
19213
  invokeAny: this.invokeAnyWithRuntimePermissions,
@@ -19070,12 +19253,45 @@ var _TinyCloudNode = class _TinyCloudNode {
19070
19253
  signature: sessionData.signature ?? ""
19071
19254
  };
19072
19255
  if (this.auth) {
19073
- this.auth.setRestoredTinyCloudSession(tcSession);
19256
+ this.auth.setRestoredTinyCloudSession(
19257
+ tcSession,
19258
+ this.config.host ? [this.config.host] : void 0
19259
+ );
19074
19260
  } else {
19075
19261
  this._restoredTcSession = tcSession;
19076
19262
  }
19077
19263
  }
19078
19264
  }
19265
+ /**
19266
+ * Resolve the host a restored session should target.
19267
+ *
19268
+ * Mirrors fresh sign-in host resolution but for the restore path:
19269
+ * an explicit/pinned host always wins, then the hosts the session was
19270
+ * persisted with, then a lazy registry/fallback resolution for sessions
19271
+ * that predate the persisted `tinycloudHosts` field. Returns `undefined`
19272
+ * only when there's nothing to resolve from (no explicit host, no
19273
+ * persisted hosts, and no address/chainId) — in which case the existing
19274
+ * `config.host` (default) is left in place.
19275
+ *
19276
+ * Resolution failures are surfaced, not swallowed: a genuinely broken
19277
+ * registry lookup throws rather than silently falling back to a wrong host.
19278
+ */
19279
+ async resolveRestoredHost(persistedHosts, address, chainId) {
19280
+ if (this.explicitHost) {
19281
+ return this.explicitHost;
19282
+ }
19283
+ if (persistedHosts && persistedHosts.length > 0) {
19284
+ return persistedHosts[0];
19285
+ }
19286
+ if (address === void 0 || chainId === void 0) {
19287
+ return void 0;
19288
+ }
19289
+ const resolved = await resolveTinyCloudHosts2(pkhDid2(address, chainId), {
19290
+ registryUrl: this.config.tinycloudRegistryUrl,
19291
+ fallbackHosts: this.config.tinycloudFallbackHosts
19292
+ });
19293
+ return resolved.hosts[0];
19294
+ }
19079
19295
  /**
19080
19296
  * Resolve the currently-active TinyCloudSession, preferring the auth
19081
19297
  * layer's value (wallet mode) and falling back to the node-level
@@ -19385,7 +19601,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19385
19601
  const signed2 = await this.signRawNetworkAuthorization({
19386
19602
  targetNode: input.targetNode,
19387
19603
  networkId: input.networkId,
19388
- action: DECRYPT_ACTION,
19604
+ action: DECRYPT_ACTION2,
19389
19605
  facts: input.facts
19390
19606
  });
19391
19607
  return {
@@ -19402,7 +19618,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19402
19618
  },
19403
19619
  wellKnown: {
19404
19620
  fetchWellKnown: async (principal, discoveryKey) => {
19405
- if (!this._address || !didPrincipalMatches(principal, this.did)) {
19621
+ if (!this._address || !didPrincipalMatches2(principal, this.did)) {
19406
19622
  return null;
19407
19623
  }
19408
19624
  if (!this.config.host) {
@@ -19611,6 +19827,9 @@ var _TinyCloudNode = class _TinyCloudNode {
19611
19827
  }
19612
19828
  };
19613
19829
  }
19830
+ },
19831
+ onSpaceRegistered: async (space) => {
19832
+ await this.account.spaces.register(space);
19614
19833
  }
19615
19834
  });
19616
19835
  this._sharingService.updateConfig({
@@ -19909,12 +20128,12 @@ var _TinyCloudNode = class _TinyCloudNode {
19909
20128
  crypto2.sha256,
19910
20129
  body
19911
20130
  ),
19912
- action: NETWORK_CREATE_ACTION
20131
+ action: NETWORK_CREATE_ACTION2
19913
20132
  };
19914
20133
  const signed2 = await this.signRawNetworkAuthorization({
19915
20134
  targetNode,
19916
20135
  networkId,
19917
- action: NETWORK_CREATE_ACTION,
20136
+ action: NETWORK_CREATE_ACTION2,
19918
20137
  facts
19919
20138
  });
19920
20139
  const response = await fetch(`${this.config.host}/encryption/networks`, {
@@ -19935,12 +20154,19 @@ var _TinyCloudNode = class _TinyCloudNode {
19935
20154
  const created = await response.json();
19936
20155
  return created.descriptor;
19937
20156
  }
19938
- async ensureEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
19939
- const existing = await this.getEncryptionNetwork(name);
20157
+ async ensureEncryptionNetwork(nameOrNetworkId = DEFAULT_ENCRYPTION_NETWORK_NAME) {
20158
+ const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
20159
+ const existing = await this.getEncryptionNetwork(networkId);
19940
20160
  if (existing) {
19941
20161
  return existing;
19942
20162
  }
19943
- return this.createEncryptionNetwork(name);
20163
+ const parsed = parseNetworkId2(networkId);
20164
+ if (!didPrincipalMatches2(parsed.ownerDid, this.did)) {
20165
+ throw new Error(
20166
+ `Cannot create encryption network ${networkId}: owner ${parsed.ownerDid} does not match signed-in DID ${this.did}`
20167
+ );
20168
+ }
20169
+ return this.createEncryptionNetwork(parsed.name);
19944
20170
  }
19945
20171
  /**
19946
20172
  * App-facing secrets API backed by the `secrets` space vault.
@@ -20088,7 +20314,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20088
20314
  throw new SessionExpiredError(delegation.expiry);
20089
20315
  }
20090
20316
  const expectedDids = [session.verificationMethod, this.sessionDid];
20091
- if (!expectedDids.some((did) => didPrincipalMatches(delegation.delegateDID, did))) {
20317
+ if (!expectedDids.some((did) => didPrincipalMatches2(delegation.delegateDID, did))) {
20092
20318
  throw new Error(
20093
20319
  `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
20094
20320
  );
@@ -20617,7 +20843,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20617
20843
  );
20618
20844
  }
20619
20845
  const target = request.delegationTargets.find(
20620
- (entry) => didPrincipalMatches(entry.did, did)
20846
+ (entry) => didPrincipalMatches2(entry.did, did)
20621
20847
  );
20622
20848
  if (!target) {
20623
20849
  throw new Error(`No manifest delegation target found for DID ${did}`);
@@ -21067,7 +21293,23 @@ var _TinyCloudNode = class _TinyCloudNode {
21067
21293
  if (granted.resource !== void 0 || requested.resource !== void 0) {
21068
21294
  return granted.resource !== void 0 && requested.resource !== void 0 && granted.resource === requested.resource && this.pathContains(granted.path, requested.path);
21069
21295
  }
21070
- return granted.spaceId !== void 0 && requested.spaceId !== void 0 && granted.spaceId === requested.spaceId && this.pathContains(granted.path, requested.path);
21296
+ return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
21297
+ }
21298
+ // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. The embedded
21299
+ // EIP-155 address is case-insensitive, but the CLI canonicalizes it to
21300
+ // lowercase when building a space URI while stored runtime delegations keep
21301
+ // the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
21302
+ // an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
21303
+ // segment and leave everything else (crucially the case-sensitive space NAME)
21304
+ // byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
21305
+ spaceIdsEqual(a, b) {
21306
+ return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
21307
+ }
21308
+ normalizeSpaceAddress(space) {
21309
+ return space.replace(
21310
+ /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
21311
+ (_match, prefix, addr) => prefix + addr.toLowerCase()
21312
+ );
21071
21313
  }
21072
21314
  actionContains(grantedAction, requestedAction) {
21073
21315
  if (grantedAction === requestedAction) {
@@ -21331,7 +21573,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21331
21573
  const targetHost = delegation.host ?? this.config.host;
21332
21574
  if (this.isSessionOnly) {
21333
21575
  const myDid = this.did;
21334
- if (!didPrincipalMatches(delegation.delegateDID, myDid)) {
21576
+ if (!didPrincipalMatches2(delegation.delegateDID, myDid)) {
21335
21577
  throw new Error(
21336
21578
  `Delegation targets ${delegation.delegateDID} but this user's DID is ${myDid}. The delegation must target this user's DID.`
21337
21579
  );
@@ -21562,7 +21804,7 @@ import {
21562
21804
  parsePkhDid,
21563
21805
  pkhDid as pkhDid3,
21564
21806
  principalDid,
21565
- principalDidEquals as principalDidEquals2,
21807
+ principalDidEquals as principalDidEquals3,
21566
21808
  parseCanonicalNetworkId
21567
21809
  } from "@tinycloud/sdk-core";
21568
21810
 
@@ -21709,6 +21951,32 @@ import {
21709
21951
  } from "@tinycloud/sdk-core";
21710
21952
 
21711
21953
  // src/delegation.ts
21954
+ async function grantAuthRequest(authority, request, options) {
21955
+ if (request.kind !== "tinycloud.auth.request") {
21956
+ throw new Error(
21957
+ `grantAuthRequest expects a tinycloud.auth.request artifact, got "${request.kind}".`
21958
+ );
21959
+ }
21960
+ if (!Array.isArray(request.requested) || request.requested.length === 0) {
21961
+ throw new Error("grantAuthRequest request has no requested capabilities.");
21962
+ }
21963
+ const expiry = options?.expiry ?? request.requestedExpiry;
21964
+ const result = await authority.delegateTo(
21965
+ request.sessionDid,
21966
+ request.requested,
21967
+ expiry !== void 0 ? { expiry } : void 0
21968
+ );
21969
+ return {
21970
+ kind: "tinycloud.auth.delegation",
21971
+ version: 1,
21972
+ requestId: request.requestId,
21973
+ delegationCid: result.delegation.cid,
21974
+ delegation: result.delegation,
21975
+ permissions: request.requested,
21976
+ expiry: result.delegation.expiry.toISOString(),
21977
+ prompted: result.prompted
21978
+ };
21979
+ }
21712
21980
  function serializeDelegation(delegation) {
21713
21981
  return JSON.stringify({
21714
21982
  ...delegation,
@@ -21749,7 +22017,7 @@ import {
21749
22017
  } from "@tinycloud/sdk-core";
21750
22018
  import {
21751
22019
  EncryptionService as EncryptionService2,
21752
- parseNetworkId,
22020
+ parseNetworkId as parseNetworkId3,
21753
22021
  buildNetworkId,
21754
22022
  isNetworkId,
21755
22023
  networkDiscoveryKey,
@@ -21784,7 +22052,7 @@ import {
21784
22052
  DEFAULT_KEY_VERSION,
21785
22053
  DECRYPT_FACT_TYPE,
21786
22054
  DECRYPT_RESULT_TYPE,
21787
- DECRYPT_ACTION as DECRYPT_ACTION2,
22055
+ DECRYPT_ACTION as DECRYPT_ACTION3,
21788
22056
  ENCRYPTION_SERVICE,
21789
22057
  ENCRYPTION_SERVICE_SHORT,
21790
22058
  encryptionError
@@ -21820,10 +22088,11 @@ import { ServiceContext as ServiceContext3 } from "@tinycloud/sdk-core";
21820
22088
  export {
21821
22089
  ACCOUNT_REGISTRY_PATH,
21822
22090
  ACCOUNT_REGISTRY_SPACE2 as ACCOUNT_REGISTRY_SPACE,
22091
+ AccountService,
21823
22092
  AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
21824
22093
  CapabilityKeyRegistry2 as CapabilityKeyRegistry,
21825
22094
  CapabilityKeyRegistryErrorCodes,
21826
- DECRYPT_ACTION2 as DECRYPT_ACTION,
22095
+ DECRYPT_ACTION3 as DECRYPT_ACTION,
21827
22096
  DECRYPT_FACT_TYPE,
21828
22097
  DECRYPT_RESULT_TYPE,
21829
22098
  DEFAULT_ENCRYPTION_ALG,
@@ -21919,6 +22188,7 @@ export {
21919
22188
  expandPermissionEntries2 as expandPermissionEntries,
21920
22189
  expandPermissionEntry,
21921
22190
  generateRandomReceiverKey,
22191
+ grantAuthRequest,
21922
22192
  hexDecode,
21923
22193
  hexEncode,
21924
22194
  isCapabilitySubset2 as isCapabilitySubset,
@@ -21931,12 +22201,12 @@ export {
21931
22201
  openWrappedKey,
21932
22202
  parseCanonicalNetworkId,
21933
22203
  parseExpiry2 as parseExpiry,
21934
- parseNetworkId,
22204
+ parseNetworkId3 as parseNetworkId,
21935
22205
  parsePkhDid,
21936
22206
  parseSpaceUri,
21937
22207
  pkhDid3 as pkhDid,
21938
22208
  principalDid,
21939
- principalDidEquals2 as principalDidEquals,
22209
+ principalDidEquals3 as principalDidEquals,
21940
22210
  resolveManifest2 as resolveManifest,
21941
22211
  resolveSecretListPrefix2 as resolveSecretListPrefix,
21942
22212
  resolveSecretPath2 as resolveSecretPath,