@tinycloud/node-sdk 2.7.0-beta.2 → 2.7.0-beta.4

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
@@ -17034,10 +17034,12 @@ import {
17034
17034
  computeCid,
17035
17035
  prepareSession,
17036
17036
  completeSessionSetup,
17037
+ validatePersistedSession,
17037
17038
  ensureEip55,
17038
17039
  makeSpaceId,
17039
17040
  createDelegation,
17040
17041
  parseRecapFromSiwe,
17042
+ parseVerifiedRecapFromSiwe,
17041
17043
  generateHostSIWEMessage,
17042
17044
  siweToDelegationHeaders,
17043
17045
  protocolVersion,
@@ -17062,6 +17064,7 @@ var _NodeWasmBindings = class _NodeWasmBindings {
17062
17064
  this.makeSpaceId = makeSpaceId;
17063
17065
  this.createDelegation = createDelegation;
17064
17066
  this.parseRecapFromSiwe = parseRecapFromSiwe;
17067
+ this.parseVerifiedRecapFromSiwe = parseVerifiedRecapFromSiwe;
17065
17068
  this.generateHostSIWEMessage = generateHostSIWEMessage;
17066
17069
  this.siweToDelegationHeaders = siweToDelegationHeaders;
17067
17070
  this.protocolVersion = protocolVersion;
@@ -17078,6 +17081,9 @@ var _NodeWasmBindings = class _NodeWasmBindings {
17078
17081
  _NodeWasmBindings.panicHookInitialized = true;
17079
17082
  }
17080
17083
  }
17084
+ validatePersistedSession(proof) {
17085
+ return validatePersistedSession(proof);
17086
+ }
17081
17087
  createSessionManager() {
17082
17088
  return new TCWSessionManager();
17083
17089
  }
@@ -17162,6 +17168,7 @@ import {
17162
17168
  HooksService as HooksService2,
17163
17169
  DataVaultService,
17164
17170
  EncryptionService,
17171
+ DecryptTransportResponseError,
17165
17172
  SecretsService,
17166
17173
  createVaultCrypto,
17167
17174
  ServiceContext as ServiceContext2,
@@ -17179,12 +17186,16 @@ import {
17179
17186
  TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
17180
17187
  bootstrapSteps,
17181
17188
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
17189
+ CaveatedDelegationUnsupportedError,
17182
17190
  PermissionNotInManifestError,
17183
17191
  SessionExpiredError,
17192
+ canonicalizeRecapCaveats,
17193
+ recapCaveatsEqual,
17184
17194
  expandPermissionEntries as expandPermissionEntriesCore,
17185
17195
  isCapabilitySubset,
17186
17196
  parseRecapCapabilities,
17187
17197
  SERVICE_LONG_TO_SHORT,
17198
+ SERVICE_SHORT_TO_LONG,
17188
17199
  KV as KV2,
17189
17200
  SQL as SQL2,
17190
17201
  DUCKDB as DUCKDB2,
@@ -17197,8 +17208,12 @@ import {
17197
17208
  pkhDid as pkhDid2,
17198
17209
  resolveTinyCloudHosts as resolveTinyCloudHosts2,
17199
17210
  principalDidEquals as principalDidEquals2,
17200
- parseNetworkId as parseNetworkId2
17211
+ parseNetworkId as parseNetworkId2,
17212
+ resolveSecretPath as resolveSecretPath2
17201
17213
  } from "@tinycloud/sdk-core";
17214
+ import {
17215
+ parsePermissionHint
17216
+ } from "@tinycloud/sdk-services";
17202
17217
 
17203
17218
  // src/authorization/NodeUserAuthorization.ts
17204
17219
  import {
@@ -17443,6 +17458,30 @@ var NodeUserAuthorization = class {
17443
17458
  this.tinycloudHosts = [...hosts];
17444
17459
  }
17445
17460
  }
17461
+ /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
17462
+ installRestoredSession(sessionManager, session, hosts) {
17463
+ this.sessionManager = sessionManager;
17464
+ this.tinycloudHosts = hosts && hosts.length > 0 ? [...hosts] : void 0;
17465
+ this._lastActivationSkippedSpaceIds = [];
17466
+ if (!session) {
17467
+ this._session = void 0;
17468
+ this._tinyCloudSession = void 0;
17469
+ this._address = void 0;
17470
+ this._chainId = void 0;
17471
+ return;
17472
+ }
17473
+ this._tinyCloudSession = { ...session };
17474
+ this._address = session.address;
17475
+ this._chainId = session.chainId;
17476
+ this._session = {
17477
+ address: session.address,
17478
+ walletAddress: session.address,
17479
+ chainId: session.chainId,
17480
+ sessionKey: session.sessionKey,
17481
+ siwe: session.siwe,
17482
+ signature: session.signature
17483
+ };
17484
+ }
17446
17485
  /**
17447
17486
  * Ensure `tinycloudHosts` are resolved before a host-needing call.
17448
17487
  *
@@ -18650,6 +18689,9 @@ var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
18650
18689
  var NETWORK_CREATE_ACTION2 = ENCRYPTION2.NETWORK_CREATE;
18651
18690
  var DECRYPT_ACTION2 = ENCRYPTION2.DECRYPT;
18652
18691
  var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
18692
+ function isSecretPayload(value) {
18693
+ return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "createdAt" in value && typeof value.createdAt === "string" && "updatedAt" in value && typeof value.updatedAt === "string";
18694
+ }
18653
18695
  var ROOT_DELEGATION_ACTIONS = [
18654
18696
  KV2.PUT,
18655
18697
  KV2.GET,
@@ -18704,6 +18746,141 @@ function didPrincipalMatches2(actual, expected) {
18704
18746
  return actual === expected;
18705
18747
  }
18706
18748
  }
18749
+ function clonePersistedSessionJwk(jwk) {
18750
+ if (jwk === null || typeof jwk !== "object" || Array.isArray(jwk)) {
18751
+ throw new Error("Persisted session has an invalid private Ed25519 session key.");
18752
+ }
18753
+ try {
18754
+ const serialized = JSON.stringify(jwk);
18755
+ const cloned = serialized === void 0 ? void 0 : JSON.parse(serialized);
18756
+ if (cloned === null || typeof cloned !== "object" || Array.isArray(cloned)) {
18757
+ throw new Error("invalid JWK object");
18758
+ }
18759
+ return cloned;
18760
+ } catch {
18761
+ throw new Error("Persisted session has an invalid private Ed25519 session key.");
18762
+ }
18763
+ }
18764
+ function cloneRecapCaveats(caveats) {
18765
+ if (!caveats) return [];
18766
+ const cloneJson = (value) => {
18767
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
18768
+ return value;
18769
+ }
18770
+ if (typeof value === "number") {
18771
+ if (Number.isFinite(value)) return value;
18772
+ throw new Error("ReCap caveats must contain only JSON values.");
18773
+ }
18774
+ if (Array.isArray(value)) return value.map(cloneJson);
18775
+ if (typeof value === "object") {
18776
+ const prototype = Object.getPrototypeOf(value);
18777
+ if (prototype !== Object.prototype && prototype !== null) {
18778
+ throw new Error("ReCap caveats must contain only JSON values.");
18779
+ }
18780
+ const copy = /* @__PURE__ */ Object.create(null);
18781
+ for (const [key2, nested] of Object.entries(value)) {
18782
+ copy[key2] = cloneJson(nested);
18783
+ }
18784
+ return copy;
18785
+ }
18786
+ throw new Error("ReCap caveats must contain only JSON values.");
18787
+ };
18788
+ try {
18789
+ return JSON.parse(JSON.stringify(caveats.map(cloneJson)));
18790
+ } catch {
18791
+ throw new Error("Verified persisted session ReCap contains invalid caveats.");
18792
+ }
18793
+ }
18794
+ var ServiceGraphLifetime = class {
18795
+ constructor(invokeFn, invokeAnyFn, fetchFn) {
18796
+ this.invokeFn = invokeFn;
18797
+ this.invokeAnyFn = invokeAnyFn;
18798
+ this.fetchFn = fetchFn;
18799
+ this.abortController = new AbortController();
18800
+ this.contexts = /* @__PURE__ */ new Set();
18801
+ this.retired = false;
18802
+ this.invoke = (session, service, path, action, facts) => {
18803
+ this.assertActive();
18804
+ return this.invokeFn(session, service, path, action, facts);
18805
+ };
18806
+ this.invokeAny = (session, entries, facts) => {
18807
+ this.assertActive();
18808
+ return this.invokeAnyFn(session, entries, facts);
18809
+ };
18810
+ this.fetch = (url, init2) => {
18811
+ this.assertActive();
18812
+ return this.fetchFn(url, {
18813
+ ...init2,
18814
+ signal: this.combineSignals(init2?.signal)
18815
+ });
18816
+ };
18817
+ }
18818
+ track(context) {
18819
+ this.assertActive();
18820
+ this.contexts.add(context);
18821
+ return context;
18822
+ }
18823
+ retire() {
18824
+ if (this.retired) return;
18825
+ this.retired = true;
18826
+ this.abortController.abort();
18827
+ try {
18828
+ for (const context of this.contexts) {
18829
+ try {
18830
+ const retire = context.retire;
18831
+ if (retire) retire.call(context);
18832
+ else context.abort();
18833
+ } catch (error) {
18834
+ console.error("Error retiring service graph context:", error);
18835
+ }
18836
+ }
18837
+ } finally {
18838
+ this.contexts.clear();
18839
+ }
18840
+ }
18841
+ assertActive() {
18842
+ if (this.retired) {
18843
+ throw new Error("Service graph has been retired by session replacement.");
18844
+ }
18845
+ }
18846
+ combineSignals(signal) {
18847
+ if (!signal) return this.abortController.signal;
18848
+ const combined = new AbortController();
18849
+ const abort = () => combined.abort();
18850
+ if (signal.aborted || this.abortController.signal.aborted) {
18851
+ abort();
18852
+ } else {
18853
+ signal.addEventListener("abort", abort, { once: true });
18854
+ this.abortController.signal.addEventListener("abort", abort, { once: true });
18855
+ }
18856
+ return combined.signal;
18857
+ }
18858
+ };
18859
+ var UnsupportedSessionRestoreError = class extends Error {
18860
+ constructor(reason = "it cannot replace and enumerate every live session signer") {
18861
+ super(`Persisted session restore is unsupported by this WASM binding because ${reason}.`);
18862
+ this.code = "RESTORE_SESSION_KEY_REPLACEMENT_UNSUPPORTED";
18863
+ this.name = "UnsupportedSessionRestoreError";
18864
+ }
18865
+ };
18866
+ function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
18867
+ if (typeof persistedVerificationMethod !== "string") return void 0;
18868
+ const principal = canonicalVerificationMethod.split("#", 1)[0];
18869
+ return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
18870
+ }
18871
+ function persistedExpiry(value) {
18872
+ if (typeof value !== "string") {
18873
+ throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
18874
+ }
18875
+ const expiry = new Date(value);
18876
+ if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
18877
+ throw new Error("Persisted session expiry is invalid or expired.");
18878
+ }
18879
+ return expiry;
18880
+ }
18881
+ function sameInstant(left, right) {
18882
+ return left.getTime() === right.getTime();
18883
+ }
18707
18884
  function sharingActionsToAbilities(path, actions) {
18708
18885
  var _a;
18709
18886
  const abilities = {};
@@ -18830,6 +19007,7 @@ var _TinyCloudNode = class _TinyCloudNode {
18830
19007
  this.auth = null;
18831
19008
  this.tc = null;
18832
19009
  this._chainId = 1;
19010
+ this._baseVaults = /* @__PURE__ */ new Map();
18833
19011
  this._baseSecrets = /* @__PURE__ */ new Map();
18834
19012
  this._secrets = /* @__PURE__ */ new Map();
18835
19013
  this.runtimePermissionGrants = [];
@@ -18849,26 +19027,55 @@ var _TinyCloudNode = class _TinyCloudNode {
18849
19027
  skipped: false
18850
19028
  };
18851
19029
  this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
18852
- return this.wasmBindings.invoke(
18853
- this.selectInvocationSession(session, service, path, action),
18854
- service,
19030
+ const operation = {
19031
+ spaceId: session.spaceId,
19032
+ service: this.invocationServiceName(service),
18855
19033
  path,
18856
- action,
18857
- facts
19034
+ action
19035
+ };
19036
+ const grant = this.findGrantForOperation(operation);
19037
+ const grantedOperation = grant?.operations.find(
19038
+ (candidate) => this.operationCovers(candidate, operation)
18858
19039
  );
19040
+ const invocationSession = !grant || grant.provenance === "primary" ? session : grant.session;
19041
+ if ((grantedOperation?.caveats?.length ?? 0) > 0) {
19042
+ if (!this.wasmBindings.invokeAny) {
19043
+ throw new Error("WASM binding does not support caveat-preserving invocation");
19044
+ }
19045
+ return this.wasmBindings.invokeAny(invocationSession, [{
19046
+ spaceId: invocationSession.spaceId,
19047
+ service,
19048
+ path,
19049
+ action,
19050
+ caveats: cloneRecapCaveats(grantedOperation.caveats)
19051
+ }], facts);
19052
+ }
19053
+ return this.wasmBindings.invoke(invocationSession, service, path, action, facts);
18859
19054
  };
18860
19055
  this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
18861
19056
  if (!this.wasmBindings.invokeAny) {
18862
19057
  throw new Error("WASM binding does not support invokeAny");
18863
19058
  }
18864
- const grant = this.findGrantForOperations(
18865
- entries.flatMap((entry) => {
18866
- const operation = this.operationFromInvokeAnyEntry(entry);
18867
- return operation ? [operation] : [];
18868
- })
18869
- );
19059
+ const operations = entries.flatMap((entry) => {
19060
+ const operation = this.operationFromInvokeAnyEntry(entry);
19061
+ return operation ? [operation] : [];
19062
+ });
19063
+ const grant = this.findGrantForOperations(operations);
18870
19064
  const invocationSession = !grant || grant.provenance === "primary" ? session : grant.session;
18871
- return this.wasmBindings.invokeAny(invocationSession, entries, facts);
19065
+ const caveatPreservingEntries = grant ? entries.map((entry) => {
19066
+ const requested = this.operationFromInvokeAnyEntry(entry);
19067
+ const granted = requested && grant.operations.find(
19068
+ (candidate) => this.operationCovers(candidate, requested)
19069
+ );
19070
+ if (!granted?.caveats?.length) {
19071
+ return entry;
19072
+ }
19073
+ if (entry.caveats !== void 0 && !recapCaveatsEqual(entry.caveats, granted.caveats)) {
19074
+ throw new Error("Invocation caveats do not match signed ReCap authority.");
19075
+ }
19076
+ return { ...entry, caveats: cloneRecapCaveats(granted.caveats) };
19077
+ }) : entries;
19078
+ return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
18872
19079
  };
18873
19080
  this.explicitHost = config.host;
18874
19081
  this.config = {
@@ -18897,16 +19104,19 @@ var _TinyCloudNode = class _TinyCloudNode {
18897
19104
  throw new Error("Failed to get session key JWK");
18898
19105
  }
18899
19106
  this.sessionKeyJwk = JSON.parse(jwkStr);
19107
+ this._serviceGraph = this.createServiceGraphLifetime();
18900
19108
  this._capabilityRegistry = new CapabilityKeyRegistry();
18901
19109
  this._keyProvider = new WasmKeyProvider({
18902
19110
  sessionManager: this.sessionManager
18903
19111
  });
18904
19112
  this.notificationHandler = config.notificationHandler ?? new SilentNotificationHandler();
19113
+ const receiveOnlyGraph = this._serviceGraph;
18905
19114
  this._sharingService = new SharingService({
18906
19115
  hosts: [this.config.host],
18907
19116
  // session: undefined - not needed for receive()
18908
- invoke: this.invokeWithRuntimePermissions,
18909
- fetch: globalThis.fetch.bind(globalThis),
19117
+ invoke: receiveOnlyGraph.invoke,
19118
+ fetch: receiveOnlyGraph.fetch,
19119
+ assertActive: () => receiveOnlyGraph.assertActive(),
18910
19120
  keyProvider: this._keyProvider,
18911
19121
  registry: this._capabilityRegistry,
18912
19122
  createDelegationWasm: (params) => this.createDelegationWrapper(params),
@@ -18918,12 +19128,12 @@ var _TinyCloudNode = class _TinyCloudNode {
18918
19128
  createKVService: (config2) => {
18919
19129
  const prefix = config2.pathPrefix?.replace(/\/$/, "");
18920
19130
  const kvService = new KVService2({ prefix });
18921
- const kvContext = new ServiceContext2({
19131
+ const kvContext = receiveOnlyGraph.track(new ServiceContext2({
18922
19132
  invoke: config2.invoke,
18923
19133
  fetch: config2.fetch ?? globalThis.fetch.bind(globalThis),
18924
19134
  hosts: config2.hosts,
18925
19135
  telemetry: this.config.telemetry
18926
- });
19136
+ }));
18927
19137
  kvContext.setSession(config2.session);
18928
19138
  kvService.initialize(kvContext);
18929
19139
  return kvService;
@@ -18997,6 +19207,16 @@ var _TinyCloudNode = class _TinyCloudNode {
18997
19207
  shouldUseBootstrapSignInRequest(config) {
18998
19208
  return config.autoBootstrapAccount !== false && config.manifest === void 0 && config.capabilityRequest === void 0 && (config.prefix ?? "default") === "default" && isOpenKeyAutoSignStrategy(config.signStrategy);
18999
19209
  }
19210
+ createServiceGraphLifetime() {
19211
+ return new ServiceGraphLifetime(
19212
+ this.invokeWithRuntimePermissions,
19213
+ this.invokeAnyWithRuntimePermissions,
19214
+ // Resolve fetch at request time. This keeps the graph-owned lifetime
19215
+ // signal while preserving the SDK's existing injectable global fetch
19216
+ // behavior (including adapters that install it after construction).
19217
+ (url, init2) => globalThis.fetch(url, init2)
19218
+ );
19219
+ }
19000
19220
  syncResolvedHostFromAuth() {
19001
19221
  const host = this.auth?.hosts[0];
19002
19222
  if (host) {
@@ -19066,6 +19286,27 @@ var _TinyCloudNode = class _TinyCloudNode {
19066
19286
  get sessionDid() {
19067
19287
  return this.sessionManager.getDID(this.sessionKeyId);
19068
19288
  }
19289
+ /**
19290
+ * Return the current session's signed ReCap capabilities after the session
19291
+ * has been authenticated or restored. This is intentionally distinct from
19292
+ * installed runtime delegations: it reports base-session authority only.
19293
+ *
19294
+ * Invalid or unparseable signed ReCap material throws so callers fail closed
19295
+ * rather than treating a malformed session as unrestricted authority.
19296
+ */
19297
+ getVerifiedSessionCapabilities() {
19298
+ const session = this.currentTinyCloudSession();
19299
+ if (!session || !session.siwe) return [];
19300
+ return parseRecapCapabilities(
19301
+ (siwe) => this.parseRecapWithCaveats(siwe),
19302
+ session.siwe
19303
+ ).map((entry) => entry.service === "tinycloud.encryption" ? {
19304
+ service: entry.service,
19305
+ path: entry.path,
19306
+ actions: [...entry.actions],
19307
+ ...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
19308
+ } : entry);
19309
+ }
19069
19310
  /**
19070
19311
  * Get the Ethereum address for this user.
19071
19312
  */
@@ -19108,6 +19349,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19108
19349
  getPrimarySpaceId: () => this.spaceId,
19109
19350
  getAccountSpaceId: () => this.accountSpaceId,
19110
19351
  getSpaces: () => this.spaces,
19352
+ getDelegationManager: () => this.delegationManager,
19111
19353
  getAccountDb: () => this.accountSpaceId ? this.sqlForSpace(this.accountSpaceId).db("account") : void 0,
19112
19354
  ensureAccountSpaceHosted: async () => {
19113
19355
  if (this.accountSpaceId && this.auth) {
@@ -19154,6 +19396,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19154
19396
  this._hooks = void 0;
19155
19397
  this._vault = void 0;
19156
19398
  this._encryption = void 0;
19399
+ this._baseVaults.clear();
19157
19400
  this._baseSecrets.clear();
19158
19401
  this._secrets.clear();
19159
19402
  this._spaceService = void 0;
@@ -19161,6 +19404,10 @@ var _TinyCloudNode = class _TinyCloudNode {
19161
19404
  this.runtimePermissionGrants = [];
19162
19405
  await this.tc.signIn(options);
19163
19406
  this.syncResolvedHostFromAuth();
19407
+ const oldGraph = this._serviceGraph;
19408
+ this._serviceGraph = this.createServiceGraphLifetime();
19409
+ oldGraph.retire();
19410
+ this.tc.retireServices();
19164
19411
  const signedInSession = this.currentTinyCloudSession();
19165
19412
  if (signedInSession) {
19166
19413
  this.sessionKeyId = signedInSession.sessionKey;
@@ -19445,7 +19692,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19445
19692
  }
19446
19693
  let operations = [];
19447
19694
  try {
19448
- const entries = this.wasmBindings.parseRecapFromSiwe(siwe);
19695
+ const entries = this.parseRecapWithCaveats(siwe);
19449
19696
  if (Array.isArray(entries)) {
19450
19697
  operations = entries.flatMap((entry) => {
19451
19698
  const service = this.invocationServiceName(entry.service);
@@ -19453,7 +19700,8 @@ var _TinyCloudNode = class _TinyCloudNode {
19453
19700
  ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
19454
19701
  service,
19455
19702
  path: entry.path,
19456
- action
19703
+ action,
19704
+ caveats: cloneRecapCaveats(entry.caveats)
19457
19705
  }));
19458
19706
  });
19459
19707
  }
@@ -19806,86 +20054,441 @@ var _TinyCloudNode = class _TinyCloudNode {
19806
20054
  */
19807
20055
  async restoreSession(sessionData) {
19808
20056
  await this.wasmBindings.ensureInitialized?.();
19809
- this._kv = void 0;
19810
- this._sql = void 0;
19811
- this._duckdb = void 0;
19812
- this._hooks = void 0;
19813
- this._vault = void 0;
19814
- this._encryption = void 0;
19815
- this._baseSecrets.clear();
19816
- this._secrets.clear();
19817
- this._spaceService = void 0;
19818
- this._serviceContext = void 0;
19819
- this.runtimePermissionGrants = [];
19820
- const restoredAddress = sessionData.address ? canonicalizeAddress2(sessionData.address) : void 0;
19821
- if (restoredAddress) {
19822
- this._address = restoredAddress;
20057
+ const restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
20058
+ if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
20059
+ throw new Error("Persisted session chain ID must be a positive safe integer.");
20060
+ }
20061
+ const stagedManager = this.wasmBindings.createSessionManager();
20062
+ const stagedReplace = stagedManager.replaceSessionKey;
20063
+ const liveKeys = this.sessionManager.listSessionKeys;
20064
+ const stagedKeys = stagedManager.listSessionKeys;
20065
+ if (typeof stagedReplace !== "function" || typeof liveKeys !== "function" || typeof stagedKeys !== "function") {
20066
+ throw new UnsupportedSessionRestoreError();
20067
+ }
20068
+ let stagedKeyId;
20069
+ let stagedJwk;
20070
+ let canonicalVerificationMethod;
20071
+ try {
20072
+ const keyIds = liveKeys.call(this.sessionManager);
20073
+ if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
20074
+ throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
20075
+ }
20076
+ for (const keyId of keyIds) {
20077
+ if (keyId === this.sessionKeyId) continue;
20078
+ const jwk = this.sessionManager.jwk(keyId);
20079
+ if (!jwk) throw new Error("missing live session key");
20080
+ stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
20081
+ }
20082
+ stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
20083
+ const stagedJwkJson = stagedManager.jwk(stagedKeyId);
20084
+ if (!stagedJwkJson) throw new Error("missing restored session key");
20085
+ stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
20086
+ canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
20087
+ } catch (error) {
20088
+ if (error instanceof UnsupportedSessionRestoreError) throw error;
20089
+ throw new Error("Persisted session has an invalid private Ed25519 session key.");
19823
20090
  }
19824
- if (sessionData.chainId) {
19825
- this._chainId = sessionData.chainId;
20091
+ const restoredVerificationMethod = canonicalRestoredVerificationMethod(
20092
+ canonicalVerificationMethod,
20093
+ sessionData.verificationMethod
20094
+ );
20095
+ if (!restoredVerificationMethod) {
20096
+ throw new Error(
20097
+ "Persisted session verification method does not match its private Ed25519 session key."
20098
+ );
20099
+ }
20100
+ const proofValues = [
20101
+ sessionData.siwe,
20102
+ sessionData.signature,
20103
+ sessionData.address,
20104
+ sessionData.chainId
20105
+ ];
20106
+ const hasPersistedProof = proofValues.every((value) => value !== void 0);
20107
+ if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
20108
+ throw new Error("Persisted session authority metadata is incomplete.");
20109
+ }
20110
+ const restoredAddress = hasPersistedProof ? canonicalizeAddress2(sessionData.address) : void 0;
20111
+ let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
20112
+ let stagedRecap = [];
20113
+ if (hasPersistedProof) {
20114
+ if (typeof this.wasmBindings.validatePersistedSession !== "function") {
20115
+ throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
20116
+ }
20117
+ const verified = this.wasmBindings.validatePersistedSession({
20118
+ delegationHeader: sessionData.delegationHeader,
20119
+ delegationCid: sessionData.delegationCid,
20120
+ spaceId: sessionData.spaceId,
20121
+ jwk: stagedJwk,
20122
+ address: restoredAddress,
20123
+ chainId: sessionData.chainId,
20124
+ siwe: sessionData.siwe,
20125
+ signature: sessionData.signature
20126
+ });
20127
+ const exactRecap = verified.verifiedRecap;
20128
+ if (!Array.isArray(exactRecap) || !exactRecap.every(
20129
+ (entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
20130
+ (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
20131
+ )
20132
+ )) {
20133
+ throw new UnsupportedSessionRestoreError(
20134
+ "it cannot reconstruct caveat-preserving persisted ReCap authority"
20135
+ );
20136
+ }
20137
+ stagedRecap = exactRecap.map((entry) => ({
20138
+ service: entry.service,
20139
+ space: entry.space,
20140
+ path: entry.path,
20141
+ actions: [...entry.actions],
20142
+ caveats: cloneRecapCaveats(entry.caveats)
20143
+ }));
20144
+ const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
20145
+ const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
20146
+ if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
20147
+ throw new Error("Persisted session expiry does not match its signed SIWE authority.");
20148
+ }
20149
+ stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
19826
20150
  }
19827
20151
  const resolvedHost = await this.resolveRestoredHost(
19828
20152
  sessionData.tinycloudHosts,
19829
20153
  restoredAddress,
19830
20154
  sessionData.chainId
19831
20155
  );
19832
- if (resolvedHost) {
19833
- this.config.host = resolvedHost;
19834
- }
19835
- this._serviceContext = new ServiceContext2({
19836
- invoke: this.invokeWithRuntimePermissions,
19837
- invokeAny: this.invokeAnyWithRuntimePermissions,
19838
- fetch: globalThis.fetch.bind(globalThis),
19839
- hosts: [this.config.host],
19840
- telemetry: this.config.telemetry
19841
- });
19842
- this._kv = new KVService2({});
19843
- this._kv.initialize(this._serviceContext);
19844
- this._serviceContext.registerService("kv", this._kv);
19845
- this._sql = new SQLService2({});
19846
- this._sql.initialize(this._serviceContext);
19847
- this._serviceContext.registerService("sql", this._sql);
19848
- this._duckdb = new DuckDbService2({});
19849
- this._duckdb.initialize(this._serviceContext);
19850
- this._serviceContext.registerService("duckdb", this._duckdb);
19851
- this._hooks = new HooksService2({});
19852
- this._hooks.initialize(this._serviceContext);
19853
- this._serviceContext.registerService("hooks", this._hooks);
20156
+ const stagedHost = resolvedHost ?? this.config.host;
20157
+ const stagedAddress = restoredAddress;
20158
+ const stagedChainId = sessionData.chainId ?? 1;
20159
+ const stagedNodeDid = stagedAddress ? pkhDid2(stagedAddress, stagedChainId) : canonicalVerificationMethod;
19854
20160
  const serviceSession = {
19855
20161
  delegationHeader: sessionData.delegationHeader,
19856
20162
  delegationCid: sessionData.delegationCid,
19857
20163
  spaceId: sessionData.spaceId,
19858
- verificationMethod: sessionData.verificationMethod,
19859
- jwk: sessionData.jwk
20164
+ verificationMethod: restoredVerificationMethod,
20165
+ jwk: stagedJwk
19860
20166
  };
19861
- this._serviceContext.setSession(serviceSession);
19862
- this._vault = this.createVaultService(sessionData.spaceId, this._kv);
19863
- this._vault.initialize(this._serviceContext);
19864
- this._serviceContext.registerService("vault", this._vault);
19865
- this.initializeV2Services(serviceSession);
19866
- if (sessionData.siwe && restoredAddress && sessionData.chainId) {
19867
- const tcSession = {
19868
- address: restoredAddress,
19869
- chainId: sessionData.chainId,
19870
- sessionKey: JSON.stringify(sessionData.jwk),
19871
- spaceId: sessionData.spaceId,
19872
- delegationCid: sessionData.delegationCid,
19873
- delegationHeader: sessionData.delegationHeader,
19874
- verificationMethod: sessionData.verificationMethod,
19875
- jwk: sessionData.jwk,
19876
- siwe: sessionData.siwe,
19877
- signature: sessionData.signature ?? ""
19878
- };
19879
- if (this.auth) {
19880
- this.auth.setRestoredTinyCloudSession(
19881
- tcSession,
19882
- this.config.host ? [this.config.host] : void 0
20167
+ const stagedTcSession = hasPersistedProof ? {
20168
+ address: restoredAddress,
20169
+ chainId: sessionData.chainId,
20170
+ sessionKey: JSON.stringify(stagedJwk),
20171
+ spaceId: sessionData.spaceId,
20172
+ spaces: sessionData.spaces,
20173
+ delegationCid: sessionData.delegationCid,
20174
+ delegationHeader: sessionData.delegationHeader,
20175
+ verificationMethod: restoredVerificationMethod,
20176
+ jwk: stagedJwk,
20177
+ siwe: sessionData.siwe,
20178
+ signature: sessionData.signature
20179
+ } : void 0;
20180
+ const stagedPrimary = this.stagePrimarySessionState(
20181
+ stagedTcSession,
20182
+ stagedNodeDid,
20183
+ stagedHost,
20184
+ stagedSessionExpiry,
20185
+ stagedRecap
20186
+ );
20187
+ const stagedGraph = this.stageRestoredServiceGraph({
20188
+ host: stagedHost,
20189
+ manager: stagedManager,
20190
+ serviceSession,
20191
+ verificationMethod: canonicalVerificationMethod,
20192
+ nodeDid: stagedNodeDid,
20193
+ address: stagedAddress,
20194
+ chainId: stagedChainId,
20195
+ tinyCloudSession: stagedTcSession,
20196
+ sessionExpiry: stagedSessionExpiry,
20197
+ recap: stagedRecap
20198
+ });
20199
+ const oldGraph = this._serviceGraph;
20200
+ const oldCore = this.tc;
20201
+ this.sessionManager = stagedManager;
20202
+ this.sessionKeyId = stagedKeyId;
20203
+ this.sessionKeyJwk = stagedJwk;
20204
+ if (resolvedHost) this.config.host = resolvedHost;
20205
+ this._address = stagedAddress;
20206
+ this._chainId = stagedChainId;
20207
+ this._serviceContext = stagedGraph.serviceContext;
20208
+ this._kv = stagedGraph.kv;
20209
+ this._sql = stagedGraph.sql;
20210
+ this._duckdb = stagedGraph.duckdb;
20211
+ this._hooks = stagedGraph.hooks;
20212
+ this._vault = stagedGraph.vault;
20213
+ this._encryption = stagedGraph.encryption;
20214
+ this._capabilityRegistry = stagedGraph.capabilityRegistry;
20215
+ this._keyProvider = stagedGraph.keyProvider;
20216
+ this._sharingService = stagedGraph.sharingService;
20217
+ this._delegationManager = stagedGraph.delegationManager;
20218
+ this._spaceService = stagedGraph.spaceService;
20219
+ this._serviceGraph = stagedGraph.graph;
20220
+ this._baseSecrets = /* @__PURE__ */ new Map();
20221
+ this._secrets = /* @__PURE__ */ new Map();
20222
+ this._publicKV = void 0;
20223
+ this._account = void 0;
20224
+ this.tc = stagedGraph.core;
20225
+ this._recapOperationsCache = stagedPrimary.cache;
20226
+ this.runtimePermissionGrants = stagedPrimary.grants;
20227
+ if (this.auth) {
20228
+ this.auth.installRestoredSession(stagedManager, stagedTcSession, [stagedHost]);
20229
+ this._restoredTcSession = void 0;
20230
+ } else {
20231
+ this._restoredTcSession = stagedTcSession;
20232
+ }
20233
+ oldGraph.retire();
20234
+ oldCore?.retireServices?.();
20235
+ }
20236
+ stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
20237
+ if (!session?.siwe) return { cache: void 0, grants: [] };
20238
+ let operations = [];
20239
+ operations = recap.flatMap((entry) => {
20240
+ const service = this.invocationServiceName(entry.service);
20241
+ return entry.actions.map((action) => ({
20242
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
20243
+ service,
20244
+ path: entry.path,
20245
+ action,
20246
+ caveats: cloneRecapCaveats(entry.caveats)
20247
+ }));
20248
+ });
20249
+ const cache = { siwe: session.siwe, operations };
20250
+ if (operations.length === 0) return { cache, grants: [] };
20251
+ const expiresAt = sessionExpiry;
20252
+ return {
20253
+ cache,
20254
+ grants: [{
20255
+ session: {
20256
+ delegationHeader: session.delegationHeader,
20257
+ delegationCid: session.delegationCid,
20258
+ spaceId: session.spaceId,
20259
+ verificationMethod: session.verificationMethod,
20260
+ jwk: session.jwk
20261
+ },
20262
+ delegation: {
20263
+ cid: session.delegationCid,
20264
+ delegationHeader: session.delegationHeader,
20265
+ delegateDID: session.verificationMethod,
20266
+ delegatorDID: verificationMethod,
20267
+ spaceId: session.spaceId,
20268
+ path: "",
20269
+ actions: [...new Set(operations.map((operation) => operation.action))],
20270
+ expiry: expiresAt,
20271
+ allowSubDelegation: true,
20272
+ ownerAddress: session.address,
20273
+ chainId: session.chainId,
20274
+ host
20275
+ },
20276
+ operations,
20277
+ expiresAt,
20278
+ provenance: "primary"
20279
+ }]
20280
+ };
20281
+ }
20282
+ stageRestoredServiceGraph(input) {
20283
+ const graph = this.createServiceGraphLifetime();
20284
+ const core = this.auth ? new TinyCloud(this.auth, {
20285
+ invokeAny: graph.invokeAny,
20286
+ telemetry: this.config.telemetry
20287
+ }) : null;
20288
+ if (core) {
20289
+ core.initializeServices(graph.invoke, [input.host], graph.fetch);
20290
+ const coreContext = graph.track(core.serviceContext);
20291
+ coreContext.setSession(input.serviceSession);
20292
+ }
20293
+ const serviceContext = graph.track(new ServiceContext2({
20294
+ invoke: graph.invoke,
20295
+ invokeAny: graph.invokeAny,
20296
+ fetch: graph.fetch,
20297
+ hosts: [input.host],
20298
+ telemetry: this.config.telemetry
20299
+ }));
20300
+ const kv = new KVService2({});
20301
+ kv.initialize(serviceContext);
20302
+ serviceContext.registerService("kv", kv);
20303
+ const sql = new SQLService2({});
20304
+ sql.initialize(serviceContext);
20305
+ serviceContext.registerService("sql", sql);
20306
+ const duckdb = new DuckDbService2({});
20307
+ duckdb.initialize(serviceContext);
20308
+ serviceContext.registerService("duckdb", duckdb);
20309
+ const hooks = new HooksService2({});
20310
+ hooks.initialize(serviceContext);
20311
+ serviceContext.registerService("hooks", hooks);
20312
+ serviceContext.setSession(input.serviceSession);
20313
+ const encryption = this.createEncryptionService({
20314
+ graph,
20315
+ host: input.host,
20316
+ session: input.serviceSession,
20317
+ did: input.nodeDid,
20318
+ address: input.address,
20319
+ chainId: input.chainId
20320
+ });
20321
+ encryption.initialize(serviceContext);
20322
+ serviceContext.registerService("encryption", encryption);
20323
+ const vault = this.createVaultService(input.serviceSession.spaceId, kv, encryption, {
20324
+ host: input.host,
20325
+ did: input.nodeDid,
20326
+ address: input.address,
20327
+ chainId: input.chainId,
20328
+ serviceGraph: graph
20329
+ });
20330
+ vault.initialize(serviceContext);
20331
+ serviceContext.registerService("vault", vault);
20332
+ const capabilityRegistry = new CapabilityKeyRegistry();
20333
+ if (input.tinyCloudSession) {
20334
+ const session = input.tinyCloudSession;
20335
+ const delegations = input.recap.map((entry) => ({
20336
+ cid: session.delegationCid,
20337
+ delegateDID: session.verificationMethod,
20338
+ spaceId: entry.space,
20339
+ path: entry.path,
20340
+ actions: [...entry.actions],
20341
+ caveats: cloneRecapCaveats(entry.caveats),
20342
+ expiry: input.sessionExpiry,
20343
+ isRevoked: false,
20344
+ allowSubDelegation: true
20345
+ }));
20346
+ if (delegations.length > 0) {
20347
+ capabilityRegistry.registerKey({
20348
+ id: session.sessionKey,
20349
+ did: session.verificationMethod,
20350
+ type: "session",
20351
+ jwk: session.jwk,
20352
+ priority: 0
20353
+ }, delegations);
20354
+ }
20355
+ }
20356
+ const delegationManager = new DelegationManager({
20357
+ hosts: [input.host],
20358
+ session: input.serviceSession,
20359
+ invoke: graph.invoke,
20360
+ invokeAny: graph.invokeAny,
20361
+ fetch: graph.fetch
20362
+ });
20363
+ const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
20364
+ const sharingService = new SharingService({
20365
+ hosts: [input.host],
20366
+ invoke: graph.invoke,
20367
+ fetch: graph.fetch,
20368
+ assertActive: () => graph.assertActive(),
20369
+ keyProvider,
20370
+ registry: capabilityRegistry,
20371
+ createDelegationWasm: (params) => this.createDelegationWrapper(params),
20372
+ computeCid: (data, codec) => {
20373
+ if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
20374
+ return this.wasmBindings.computeCid(data, codec);
20375
+ },
20376
+ createKVService: (config) => {
20377
+ const service = new KVService2({ prefix: config.pathPrefix?.replace(/\/$/, "") });
20378
+ const context = graph.track(new ServiceContext2({
20379
+ invoke: config.invoke,
20380
+ fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
20381
+ hosts: config.hosts,
20382
+ telemetry: this.config.telemetry
20383
+ }));
20384
+ context.setSession(config.session);
20385
+ service.initialize(context);
20386
+ return service;
20387
+ }
20388
+ });
20389
+ sharingService.updateConfig({
20390
+ session: input.serviceSession,
20391
+ delegationManager,
20392
+ sessionExpiry: input.sessionExpiry,
20393
+ createDelegationWasm: (params) => {
20394
+ graph.assertActive();
20395
+ return this.createDelegationWrapper(params);
20396
+ },
20397
+ onRootDelegationNeeded: this.signer ? async (params) => {
20398
+ graph.assertActive();
20399
+ const delegation = await this.createRootDelegationForSharing(
20400
+ params,
20401
+ () => graph.assertActive()
19883
20402
  );
19884
- } else {
19885
- this._restoredTcSession = tcSession;
20403
+ graph.assertActive();
20404
+ return delegation;
20405
+ } : void 0
20406
+ });
20407
+ const spaceService = new SpaceService({
20408
+ hosts: [input.host],
20409
+ session: input.serviceSession,
20410
+ invoke: graph.invoke,
20411
+ fetch: graph.fetch,
20412
+ capabilityRegistry,
20413
+ userDid: input.nodeDid,
20414
+ createKVService: (spaceId) => {
20415
+ graph.assertActive();
20416
+ const scopedKv = new KVService2({});
20417
+ const context = graph.track(new ServiceContext2({
20418
+ invoke: graph.invoke,
20419
+ invokeAny: graph.invokeAny,
20420
+ fetch: graph.fetch,
20421
+ hosts: [input.host],
20422
+ telemetry: this.config.telemetry
20423
+ }));
20424
+ context.setSession({ ...input.serviceSession, spaceId });
20425
+ scopedKv.initialize(context);
20426
+ return scopedKv;
20427
+ },
20428
+ createVaultService: (spaceId) => {
20429
+ graph.assertActive();
20430
+ const scopedKv = new KVService2({});
20431
+ const context = graph.track(new ServiceContext2({
20432
+ invoke: graph.invoke,
20433
+ invokeAny: graph.invokeAny,
20434
+ fetch: graph.fetch,
20435
+ hosts: [input.host],
20436
+ telemetry: this.config.telemetry
20437
+ }));
20438
+ context.setSession({ ...input.serviceSession, spaceId });
20439
+ scopedKv.initialize(context);
20440
+ const scopedVault = this.createVaultService(spaceId, scopedKv, encryption, {
20441
+ host: input.host,
20442
+ did: input.nodeDid,
20443
+ address: input.address,
20444
+ chainId: input.chainId,
20445
+ serviceGraph: graph
20446
+ });
20447
+ scopedVault.initialize(context);
20448
+ return scopedVault;
20449
+ },
20450
+ createSecretsService: (spaceId) => {
20451
+ graph.assertActive();
20452
+ return this.secretsForSpace(spaceId);
20453
+ },
20454
+ createDelegation: async (params) => {
20455
+ graph.assertActive();
20456
+ try {
20457
+ const portableDelegation = await this.createDelegation({
20458
+ delegateDID: params.delegateDID,
20459
+ path: params.path,
20460
+ actions: params.actions,
20461
+ disableSubDelegation: params.disableSubDelegation,
20462
+ expiryMs: params.expiry ? params.expiry.getTime() - Date.now() : void 0
20463
+ });
20464
+ return { ok: true, data: {
20465
+ cid: portableDelegation.cid,
20466
+ delegateDID: portableDelegation.delegateDID,
20467
+ delegatorDID: this.did,
20468
+ spaceId: portableDelegation.spaceId,
20469
+ path: portableDelegation.path,
20470
+ actions: portableDelegation.actions,
20471
+ expiry: portableDelegation.expiry,
20472
+ isRevoked: false,
20473
+ allowSubDelegation: !portableDelegation.disableSubDelegation,
20474
+ createdAt: /* @__PURE__ */ new Date(),
20475
+ authHeader: portableDelegation.delegationHeader.Authorization
20476
+ } };
20477
+ } catch (error) {
20478
+ return { ok: false, error: {
20479
+ code: "CREATION_FAILED",
20480
+ message: error instanceof Error ? error.message : String(error),
20481
+ service: "delegation"
20482
+ } };
20483
+ }
20484
+ },
20485
+ onSpaceRegistered: async (space) => {
20486
+ graph.assertActive();
20487
+ await this.account.spaces.register(space);
19886
20488
  }
19887
- this.registerPrimarySessionGrant(tcSession);
19888
- }
20489
+ });
20490
+ spaceService.updateConfig({ sharingService });
20491
+ return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
19889
20492
  }
19890
20493
  /**
19891
20494
  * Resolve the host a restored session should target.
@@ -20047,14 +20650,19 @@ var _TinyCloudNode = class _TinyCloudNode {
20047
20650
  if (!session) {
20048
20651
  return;
20049
20652
  }
20050
- this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
20051
- this._serviceContext = new ServiceContext2({
20052
- invoke: this.invokeWithRuntimePermissions,
20053
- invokeAny: this.invokeAnyWithRuntimePermissions,
20054
- fetch: globalThis.fetch.bind(globalThis),
20653
+ this.tc.initializeServices(
20654
+ this._serviceGraph.invoke,
20655
+ [this.config.host],
20656
+ this._serviceGraph.fetch
20657
+ );
20658
+ this._serviceGraph.track(this.tc.serviceContext);
20659
+ this._serviceContext = this._serviceGraph.track(new ServiceContext2({
20660
+ invoke: this._serviceGraph.invoke,
20661
+ invokeAny: this._serviceGraph.invokeAny,
20662
+ fetch: this._serviceGraph.fetch,
20055
20663
  hosts: [this.config.host],
20056
20664
  telemetry: this.config.telemetry
20057
- });
20665
+ }));
20058
20666
  this._kv = new KVService2({});
20059
20667
  this._kv.initialize(this._serviceContext);
20060
20668
  this._serviceContext.registerService("kv", this._kv);
@@ -20089,12 +20697,12 @@ var _TinyCloudNode = class _TinyCloudNode {
20089
20697
  createSpaceScopedKVService(spaceId) {
20090
20698
  const kvService = new KVService2({});
20091
20699
  if (this._serviceContext) {
20092
- const spaceScopedContext = new ServiceContext2({
20700
+ const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
20093
20701
  invoke: this._serviceContext.invoke,
20094
20702
  fetch: this._serviceContext.fetch,
20095
20703
  hosts: this._serviceContext.hosts,
20096
20704
  telemetry: this.config.telemetry
20097
- });
20705
+ }));
20098
20706
  const session = this._serviceContext.session;
20099
20707
  if (session) {
20100
20708
  spaceScopedContext.setSession({ ...session, spaceId });
@@ -20127,6 +20735,18 @@ var _TinyCloudNode = class _TinyCloudNode {
20127
20735
  }
20128
20736
  return session;
20129
20737
  }
20738
+ /**
20739
+ * Runtime-permission helpers can be used from an authenticated Node session
20740
+ * before its public ServiceContext is initialized. They still need the
20741
+ * current signed session, never an optional or stale authority object.
20742
+ */
20743
+ requireEncryptionSession() {
20744
+ const session = this._serviceContext?.session ?? this.auth?.tinyCloudSession;
20745
+ if (!session) {
20746
+ throw new Error("Not signed in. Call signIn() first.");
20747
+ }
20748
+ return session;
20749
+ }
20130
20750
  createEncryptionCrypto() {
20131
20751
  const wasm = this.wasmBindings;
20132
20752
  const columnEncrypt = (key2, plaintext) => {
@@ -20182,14 +20802,15 @@ var _TinyCloudNode = class _TinyCloudNode {
20182
20802
  }
20183
20803
  return info.nodeId;
20184
20804
  }
20185
- async signRawNetworkAuthorization(input) {
20805
+ async signRawNetworkAuthorization(input, binding) {
20806
+ binding?.graph.assertActive();
20186
20807
  if (!this.wasmBindings.invokeAny) {
20187
20808
  throw new Error("WASM binding does not support raw-resource invokeAny");
20188
20809
  }
20189
20810
  if (!this.wasmBindings.computeCid) {
20190
20811
  throw new Error("WASM binding does not support invocation CID computation");
20191
20812
  }
20192
- const session = this.requireServiceSession();
20813
+ const session = binding?.session ?? this.requireEncryptionSession();
20193
20814
  const headers = this.invokeAnyWithRuntimePermissions(
20194
20815
  session,
20195
20816
  [
@@ -20208,6 +20829,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20208
20829
  input.targetNode,
20209
20830
  session.jwk
20210
20831
  );
20832
+ binding?.graph.assertActive();
20211
20833
  return {
20212
20834
  authorization: audienceBound,
20213
20835
  invocationCid: this.wasmBindings.computeCid(
@@ -20216,25 +20838,55 @@ var _TinyCloudNode = class _TinyCloudNode {
20216
20838
  )
20217
20839
  };
20218
20840
  }
20219
- createEncryptionService() {
20841
+ async fetchEncryptionNetworkAt(host, networkId, fetchFn) {
20842
+ const response = await fetchFn(
20843
+ `${host}/encryption/networks/${encodeURIComponent(networkId)}`
20844
+ );
20845
+ if (response.status === 404) return null;
20846
+ if (!response.ok) {
20847
+ throw new Error(
20848
+ `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
20849
+ );
20850
+ }
20851
+ const body = await response.json();
20852
+ return "descriptor" in body && body.descriptor ? body.descriptor : body;
20853
+ }
20854
+ createEncryptionService(binding) {
20855
+ const graph = binding?.graph ?? this._serviceGraph;
20856
+ const host = binding?.host ?? this.config.host;
20857
+ const session = binding?.session ?? this.requireEncryptionSession();
20858
+ const did = binding?.did ?? this.did;
20859
+ const address = binding?.address ?? this._address;
20860
+ const chainId = binding?.chainId ?? this._chainId;
20220
20861
  const crypto2 = this.createEncryptionCrypto();
20221
20862
  const transport = {
20222
- postDecrypt: async ({ networkId, authorization, canonicalBody }) => {
20223
- const response = await fetch(
20224
- `${this.config.host}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
20863
+ postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
20864
+ graph.assertActive();
20865
+ const response = await graph.fetch(
20866
+ `${host}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
20225
20867
  {
20226
20868
  method: "POST",
20227
20869
  headers: {
20228
20870
  Authorization: authorization,
20229
20871
  "Content-Type": "application/json"
20230
20872
  },
20231
- body: canonicalBody
20873
+ body: canonicalBody,
20874
+ signal
20232
20875
  }
20233
20876
  );
20877
+ graph.assertActive();
20234
20878
  if (!response.ok) {
20235
- throw new Error(
20236
- `decrypt failed ${response.status}: ${await response.text()}`
20237
- );
20879
+ let permissionHint;
20880
+ if (response.status === 401 || response.status === 403) {
20881
+ try {
20882
+ const body = await response.json();
20883
+ const record = typeof body === "object" && body !== null ? body : void 0;
20884
+ const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
20885
+ permissionHint = parsePermissionHint(record?.permissionHint) ?? parsePermissionHint(nested?.permissionHint);
20886
+ } catch {
20887
+ }
20888
+ }
20889
+ throw new DecryptTransportResponseError(response.status, permissionHint);
20238
20890
  }
20239
20891
  return await response.json();
20240
20892
  }
@@ -20243,12 +20895,14 @@ var _TinyCloudNode = class _TinyCloudNode {
20243
20895
  crypto: crypto2,
20244
20896
  signer: {
20245
20897
  signDecryptInvocation: async (input) => {
20898
+ graph.assertActive();
20246
20899
  const signed2 = await this.signRawNetworkAuthorization({
20247
20900
  targetNode: input.targetNode,
20248
20901
  networkId: input.networkId,
20249
20902
  action: DECRYPT_ACTION2,
20250
20903
  facts: input.facts
20251
- });
20904
+ }, { graph, session });
20905
+ graph.assertActive();
20252
20906
  return {
20253
20907
  ...signed2,
20254
20908
  canonicalBody: canonicalizeEncryptionJson(
@@ -20259,25 +20913,35 @@ var _TinyCloudNode = class _TinyCloudNode {
20259
20913
  },
20260
20914
  transport,
20261
20915
  node: {
20262
- fetchByNetworkId: (networkId) => this.getEncryptionNetwork(networkId)
20916
+ fetchByNetworkId: async (networkId) => {
20917
+ graph.assertActive();
20918
+ const descriptor = await this.fetchEncryptionNetworkAt(host, networkId, graph.fetch);
20919
+ graph.assertActive();
20920
+ return descriptor;
20921
+ }
20263
20922
  },
20264
20923
  wellKnown: {
20265
20924
  fetchWellKnown: async (principal, discoveryKey) => {
20266
- if (!this._address || !didPrincipalMatches2(principal, this.did)) {
20267
- return null;
20268
- }
20269
- if (!this.config.host) {
20925
+ graph.assertActive();
20926
+ if (!address || !didPrincipalMatches2(principal, did)) {
20270
20927
  return null;
20271
20928
  }
20272
- const publicSpaceId = makePublicSpaceId(this._address, this._chainId);
20273
- const result = await TinyCloud.readPublicSpace(this.config.host, publicSpaceId, discoveryKey);
20274
- if (!result.ok) {
20929
+ const publicSpaceId = makePublicSpaceId(address, chainId);
20930
+ const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
20931
+ const response = await graph.fetch(
20932
+ `${host}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
20933
+ { method: "GET" }
20934
+ );
20935
+ graph.assertActive();
20936
+ if (!response.ok) {
20275
20937
  return null;
20276
20938
  }
20277
- const body = result.data;
20939
+ const body = await response.json();
20940
+ graph.assertActive();
20278
20941
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
20279
20942
  }
20280
- }
20943
+ },
20944
+ assertActive: () => graph.assertActive()
20281
20945
  });
20282
20946
  }
20283
20947
  getEncryptionService() {
@@ -20291,8 +20955,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20291
20955
  }
20292
20956
  return this._encryption;
20293
20957
  }
20294
- createVaultService(spaceId, kv) {
20958
+ createVaultService(spaceId, kv, encryptionService = this.getEncryptionService(), nodeContext) {
20295
20959
  const wasm = this.wasmBindings;
20960
+ const serviceGraph = nodeContext?.serviceGraph ?? this._serviceGraph;
20296
20961
  const vaultCrypto = createVaultCrypto({
20297
20962
  vault_encrypt: wasm.vault_encrypt,
20298
20963
  vault_decrypt: wasm.vault_decrypt,
@@ -20303,12 +20968,17 @@ var _TinyCloudNode = class _TinyCloudNode {
20303
20968
  vault_sha256: wasm.vault_sha256
20304
20969
  });
20305
20970
  const self2 = this;
20971
+ const did = nodeContext?.did ?? this.did;
20972
+ const address = nodeContext?.address ?? this._address;
20973
+ const chainId = nodeContext?.chainId ?? this._chainId;
20974
+ const host = nodeContext?.host ?? this.config.host;
20975
+ const ownerDid = this.ownerDidFromSpaceId(spaceId) ?? did;
20306
20976
  return new DataVaultService({
20307
20977
  spaceId,
20308
20978
  crypto: vaultCrypto,
20309
20979
  encryption: {
20310
- networkId: this.getEncryptionNetworkIdForSpace(spaceId),
20311
- service: this.getEncryptionService(),
20980
+ networkId: `urn:tinycloud:encryption:${ownerDid}:${DEFAULT_ENCRYPTION_NETWORK_NAME}`,
20981
+ service: encryptionService,
20312
20982
  decryptCapabilityProof: () => ({
20313
20983
  proofs: [this.requireServiceSession().delegationCid]
20314
20984
  })
@@ -20317,6 +20987,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20317
20987
  kv,
20318
20988
  ensurePublicSpace: async () => {
20319
20989
  try {
20990
+ serviceGraph.assertActive();
20320
20991
  await self2.ensurePublicSpace();
20321
20992
  return { ok: true, data: void 0 };
20322
20993
  } catch (error) {
@@ -20324,14 +20995,15 @@ var _TinyCloudNode = class _TinyCloudNode {
20324
20995
  }
20325
20996
  },
20326
20997
  get publicKV() {
20998
+ serviceGraph.assertActive();
20327
20999
  return self2._publicKV ?? self2.tc.publicKV;
20328
21000
  },
20329
- readPublicSpace: (host, targetSpaceId, key2) => TinyCloud.readPublicSpace(host, targetSpaceId, key2),
21001
+ readPublicSpace: (host2, targetSpaceId, key2) => TinyCloud.readPublicSpace(host2, targetSpaceId, key2),
20330
21002
  makePublicSpaceId: TinyCloud.makePublicSpaceId,
20331
- did: this.did,
20332
- address: this._address ?? "",
20333
- chainId: this._chainId,
20334
- hosts: [this.config.host]
21003
+ did,
21004
+ address: address ?? "",
21005
+ chainId,
21006
+ hosts: [host]
20335
21007
  }
20336
21008
  });
20337
21009
  }
@@ -20340,6 +21012,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20340
21012
  * @internal
20341
21013
  */
20342
21014
  initializeV2Services(serviceSession) {
21015
+ const graph = this._serviceGraph;
20343
21016
  this._capabilityRegistry = new CapabilityKeyRegistry();
20344
21017
  const tcSession = this.auth?.tinyCloudSession;
20345
21018
  if (tcSession && this._address) {
@@ -20382,22 +21055,25 @@ var _TinyCloudNode = class _TinyCloudNode {
20382
21055
  }
20383
21056
  this._delegationManager = new DelegationManager({
20384
21057
  hosts: [this.config.host],
21058
+ accountSpaceId: this.accountSpaceId,
20385
21059
  session: serviceSession,
20386
- invoke: this.invokeWithRuntimePermissions,
20387
- invokeAny: this.invokeAnyWithRuntimePermissions,
20388
- fetch: globalThis.fetch.bind(globalThis)
21060
+ invoke: graph.invoke,
21061
+ invokeAny: graph.invokeAny,
21062
+ fetch: graph.fetch
20389
21063
  });
20390
21064
  this._spaceService = new SpaceService({
20391
21065
  hosts: [this.config.host],
20392
21066
  session: serviceSession,
20393
- invoke: this.wasmBindings.invoke,
20394
- fetch: globalThis.fetch.bind(globalThis),
21067
+ invoke: graph.invoke,
21068
+ fetch: graph.fetch,
20395
21069
  capabilityRegistry: this._capabilityRegistry,
20396
21070
  userDid: this.did,
20397
21071
  createKVService: (spaceId) => {
21072
+ graph.assertActive();
20398
21073
  return this.createSpaceScopedKVService(spaceId);
20399
21074
  },
20400
21075
  createVaultService: (spaceId) => {
21076
+ graph.assertActive();
20401
21077
  const kvService = this.createSpaceScopedKVService(spaceId);
20402
21078
  const vaultService = this.createVaultService(spaceId, kvService);
20403
21079
  if (this._serviceContext) {
@@ -20406,11 +21082,13 @@ var _TinyCloudNode = class _TinyCloudNode {
20406
21082
  return vaultService;
20407
21083
  },
20408
21084
  createSecretsService: (spaceId) => {
21085
+ graph.assertActive();
20409
21086
  return this.secretsForSpace(spaceId);
20410
21087
  },
20411
21088
  // Enable space.delegations.create() via SIWE-based delegation
20412
21089
  createDelegation: async (params) => {
20413
21090
  try {
21091
+ graph.assertActive();
20414
21092
  const portableDelegation = await this.createDelegation({
20415
21093
  delegateDID: params.delegateDID,
20416
21094
  path: params.path,
@@ -20444,18 +21122,50 @@ var _TinyCloudNode = class _TinyCloudNode {
20444
21122
  }
20445
21123
  },
20446
21124
  onSpaceRegistered: async (space) => {
21125
+ graph.assertActive();
20447
21126
  await this.account.spaces.register(space);
20448
21127
  }
20449
21128
  });
20450
- this._sharingService.updateConfig({
21129
+ this._sharingService = new SharingService({
21130
+ hosts: [this.config.host],
20451
21131
  session: serviceSession,
21132
+ invoke: graph.invoke,
21133
+ fetch: graph.fetch,
21134
+ assertActive: () => graph.assertActive(),
21135
+ keyProvider: this._keyProvider,
21136
+ registry: this._capabilityRegistry,
20452
21137
  delegationManager: this._delegationManager,
20453
21138
  sessionExpiry: this.getSessionExpiry(),
20454
- // WASM-based delegation creation (preferred - no server roundtrip)
20455
- createDelegationWasm: (params) => this.createDelegationWrapper(params),
20456
- // Root delegation for long-lived share links (bypasses session expiry)
20457
- // In node-sdk we have direct signer access, so no popup needed
20458
- onRootDelegationNeeded: this.signer ? async (params) => this.createRootDelegationForSharing(params) : void 0
21139
+ createDelegationWasm: (params) => {
21140
+ graph.assertActive();
21141
+ return this.createDelegationWrapper(params);
21142
+ },
21143
+ computeCid: (data, codec) => {
21144
+ if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
21145
+ return this.wasmBindings.computeCid(data, codec);
21146
+ },
21147
+ createKVService: (config) => {
21148
+ graph.assertActive();
21149
+ const service = new KVService2({ prefix: config.pathPrefix?.replace(/\/$/, "") });
21150
+ const context = graph.track(new ServiceContext2({
21151
+ invoke: config.invoke,
21152
+ fetch: config.fetch ?? graph.fetch,
21153
+ hosts: config.hosts,
21154
+ telemetry: this.config.telemetry
21155
+ }));
21156
+ context.setSession(config.session);
21157
+ service.initialize(context);
21158
+ return service;
21159
+ },
21160
+ onRootDelegationNeeded: this.signer ? async (params) => {
21161
+ graph.assertActive();
21162
+ const delegation = await this.createRootDelegationForSharing(
21163
+ params,
21164
+ () => graph.assertActive()
21165
+ );
21166
+ graph.assertActive();
21167
+ return delegation;
21168
+ } : void 0
20459
21169
  });
20460
21170
  this._spaceService.updateConfig({
20461
21171
  sharingService: this._sharingService
@@ -20522,7 +21232,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20522
21232
  * with expiry longer than the current session.
20523
21233
  * @internal
20524
21234
  */
20525
- async createOwnerDelegation(params) {
21235
+ async createOwnerDelegation(params, assertActive) {
21236
+ const assertOwnerGraphActive = assertActive ?? this._serviceGraph.assertActive.bind(this._serviceGraph);
21237
+ assertOwnerGraphActive();
20526
21238
  if (!params.delegateDid.startsWith("did:key:") || params.actions.length === 0 || params.path.length === 0) {
20527
21239
  throw new Error("Owner delegation requires an external did:key audience and bounded capabilities");
20528
21240
  }
@@ -20547,8 +21259,10 @@ var _TinyCloudNode = class _TinyCloudNode {
20547
21259
  delegateUri: params.delegateDid
20548
21260
  });
20549
21261
  const signature2 = await this.signer.signMessage(prepared.siwe);
21262
+ assertOwnerGraphActive();
20550
21263
  const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature: signature2 });
20551
21264
  const activation = await activateSessionWithHost2(host, delegationSession.delegationHeader);
21265
+ assertOwnerGraphActive();
20552
21266
  if (!activation.success) {
20553
21267
  throw new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim());
20554
21268
  }
@@ -20576,7 +21290,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20576
21290
  }
20577
21291
  };
20578
21292
  }
20579
- async createRootDelegationForSharing(params) {
21293
+ async createRootDelegationForSharing(params, assertActive) {
20580
21294
  try {
20581
21295
  return (await this.createOwnerDelegation({
20582
21296
  delegateDid: params.shareKeyDID,
@@ -20584,8 +21298,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20584
21298
  path: params.path,
20585
21299
  actions: params.actions,
20586
21300
  expiresAt: params.requestedExpiry
20587
- })).delegation;
21301
+ }, assertActive)).delegation;
20588
21302
  } catch {
21303
+ assertActive?.();
20589
21304
  return void 0;
20590
21305
  }
20591
21306
  }
@@ -20657,13 +21372,13 @@ var _TinyCloudNode = class _TinyCloudNode {
20657
21372
  throw new Error("Not signed in. Call signIn() first.");
20658
21373
  }
20659
21374
  const sql = new SQLService2({});
20660
- const spaceScopedContext = new ServiceContext2({
21375
+ const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
20661
21376
  invoke: this._serviceContext.invoke,
20662
21377
  invokeAny: this._serviceContext.invokeAny,
20663
21378
  fetch: this._serviceContext.fetch,
20664
21379
  hosts: this._serviceContext.hosts,
20665
21380
  telemetry: this.config.telemetry
20666
- });
21381
+ }));
20667
21382
  spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
20668
21383
  sql.initialize(spaceScopedContext);
20669
21384
  return sql;
@@ -20687,12 +21402,12 @@ var _TinyCloudNode = class _TinyCloudNode {
20687
21402
  throw new Error("Not signed in. Call signIn() first.");
20688
21403
  }
20689
21404
  const kv = new KVService2({});
20690
- const spaceScopedContext = new ServiceContext2({
21405
+ const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
20691
21406
  invoke: this._serviceContext.invoke,
20692
21407
  invokeAny: this._serviceContext.invokeAny,
20693
21408
  fetch: this._serviceContext.fetch,
20694
21409
  hosts: this._serviceContext.hosts
20695
- });
21410
+ }));
20696
21411
  spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
20697
21412
  kv.initialize(spaceScopedContext);
20698
21413
  return kv;
@@ -20728,19 +21443,11 @@ var _TinyCloudNode = class _TinyCloudNode {
20728
21443
  }
20729
21444
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
20730
21445
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
20731
- const response = await fetch(
20732
- `${this.config.host}/encryption/networks/${encodeURIComponent(networkId)}`
21446
+ return this.fetchEncryptionNetworkAt(
21447
+ this.config.host,
21448
+ networkId,
21449
+ globalThis.fetch.bind(globalThis)
20733
21450
  );
20734
- if (response.status === 404) {
20735
- return null;
20736
- }
20737
- if (!response.ok) {
20738
- throw new Error(
20739
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
20740
- );
20741
- }
20742
- const body = await response.json();
20743
- return "descriptor" in body && body.descriptor ? body.descriptor : body;
20744
21451
  }
20745
21452
  async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
20746
21453
  const targetNode = await this.fetchNodeId();
@@ -20834,6 +21541,38 @@ var _TinyCloudNode = class _TinyCloudNode {
20834
21541
  }
20835
21542
  return secrets;
20836
21543
  }
21544
+ /**
21545
+ * Read a secret from an explicit target space without requesting or
21546
+ * auto-signing authority. Active runtime delegations are selected by the
21547
+ * normal KV and encryption invocation paths.
21548
+ */
21549
+ async readSecret(input) {
21550
+ const resolved = resolveSecretPath2(input.name, { scope: input.scope });
21551
+ const targetSpace = input.space.startsWith("tinycloud:") ? input.space : this.ownedSpaceId(input.space);
21552
+ const result = await this.getBaseVault(targetSpace).readNetworkEncrypted(
21553
+ resolved.vaultKey
21554
+ );
21555
+ if (result.status !== "ok") {
21556
+ if (result.status === "permission_required") {
21557
+ const hint = parsePermissionHint(result.hint);
21558
+ if (hint === void 0) return { status: "read_failed" };
21559
+ return {
21560
+ status: "permission_required",
21561
+ hint: {
21562
+ service: hint.service,
21563
+ ...hint.space === void 0 ? {} : { space: hint.space },
21564
+ path: hint.path,
21565
+ actions: [...hint.actions]
21566
+ }
21567
+ };
21568
+ }
21569
+ return { status: result.status };
21570
+ }
21571
+ if (!isSecretPayload(result.entry.value)) {
21572
+ return { status: "invalid_payload" };
21573
+ }
21574
+ return { status: "ok", value: result.entry.value.value };
21575
+ }
20837
21576
  getBaseSecrets(spaceId) {
20838
21577
  if (!this._spaceService) {
20839
21578
  throw new Error("Not signed in. Call signIn() first.");
@@ -20841,16 +21580,29 @@ var _TinyCloudNode = class _TinyCloudNode {
20841
21580
  const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
20842
21581
  let secrets = this._baseSecrets.get(resolvedSpace);
20843
21582
  if (!secrets) {
20844
- const kvService = this.createSpaceScopedKVService(resolvedSpace);
20845
- const vaultService = this.createVaultService(resolvedSpace, kvService);
20846
- if (this._serviceContext) {
20847
- vaultService.initialize(this._serviceContext);
20848
- }
20849
- secrets = new SecretsService(() => vaultService);
21583
+ secrets = new SecretsService(() => this.getBaseVault(resolvedSpace));
20850
21584
  this._baseSecrets.set(resolvedSpace, secrets);
20851
21585
  }
20852
21586
  return secrets;
20853
21587
  }
21588
+ getBaseVault(spaceId) {
21589
+ if (!this._spaceService) {
21590
+ throw new Error("Not signed in. Call signIn() first.");
21591
+ }
21592
+ const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
21593
+ let vault = this._baseVaults.get(resolvedSpace);
21594
+ if (!vault) {
21595
+ vault = this.createVaultService(
21596
+ resolvedSpace,
21597
+ this.createSpaceScopedKVService(resolvedSpace)
21598
+ );
21599
+ if (this._serviceContext) {
21600
+ vault.initialize(this._serviceContext);
21601
+ }
21602
+ this._baseVaults.set(resolvedSpace, vault);
21603
+ }
21604
+ return vault;
21605
+ }
20854
21606
  /**
20855
21607
  * Hooks write stream subscription API.
20856
21608
  */
@@ -20955,6 +21707,63 @@ var _TinyCloudNode = class _TinyCloudNode {
20955
21707
  (grant) => grant.delegation
20956
21708
  );
20957
21709
  }
21710
+ /**
21711
+ * Return the effective capabilities of installed runtime grants.
21712
+ *
21713
+ * The result is projected from the SDK's activated, expiry-pruned grant
21714
+ * operations rather than from PortableDelegation transport metadata. Base
21715
+ * session authority is intentionally omitted; callers that need the full
21716
+ * live authority must combine this with {@link getVerifiedSessionCapabilities}.
21717
+ */
21718
+ getEffectiveRuntimePermissionEntries() {
21719
+ const session = this.currentTinyCloudSession();
21720
+ if (!session) return [];
21721
+ this.pruneExpiredRuntimePermissionGrants();
21722
+ const grouped = /* @__PURE__ */ new Map();
21723
+ for (const grant of this.runtimePermissionGrants) {
21724
+ if (grant.provenance === "primary") continue;
21725
+ for (const operation of grant.operations) {
21726
+ const service = SERVICE_SHORT_TO_LONG[operation.service];
21727
+ if (service === void 0 || typeof operation.path !== "string" || typeof operation.action !== "string") {
21728
+ continue;
21729
+ }
21730
+ let space;
21731
+ if (service !== "tinycloud.encryption") {
21732
+ if (typeof operation.spaceId !== "string") continue;
21733
+ try {
21734
+ space = this.resolvePermissionSpace(operation.spaceId, session);
21735
+ } catch {
21736
+ continue;
21737
+ }
21738
+ }
21739
+ const caveats = operation.caveats === void 0 || operation.caveats.length === 0 ? void 0 : cloneRecapCaveats(operation.caveats);
21740
+ const identity = JSON.stringify({
21741
+ service,
21742
+ ...space === void 0 ? {} : { space },
21743
+ path: operation.path,
21744
+ caveats: canonicalizeRecapCaveats(caveats)
21745
+ });
21746
+ const existing = grouped.get(identity);
21747
+ if (existing === void 0) {
21748
+ grouped.set(identity, {
21749
+ service,
21750
+ ...space === void 0 ? {} : { space },
21751
+ path: operation.path,
21752
+ actions: [operation.action],
21753
+ ...caveats === void 0 ? {} : { caveats }
21754
+ });
21755
+ continue;
21756
+ }
21757
+ if (!existing.actions.includes(operation.action)) {
21758
+ existing.actions.push(operation.action);
21759
+ existing.actions.sort();
21760
+ }
21761
+ }
21762
+ }
21763
+ return [...grouped.values()].sort(
21764
+ (left, right) => left.service.localeCompare(right.service) || (left.space ?? "").localeCompare(right.space ?? "") || left.path.localeCompare(right.path) || left.actions.join("\0").localeCompare(right.actions.join("\0"))
21765
+ );
21766
+ }
20958
21767
  /**
20959
21768
  * Install a portable runtime permission delegation into this SDK instance so
20960
21769
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -21023,6 +21832,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21023
21832
  "grantRuntimePermissions requires wallet mode with a signer or privateKey."
21024
21833
  );
21025
21834
  }
21835
+ this.assertDelegationCaveatsPreservable(expanded);
21026
21836
  const rawEntries = expanded.filter(
21027
21837
  (entry) => this.isEncryptionPermissionEntry(entry)
21028
21838
  );
@@ -21284,12 +22094,12 @@ var _TinyCloudNode = class _TinyCloudNode {
21284
22094
  }
21285
22095
  if (this._serviceContext) {
21286
22096
  const publicKV = new KVService2({ prefix: "" });
21287
- const publicContext = new ServiceContext2({
21288
- invoke: this.invokeWithRuntimePermissions,
21289
- fetch: this._serviceContext.fetch,
22097
+ const publicContext = this._serviceGraph.track(new ServiceContext2({
22098
+ invoke: this._serviceGraph.invoke,
22099
+ fetch: this._serviceGraph.fetch,
21290
22100
  hosts: this._serviceContext.hosts,
21291
22101
  telemetry: this.config.telemetry
21292
- });
22102
+ }));
21293
22103
  publicContext.setSession({
21294
22104
  delegationHeader: delegationSession.delegationHeader,
21295
22105
  delegationCid: delegationSession.delegationCid,
@@ -21356,13 +22166,14 @@ var _TinyCloudNode = class _TinyCloudNode {
21356
22166
  async getDelegationStatus(cid) {
21357
22167
  return this.delegationManager.status(cid);
21358
22168
  }
21359
- /** Compute the canonical CID of a compact delegation authorization. */
22169
+ /** Compute the canonical CID of a compact UCAN or DAG-CBOR delegation authorization. */
21360
22170
  computeDelegationCid(authorization) {
21361
22171
  if (!authorization || !this.wasmBindings.computeCid) {
21362
22172
  throw new Error("Delegation CID computation is unavailable");
21363
22173
  }
22174
+ const compact = authorization.replace(/^Bearer /i, "");
21364
22175
  return this.wasmBindings.computeCid(
21365
- new TextEncoder().encode(authorization),
22176
+ compact.includes(".") ? new TextEncoder().encode(compact) : decodeAuthorizationBytes(authorization),
21366
22177
  0x55n
21367
22178
  );
21368
22179
  }
@@ -21454,6 +22265,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21454
22265
  "delegateTo with forceWalletSign=true supports at most one PermissionEntry. Multi-entry requests must go through the session-key UCAN path (drop forceWalletSign) or the legacy createDelegation method."
21455
22266
  );
21456
22267
  }
22268
+ this.assertDelegationCaveatsPreservable(expandedEntries);
21457
22269
  const delegation2 = await this.createDelegationLegacyWalletPath(
21458
22270
  did,
21459
22271
  expandedEntries[0],
@@ -21462,13 +22274,14 @@ var _TinyCloudNode = class _TinyCloudNode {
21462
22274
  return { delegation: delegation2, prompted: true };
21463
22275
  }
21464
22276
  const granted = parseRecapCapabilities(
21465
- (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
22277
+ (siwe) => this.parseRecapWithCaveats(siwe),
21466
22278
  session.siwe
21467
22279
  );
21468
22280
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
21469
22281
  if (!subset) {
22282
+ const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
21470
22283
  const runtimeGrant = this.findGrantForOperations(
21471
- this.permissionEntriesToOperations(expandedEntries, session),
22284
+ runtimeOperations,
21472
22285
  { excludePrimary: true }
21473
22286
  );
21474
22287
  if (runtimeGrant) {
@@ -21481,7 +22294,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21481
22294
  did,
21482
22295
  expandedEntries,
21483
22296
  runtimeExpiration,
21484
- runtimeGrant
22297
+ runtimeGrant,
22298
+ runtimeOperations
21485
22299
  );
21486
22300
  return { delegation: delegation2, prompted: false };
21487
22301
  }
@@ -21551,6 +22365,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21551
22365
  "createDelegationViaWasmPath requires a non-empty entries array"
21552
22366
  );
21553
22367
  }
22368
+ this.assertDelegationCaveatsPreservable(entries);
21554
22369
  const resolvedSpaces = /* @__PURE__ */ new Set();
21555
22370
  for (const entry of entries) {
21556
22371
  if (this.isEncryptionPermissionEntry(entry)) {
@@ -21631,7 +22446,9 @@ var _TinyCloudNode = class _TinyCloudNode {
21631
22446
  host: this.config.host
21632
22447
  };
21633
22448
  }
21634
- async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
22449
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations) {
22450
+ this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
22451
+ this.assertDelegationCaveatsPreservable(entries);
21635
22452
  const result = this.createDelegationWrapper({
21636
22453
  session: grant.session,
21637
22454
  delegateDID: did,
@@ -21685,6 +22502,74 @@ var _TinyCloudNode = class _TinyCloudNode {
21685
22502
  expandPermissionEntries(permissions) {
21686
22503
  return expandPermissionEntriesCore(permissions);
21687
22504
  }
22505
+ /**
22506
+ * The current WASM child-delegation APIs accept action-only maps. Refuse any
22507
+ * constrained branch rather than signing a broader action-only child UCAN.
22508
+ */
22509
+ assertDelegationCaveatsPreservable(entries) {
22510
+ const caveated = entries.filter(
22511
+ (entry) => !recapCaveatsEqual(entry.caveats, void 0)
22512
+ );
22513
+ if (caveated.length > 0) {
22514
+ throw new CaveatedDelegationUnsupportedError(caveated);
22515
+ }
22516
+ }
22517
+ /**
22518
+ * Runtime grant selection intentionally allows an uncaveated operation to
22519
+ * select a caveated grant so invocation can restore the signed caveat before
22520
+ * crossing the caveat-aware WASM boundary. Child delegation cannot do that:
22521
+ * it only accepts action maps, so reject the selected constrained branch.
22522
+ */
22523
+ assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant) {
22524
+ let operationIndex = 0;
22525
+ const caveated = [];
22526
+ for (const entry of entries) {
22527
+ for (const action of entry.actions) {
22528
+ const requested = requestedOperations[operationIndex++];
22529
+ if (!requested) continue;
22530
+ const constrained = grant.operations.find(
22531
+ (granted) => this.operationCovers(granted, requested) && !recapCaveatsEqual(granted.caveats, void 0)
22532
+ );
22533
+ if (constrained) {
22534
+ caveated.push({
22535
+ ...entry,
22536
+ actions: [action],
22537
+ caveats: cloneRecapCaveats(constrained.caveats)
22538
+ });
22539
+ }
22540
+ }
22541
+ }
22542
+ if (caveated.length > 0) {
22543
+ throw new CaveatedDelegationUnsupportedError(caveated);
22544
+ }
22545
+ }
22546
+ /** Reject caveated parent branches before action-only child signing. */
22547
+ assertPortableDelegationCaveatsPreservable(delegation) {
22548
+ const entries = [];
22549
+ const add3 = (service, space, path, actions, caveats) => {
22550
+ if (recapCaveatsEqual(caveats, void 0)) return;
22551
+ entries.push({
22552
+ service: service.startsWith("tinycloud.") ? service : `tinycloud.${service}`,
22553
+ space,
22554
+ path,
22555
+ actions: [...actions],
22556
+ caveats: cloneRecapCaveats(caveats)
22557
+ });
22558
+ };
22559
+ add3(
22560
+ delegation.actions[0]?.split("/", 1)[0] ?? "tinycloud.unknown",
22561
+ delegation.spaceId,
22562
+ delegation.path,
22563
+ delegation.actions,
22564
+ delegation.caveats
22565
+ );
22566
+ for (const resource of delegation.resources ?? []) {
22567
+ add3(resource.service, resource.space, resource.path, resource.actions, resource.caveats);
22568
+ }
22569
+ if (entries.length > 0) {
22570
+ throw new CaveatedDelegationUnsupportedError(entries);
22571
+ }
22572
+ }
21688
22573
  shortServiceName(service) {
21689
22574
  const short = SERVICE_LONG_TO_SHORT[service];
21690
22575
  if (short === void 0) {
@@ -21739,14 +22624,15 @@ var _TinyCloudNode = class _TinyCloudNode {
21739
22624
  ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
21740
22625
  service,
21741
22626
  path: entry.path,
21742
- action
22627
+ action,
22628
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
21743
22629
  }));
21744
22630
  });
21745
22631
  }
21746
22632
  sessionCoversPermissionEntries(session, entries) {
21747
22633
  try {
21748
22634
  const granted = parseRecapCapabilities(
21749
- (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
22635
+ (siwe) => this.parseRecapWithCaveats(siwe),
21750
22636
  session.siwe
21751
22637
  );
21752
22638
  return isCapabilitySubset(entries, granted).subset;
@@ -21762,7 +22648,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21762
22648
  ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
21763
22649
  service,
21764
22650
  path: entry.path,
21765
- action
22651
+ action,
22652
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
21766
22653
  }));
21767
22654
  });
21768
22655
  }
@@ -21838,7 +22725,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21838
22725
  service: this.shortServiceName(entry.service),
21839
22726
  space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
21840
22727
  path: entry.path,
21841
- actions: [...entry.actions]
22728
+ actions: [...entry.actions],
22729
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
21842
22730
  }));
21843
22731
  }
21844
22732
  operationsFromDelegation(delegation) {
@@ -21849,7 +22737,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21849
22737
  ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
21850
22738
  service,
21851
22739
  path: resource.path,
21852
- action
22740
+ action,
22741
+ caveats: cloneRecapCaveats(resource.caveats)
21853
22742
  }));
21854
22743
  });
21855
22744
  }
@@ -21865,7 +22754,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21865
22754
  service,
21866
22755
  space: delegation.spaceId,
21867
22756
  path: delegation.path,
21868
- actions
22757
+ actions,
22758
+ ...delegation.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(delegation.caveats) }
21869
22759
  }));
21870
22760
  }
21871
22761
  /**
@@ -21966,6 +22856,9 @@ var _TinyCloudNode = class _TinyCloudNode {
21966
22856
  if (granted.service !== requested.service || !this.actionContains(granted.action, requested.action)) {
21967
22857
  return false;
21968
22858
  }
22859
+ if (requested.caveats !== void 0 && !recapCaveatsEqual(granted.caveats, requested.caveats)) {
22860
+ return false;
22861
+ }
21969
22862
  if (granted.resource !== void 0 || requested.resource !== void 0) {
21970
22863
  return granted.resource !== void 0 && requested.resource !== void 0 && granted.resource === requested.resource && this.pathContains(granted.path, requested.path);
21971
22864
  }
@@ -22000,6 +22893,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22000
22893
  invocationServiceName(service) {
22001
22894
  return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
22002
22895
  }
22896
+ /** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
22897
+ parseRecapWithCaveats(siwe) {
22898
+ return this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe) ?? this.wasmBindings.parseRecapFromSiwe(siwe);
22899
+ }
22003
22900
  isEncryptionNetworkOperation(service, path) {
22004
22901
  return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
22005
22902
  }
@@ -22010,7 +22907,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22010
22907
  resource: entry.resource,
22011
22908
  service,
22012
22909
  path: entry.path,
22013
- action: entry.action
22910
+ action: entry.action,
22911
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
22014
22912
  };
22015
22913
  }
22016
22914
  if (this.isEncryptionNetworkOperation(service, entry.path)) {
@@ -22018,7 +22916,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22018
22916
  resource: entry.path,
22019
22917
  service,
22020
22918
  path: entry.path,
22021
- action: entry.action
22919
+ action: entry.action,
22920
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
22022
22921
  };
22023
22922
  }
22024
22923
  if (typeof entry.spaceId === "string") {
@@ -22026,7 +22925,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22026
22925
  spaceId: entry.spaceId,
22027
22926
  service,
22028
22927
  path: entry.path,
22029
- action: entry.action
22928
+ action: entry.action,
22929
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
22030
22930
  };
22031
22931
  }
22032
22932
  return void 0;
@@ -22366,6 +23266,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22366
23266
  * @returns A portable delegation for the sub-delegate
22367
23267
  */
22368
23268
  async createSubDelegation(parentDelegation, params) {
23269
+ this.assertPortableDelegationCaveatsPreservable(parentDelegation);
22369
23270
  if (!this.signer) {
22370
23271
  throw new Error("Cannot createSubDelegation() in session-only mode. Requires wallet mode.");
22371
23272
  }
@@ -22482,7 +23383,7 @@ import {
22482
23383
  parsePkhDid,
22483
23384
  pkhDid as pkhDid3,
22484
23385
  principalDid,
22485
- principalDidEquals as principalDidEquals3,
23386
+ principalDidEquals as principalDidEquals4,
22486
23387
  parseCanonicalNetworkId,
22487
23388
  TinyCloudDebugLogger,
22488
23389
  tinyCloudDebugLogger,
@@ -22620,6 +23521,7 @@ import {
22620
23521
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
22621
23522
  DEFAULT_MANIFEST_VERSION,
22622
23523
  VAULT_PERMISSION_SERVICE,
23524
+ CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
22623
23525
  PermissionNotInManifestError as PermissionNotInManifestError2,
22624
23526
  SessionExpiredError as SessionExpiredError2,
22625
23527
  ManifestValidationError,
@@ -22636,6 +23538,279 @@ import {
22636
23538
  } from "@tinycloud/sdk-core";
22637
23539
 
22638
23540
  // src/delegation.ts
23541
+ import {
23542
+ canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
23543
+ principalDidEquals as principalDidEquals3
23544
+ } from "@tinycloud/sdk-core";
23545
+ var SERVICE_BY_SHORT_NAME = {
23546
+ kv: "tinycloud.kv",
23547
+ sql: "tinycloud.sql",
23548
+ duckdb: "tinycloud.duckdb",
23549
+ hooks: "tinycloud.hooks"
23550
+ };
23551
+ function authorizationWithoutBearer(authorization) {
23552
+ return authorization.replace(/^Bearer /i, "");
23553
+ }
23554
+ function normalizedHost(host) {
23555
+ if (typeof host !== "string" || host.length === 0) {
23556
+ throw new Error("Validated runtime delegation requires a non-empty host.");
23557
+ }
23558
+ return host.replace(/\/+$/, "");
23559
+ }
23560
+ function didPrincipalsMatch(left, right) {
23561
+ try {
23562
+ return principalDidEquals3(left, right);
23563
+ } catch {
23564
+ return left === right;
23565
+ }
23566
+ }
23567
+ function compactUcanPayload(authorization) {
23568
+ const compact = authorizationWithoutBearer(authorization);
23569
+ const parts = compact.split(".");
23570
+ if (parts.length !== 3 || parts.some((part) => part.length === 0)) {
23571
+ throw new Error(
23572
+ "Validated runtime delegation requires a compact UCAN authorization so its signed authority can be derived."
23573
+ );
23574
+ }
23575
+ try {
23576
+ const payload = JSON.parse(
23577
+ Buffer.from(parts[1], "base64url").toString("utf8")
23578
+ );
23579
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
23580
+ throw new Error("payload is not an object");
23581
+ }
23582
+ return payload;
23583
+ } catch {
23584
+ throw new Error(
23585
+ "Validated runtime delegation authorization has an invalid compact UCAN payload."
23586
+ );
23587
+ }
23588
+ }
23589
+ function canonicalPermissionEntries(permissions) {
23590
+ return permissions.map((permission) => ({
23591
+ ...permission,
23592
+ actions: [...permission.actions].sort()
23593
+ })).sort(
23594
+ (left, right) => left.service.localeCompare(right.service) || (left.space ?? "").localeCompare(right.space ?? "") || left.path.localeCompare(right.path) || left.actions.join("\0").localeCompare(right.actions.join("\0")) || canonicalizeRecapCaveats2(left.caveats).localeCompare(canonicalizeRecapCaveats2(right.caveats))
23595
+ );
23596
+ }
23597
+ function signedCaveats(value) {
23598
+ if (!Array.isArray(value) || value.some(
23599
+ (caveat) => caveat === null || typeof caveat !== "object" || Array.isArray(caveat)
23600
+ )) {
23601
+ throw new Error("Validated runtime delegation has malformed signed ReCap caveats.");
23602
+ }
23603
+ const cloned = JSON.parse(JSON.stringify(value));
23604
+ return canonicalizeRecapCaveats2(cloned) === canonicalizeRecapCaveats2(void 0) ? void 0 : cloned;
23605
+ }
23606
+ function groupedSignedEntries(service, space, path, abilities) {
23607
+ const groups = /* @__PURE__ */ new Map();
23608
+ for (const [action, rawCaveats] of Object.entries(abilities)) {
23609
+ const caveats = signedCaveats(rawCaveats);
23610
+ const identity = canonicalizeRecapCaveats2(caveats);
23611
+ const group = groups.get(identity) ?? { actions: [], caveats };
23612
+ group.actions.push(action);
23613
+ groups.set(identity, group);
23614
+ }
23615
+ return [...groups.values()].map((group) => ({
23616
+ service,
23617
+ ...space === void 0 ? {} : { space },
23618
+ path,
23619
+ actions: group.actions.sort(),
23620
+ ...group.caveats === void 0 ? {} : { caveats: group.caveats }
23621
+ }));
23622
+ }
23623
+ function signedAuthorityFromCompactUcan(authorization) {
23624
+ const payload = compactUcanPayload(authorization);
23625
+ if (typeof payload.aud !== "string" || payload.aud.length === 0) {
23626
+ throw new Error("Validated runtime delegation is missing a signed audience.");
23627
+ }
23628
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
23629
+ throw new Error("Validated runtime delegation is missing a signed expiry.");
23630
+ }
23631
+ const expiry = new Date(payload.exp * 1e3);
23632
+ if (Number.isNaN(expiry.getTime())) {
23633
+ throw new Error("Validated runtime delegation has an invalid signed expiry.");
23634
+ }
23635
+ if (payload.att === null || typeof payload.att !== "object" || Array.isArray(payload.att)) {
23636
+ throw new Error("Validated runtime delegation is missing a signed attenuation.");
23637
+ }
23638
+ const permissions = [];
23639
+ for (const [resource, abilities] of Object.entries(payload.att)) {
23640
+ if (abilities === null || typeof abilities !== "object" || Array.isArray(abilities)) {
23641
+ throw new Error("Validated runtime delegation has an invalid signed attenuation.");
23642
+ }
23643
+ const abilityEntries = Object.entries(abilities);
23644
+ if (abilityEntries.length === 0) {
23645
+ throw new Error("Validated runtime delegation has an empty signed capability.");
23646
+ }
23647
+ if (resource.startsWith("urn:tinycloud:encryption:")) {
23648
+ permissions.push(...groupedSignedEntries(
23649
+ "tinycloud.encryption",
23650
+ void 0,
23651
+ resource,
23652
+ abilities
23653
+ ));
23654
+ continue;
23655
+ }
23656
+ const match = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
23657
+ if (!match) {
23658
+ throw new Error(
23659
+ `Validated runtime delegation has an unsupported signed resource '${resource}'.`
23660
+ );
23661
+ }
23662
+ const [, space, shortService, path] = match;
23663
+ const service = SERVICE_BY_SHORT_NAME[shortService];
23664
+ if (service === void 0) {
23665
+ throw new Error(
23666
+ `Validated runtime delegation has an unsupported signed service '${shortService}'.`
23667
+ );
23668
+ }
23669
+ permissions.push(...groupedSignedEntries(
23670
+ service,
23671
+ space,
23672
+ path,
23673
+ abilities
23674
+ ));
23675
+ }
23676
+ if (permissions.length === 0) {
23677
+ throw new Error("Validated runtime delegation has no signed capabilities.");
23678
+ }
23679
+ const canonicalPermissions = canonicalPermissionEntries(permissions);
23680
+ const canonicalResources = canonicalPermissions.map((permission) => ({
23681
+ service: permission.service === "tinycloud.encryption" ? "encryption" : permission.service.slice("tinycloud.".length),
23682
+ space: permission.service === "tinycloud.encryption" ? "encryption" : permission.space,
23683
+ path: permission.path,
23684
+ actions: [...permission.actions],
23685
+ ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
23686
+ }));
23687
+ return {
23688
+ audience: payload.aud,
23689
+ expiry,
23690
+ permissions: canonicalPermissions,
23691
+ resources: canonicalResources
23692
+ };
23693
+ }
23694
+ function canonicalResourcesFromPortableDelegation(delegation) {
23695
+ if (delegation.resources !== void 0 && delegation.resources.length > 0) {
23696
+ return delegation.resources.map((resource) => ({ ...resource, actions: [...resource.actions].sort() })).sort(
23697
+ (left, right) => left.service.localeCompare(right.service) || left.space.localeCompare(right.space) || left.path.localeCompare(right.path) || left.actions.join("\0").localeCompare(right.actions.join("\0")) || canonicalizeRecapCaveats2(left.caveats).localeCompare(canonicalizeRecapCaveats2(right.caveats))
23698
+ );
23699
+ }
23700
+ const byService = /* @__PURE__ */ new Map();
23701
+ for (const action of delegation.actions) {
23702
+ const [service] = action.split("/", 1);
23703
+ const shortService = service.replace(/^tinycloud\./, "");
23704
+ const values = byService.get(shortService) ?? [];
23705
+ values.push(action);
23706
+ byService.set(shortService, values);
23707
+ }
23708
+ return [...byService.entries()].map(([service, actions]) => ({
23709
+ service,
23710
+ space: service === "encryption" ? "encryption" : delegation.spaceId,
23711
+ path: delegation.path,
23712
+ actions: [...new Set(actions)].sort(),
23713
+ ...delegation.caveats === void 0 ? {} : { caveats: delegation.caveats }
23714
+ })).sort(
23715
+ (left, right) => left.service.localeCompare(right.service) || left.space.localeCompare(right.space) || left.path.localeCompare(right.path)
23716
+ );
23717
+ }
23718
+ function resourcesMatch(actual, expected) {
23719
+ const comparable = (resource) => ({
23720
+ ...resource,
23721
+ caveats: canonicalizeRecapCaveats2(resource.caveats)
23722
+ });
23723
+ return JSON.stringify(actual.map(comparable)) === JSON.stringify(expected.map(comparable));
23724
+ }
23725
+ async function activateValidatedRuntimeDelegation(node, delegation, options) {
23726
+ const host = normalizedHost(options.host);
23727
+ if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
23728
+ throw new Error(
23729
+ `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
23730
+ );
23731
+ }
23732
+ if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
23733
+ throw new Error("Runtime delegation has an invalid expiry.");
23734
+ }
23735
+ if (delegation.expiry.getTime() <= Date.now()) {
23736
+ throw new Error("Runtime delegation is expired.");
23737
+ }
23738
+ const authorization = delegation.delegationHeader?.Authorization;
23739
+ if (typeof authorization !== "string" || authorization.length === 0) {
23740
+ throw new Error("Runtime delegation is missing authorization bytes.");
23741
+ }
23742
+ const cid = node.computeDelegationCid(authorization);
23743
+ if (cid !== delegation.cid) {
23744
+ throw new Error("Runtime delegation CID does not match authorization bytes.");
23745
+ }
23746
+ const signed2 = signedAuthorityFromCompactUcan(authorization);
23747
+ if (signed2.expiry.getTime() <= Date.now()) {
23748
+ throw new Error("Runtime delegation is expired.");
23749
+ }
23750
+ if (delegation.expiry.getTime() !== signed2.expiry.getTime()) {
23751
+ throw new Error("Runtime delegation expiry does not match signed authority.");
23752
+ }
23753
+ if (!didPrincipalsMatch(signed2.audience, node.sessionDid)) {
23754
+ throw new Error(
23755
+ `Runtime delegation targets ${signed2.audience} but this session is ${node.sessionDid}.`
23756
+ );
23757
+ }
23758
+ if (!didPrincipalsMatch(delegation.delegateDID, signed2.audience)) {
23759
+ throw new Error("Runtime delegation audience does not match signed authority.");
23760
+ }
23761
+ const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
23762
+ if (!resourcesMatch(declaredResources, signed2.resources)) {
23763
+ throw new Error("Runtime delegation resources do not match signed authority.");
23764
+ }
23765
+ const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
23766
+ if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
23767
+ throw new Error(
23768
+ "A different authorization is already installed for this runtime delegation CID."
23769
+ );
23770
+ }
23771
+ const primary = signed2.resources[0];
23772
+ const signedSpace = signed2.permissions.find(
23773
+ (permission) => permission.service !== "tinycloud.encryption"
23774
+ )?.space;
23775
+ const installedCandidate = {
23776
+ cid,
23777
+ delegationHeader: { Authorization: authorization },
23778
+ ownerAddress: delegation.ownerAddress,
23779
+ chainId: delegation.chainId,
23780
+ spaceId: signedSpace ?? delegation.spaceId,
23781
+ path: primary.path,
23782
+ actions: [...primary.actions],
23783
+ ...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
23784
+ resources: signed2.resources.map((resource) => ({
23785
+ ...resource,
23786
+ actions: [...resource.actions]
23787
+ })),
23788
+ expiry: new Date(signed2.expiry),
23789
+ delegateDID: signed2.audience,
23790
+ host
23791
+ };
23792
+ await node.useRuntimeDelegation(installedCandidate);
23793
+ const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
23794
+ if (!installed) {
23795
+ throw new Error("Runtime delegation activation did not install the validated authority.");
23796
+ }
23797
+ if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed2.audience) || installed.expiry.getTime() !== signed2.expiry.getTime() || !resourcesMatch(
23798
+ canonicalResourcesFromPortableDelegation(installed),
23799
+ signed2.resources
23800
+ ) || normalizedHost(installed.host ?? "") !== host) {
23801
+ throw new Error(
23802
+ "Runtime delegation activation installed authority that differs from the validated authority."
23803
+ );
23804
+ }
23805
+ return {
23806
+ cid,
23807
+ delegation: installed,
23808
+ effectivePermissions: signed2.permissions,
23809
+ expiry: new Date(signed2.expiry),
23810
+ audience: signed2.audience,
23811
+ host: normalizedHost(installed.host ?? host)
23812
+ };
23813
+ }
22639
23814
  async function grantAuthRequest(authority, request, options) {
22640
23815
  if (request.kind !== "tinycloud.auth.request") {
22641
23816
  throw new Error(
@@ -22698,10 +23873,11 @@ import {
22698
23873
  SECRET_NAME_RE,
22699
23874
  canonicalizeSecretScope,
22700
23875
  resolveSecretListPrefix as resolveSecretListPrefix2,
22701
- resolveSecretPath as resolveSecretPath2
23876
+ resolveSecretPath as resolveSecretPath3
22702
23877
  } from "@tinycloud/sdk-core";
22703
23878
  import {
22704
23879
  EncryptionService as EncryptionService2,
23880
+ DecryptTransportResponseError as DecryptTransportResponseError2,
22705
23881
  parseNetworkId as parseNetworkId3,
22706
23882
  buildNetworkId,
22707
23883
  isNetworkId,
@@ -22777,6 +23953,7 @@ export {
22777
23953
  AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
22778
23954
  CapabilityKeyRegistry2 as CapabilityKeyRegistry,
22779
23955
  CapabilityKeyRegistryErrorCodes,
23956
+ CaveatedDelegationUnsupportedError2 as CaveatedDelegationUnsupportedError,
22780
23957
  DECRYPT_ACTION3 as DECRYPT_ACTION,
22781
23958
  DECRYPT_FACT_TYPE,
22782
23959
  DECRYPT_RESULT_TYPE,
@@ -22787,6 +23964,7 @@ export {
22787
23964
  DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
22788
23965
  DataVaultService2 as DataVaultService,
22789
23966
  DatabaseHandle,
23967
+ DecryptTransportResponseError2 as DecryptTransportResponseError,
22790
23968
  DelegatedAccess,
22791
23969
  DelegationErrorCodes,
22792
23970
  DelegationManager2 as DelegationManager,
@@ -22827,11 +24005,13 @@ export {
22827
24005
  TinyCloudDebugLogger,
22828
24006
  TinyCloudNode,
22829
24007
  UnsupportedFeatureError2 as UnsupportedFeatureError,
24008
+ UnsupportedSessionRestoreError,
22830
24009
  VAULT_PERMISSION_SERVICE,
22831
24010
  VaultHeaders,
22832
24011
  VaultPublicSpaceKVActions,
22833
24012
  VersionCheckError,
22834
24013
  WasmKeyProvider,
24014
+ activateValidatedRuntimeDelegation,
22835
24015
  addressStorageKey,
22836
24016
  buildCanonicalDecryptRequest,
22837
24017
  buildDecryptAttenuation,
@@ -22898,10 +24078,10 @@ export {
22898
24078
  parseSpaceUri,
22899
24079
  pkhDid3 as pkhDid,
22900
24080
  principalDid,
22901
- principalDidEquals3 as principalDidEquals,
24081
+ principalDidEquals4 as principalDidEquals,
22902
24082
  resolveManifest2 as resolveManifest,
22903
24083
  resolveSecretListPrefix2 as resolveSecretListPrefix,
22904
- resolveSecretPath2 as resolveSecretPath,
24084
+ resolveSecretPath3 as resolveSecretPath,
22905
24085
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
22906
24086
  serializeDelegation,
22907
24087
  tinyCloudDebugLogger,