@tinycloud/node-sdk 2.7.0-beta.3 → 2.7.0-beta.5

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
  */
@@ -19155,6 +19396,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19155
19396
  this._hooks = void 0;
19156
19397
  this._vault = void 0;
19157
19398
  this._encryption = void 0;
19399
+ this._baseVaults.clear();
19158
19400
  this._baseSecrets.clear();
19159
19401
  this._secrets.clear();
19160
19402
  this._spaceService = void 0;
@@ -19162,6 +19404,10 @@ var _TinyCloudNode = class _TinyCloudNode {
19162
19404
  this.runtimePermissionGrants = [];
19163
19405
  await this.tc.signIn(options);
19164
19406
  this.syncResolvedHostFromAuth();
19407
+ const oldGraph = this._serviceGraph;
19408
+ this._serviceGraph = this.createServiceGraphLifetime();
19409
+ oldGraph.retire();
19410
+ this.tc.retireServices();
19165
19411
  const signedInSession = this.currentTinyCloudSession();
19166
19412
  if (signedInSession) {
19167
19413
  this.sessionKeyId = signedInSession.sessionKey;
@@ -19446,7 +19692,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19446
19692
  }
19447
19693
  let operations = [];
19448
19694
  try {
19449
- const entries = this.wasmBindings.parseRecapFromSiwe(siwe);
19695
+ const entries = this.parseRecapWithCaveats(siwe);
19450
19696
  if (Array.isArray(entries)) {
19451
19697
  operations = entries.flatMap((entry) => {
19452
19698
  const service = this.invocationServiceName(entry.service);
@@ -19454,7 +19700,8 @@ var _TinyCloudNode = class _TinyCloudNode {
19454
19700
  ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
19455
19701
  service,
19456
19702
  path: entry.path,
19457
- action
19703
+ action,
19704
+ caveats: cloneRecapCaveats(entry.caveats)
19458
19705
  }));
19459
19706
  });
19460
19707
  }
@@ -19807,86 +20054,441 @@ var _TinyCloudNode = class _TinyCloudNode {
19807
20054
  */
19808
20055
  async restoreSession(sessionData) {
19809
20056
  await this.wasmBindings.ensureInitialized?.();
19810
- this._kv = void 0;
19811
- this._sql = void 0;
19812
- this._duckdb = void 0;
19813
- this._hooks = void 0;
19814
- this._vault = void 0;
19815
- this._encryption = void 0;
19816
- this._baseSecrets.clear();
19817
- this._secrets.clear();
19818
- this._spaceService = void 0;
19819
- this._serviceContext = void 0;
19820
- this.runtimePermissionGrants = [];
19821
- const restoredAddress = sessionData.address ? canonicalizeAddress2(sessionData.address) : void 0;
19822
- if (restoredAddress) {
19823
- 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.");
19824
20090
  }
19825
- if (sessionData.chainId) {
19826
- 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);
19827
20150
  }
19828
20151
  const resolvedHost = await this.resolveRestoredHost(
19829
20152
  sessionData.tinycloudHosts,
19830
20153
  restoredAddress,
19831
20154
  sessionData.chainId
19832
20155
  );
19833
- if (resolvedHost) {
19834
- this.config.host = resolvedHost;
19835
- }
19836
- this._serviceContext = new ServiceContext2({
19837
- invoke: this.invokeWithRuntimePermissions,
19838
- invokeAny: this.invokeAnyWithRuntimePermissions,
19839
- fetch: globalThis.fetch.bind(globalThis),
19840
- hosts: [this.config.host],
19841
- telemetry: this.config.telemetry
19842
- });
19843
- this._kv = new KVService2({});
19844
- this._kv.initialize(this._serviceContext);
19845
- this._serviceContext.registerService("kv", this._kv);
19846
- this._sql = new SQLService2({});
19847
- this._sql.initialize(this._serviceContext);
19848
- this._serviceContext.registerService("sql", this._sql);
19849
- this._duckdb = new DuckDbService2({});
19850
- this._duckdb.initialize(this._serviceContext);
19851
- this._serviceContext.registerService("duckdb", this._duckdb);
19852
- this._hooks = new HooksService2({});
19853
- this._hooks.initialize(this._serviceContext);
19854
- 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;
19855
20160
  const serviceSession = {
19856
20161
  delegationHeader: sessionData.delegationHeader,
19857
20162
  delegationCid: sessionData.delegationCid,
19858
20163
  spaceId: sessionData.spaceId,
19859
- verificationMethod: sessionData.verificationMethod,
19860
- jwk: sessionData.jwk
20164
+ verificationMethod: restoredVerificationMethod,
20165
+ jwk: stagedJwk
19861
20166
  };
19862
- this._serviceContext.setSession(serviceSession);
19863
- this._vault = this.createVaultService(sessionData.spaceId, this._kv);
19864
- this._vault.initialize(this._serviceContext);
19865
- this._serviceContext.registerService("vault", this._vault);
19866
- this.initializeV2Services(serviceSession);
19867
- if (sessionData.siwe && restoredAddress && sessionData.chainId) {
19868
- const tcSession = {
19869
- address: restoredAddress,
19870
- chainId: sessionData.chainId,
19871
- sessionKey: JSON.stringify(sessionData.jwk),
19872
- spaceId: sessionData.spaceId,
19873
- delegationCid: sessionData.delegationCid,
19874
- delegationHeader: sessionData.delegationHeader,
19875
- verificationMethod: sessionData.verificationMethod,
19876
- jwk: sessionData.jwk,
19877
- siwe: sessionData.siwe,
19878
- signature: sessionData.signature ?? ""
19879
- };
19880
- if (this.auth) {
19881
- this.auth.setRestoredTinyCloudSession(
19882
- tcSession,
19883
- 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()
19884
20402
  );
19885
- } else {
19886
- 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);
19887
20488
  }
19888
- this.registerPrimarySessionGrant(tcSession);
19889
- }
20489
+ });
20490
+ spaceService.updateConfig({ sharingService });
20491
+ return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
19890
20492
  }
19891
20493
  /**
19892
20494
  * Resolve the host a restored session should target.
@@ -20048,14 +20650,19 @@ var _TinyCloudNode = class _TinyCloudNode {
20048
20650
  if (!session) {
20049
20651
  return;
20050
20652
  }
20051
- this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
20052
- this._serviceContext = new ServiceContext2({
20053
- invoke: this.invokeWithRuntimePermissions,
20054
- invokeAny: this.invokeAnyWithRuntimePermissions,
20055
- 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,
20056
20663
  hosts: [this.config.host],
20057
20664
  telemetry: this.config.telemetry
20058
- });
20665
+ }));
20059
20666
  this._kv = new KVService2({});
20060
20667
  this._kv.initialize(this._serviceContext);
20061
20668
  this._serviceContext.registerService("kv", this._kv);
@@ -20090,12 +20697,12 @@ var _TinyCloudNode = class _TinyCloudNode {
20090
20697
  createSpaceScopedKVService(spaceId) {
20091
20698
  const kvService = new KVService2({});
20092
20699
  if (this._serviceContext) {
20093
- const spaceScopedContext = new ServiceContext2({
20700
+ const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
20094
20701
  invoke: this._serviceContext.invoke,
20095
20702
  fetch: this._serviceContext.fetch,
20096
20703
  hosts: this._serviceContext.hosts,
20097
20704
  telemetry: this.config.telemetry
20098
- });
20705
+ }));
20099
20706
  const session = this._serviceContext.session;
20100
20707
  if (session) {
20101
20708
  spaceScopedContext.setSession({ ...session, spaceId });
@@ -20128,6 +20735,18 @@ var _TinyCloudNode = class _TinyCloudNode {
20128
20735
  }
20129
20736
  return session;
20130
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
+ }
20131
20750
  createEncryptionCrypto() {
20132
20751
  const wasm = this.wasmBindings;
20133
20752
  const columnEncrypt = (key2, plaintext) => {
@@ -20183,14 +20802,15 @@ var _TinyCloudNode = class _TinyCloudNode {
20183
20802
  }
20184
20803
  return info.nodeId;
20185
20804
  }
20186
- async signRawNetworkAuthorization(input) {
20805
+ async signRawNetworkAuthorization(input, binding) {
20806
+ binding?.graph.assertActive();
20187
20807
  if (!this.wasmBindings.invokeAny) {
20188
20808
  throw new Error("WASM binding does not support raw-resource invokeAny");
20189
20809
  }
20190
20810
  if (!this.wasmBindings.computeCid) {
20191
20811
  throw new Error("WASM binding does not support invocation CID computation");
20192
20812
  }
20193
- const session = this.requireServiceSession();
20813
+ const session = binding?.session ?? this.requireEncryptionSession();
20194
20814
  const headers = this.invokeAnyWithRuntimePermissions(
20195
20815
  session,
20196
20816
  [
@@ -20209,6 +20829,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20209
20829
  input.targetNode,
20210
20830
  session.jwk
20211
20831
  );
20832
+ binding?.graph.assertActive();
20212
20833
  return {
20213
20834
  authorization: audienceBound,
20214
20835
  invocationCid: this.wasmBindings.computeCid(
@@ -20217,25 +20838,55 @@ var _TinyCloudNode = class _TinyCloudNode {
20217
20838
  )
20218
20839
  };
20219
20840
  }
20220
- 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;
20221
20861
  const crypto2 = this.createEncryptionCrypto();
20222
20862
  const transport = {
20223
- postDecrypt: async ({ networkId, authorization, canonicalBody }) => {
20224
- const response = await fetch(
20225
- `${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`,
20226
20867
  {
20227
20868
  method: "POST",
20228
20869
  headers: {
20229
20870
  Authorization: authorization,
20230
20871
  "Content-Type": "application/json"
20231
20872
  },
20232
- body: canonicalBody
20873
+ body: canonicalBody,
20874
+ signal
20233
20875
  }
20234
20876
  );
20877
+ graph.assertActive();
20235
20878
  if (!response.ok) {
20236
- throw new Error(
20237
- `decrypt failed ${response.status}: ${await response.text()}`
20238
- );
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);
20239
20890
  }
20240
20891
  return await response.json();
20241
20892
  }
@@ -20244,12 +20895,14 @@ var _TinyCloudNode = class _TinyCloudNode {
20244
20895
  crypto: crypto2,
20245
20896
  signer: {
20246
20897
  signDecryptInvocation: async (input) => {
20898
+ graph.assertActive();
20247
20899
  const signed2 = await this.signRawNetworkAuthorization({
20248
20900
  targetNode: input.targetNode,
20249
20901
  networkId: input.networkId,
20250
20902
  action: DECRYPT_ACTION2,
20251
20903
  facts: input.facts
20252
- });
20904
+ }, { graph, session });
20905
+ graph.assertActive();
20253
20906
  return {
20254
20907
  ...signed2,
20255
20908
  canonicalBody: canonicalizeEncryptionJson(
@@ -20260,25 +20913,35 @@ var _TinyCloudNode = class _TinyCloudNode {
20260
20913
  },
20261
20914
  transport,
20262
20915
  node: {
20263
- 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
+ }
20264
20922
  },
20265
20923
  wellKnown: {
20266
20924
  fetchWellKnown: async (principal, discoveryKey) => {
20267
- if (!this._address || !didPrincipalMatches2(principal, this.did)) {
20268
- return null;
20269
- }
20270
- if (!this.config.host) {
20925
+ graph.assertActive();
20926
+ if (!address || !didPrincipalMatches2(principal, did)) {
20271
20927
  return null;
20272
20928
  }
20273
- const publicSpaceId = makePublicSpaceId(this._address, this._chainId);
20274
- const result = await TinyCloud.readPublicSpace(this.config.host, publicSpaceId, discoveryKey);
20275
- 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) {
20276
20937
  return null;
20277
20938
  }
20278
- const body = result.data;
20939
+ const body = await response.json();
20940
+ graph.assertActive();
20279
20941
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
20280
20942
  }
20281
- }
20943
+ },
20944
+ assertActive: () => graph.assertActive()
20282
20945
  });
20283
20946
  }
20284
20947
  getEncryptionService() {
@@ -20292,8 +20955,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20292
20955
  }
20293
20956
  return this._encryption;
20294
20957
  }
20295
- createVaultService(spaceId, kv) {
20958
+ createVaultService(spaceId, kv, encryptionService = this.getEncryptionService(), nodeContext) {
20296
20959
  const wasm = this.wasmBindings;
20960
+ const serviceGraph = nodeContext?.serviceGraph ?? this._serviceGraph;
20297
20961
  const vaultCrypto = createVaultCrypto({
20298
20962
  vault_encrypt: wasm.vault_encrypt,
20299
20963
  vault_decrypt: wasm.vault_decrypt,
@@ -20304,12 +20968,17 @@ var _TinyCloudNode = class _TinyCloudNode {
20304
20968
  vault_sha256: wasm.vault_sha256
20305
20969
  });
20306
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;
20307
20976
  return new DataVaultService({
20308
20977
  spaceId,
20309
20978
  crypto: vaultCrypto,
20310
20979
  encryption: {
20311
- networkId: this.getEncryptionNetworkIdForSpace(spaceId),
20312
- service: this.getEncryptionService(),
20980
+ networkId: `urn:tinycloud:encryption:${ownerDid}:${DEFAULT_ENCRYPTION_NETWORK_NAME}`,
20981
+ service: encryptionService,
20313
20982
  decryptCapabilityProof: () => ({
20314
20983
  proofs: [this.requireServiceSession().delegationCid]
20315
20984
  })
@@ -20318,6 +20987,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20318
20987
  kv,
20319
20988
  ensurePublicSpace: async () => {
20320
20989
  try {
20990
+ serviceGraph.assertActive();
20321
20991
  await self2.ensurePublicSpace();
20322
20992
  return { ok: true, data: void 0 };
20323
20993
  } catch (error) {
@@ -20325,14 +20995,15 @@ var _TinyCloudNode = class _TinyCloudNode {
20325
20995
  }
20326
20996
  },
20327
20997
  get publicKV() {
20998
+ serviceGraph.assertActive();
20328
20999
  return self2._publicKV ?? self2.tc.publicKV;
20329
21000
  },
20330
- readPublicSpace: (host, targetSpaceId, key2) => TinyCloud.readPublicSpace(host, targetSpaceId, key2),
21001
+ readPublicSpace: (host2, targetSpaceId, key2) => TinyCloud.readPublicSpace(host2, targetSpaceId, key2),
20331
21002
  makePublicSpaceId: TinyCloud.makePublicSpaceId,
20332
- did: this.did,
20333
- address: this._address ?? "",
20334
- chainId: this._chainId,
20335
- hosts: [this.config.host]
21003
+ did,
21004
+ address: address ?? "",
21005
+ chainId,
21006
+ hosts: [host]
20336
21007
  }
20337
21008
  });
20338
21009
  }
@@ -20341,6 +21012,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20341
21012
  * @internal
20342
21013
  */
20343
21014
  initializeV2Services(serviceSession) {
21015
+ const graph = this._serviceGraph;
20344
21016
  this._capabilityRegistry = new CapabilityKeyRegistry();
20345
21017
  const tcSession = this.auth?.tinyCloudSession;
20346
21018
  if (tcSession && this._address) {
@@ -20385,21 +21057,23 @@ var _TinyCloudNode = class _TinyCloudNode {
20385
21057
  hosts: [this.config.host],
20386
21058
  accountSpaceId: this.accountSpaceId,
20387
21059
  session: serviceSession,
20388
- invoke: this.invokeWithRuntimePermissions,
20389
- invokeAny: this.invokeAnyWithRuntimePermissions,
20390
- fetch: globalThis.fetch.bind(globalThis)
21060
+ invoke: graph.invoke,
21061
+ invokeAny: graph.invokeAny,
21062
+ fetch: graph.fetch
20391
21063
  });
20392
21064
  this._spaceService = new SpaceService({
20393
21065
  hosts: [this.config.host],
20394
21066
  session: serviceSession,
20395
- invoke: this.wasmBindings.invoke,
20396
- fetch: globalThis.fetch.bind(globalThis),
21067
+ invoke: graph.invoke,
21068
+ fetch: graph.fetch,
20397
21069
  capabilityRegistry: this._capabilityRegistry,
20398
21070
  userDid: this.did,
20399
21071
  createKVService: (spaceId) => {
21072
+ graph.assertActive();
20400
21073
  return this.createSpaceScopedKVService(spaceId);
20401
21074
  },
20402
21075
  createVaultService: (spaceId) => {
21076
+ graph.assertActive();
20403
21077
  const kvService = this.createSpaceScopedKVService(spaceId);
20404
21078
  const vaultService = this.createVaultService(spaceId, kvService);
20405
21079
  if (this._serviceContext) {
@@ -20408,11 +21082,13 @@ var _TinyCloudNode = class _TinyCloudNode {
20408
21082
  return vaultService;
20409
21083
  },
20410
21084
  createSecretsService: (spaceId) => {
21085
+ graph.assertActive();
20411
21086
  return this.secretsForSpace(spaceId);
20412
21087
  },
20413
21088
  // Enable space.delegations.create() via SIWE-based delegation
20414
21089
  createDelegation: async (params) => {
20415
21090
  try {
21091
+ graph.assertActive();
20416
21092
  const portableDelegation = await this.createDelegation({
20417
21093
  delegateDID: params.delegateDID,
20418
21094
  path: params.path,
@@ -20446,18 +21122,50 @@ var _TinyCloudNode = class _TinyCloudNode {
20446
21122
  }
20447
21123
  },
20448
21124
  onSpaceRegistered: async (space) => {
21125
+ graph.assertActive();
20449
21126
  await this.account.spaces.register(space);
20450
21127
  }
20451
21128
  });
20452
- this._sharingService.updateConfig({
21129
+ this._sharingService = new SharingService({
21130
+ hosts: [this.config.host],
20453
21131
  session: serviceSession,
21132
+ invoke: graph.invoke,
21133
+ fetch: graph.fetch,
21134
+ assertActive: () => graph.assertActive(),
21135
+ keyProvider: this._keyProvider,
21136
+ registry: this._capabilityRegistry,
20454
21137
  delegationManager: this._delegationManager,
20455
21138
  sessionExpiry: this.getSessionExpiry(),
20456
- // WASM-based delegation creation (preferred - no server roundtrip)
20457
- createDelegationWasm: (params) => this.createDelegationWrapper(params),
20458
- // Root delegation for long-lived share links (bypasses session expiry)
20459
- // In node-sdk we have direct signer access, so no popup needed
20460
- 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
20461
21169
  });
20462
21170
  this._spaceService.updateConfig({
20463
21171
  sharingService: this._sharingService
@@ -20524,7 +21232,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20524
21232
  * with expiry longer than the current session.
20525
21233
  * @internal
20526
21234
  */
20527
- async createOwnerDelegation(params) {
21235
+ async createOwnerDelegation(params, assertActive) {
21236
+ const assertOwnerGraphActive = assertActive ?? this._serviceGraph.assertActive.bind(this._serviceGraph);
21237
+ assertOwnerGraphActive();
20528
21238
  if (!params.delegateDid.startsWith("did:key:") || params.actions.length === 0 || params.path.length === 0) {
20529
21239
  throw new Error("Owner delegation requires an external did:key audience and bounded capabilities");
20530
21240
  }
@@ -20549,8 +21259,10 @@ var _TinyCloudNode = class _TinyCloudNode {
20549
21259
  delegateUri: params.delegateDid
20550
21260
  });
20551
21261
  const signature2 = await this.signer.signMessage(prepared.siwe);
21262
+ assertOwnerGraphActive();
20552
21263
  const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature: signature2 });
20553
21264
  const activation = await activateSessionWithHost2(host, delegationSession.delegationHeader);
21265
+ assertOwnerGraphActive();
20554
21266
  if (!activation.success) {
20555
21267
  throw new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim());
20556
21268
  }
@@ -20578,7 +21290,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20578
21290
  }
20579
21291
  };
20580
21292
  }
20581
- async createRootDelegationForSharing(params) {
21293
+ async createRootDelegationForSharing(params, assertActive) {
20582
21294
  try {
20583
21295
  return (await this.createOwnerDelegation({
20584
21296
  delegateDid: params.shareKeyDID,
@@ -20586,8 +21298,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20586
21298
  path: params.path,
20587
21299
  actions: params.actions,
20588
21300
  expiresAt: params.requestedExpiry
20589
- })).delegation;
21301
+ }, assertActive)).delegation;
20590
21302
  } catch {
21303
+ assertActive?.();
20591
21304
  return void 0;
20592
21305
  }
20593
21306
  }
@@ -20659,13 +21372,13 @@ var _TinyCloudNode = class _TinyCloudNode {
20659
21372
  throw new Error("Not signed in. Call signIn() first.");
20660
21373
  }
20661
21374
  const sql = new SQLService2({});
20662
- const spaceScopedContext = new ServiceContext2({
21375
+ const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
20663
21376
  invoke: this._serviceContext.invoke,
20664
21377
  invokeAny: this._serviceContext.invokeAny,
20665
21378
  fetch: this._serviceContext.fetch,
20666
21379
  hosts: this._serviceContext.hosts,
20667
21380
  telemetry: this.config.telemetry
20668
- });
21381
+ }));
20669
21382
  spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
20670
21383
  sql.initialize(spaceScopedContext);
20671
21384
  return sql;
@@ -20689,12 +21402,12 @@ var _TinyCloudNode = class _TinyCloudNode {
20689
21402
  throw new Error("Not signed in. Call signIn() first.");
20690
21403
  }
20691
21404
  const kv = new KVService2({});
20692
- const spaceScopedContext = new ServiceContext2({
21405
+ const spaceScopedContext = this._serviceGraph.track(new ServiceContext2({
20693
21406
  invoke: this._serviceContext.invoke,
20694
21407
  invokeAny: this._serviceContext.invokeAny,
20695
21408
  fetch: this._serviceContext.fetch,
20696
21409
  hosts: this._serviceContext.hosts
20697
- });
21410
+ }));
20698
21411
  spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
20699
21412
  kv.initialize(spaceScopedContext);
20700
21413
  return kv;
@@ -20730,19 +21443,11 @@ var _TinyCloudNode = class _TinyCloudNode {
20730
21443
  }
20731
21444
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
20732
21445
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
20733
- const response = await fetch(
20734
- `${this.config.host}/encryption/networks/${encodeURIComponent(networkId)}`
21446
+ return this.fetchEncryptionNetworkAt(
21447
+ this.config.host,
21448
+ networkId,
21449
+ globalThis.fetch.bind(globalThis)
20735
21450
  );
20736
- if (response.status === 404) {
20737
- return null;
20738
- }
20739
- if (!response.ok) {
20740
- throw new Error(
20741
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
20742
- );
20743
- }
20744
- const body = await response.json();
20745
- return "descriptor" in body && body.descriptor ? body.descriptor : body;
20746
21451
  }
20747
21452
  async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
20748
21453
  const targetNode = await this.fetchNodeId();
@@ -20836,6 +21541,38 @@ var _TinyCloudNode = class _TinyCloudNode {
20836
21541
  }
20837
21542
  return secrets;
20838
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
+ }
20839
21576
  getBaseSecrets(spaceId) {
20840
21577
  if (!this._spaceService) {
20841
21578
  throw new Error("Not signed in. Call signIn() first.");
@@ -20843,16 +21580,29 @@ var _TinyCloudNode = class _TinyCloudNode {
20843
21580
  const resolvedSpace = spaceId.startsWith("tinycloud:") ? spaceId : this.ownedSpaceId(spaceId);
20844
21581
  let secrets = this._baseSecrets.get(resolvedSpace);
20845
21582
  if (!secrets) {
20846
- const kvService = this.createSpaceScopedKVService(resolvedSpace);
20847
- const vaultService = this.createVaultService(resolvedSpace, kvService);
20848
- if (this._serviceContext) {
20849
- vaultService.initialize(this._serviceContext);
20850
- }
20851
- secrets = new SecretsService(() => vaultService);
21583
+ secrets = new SecretsService(() => this.getBaseVault(resolvedSpace));
20852
21584
  this._baseSecrets.set(resolvedSpace, secrets);
20853
21585
  }
20854
21586
  return secrets;
20855
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
+ }
20856
21606
  /**
20857
21607
  * Hooks write stream subscription API.
20858
21608
  */
@@ -20957,6 +21707,63 @@ var _TinyCloudNode = class _TinyCloudNode {
20957
21707
  (grant) => grant.delegation
20958
21708
  );
20959
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
+ }
20960
21767
  /**
20961
21768
  * Install a portable runtime permission delegation into this SDK instance so
20962
21769
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -21025,6 +21832,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21025
21832
  "grantRuntimePermissions requires wallet mode with a signer or privateKey."
21026
21833
  );
21027
21834
  }
21835
+ this.assertDelegationCaveatsPreservable(expanded);
21028
21836
  const rawEntries = expanded.filter(
21029
21837
  (entry) => this.isEncryptionPermissionEntry(entry)
21030
21838
  );
@@ -21286,12 +22094,12 @@ var _TinyCloudNode = class _TinyCloudNode {
21286
22094
  }
21287
22095
  if (this._serviceContext) {
21288
22096
  const publicKV = new KVService2({ prefix: "" });
21289
- const publicContext = new ServiceContext2({
21290
- invoke: this.invokeWithRuntimePermissions,
21291
- fetch: this._serviceContext.fetch,
22097
+ const publicContext = this._serviceGraph.track(new ServiceContext2({
22098
+ invoke: this._serviceGraph.invoke,
22099
+ fetch: this._serviceGraph.fetch,
21292
22100
  hosts: this._serviceContext.hosts,
21293
22101
  telemetry: this.config.telemetry
21294
- });
22102
+ }));
21295
22103
  publicContext.setSession({
21296
22104
  delegationHeader: delegationSession.delegationHeader,
21297
22105
  delegationCid: delegationSession.delegationCid,
@@ -21358,13 +22166,14 @@ var _TinyCloudNode = class _TinyCloudNode {
21358
22166
  async getDelegationStatus(cid) {
21359
22167
  return this.delegationManager.status(cid);
21360
22168
  }
21361
- /** Compute the canonical CID of a compact delegation authorization. */
22169
+ /** Compute the canonical CID of a compact UCAN or DAG-CBOR delegation authorization. */
21362
22170
  computeDelegationCid(authorization) {
21363
22171
  if (!authorization || !this.wasmBindings.computeCid) {
21364
22172
  throw new Error("Delegation CID computation is unavailable");
21365
22173
  }
22174
+ const compact = authorization.replace(/^Bearer /i, "");
21366
22175
  return this.wasmBindings.computeCid(
21367
- new TextEncoder().encode(authorization),
22176
+ compact.includes(".") ? new TextEncoder().encode(compact) : decodeAuthorizationBytes(authorization),
21368
22177
  0x55n
21369
22178
  );
21370
22179
  }
@@ -21456,6 +22265,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21456
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."
21457
22266
  );
21458
22267
  }
22268
+ this.assertDelegationCaveatsPreservable(expandedEntries);
21459
22269
  const delegation2 = await this.createDelegationLegacyWalletPath(
21460
22270
  did,
21461
22271
  expandedEntries[0],
@@ -21464,13 +22274,14 @@ var _TinyCloudNode = class _TinyCloudNode {
21464
22274
  return { delegation: delegation2, prompted: true };
21465
22275
  }
21466
22276
  const granted = parseRecapCapabilities(
21467
- (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
22277
+ (siwe) => this.parseRecapWithCaveats(siwe),
21468
22278
  session.siwe
21469
22279
  );
21470
22280
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
21471
22281
  if (!subset) {
22282
+ const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
21472
22283
  const runtimeGrant = this.findGrantForOperations(
21473
- this.permissionEntriesToOperations(expandedEntries, session),
22284
+ runtimeOperations,
21474
22285
  { excludePrimary: true }
21475
22286
  );
21476
22287
  if (runtimeGrant) {
@@ -21483,7 +22294,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21483
22294
  did,
21484
22295
  expandedEntries,
21485
22296
  runtimeExpiration,
21486
- runtimeGrant
22297
+ runtimeGrant,
22298
+ runtimeOperations
21487
22299
  );
21488
22300
  return { delegation: delegation2, prompted: false };
21489
22301
  }
@@ -21553,6 +22365,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21553
22365
  "createDelegationViaWasmPath requires a non-empty entries array"
21554
22366
  );
21555
22367
  }
22368
+ this.assertDelegationCaveatsPreservable(entries);
21556
22369
  const resolvedSpaces = /* @__PURE__ */ new Set();
21557
22370
  for (const entry of entries) {
21558
22371
  if (this.isEncryptionPermissionEntry(entry)) {
@@ -21633,7 +22446,9 @@ var _TinyCloudNode = class _TinyCloudNode {
21633
22446
  host: this.config.host
21634
22447
  };
21635
22448
  }
21636
- async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
22449
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations) {
22450
+ this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
22451
+ this.assertDelegationCaveatsPreservable(entries);
21637
22452
  const result = this.createDelegationWrapper({
21638
22453
  session: grant.session,
21639
22454
  delegateDID: did,
@@ -21687,6 +22502,74 @@ var _TinyCloudNode = class _TinyCloudNode {
21687
22502
  expandPermissionEntries(permissions) {
21688
22503
  return expandPermissionEntriesCore(permissions);
21689
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
+ }
21690
22573
  shortServiceName(service) {
21691
22574
  const short = SERVICE_LONG_TO_SHORT[service];
21692
22575
  if (short === void 0) {
@@ -21741,14 +22624,15 @@ var _TinyCloudNode = class _TinyCloudNode {
21741
22624
  ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
21742
22625
  service,
21743
22626
  path: entry.path,
21744
- action
22627
+ action,
22628
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
21745
22629
  }));
21746
22630
  });
21747
22631
  }
21748
22632
  sessionCoversPermissionEntries(session, entries) {
21749
22633
  try {
21750
22634
  const granted = parseRecapCapabilities(
21751
- (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
22635
+ (siwe) => this.parseRecapWithCaveats(siwe),
21752
22636
  session.siwe
21753
22637
  );
21754
22638
  return isCapabilitySubset(entries, granted).subset;
@@ -21764,7 +22648,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21764
22648
  ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
21765
22649
  service,
21766
22650
  path: entry.path,
21767
- action
22651
+ action,
22652
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
21768
22653
  }));
21769
22654
  });
21770
22655
  }
@@ -21840,7 +22725,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21840
22725
  service: this.shortServiceName(entry.service),
21841
22726
  space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
21842
22727
  path: entry.path,
21843
- actions: [...entry.actions]
22728
+ actions: [...entry.actions],
22729
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
21844
22730
  }));
21845
22731
  }
21846
22732
  operationsFromDelegation(delegation) {
@@ -21851,7 +22737,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21851
22737
  ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
21852
22738
  service,
21853
22739
  path: resource.path,
21854
- action
22740
+ action,
22741
+ caveats: cloneRecapCaveats(resource.caveats)
21855
22742
  }));
21856
22743
  });
21857
22744
  }
@@ -21867,7 +22754,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21867
22754
  service,
21868
22755
  space: delegation.spaceId,
21869
22756
  path: delegation.path,
21870
- actions
22757
+ actions,
22758
+ ...delegation.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(delegation.caveats) }
21871
22759
  }));
21872
22760
  }
21873
22761
  /**
@@ -21968,6 +22856,9 @@ var _TinyCloudNode = class _TinyCloudNode {
21968
22856
  if (granted.service !== requested.service || !this.actionContains(granted.action, requested.action)) {
21969
22857
  return false;
21970
22858
  }
22859
+ if (requested.caveats !== void 0 && !recapCaveatsEqual(granted.caveats, requested.caveats)) {
22860
+ return false;
22861
+ }
21971
22862
  if (granted.resource !== void 0 || requested.resource !== void 0) {
21972
22863
  return granted.resource !== void 0 && requested.resource !== void 0 && granted.resource === requested.resource && this.pathContains(granted.path, requested.path);
21973
22864
  }
@@ -22002,6 +22893,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22002
22893
  invocationServiceName(service) {
22003
22894
  return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
22004
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
+ }
22005
22900
  isEncryptionNetworkOperation(service, path) {
22006
22901
  return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
22007
22902
  }
@@ -22012,7 +22907,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22012
22907
  resource: entry.resource,
22013
22908
  service,
22014
22909
  path: entry.path,
22015
- action: entry.action
22910
+ action: entry.action,
22911
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
22016
22912
  };
22017
22913
  }
22018
22914
  if (this.isEncryptionNetworkOperation(service, entry.path)) {
@@ -22020,7 +22916,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22020
22916
  resource: entry.path,
22021
22917
  service,
22022
22918
  path: entry.path,
22023
- action: entry.action
22919
+ action: entry.action,
22920
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
22024
22921
  };
22025
22922
  }
22026
22923
  if (typeof entry.spaceId === "string") {
@@ -22028,7 +22925,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22028
22925
  spaceId: entry.spaceId,
22029
22926
  service,
22030
22927
  path: entry.path,
22031
- action: entry.action
22928
+ action: entry.action,
22929
+ ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
22032
22930
  };
22033
22931
  }
22034
22932
  return void 0;
@@ -22368,6 +23266,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22368
23266
  * @returns A portable delegation for the sub-delegate
22369
23267
  */
22370
23268
  async createSubDelegation(parentDelegation, params) {
23269
+ this.assertPortableDelegationCaveatsPreservable(parentDelegation);
22371
23270
  if (!this.signer) {
22372
23271
  throw new Error("Cannot createSubDelegation() in session-only mode. Requires wallet mode.");
22373
23272
  }
@@ -22484,7 +23383,7 @@ import {
22484
23383
  parsePkhDid,
22485
23384
  pkhDid as pkhDid3,
22486
23385
  principalDid,
22487
- principalDidEquals as principalDidEquals3,
23386
+ principalDidEquals as principalDidEquals4,
22488
23387
  parseCanonicalNetworkId,
22489
23388
  TinyCloudDebugLogger,
22490
23389
  tinyCloudDebugLogger,
@@ -22622,6 +23521,7 @@ import {
22622
23521
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
22623
23522
  DEFAULT_MANIFEST_VERSION,
22624
23523
  VAULT_PERMISSION_SERVICE,
23524
+ CaveatedDelegationUnsupportedError as CaveatedDelegationUnsupportedError2,
22625
23525
  PermissionNotInManifestError as PermissionNotInManifestError2,
22626
23526
  SessionExpiredError as SessionExpiredError2,
22627
23527
  ManifestValidationError,
@@ -22638,6 +23538,283 @@ import {
22638
23538
  } from "@tinycloud/sdk-core";
22639
23539
 
22640
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
+ if (node.isSessionOnly === true && typeof node.did === "string" && principalDidEquals3(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
23793
+ await node.useDelegation(installedCandidate);
23794
+ } else {
23795
+ await node.useRuntimeDelegation(installedCandidate);
23796
+ }
23797
+ const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
23798
+ if (!installed) {
23799
+ throw new Error("Runtime delegation activation did not install the validated authority.");
23800
+ }
23801
+ if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed2.audience) || installed.expiry.getTime() !== signed2.expiry.getTime() || !resourcesMatch(
23802
+ canonicalResourcesFromPortableDelegation(installed),
23803
+ signed2.resources
23804
+ ) || normalizedHost(installed.host ?? "") !== host) {
23805
+ throw new Error(
23806
+ "Runtime delegation activation installed authority that differs from the validated authority."
23807
+ );
23808
+ }
23809
+ return {
23810
+ cid,
23811
+ delegation: installed,
23812
+ effectivePermissions: signed2.permissions,
23813
+ expiry: new Date(signed2.expiry),
23814
+ audience: signed2.audience,
23815
+ host: normalizedHost(installed.host ?? host)
23816
+ };
23817
+ }
22641
23818
  async function grantAuthRequest(authority, request, options) {
22642
23819
  if (request.kind !== "tinycloud.auth.request") {
22643
23820
  throw new Error(
@@ -22700,10 +23877,11 @@ import {
22700
23877
  SECRET_NAME_RE,
22701
23878
  canonicalizeSecretScope,
22702
23879
  resolveSecretListPrefix as resolveSecretListPrefix2,
22703
- resolveSecretPath as resolveSecretPath2
23880
+ resolveSecretPath as resolveSecretPath3
22704
23881
  } from "@tinycloud/sdk-core";
22705
23882
  import {
22706
23883
  EncryptionService as EncryptionService2,
23884
+ DecryptTransportResponseError as DecryptTransportResponseError2,
22707
23885
  parseNetworkId as parseNetworkId3,
22708
23886
  buildNetworkId,
22709
23887
  isNetworkId,
@@ -22779,6 +23957,7 @@ export {
22779
23957
  AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
22780
23958
  CapabilityKeyRegistry2 as CapabilityKeyRegistry,
22781
23959
  CapabilityKeyRegistryErrorCodes,
23960
+ CaveatedDelegationUnsupportedError2 as CaveatedDelegationUnsupportedError,
22782
23961
  DECRYPT_ACTION3 as DECRYPT_ACTION,
22783
23962
  DECRYPT_FACT_TYPE,
22784
23963
  DECRYPT_RESULT_TYPE,
@@ -22789,6 +23968,7 @@ export {
22789
23968
  DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
22790
23969
  DataVaultService2 as DataVaultService,
22791
23970
  DatabaseHandle,
23971
+ DecryptTransportResponseError2 as DecryptTransportResponseError,
22792
23972
  DelegatedAccess,
22793
23973
  DelegationErrorCodes,
22794
23974
  DelegationManager2 as DelegationManager,
@@ -22829,11 +24009,13 @@ export {
22829
24009
  TinyCloudDebugLogger,
22830
24010
  TinyCloudNode,
22831
24011
  UnsupportedFeatureError2 as UnsupportedFeatureError,
24012
+ UnsupportedSessionRestoreError,
22832
24013
  VAULT_PERMISSION_SERVICE,
22833
24014
  VaultHeaders,
22834
24015
  VaultPublicSpaceKVActions,
22835
24016
  VersionCheckError,
22836
24017
  WasmKeyProvider,
24018
+ activateValidatedRuntimeDelegation,
22837
24019
  addressStorageKey,
22838
24020
  buildCanonicalDecryptRequest,
22839
24021
  buildDecryptAttenuation,
@@ -22900,10 +24082,10 @@ export {
22900
24082
  parseSpaceUri,
22901
24083
  pkhDid3 as pkhDid,
22902
24084
  principalDid,
22903
- principalDidEquals3 as principalDidEquals,
24085
+ principalDidEquals4 as principalDidEquals,
22904
24086
  resolveManifest2 as resolveManifest,
22905
24087
  resolveSecretListPrefix2 as resolveSecretListPrefix,
22906
- resolveSecretPath2 as resolveSecretPath,
24088
+ resolveSecretPath3 as resolveSecretPath,
22907
24089
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
22908
24090
  serializeDelegation,
22909
24091
  tinyCloudDebugLogger,