@tinycloud/node-sdk 3.0.0-beta.2 → 3.0.0-beta.20

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/core.cjs CHANGED
@@ -50,6 +50,7 @@ __export(core_exports, {
50
50
  DuckDbService: () => import_sdk_core15.DuckDbService,
51
51
  FileSessionStorage: () => FileSessionStorage,
52
52
  IdentityParseError: () => import_sdk_core11.IdentityParseError,
53
+ InvalidRestoredSessionError: () => InvalidRestoredSessionError,
53
54
  KVService: () => import_sdk_core13.KVService,
54
55
  ManifestValidationError: () => import_sdk_core12.ManifestValidationError,
55
56
  MemorySessionStorage: () => MemorySessionStorage,
@@ -115,6 +116,7 @@ __export(core_exports, {
115
116
  parseCanonicalNetworkId: () => import_sdk_core11.parseCanonicalNetworkId,
116
117
  parseExpiry: () => import_sdk_core12.parseExpiry,
117
118
  parsePkhDid: () => import_sdk_core11.parsePkhDid,
119
+ parseSecretCatalogKey: () => import_sdk_core16.parseSecretCatalogKey,
118
120
  parseSpaceUri: () => import_sdk_core19.parseSpaceUri,
119
121
  pkhDid: () => import_sdk_core11.pkhDid,
120
122
  principalDid: () => import_sdk_core11.principalDid,
@@ -2081,6 +2083,11 @@ var NodeSecretsService = class {
2081
2083
  if (!permission.ok) return permission;
2082
2084
  return options === void 0 ? this.service.list() : this.service.list(options);
2083
2085
  }
2086
+ async listAll() {
2087
+ const permission = await this.ensurePermission("", void 0, "list");
2088
+ if (!permission.ok) return permission;
2089
+ return this.service.listAll();
2090
+ }
2084
2091
  get service() {
2085
2092
  return this.config.getService();
2086
2093
  }
@@ -2142,7 +2149,11 @@ var NodeSecretsService = class {
2142
2149
  (entry) => manifests.some((candidate) => {
2143
2150
  const resolved = (0, import_sdk_core7.resolveManifest)(candidate);
2144
2151
  return resolved.resources.some(
2145
- (resource) => resource.service === entry.service && spaceMatches(resource.space, entry.space, this.config.resolveSpace) && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
2152
+ (resource) => resource.service === entry.service && spaceMatches(
2153
+ resource.space,
2154
+ entry.space,
2155
+ this.config.resolveSpace
2156
+ ) && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
2146
2157
  );
2147
2158
  })
2148
2159
  );
@@ -2275,6 +2286,22 @@ function cloneRecapCaveats(caveats) {
2275
2286
  throw new Error("Verified persisted session ReCap contains invalid caveats.");
2276
2287
  }
2277
2288
  }
2289
+ function jsonFromVerifier(value) {
2290
+ if (value === void 0) return null;
2291
+ if (Array.isArray(value)) return value.map(jsonFromVerifier);
2292
+ if (value instanceof Map) {
2293
+ const entries = [...value.entries()];
2294
+ if (!entries.every(([key]) => typeof key === "string")) return value;
2295
+ return Object.fromEntries(entries.map(([key, nested]) => [key, jsonFromVerifier(nested)]));
2296
+ }
2297
+ if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
2298
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, jsonFromVerifier(nested)]));
2299
+ }
2300
+ return value;
2301
+ }
2302
+ function verifierRecapEntries(entries) {
2303
+ return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
2304
+ }
2278
2305
  var ServiceGraphLifetime = class {
2279
2306
  constructor(invokeFn, invokeAnyFn, fetchFn) {
2280
2307
  this.invokeFn = invokeFn;
@@ -2347,18 +2374,23 @@ var UnsupportedSessionRestoreError = class extends Error {
2347
2374
  this.name = "UnsupportedSessionRestoreError";
2348
2375
  }
2349
2376
  };
2377
+ var InvalidRestoredSessionError = class extends Error {
2378
+ constructor(message = "Persisted session authority is invalid or expired.") {
2379
+ super(message);
2380
+ this.code = "AUTH_EXPIRED";
2381
+ this.name = "InvalidRestoredSessionError";
2382
+ }
2383
+ };
2350
2384
  function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
2351
2385
  if (typeof persistedVerificationMethod !== "string") return void 0;
2352
2386
  const principal = canonicalVerificationMethod.split("#", 1)[0];
2353
2387
  return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
2354
2388
  }
2355
2389
  function persistedExpiry(value) {
2356
- if (typeof value !== "string") {
2357
- throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
2358
- }
2390
+ if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
2359
2391
  const expiry = new Date(value);
2360
2392
  if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
2361
- throw new Error("Persisted session expiry is invalid or expired.");
2393
+ throw new InvalidRestoredSessionError();
2362
2394
  }
2363
2395
  return expiry;
2364
2396
  }
@@ -2837,6 +2869,29 @@ var _TinyCloudNode = class _TinyCloudNode {
2837
2869
  const authHosts = this.auth?.hosts ?? [];
2838
2870
  return authHosts.length > 0 ? authHosts : [this.config.host];
2839
2871
  }
2872
+ /** The registry-resolved Node endpoint and the identity returned by that exact Node. */
2873
+ async activeNodeIdentity() {
2874
+ const host = this.hosts[0];
2875
+ if (host === void 0) throw new Error("TinyCloud node host has not been resolved");
2876
+ const parsed = new URL(host);
2877
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) || parsed.origin !== host) {
2878
+ throw new Error("TinyCloud node host is not a canonical origin");
2879
+ }
2880
+ const nodeDid = await this.fetchNodeId();
2881
+ if (!nodeDid.startsWith("did:key:z")) throw new Error("TinyCloud node identity is not a did:key");
2882
+ return Object.freeze({ origin: parsed.origin, nodeDid });
2883
+ }
2884
+ /** Publish the active owner Node as a session-signed registry record. */
2885
+ async publishActiveNodeLocation(registryUrl, fetchFn = globalThis.fetch.bind(globalThis)) {
2886
+ const node = await this.activeNodeIdentity();
2887
+ return (0, import_sdk_core8.publishLocationRecord)({
2888
+ registryUrl,
2889
+ subject: this.credentialHolderDid,
2890
+ nodeOrigin: node.origin,
2891
+ signer: { type: "did:key", signBytes: (bytes) => this.signSessionBytes(bytes) },
2892
+ fetch: fetchFn
2893
+ });
2894
+ }
2840
2895
  /**
2841
2896
  * Get the primary identity DID for this user.
2842
2897
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -2878,16 +2933,26 @@ var _TinyCloudNode = class _TinyCloudNode {
2878
2933
  */
2879
2934
  getVerifiedSessionCapabilities() {
2880
2935
  const session = this.currentTinyCloudSession();
2881
- if (!session || !session.siwe) return [];
2882
- return (0, import_sdk_core8.parseRecapCapabilities)(
2883
- (siwe) => this.parseRecapWithCaveats(siwe),
2884
- session.siwe
2885
- ).map((entry) => entry.service === "tinycloud.encryption" ? {
2886
- service: entry.service,
2887
- path: entry.path,
2888
- actions: [...entry.actions],
2889
- ...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
2890
- } : entry);
2936
+ return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
2937
+ }
2938
+ /** Normalize signed capabilities without collapsing nested encryption into raw authority. */
2939
+ projectSignedRecapCapabilities(siwe) {
2940
+ const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
2941
+ const normalized = (0, import_sdk_core8.parseRecapCapabilities)(() => [entry], siwe)[0];
2942
+ if (normalized.service !== "tinycloud.encryption") return normalized;
2943
+ if (!this.isEncryptionNetworkOperation("encryption", entry.path, entry.space)) {
2944
+ return { ...normalized, space: entry.space };
2945
+ }
2946
+ return {
2947
+ service: normalized.service,
2948
+ path: normalized.path,
2949
+ actions: normalized.actions,
2950
+ ...normalized.caveats === void 0 ? {} : { caveats: normalized.caveats }
2951
+ };
2952
+ });
2953
+ return projected.sort(
2954
+ (left, right) => (left.space ?? "").localeCompare(right.space ?? "") || left.service.localeCompare(right.service) || left.path.localeCompare(right.path)
2955
+ );
2891
2956
  }
2892
2957
  /**
2893
2958
  * Get the Ethereum address for this user.
@@ -3342,9 +3407,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3342
3407
  * We must keep the full owner-scoped URI so a synthetic primary grant can
3343
3408
  * never cover an operation on a different owner's space.
3344
3409
  *
3345
- * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
3346
- * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
3347
- * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
3410
+ * Only top-level encryption network entries (`space: "encryption"` or
3411
+ * absent) become `resource` ops. A network-shaped path nested under a
3412
+ * data space remains a `spaceId` op, not raw network authority.
3348
3413
  * One op per action.
3349
3414
  *
3350
3415
  * Returns `[]` for session-only / restored-without-siwe modes and for any
@@ -3365,7 +3430,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3365
3430
  operations = entries.flatMap((entry) => {
3366
3431
  const service = this.invocationServiceName(entry.service);
3367
3432
  return entry.actions.map((action) => ({
3368
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
3433
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
3369
3434
  service,
3370
3435
  path: entry.path,
3371
3436
  action,
@@ -3527,7 +3592,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3527
3592
  return;
3528
3593
  } catch (error) {
3529
3594
  const message = error instanceof Error ? error.message : String(error);
3530
- if (/Unauthorized Action|\b401\b/.test(message)) {
3595
+ if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
3531
3596
  console.warn(
3532
3597
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
3533
3598
  error
@@ -3777,9 +3842,14 @@ var _TinyCloudNode = class _TinyCloudNode {
3777
3842
  */
3778
3843
  async restoreSession(sessionData) {
3779
3844
  await this.wasmBindings.ensureInitialized?.();
3780
- const restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
3845
+ let restoredJwk;
3846
+ try {
3847
+ restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
3848
+ } catch {
3849
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
3850
+ }
3781
3851
  if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
3782
- throw new Error("Persisted session chain ID must be a positive safe integer.");
3852
+ throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
3783
3853
  }
3784
3854
  const stagedManager = this.wasmBindings.createSessionManager();
3785
3855
  const stagedReplace = stagedManager.replaceSessionKey;
@@ -3791,32 +3861,31 @@ var _TinyCloudNode = class _TinyCloudNode {
3791
3861
  let stagedKeyId;
3792
3862
  let stagedJwk;
3793
3863
  let canonicalVerificationMethod;
3864
+ const keyIds = liveKeys.call(this.sessionManager);
3865
+ if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
3866
+ throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
3867
+ }
3868
+ for (const keyId of keyIds) {
3869
+ if (keyId === this.sessionKeyId) continue;
3870
+ const jwk = this.sessionManager.jwk(keyId);
3871
+ if (!jwk) throw new Error("missing live session key");
3872
+ stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
3873
+ }
3794
3874
  try {
3795
- const keyIds = liveKeys.call(this.sessionManager);
3796
- if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
3797
- throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
3798
- }
3799
- for (const keyId of keyIds) {
3800
- if (keyId === this.sessionKeyId) continue;
3801
- const jwk = this.sessionManager.jwk(keyId);
3802
- if (!jwk) throw new Error("missing live session key");
3803
- stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
3804
- }
3805
3875
  stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
3806
- const stagedJwkJson = stagedManager.jwk(stagedKeyId);
3807
- if (!stagedJwkJson) throw new Error("missing restored session key");
3808
- stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
3809
- canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
3810
- } catch (error) {
3811
- if (error instanceof UnsupportedSessionRestoreError) throw error;
3812
- throw new Error("Persisted session has an invalid private Ed25519 session key.");
3876
+ } catch {
3877
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
3813
3878
  }
3879
+ const stagedJwkJson = stagedManager.jwk(stagedKeyId);
3880
+ if (!stagedJwkJson) throw new Error("missing restored session key");
3881
+ stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
3882
+ canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
3814
3883
  const restoredVerificationMethod = canonicalRestoredVerificationMethod(
3815
3884
  canonicalVerificationMethod,
3816
3885
  sessionData.verificationMethod
3817
3886
  );
3818
3887
  if (!restoredVerificationMethod) {
3819
- throw new Error(
3888
+ throw new InvalidRestoredSessionError(
3820
3889
  "Persisted session verification method does not match its private Ed25519 session key."
3821
3890
  );
3822
3891
  }
@@ -3828,26 +3897,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3828
3897
  ];
3829
3898
  const hasPersistedProof = proofValues.every((value) => value !== void 0);
3830
3899
  if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
3831
- throw new Error("Persisted session authority metadata is incomplete.");
3900
+ throw new InvalidRestoredSessionError("Persisted session authority metadata is incomplete.");
3901
+ }
3902
+ let restoredAddress;
3903
+ try {
3904
+ restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
3905
+ } catch {
3906
+ throw new InvalidRestoredSessionError("Persisted session has an invalid wallet address.");
3832
3907
  }
3833
- const restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
3834
3908
  let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
3835
3909
  let stagedRecap = [];
3836
3910
  if (hasPersistedProof) {
3837
3911
  if (typeof this.wasmBindings.validatePersistedSession !== "function") {
3838
3912
  throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
3839
3913
  }
3840
- const verified = this.wasmBindings.validatePersistedSession({
3841
- delegationHeader: sessionData.delegationHeader,
3842
- delegationCid: sessionData.delegationCid,
3843
- spaceId: sessionData.spaceId,
3844
- jwk: stagedJwk,
3845
- address: restoredAddress,
3846
- chainId: sessionData.chainId,
3847
- siwe: sessionData.siwe,
3848
- signature: sessionData.signature
3849
- });
3850
- const exactRecap = verified.verifiedRecap;
3914
+ const verified = (() => {
3915
+ try {
3916
+ return this.wasmBindings.validatePersistedSession({
3917
+ delegationHeader: sessionData.delegationHeader,
3918
+ delegationCid: sessionData.delegationCid,
3919
+ spaceId: sessionData.spaceId,
3920
+ jwk: stagedJwk,
3921
+ address: restoredAddress,
3922
+ chainId: sessionData.chainId,
3923
+ siwe: sessionData.siwe,
3924
+ signature: sessionData.signature
3925
+ });
3926
+ } catch (error) {
3927
+ throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3928
+ }
3929
+ })();
3930
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
3851
3931
  if (!Array.isArray(exactRecap) || !exactRecap.every(
3852
3932
  (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(
3853
3933
  (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
@@ -3867,7 +3947,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3867
3947
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3868
3948
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3869
3949
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
3870
- throw new Error("Persisted session expiry does not match its signed SIWE authority.");
3950
+ throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
3871
3951
  }
3872
3952
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
3873
3953
  }
@@ -3964,7 +4044,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3964
4044
  operations = recap.flatMap((entry) => {
3965
4045
  const service = this.invocationServiceName(entry.service);
3966
4046
  return entry.actions.map((action) => ({
3967
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
4047
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
3968
4048
  service,
3969
4049
  path: entry.path,
3970
4050
  action,
@@ -4060,7 +4140,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4060
4140
  const delegations = input.recap.map((entry) => ({
4061
4141
  cid: session.delegationCid,
4062
4142
  delegateDID: session.verificationMethod,
4063
- spaceId: entry.space,
4143
+ spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
4064
4144
  path: entry.path,
4065
4145
  actions: [...entry.actions],
4066
4146
  caveats: cloneRecapCaveats(entry.caveats),
@@ -4750,16 +4830,11 @@ var _TinyCloudNode = class _TinyCloudNode {
4750
4830
  );
4751
4831
  graph.assertActive();
4752
4832
  if (!response.ok) {
4753
- let permissionHint;
4754
- if (response.status === 401 || response.status === 403) {
4755
- try {
4756
- const body = await response.json();
4757
- const record = typeof body === "object" && body !== null ? body : void 0;
4758
- const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
4759
- permissionHint = (0, import_sdk_services.parsePermissionHint)(record?.permissionHint) ?? (0, import_sdk_services.parsePermissionHint)(nested?.permissionHint);
4760
- } catch {
4761
- }
4762
- }
4833
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services.parsePermissionHint)({
4834
+ service: "tinycloud.encryption",
4835
+ path: networkId,
4836
+ actions: ["tinycloud.encryption/decrypt"]
4837
+ }) : void 0;
4763
4838
  throw new import_sdk_core8.DecryptTransportResponseError(response.status, permissionHint);
4764
4839
  }
4765
4840
  return await response.json();
@@ -5184,63 +5259,62 @@ var _TinyCloudNode = class _TinyCloudNode {
5184
5259
  }
5185
5260
  };
5186
5261
  }
5187
- /** Register a policy that is already bound to an activated owner delegation. */
5188
- async registerOwnerSharePolicy(params) {
5262
+ /** Register a signed policy and sibling roots with this Node's Policy/v3 runtime. */
5263
+ async registerPolicy(params) {
5189
5264
  this._serviceGraph.assertActive();
5190
- if (params.policy.bytes.byteLength > 2 * 1024 * 1024) throw new Error("Owner share policy is too large");
5191
- const response = await fetch(`${this.config.host}/share/v2/policies`, {
5192
- method: "POST",
5193
- headers: { "content-type": "application/json", accept: "application/json" },
5194
- body: JSON.stringify({
5195
- policy: { bytes: base64UrlEncode(params.policy.bytes), cid: params.policy.cid, proof: params.policy.proof },
5196
- ownerDelegation: { cid: params.ownerDelegation.delegationCid, dagCbor: base64UrlEncode(params.ownerDelegation.signedDagCbor) },
5197
- enforcementDelegation: params.enforcementDelegation,
5198
- contentSourceDigest: params.contentSourceDigest
5199
- })
5265
+ return (0, import_sdk_core8.registerPolicyV3)({
5266
+ ...params,
5267
+ nodeOrigin: this.config.host,
5268
+ fetch: globalThis.fetch.bind(globalThis)
5200
5269
  });
5201
- if (!response.ok) {
5202
- let code = "";
5203
- try {
5204
- const body = await response.clone().json();
5205
- if (typeof body.error?.code === "string") code = ` ${body.error.code}`;
5206
- } catch {
5207
- }
5208
- throw new Error(`Owner share policy registration failed: ${response.status}${code}`);
5209
- }
5210
- const responseBytes = new Uint8Array(await response.arrayBuffer());
5211
- return (0, import_sdk_core8.validateOwnerSharePolicyRegistrationBytes)(responseBytes, params);
5212
5270
  }
5213
- /** Authorize a short-lived, one-use v2 delivery using the authenticated invocation chain. */
5214
- async authorizeShareDelivery(input) {
5271
+ /** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
5272
+ async authorizeShareDeliveryV3(input) {
5215
5273
  const session = this.currentTinyCloudSession();
5216
5274
  const serviceSession = this._serviceContext?.session;
5217
5275
  if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
5276
+ const idempotencyBytes = new TextEncoder().encode(input.idempotencyKey);
5277
+ if (idempotencyBytes.byteLength === 0 || idempotencyBytes.byteLength > 512) {
5278
+ throw new Error("Share delivery idempotency key is invalid");
5279
+ }
5280
+ const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
5218
5281
  const body = {
5219
- envelopeCid: input.envelopeCid,
5282
+ envelope: input.envelope,
5283
+ sealedEnvelope: input.sealedEnvelope,
5284
+ envelopeKey: input.envelopeKey,
5220
5285
  shareCid: input.shareCid,
5221
- shareId: input.shareId,
5222
- registrationCid: input.registrationCid,
5223
- policyCid: input.policyCid,
5224
- delegationCid: input.delegationCid,
5225
- enforcementDelegationCid: input.enforcementDelegationCid,
5226
5286
  recipientEmail: input.recipientEmail,
5227
5287
  shareUrl: input.shareUrl,
5228
5288
  documentName: input.documentName,
5229
- jti: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
5230
- idempotencyKey: input.idempotencyKey,
5231
- expiresAt: input.expiresAt
5289
+ jti: base64UrlEncode(jtiDigest.slice(0, 16)),
5290
+ expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
5232
5291
  };
5233
5292
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core8.canonicalizeEncryptionJson)(body)))));
5234
5293
  const request = { ...body, requestBodyDigest };
5235
5294
  const authorization = authorizationHeader(this.invokeAnyWithRuntimePermissions(serviceSession, [{ spaceId: session.spaceId, service: "kv", path: input.resourcePath, action: "tinycloud.kv/get" }]));
5236
- const response = await fetch(`${this.config.host}/share/v2/deliveries/authorize`, {
5295
+ const response = await fetch(`${this.config.host}/policy/v3/deliveries/authorize`, {
5237
5296
  method: "POST",
5238
5297
  headers: { accept: "application/json", "content-type": "application/json", authorization },
5239
5298
  body: (0, import_sdk_core8.canonicalizeEncryptionJson)(request)
5240
5299
  });
5241
- if (!response.ok) throw new Error(`Share delivery authorization failed: ${response.status}`);
5242
- const responseBytes = new Uint8Array(await response.arrayBuffer());
5243
- return (0, import_sdk_core8.validateShareDeliveryAuthorizationBytes)(responseBytes, { request, nodeProof: input.nodeProof, credentialsAudience: input.credentialsAudience });
5300
+ if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
5301
+ const verified = (0, import_sdk_core8.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
5302
+ request,
5303
+ senderKeyDid: this.credentialHolderDid,
5304
+ deliveryAudience: input.deliveryAudience
5305
+ });
5306
+ const requestDigest = new Uint8Array(await crypto.subtle.digest(
5307
+ "SHA-256",
5308
+ new TextEncoder().encode(`${import_sdk_core8.CREDENTIAL_INVITATION_REQUEST_DOMAIN}${(0, import_sdk_core8.canonicalizeEncryptionJson)(verified.request)}`)
5309
+ ));
5310
+ return {
5311
+ ...verified,
5312
+ proof: {
5313
+ alg: "EdDSA",
5314
+ kid: this.credentialHolderDid,
5315
+ signature: base64UrlEncode(await this.signSessionBytes(requestDigest))
5316
+ }
5317
+ };
5244
5318
  }
5245
5319
  async createRootDelegationForSharing(params, assertActive) {
5246
5320
  try {
@@ -5695,7 +5769,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5695
5769
  continue;
5696
5770
  }
5697
5771
  let space;
5698
- if (service !== "tinycloud.encryption") {
5772
+ if (service !== "tinycloud.encryption" || operation.resource === void 0) {
5699
5773
  if (typeof operation.spaceId !== "string") continue;
5700
5774
  try {
5701
5775
  space = this.resolvePermissionSpace(operation.spaceId, session);
@@ -6240,11 +6314,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6240
6314
  );
6241
6315
  return { delegation: delegation2, prompted: true };
6242
6316
  }
6243
- const granted = (0, import_sdk_core8.parseRecapCapabilities)(
6244
- (siwe) => this.parseRecapWithCaveats(siwe),
6245
- session.siwe
6246
- );
6247
- const { subset, missing } = (0, import_sdk_core8.isCapabilitySubset)(expandedEntries, granted);
6317
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
6318
+ const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
6248
6319
  if (!subset) {
6249
6320
  const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
6250
6321
  const runtimeGrant = this.findGrantForOperations(
@@ -6564,7 +6635,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6564
6635
  return abilities;
6565
6636
  }
6566
6637
  isEncryptionPermissionEntry(entry) {
6567
- return entry.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE && entry.path.startsWith("urn:tinycloud:encryption:");
6638
+ return entry.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
6568
6639
  }
6569
6640
  permissionsToRawAbilities(entries) {
6570
6641
  const rawAbilities = {};
@@ -6588,7 +6659,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6588
6659
  return entries.flatMap((entry) => {
6589
6660
  const service = this.shortServiceName(entry.service);
6590
6661
  return entry.actions.map((action) => ({
6591
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
6662
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6592
6663
  service,
6593
6664
  path: entry.path,
6594
6665
  action,
@@ -6596,13 +6667,21 @@ var _TinyCloudNode = class _TinyCloudNode {
6596
6667
  }));
6597
6668
  });
6598
6669
  }
6670
+ /** Raw network authority cannot be covered by a nested encryption space. */
6671
+ signedCapabilitySubset(requested, granted) {
6672
+ const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
6673
+ const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
6674
+ const missing = requested.filter((entry) => {
6675
+ const raw = this.isEncryptionPermissionEntry(entry);
6676
+ const comparable = raw ? { ...entry, space: "encryption" } : entry;
6677
+ return !(0, import_sdk_core8.isCapabilitySubset)([comparable], raw ? rawGranted : spaceGranted).subset;
6678
+ });
6679
+ return { subset: missing.length === 0, missing };
6680
+ }
6599
6681
  sessionCoversPermissionEntries(session, entries) {
6600
6682
  try {
6601
- const granted = (0, import_sdk_core8.parseRecapCapabilities)(
6602
- (siwe) => this.parseRecapWithCaveats(siwe),
6603
- session.siwe
6604
- );
6605
- return (0, import_sdk_core8.isCapabilitySubset)(entries, granted).subset;
6683
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
6684
+ return this.signedCapabilitySubset(entries, granted).subset;
6606
6685
  } catch {
6607
6686
  return false;
6608
6687
  }
@@ -6612,7 +6691,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6612
6691
  const spaceId = this.resolvePermissionSpace(entry.space, session);
6613
6692
  const service = this.shortServiceName(entry.service);
6614
6693
  return entry.actions.map((action) => ({
6615
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
6694
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6616
6695
  service,
6617
6696
  path: entry.path,
6618
6697
  action,
@@ -6701,7 +6780,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6701
6780
  return resources.flatMap((resource) => {
6702
6781
  const service = this.invocationServiceName(resource.service);
6703
6782
  return resource.actions.map((action) => ({
6704
- ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
6783
+ ...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
6705
6784
  service,
6706
6785
  path: resource.path,
6707
6786
  action,
@@ -6762,7 +6841,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6762
6841
  };
6763
6842
  for (const resource of resources) {
6764
6843
  const service = this.invocationServiceName(resource.service);
6765
- if (this.isEncryptionNetworkOperation(service, resource.path)) {
6844
+ if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
6766
6845
  rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
6767
6846
  addActions(rawAbilities[resource.path], resource.actions);
6768
6847
  continue;
@@ -6831,21 +6910,15 @@ var _TinyCloudNode = class _TinyCloudNode {
6831
6910
  }
6832
6911
  return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
6833
6912
  }
6834
- // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. The embedded
6835
- // EIP-155 address is case-insensitive, but the CLI canonicalizes it to
6836
- // lowercase when building a space URI while stored runtime delegations keep
6837
- // the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
6838
- // an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
6839
- // segment and leave everything else (crucially the case-sensitive space NAME)
6840
- // byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
6913
+ // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. Compare
6914
+ // only the embedded EIP-155 address without case sensitivity; chain, name,
6915
+ // and all non-PKH space identifiers remain byte-exact.
6841
6916
  spaceIdsEqual(a, b) {
6842
6917
  return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
6843
6918
  }
6844
6919
  normalizeSpaceAddress(space) {
6845
- return space.replace(
6846
- /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
6847
- (_match, prefix, addr) => prefix + addr.toLowerCase()
6848
- );
6920
+ const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
6921
+ return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
6849
6922
  }
6850
6923
  actionContains(grantedAction, requestedAction) {
6851
6924
  if (grantedAction === requestedAction) {
@@ -6862,10 +6935,11 @@ var _TinyCloudNode = class _TinyCloudNode {
6862
6935
  }
6863
6936
  /** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
6864
6937
  parseRecapWithCaveats(siwe) {
6865
- return this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe) ?? this.wasmBindings.parseRecapFromSiwe(siwe);
6938
+ const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
6939
+ return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
6866
6940
  }
6867
- isEncryptionNetworkOperation(service, path) {
6868
- return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
6941
+ isEncryptionNetworkOperation(service, path, space) {
6942
+ return service === "encryption" && (space === void 0 || space === "encryption") && path.startsWith("urn:tinycloud:encryption:");
6869
6943
  }
6870
6944
  operationFromInvokeAnyEntry(entry) {
6871
6945
  const service = this.invocationServiceName(entry.service);
@@ -6878,7 +6952,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6878
6952
  ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
6879
6953
  };
6880
6954
  }
6881
- if (this.isEncryptionNetworkOperation(service, entry.path)) {
6955
+ if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
6882
6956
  return {
6883
6957
  resource: entry.path,
6884
6958
  service,
@@ -7163,8 +7237,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7163
7237
  const jwk = mySession.jwk;
7164
7238
  const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
7165
7239
  const now = /* @__PURE__ */ new Date();
7166
- const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
7167
- const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
7240
+ const expirationTime = delegation.expiry;
7168
7241
  const prepared = this.wasmBindings.prepareSession({
7169
7242
  abilities,
7170
7243
  address: this.wasmBindings.ensureEip55(mySession.address),
@@ -7268,8 +7341,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7268
7341
  );
7269
7342
  }
7270
7343
  const now = /* @__PURE__ */ new Date();
7271
- const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
7272
- const requestedExpiry = new Date(now.getTime() + expiryMs);
7344
+ const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
7273
7345
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
7274
7346
  const abilities = {};
7275
7347
  const rawAbilities = {};
@@ -7725,6 +7797,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
7725
7797
  DuckDbService,
7726
7798
  FileSessionStorage,
7727
7799
  IdentityParseError,
7800
+ InvalidRestoredSessionError,
7728
7801
  KVService,
7729
7802
  ManifestValidationError,
7730
7803
  MemorySessionStorage,
@@ -7790,6 +7863,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
7790
7863
  parseCanonicalNetworkId,
7791
7864
  parseExpiry,
7792
7865
  parsePkhDid,
7866
+ parseSecretCatalogKey,
7793
7867
  parseSpaceUri,
7794
7868
  pkhDid,
7795
7869
  principalDid,