@tinycloud/sdk-services 3.11.0-beta.2 → 3.12.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/{BaseService-Ma2MwJAd.d.ts → BaseService-BRDG69KR.d.ts} +1 -1
  2. package/dist/{BaseService-O0iyuFLk.d.cts → BaseService-CdBkfPGE.d.cts} +1 -1
  3. package/dist/{IHooksService-4QNODVy6.d.cts → IHooksService-C9p3stsB.d.cts} +1 -1
  4. package/dist/{IHooksService-CvTpJbQD.d.ts → IHooksService-D6eyZat1.d.ts} +1 -1
  5. package/dist/{ISQLService-Aly1CBsW.d.ts → ISQLService-B_4KcGA-.d.ts} +1 -1
  6. package/dist/{ISQLService-BOgWzHLT.d.cts → ISQLService-C6nNpcls.d.cts} +1 -1
  7. package/dist/context.cjs +4 -0
  8. package/dist/context.cjs.map +1 -1
  9. package/dist/context.d.cts +1 -1
  10. package/dist/context.d.ts +1 -1
  11. package/dist/context.js +4 -0
  12. package/dist/context.js.map +1 -1
  13. package/dist/encryption/index.cjs +276 -14
  14. package/dist/encryption/index.cjs.map +1 -1
  15. package/dist/encryption/index.d.cts +26 -28
  16. package/dist/encryption/index.d.ts +26 -28
  17. package/dist/encryption/index.js +268 -14
  18. package/dist/encryption/index.js.map +1 -1
  19. package/dist/encryption/named-keys.cjs +225 -14
  20. package/dist/encryption/named-keys.cjs.map +1 -1
  21. package/dist/encryption/named-keys.d.cts +7 -2
  22. package/dist/encryption/named-keys.d.ts +7 -2
  23. package/dist/encryption/named-keys.js +225 -14
  24. package/dist/encryption/named-keys.js.map +1 -1
  25. package/dist/encryption/resource.cjs.map +1 -1
  26. package/dist/encryption/resource.js.map +1 -1
  27. package/dist/feed/index.cjs +4 -0
  28. package/dist/feed/index.cjs.map +1 -1
  29. package/dist/feed/index.d.cts +3 -3
  30. package/dist/feed/index.d.ts +3 -3
  31. package/dist/feed/index.js +4 -0
  32. package/dist/feed/index.js.map +1 -1
  33. package/dist/index.cjs +509 -18
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +14 -12
  36. package/dist/index.d.ts +14 -12
  37. package/dist/index.js +499 -18
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  40. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  41. package/dist/kv/index.cjs +34 -0
  42. package/dist/kv/index.cjs.map +1 -1
  43. package/dist/kv/index.d.cts +4 -4
  44. package/dist/kv/index.d.ts +4 -4
  45. package/dist/kv/index.js +34 -0
  46. package/dist/kv/index.js.map +1 -1
  47. package/dist/kv/replication/index.cjs +39 -8
  48. package/dist/kv/replication/index.cjs.map +1 -1
  49. package/dist/kv/replication/index.d.cts +3 -3
  50. package/dist/kv/replication/index.d.ts +3 -3
  51. package/dist/kv/replication/index.js +38 -7
  52. package/dist/kv/replication/index.js.map +1 -1
  53. package/dist/{scope-DEgc9f-W.d.cts → scope-DtcbyRKY.d.cts} +1 -1
  54. package/dist/{scope-BZr0QwxX.d.ts → scope-J20PQP3u.d.ts} +1 -1
  55. package/dist/sql/index.cjs +36 -0
  56. package/dist/sql/index.cjs.map +1 -1
  57. package/dist/sql/index.d.cts +4 -4
  58. package/dist/sql/index.d.ts +4 -4
  59. package/dist/sql/index.js +36 -0
  60. package/dist/sql/index.js.map +1 -1
  61. package/dist/telemetry/requestLog.cjs +4 -0
  62. package/dist/telemetry/requestLog.cjs.map +1 -1
  63. package/dist/telemetry/requestLog.d.cts +1 -1
  64. package/dist/telemetry/requestLog.d.ts +1 -1
  65. package/dist/telemetry/requestLog.js +4 -0
  66. package/dist/telemetry/requestLog.js.map +1 -1
  67. package/dist/{types-D4XbIfFJ.d.cts → types-DfqNz9Nu.d.cts} +135 -6
  68. package/dist/{types-CSd0RYir.d.ts → types-DjAP8dve.d.cts} +4 -0
  69. package/dist/{types-CSd0RYir.d.cts → types-DjAP8dve.d.ts} +4 -0
  70. package/dist/{types-Bf91gup6.d.ts → types-Nvi_RckJ.d.ts} +135 -6
  71. package/package.json +4 -1
package/dist/index.cjs CHANGED
@@ -51,7 +51,11 @@ __export(src_exports, {
51
51
  MAX_SECRET_SCOPE_LENGTH: () => MAX_SECRET_SCOPE_LENGTH,
52
52
  NETWORK_NAME_PATTERN: () => NETWORK_NAME_PATTERN,
53
53
  NetworkIdError: () => NetworkIdError,
54
+ NodeIdentityMismatchError: () => NodeIdentityMismatchError,
55
+ NodeNotRespondingError: () => NodeNotRespondingError,
56
+ PartialNodeWriteError: () => PartialNodeWriteError,
54
57
  PrefixedKVService: () => PrefixedKVService,
58
+ ResourceNotRoutedError: () => ResourceNotRoutedError,
55
59
  RetryPolicySchema: () => RetryPolicySchema,
56
60
  SECRET_NAME_RE: () => SECRET_NAME_RE,
57
61
  SECRET_SCOPE_RE: () => SECRET_SCOPE_RE,
@@ -99,6 +103,8 @@ __export(src_exports, {
99
103
  checkDecryptInvocationInput: () => checkDecryptInvocationInput,
100
104
  clearTinyCloudDebugLogs: () => clearTinyCloudDebugLogs,
101
105
  createKVResponseSchema: () => createKVResponseSchema,
106
+ createResourceRoutingFetch: () => createResourceRoutingFetch,
107
+ createResourceTransportInvocationFetch: () => createResourceTransportInvocationFetch,
102
108
  createResultSchema: () => createResultSchema,
103
109
  createVaultCrypto: () => createVaultCrypto,
104
110
  decryptEnvelopeWithKey: () => decryptEnvelopeWithKey,
@@ -141,6 +147,9 @@ __export(src_exports, {
141
147
  requiresSecretsOptIn: () => requiresSecretsOptIn,
142
148
  resolveSecretListPrefix: () => resolveSecretListPrefix,
143
149
  resolveSecretPath: () => resolveSecretPath,
150
+ routeResourceRequest: () => routeResourceRequest,
151
+ routingFailure: () => routingFailure,
152
+ routingResourceOf: () => routingResourceOf,
144
153
  serviceError: () => serviceError,
145
154
  storageLimitReachedError: () => storageLimitReachedError,
146
155
  storageQuotaExceededError: () => storageQuotaExceededError,
@@ -161,6 +170,7 @@ __export(src_exports, {
161
170
  validateTelemetrySpanEvent: () => validateTelemetrySpanEvent,
162
171
  validatedCapabilityOf: () => validatedCapabilityOf,
163
172
  verifyDecryptResponse: () => verifyDecryptResponse,
173
+ verifyResourceDestination: () => verifyResourceDestination,
164
174
  wrapError: () => wrapError
165
175
  });
166
176
  module.exports = __toCommonJS(src_exports);
@@ -177,6 +187,10 @@ var ErrorCodes = {
177
187
  /** HTTP 401: a delegation the cited one rests on was revoked. */
178
188
  AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
179
189
  NETWORK_ERROR: "NETWORK_ERROR",
190
+ NODE_NOT_RESPONDING: "NODE_NOT_RESPONDING",
191
+ NODE_IDENTITY_MISMATCH: "NODE_IDENTITY_MISMATCH",
192
+ PARTIAL_NODE_WRITE: "PARTIAL_NODE_WRITE",
193
+ RESOURCE_NOT_ROUTED: "RESOURCE_NOT_ROUTED",
180
194
  TIMEOUT: "TIMEOUT",
181
195
  ABORTED: "ABORTED",
182
196
  INVALID_INPUT: "INVALID_INPUT",
@@ -951,6 +965,237 @@ function networkDiscoveryKey(name) {
951
965
  var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
952
966
  var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
953
967
 
968
+ // src/resourceRouting.ts
969
+ async function verifyResourceDestination(target, fetchFn) {
970
+ if (!target.nodeDid) return;
971
+ let info;
972
+ let keys;
973
+ try {
974
+ const signal = AbortSignal.timeout(5e3);
975
+ const responses = await Promise.all([
976
+ fetchFn(`${target.url}/info`, { signal, redirect: "error" }),
977
+ fetchFn(`${target.url}/.well-known/tinycloud/node-keys`, { signal, redirect: "error" })
978
+ ]);
979
+ if (responses.some((response) => response.status >= 500)) throw new NodeNotRespondingError([target.url]);
980
+ if (responses.some((response) => !response.ok)) throw new NodeIdentityMismatchError(target.url, target.nodeDid);
981
+ try {
982
+ [info, keys] = await Promise.all(responses.map((response) => response.json()));
983
+ } catch {
984
+ throw new NodeIdentityMismatchError(target.url, target.nodeDid);
985
+ }
986
+ } catch (error) {
987
+ if (error instanceof NodeIdentityMismatchError || error instanceof NodeNotRespondingError) throw error;
988
+ throw new NodeNotRespondingError([target.url], error);
989
+ }
990
+ const infoDid = typeof info === "object" && info !== null && "nodeId" in info && typeof info.nodeId === "string" ? info.nodeId : void 0;
991
+ const keysDid = typeof keys === "object" && keys !== null && "nodeDid" in keys && typeof keys.nodeDid === "string" ? keys.nodeDid : void 0;
992
+ if (infoDid !== target.nodeDid || keysDid !== target.nodeDid) {
993
+ throw new NodeIdentityMismatchError(target.url, target.nodeDid, infoDid !== target.nodeDid ? infoDid : keysDid);
994
+ }
995
+ return info;
996
+ }
997
+ var NodeNotRespondingError = class extends Error {
998
+ constructor(hosts, cause) {
999
+ super("The place your data is stored isn't responding right now. Please try again.");
1000
+ this.hosts = hosts;
1001
+ this.cause = cause;
1002
+ this.code = "NODE_NOT_RESPONDING";
1003
+ this.name = "NodeNotRespondingError";
1004
+ }
1005
+ };
1006
+ var NodeIdentityMismatchError = class extends Error {
1007
+ constructor(url, expectedDid, actualDid) {
1008
+ super(`TinyCloud node identity does not match the registry at ${url}`);
1009
+ this.url = url;
1010
+ this.expectedDid = expectedDid;
1011
+ this.actualDid = actualDid;
1012
+ this.code = "NODE_IDENTITY_MISMATCH";
1013
+ this.name = "NodeIdentityMismatchError";
1014
+ }
1015
+ };
1016
+ var ResourceNotRoutedError = class extends Error {
1017
+ constructor(resource) {
1018
+ super(`No registered destination can serve ${resource}`);
1019
+ this.resource = resource;
1020
+ this.code = "RESOURCE_NOT_ROUTED";
1021
+ this.name = "ResourceNotRoutedError";
1022
+ }
1023
+ };
1024
+ var PartialNodeWriteError = class extends Error {
1025
+ constructor(succeeded, failed) {
1026
+ super("The write did not complete on every registered writable node.");
1027
+ this.succeeded = succeeded;
1028
+ this.failed = failed;
1029
+ this.code = "PARTIAL_NODE_WRITE";
1030
+ this.name = "PartialNodeWriteError";
1031
+ }
1032
+ };
1033
+ var ROUTING_FAILURE_CODES = {
1034
+ NODE_NOT_RESPONDING: true,
1035
+ PARTIAL_NODE_WRITE: true,
1036
+ NODE_IDENTITY_MISMATCH: true,
1037
+ RESOURCE_NOT_ROUTED: true,
1038
+ DECRYPTION_DEPENDENCY_UNAVAILABLE: true,
1039
+ registry_unavailable: true,
1040
+ node_not_found: true,
1041
+ node_registry_entry_exists: true
1042
+ };
1043
+ function isRoutingFailureCode(code) {
1044
+ return Object.prototype.hasOwnProperty.call(ROUTING_FAILURE_CODES, code);
1045
+ }
1046
+ function routingFailure(error) {
1047
+ if (typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && typeof error.message === "string" && isRoutingFailureCode(error.code)) {
1048
+ const candidate = "meta" in error ? error.meta : void 0;
1049
+ let meta = typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : void 0;
1050
+ if (error.code === "NODE_NOT_RESPONDING" && "hosts" in error && Array.isArray(error.hosts)) {
1051
+ meta = { ...meta, hosts: error.hosts };
1052
+ } else if (error.code === "PARTIAL_NODE_WRITE" && "succeeded" in error && Array.isArray(error.succeeded) && "failed" in error && Array.isArray(error.failed)) {
1053
+ meta = { ...meta, succeeded: error.succeeded, failed: error.failed };
1054
+ }
1055
+ return { code: error.code, message: error.message, ...error instanceof Error ? { cause: error } : {}, ...meta ? { meta } : {} };
1056
+ }
1057
+ return void 0;
1058
+ }
1059
+ function authorizationOf(headers) {
1060
+ if (headers === void 0) return void 0;
1061
+ return (Array.isArray(headers) ? headers : Object.entries(headers)).find(([name]) => name.toLowerCase() === "authorization")?.[1];
1062
+ }
1063
+ function routingResourceOf(uri) {
1064
+ if (uri.startsWith("tinycloud://")) {
1065
+ const resource = uri.slice("tinycloud://".length).split("/", 1)[0];
1066
+ if (resource) return resource;
1067
+ } else if (uri.startsWith("urn:tinycloud:encryption:")) {
1068
+ return uri;
1069
+ } else if (uri.startsWith("tinycloud:")) {
1070
+ return uri.split("/", 1)[0];
1071
+ }
1072
+ throw new ResourceNotRoutedError(uri);
1073
+ }
1074
+ function invocationResources(authorization) {
1075
+ const encoded = authorization.split(".")[1];
1076
+ if (!encoded) throw new ResourceNotRoutedError("invalid invocation");
1077
+ const payload = JSON.parse(atob(encoded.replace(/-/g, "+").replace(/_/g, "/")));
1078
+ if (typeof payload !== "object" || payload === null || !("att" in payload) || typeof payload.att !== "object" || payload.att === null || Array.isArray(payload.att)) {
1079
+ throw new ResourceNotRoutedError("invalid invocation attenuation");
1080
+ }
1081
+ const resources = [];
1082
+ for (const [uri, abilities] of Object.entries(payload.att)) {
1083
+ if (typeof abilities !== "object" || abilities === null || Array.isArray(abilities)) throw new ResourceNotRoutedError(uri);
1084
+ for (const action of Object.keys(abilities)) {
1085
+ const resource = routingResourceOf(uri);
1086
+ const read2 = /^(tinycloud\.(kv\/(get|list|metadata|head|sync)|sql\/(read|export)|duckdb\/(read|describe|export)|capabilities\/read|space\/(list|info)|hooks\/list))$/.test(action);
1087
+ const access = resource.startsWith("urn:tinycloud:encryption:") ? "encryption" : action === "tinycloud.space/host" ? "host" : read2 ? "read" : "write";
1088
+ if (!resources.some((entry) => entry.resource === resource && entry.access === access)) resources.push({ resource, access });
1089
+ }
1090
+ }
1091
+ if (resources.length === 0) throw new ResourceNotRoutedError("empty invocation");
1092
+ return resources;
1093
+ }
1094
+ async function routeResourceRequest(resources, routing, send, options) {
1095
+ if (resources.length === 0) throw new ResourceNotRoutedError("empty request");
1096
+ const routes = await Promise.all(resources.map(({ resource, access }) => routing.resolveResource(resource, access)));
1097
+ if (routes.some((route) => route.length === 0)) throw new ResourceNotRoutedError(resources[routes.findIndex((route) => route.length === 0)].resource);
1098
+ const write = options?.independentResources === true || resources.some(({ access }) => access === "write" || access === "host");
1099
+ let targets;
1100
+ if (options?.independentResources) {
1101
+ targets = routes.flat();
1102
+ const identities = /* @__PURE__ */ new Map();
1103
+ for (const target of targets) {
1104
+ if (identities.has(target.url) && identities.get(target.url) !== target.nodeDid) throw new ResourceNotRoutedError(target.url);
1105
+ identities.set(target.url, target.nodeDid);
1106
+ }
1107
+ } else {
1108
+ targets = routes[0].filter((target) => routes.every((route) => route.some((other) => other.url === target.url && other.nodeDid === target.nodeDid)));
1109
+ if (targets.length === 0 || write && routes.some((route) => route.length !== targets.length)) throw new ResourceNotRoutedError(resources.map(({ resource }) => resource).join(", "));
1110
+ }
1111
+ targets = targets.filter((target, index) => targets.findIndex((other) => other.url === target.url) === index);
1112
+ if (options?.nodeDid) targets = targets.filter((target) => target.nodeDid === options.nodeDid || target.nodeDid === void 0);
1113
+ if (targets.length === 0 || options?.nodeLocal && targets.length !== 1) throw new ResourceNotRoutedError(resources.map(({ resource }) => resource).join(", "));
1114
+ if (!write) {
1115
+ let response2;
1116
+ let failure;
1117
+ for (const target of targets) {
1118
+ try {
1119
+ response2 = await send(target);
1120
+ if (response2.ok || response2.status !== 404) return response2;
1121
+ } catch (error) {
1122
+ if (!(error instanceof NodeNotRespondingError)) throw error;
1123
+ failure = error;
1124
+ }
1125
+ }
1126
+ if (failure) throw new NodeNotRespondingError(targets.map(({ url }) => url), failure);
1127
+ if (response2) return response2;
1128
+ throw new NodeNotRespondingError(targets.map(({ url }) => url), failure);
1129
+ }
1130
+ const succeeded = [];
1131
+ const failed = [];
1132
+ let response;
1133
+ let failureResponse;
1134
+ for (const target of targets) {
1135
+ try {
1136
+ const result = await send(target);
1137
+ if (result.ok) {
1138
+ succeeded.push(target.url);
1139
+ response ?? (response = result);
1140
+ } else {
1141
+ failed.push({ url: target.url, status: result.status });
1142
+ failureResponse ?? (failureResponse = result);
1143
+ }
1144
+ } catch (error) {
1145
+ if (!routingFailure(error) && succeeded.length === 0) throw error;
1146
+ failed.push({ url: target.url, cause: error });
1147
+ }
1148
+ }
1149
+ if (failed.length > 0) {
1150
+ if (succeeded.length > 0 || targets.length > 1) throw new PartialNodeWriteError(succeeded, failed);
1151
+ if (failureResponse) return failureResponse;
1152
+ if (failed[0]?.cause && !(failed[0].cause instanceof NodeNotRespondingError)) throw failed[0].cause;
1153
+ throw new NodeNotRespondingError(targets.map(({ url }) => url), failed[0]?.cause);
1154
+ }
1155
+ return response;
1156
+ }
1157
+ function createResourceTransportInvocationFetch(transport, session) {
1158
+ return async (url, init) => {
1159
+ const parsed = new URL(url);
1160
+ if (parsed.pathname !== "/invoke") throw new ResourceNotRoutedError(parsed.pathname);
1161
+ const authorization = authorizationOf(init?.headers);
1162
+ if (!authorization) throw new ResourceNotRoutedError("unsigned invocation");
1163
+ return transport(invocationResources(authorization), async (target) => {
1164
+ const signed = await target.bindAudience(authorization);
1165
+ const headers = (Array.isArray(init?.headers) ? init.headers : Object.entries(init?.headers ?? {})).filter(([name]) => name.toLowerCase() !== "authorization");
1166
+ headers.push(["Authorization", signed]);
1167
+ return target.fetch(`${target.url}${parsed.pathname}${parsed.search}`, { ...init, headers });
1168
+ }, { invocationSession: session });
1169
+ };
1170
+ }
1171
+ function createResourceRoutingFetch(fetchFn, routing) {
1172
+ return async (url, init) => {
1173
+ const parsed = new URL(url);
1174
+ if (parsed.pathname !== "/invoke") return fetchFn(url, init);
1175
+ const authorization = authorizationOf(init?.headers);
1176
+ if (!authorization) throw new ResourceNotRoutedError("unsigned invocation");
1177
+ const resources = invocationResources(authorization);
1178
+ const send = async (target) => {
1179
+ init?.signal?.throwIfAborted();
1180
+ if (routing.verifyDestination) await routing.verifyDestination(target);
1181
+ else await verifyResourceDestination(target, fetchFn);
1182
+ const signed = target.nodeDid ? await routing.bindAudience(authorization, target.nodeDid) : authorization;
1183
+ const headers = (Array.isArray(init?.headers) ? init.headers : Object.entries(init?.headers ?? {})).filter(([name]) => name.toLowerCase() !== "authorization");
1184
+ headers.push(["Authorization", signed]);
1185
+ const timeout = AbortSignal.timeout(5e3);
1186
+ const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
1187
+ try {
1188
+ return await fetchFn(`${target.url}${parsed.pathname}${parsed.search}`, { ...init, headers, signal, redirect: "error" });
1189
+ } catch (error) {
1190
+ if (init?.signal?.aborted) throw error;
1191
+ if (routingFailure(error)) throw error;
1192
+ throw new NodeNotRespondingError([target.url], error);
1193
+ }
1194
+ };
1195
+ return routeResourceRequest(resources, routing, send);
1196
+ };
1197
+ }
1198
+
954
1199
  // src/errors.ts
955
1200
  function parsePermissionHint(value) {
956
1201
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -1256,6 +1501,8 @@ function isStorageFullError(error) {
1256
1501
  return error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error?.code === ErrorCodes.STORAGE_LIMIT_REACHED;
1257
1502
  }
1258
1503
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1504
+ const routing = routingFailure(error);
1505
+ if (routing) return { ...routing, service };
1259
1506
  if (error instanceof Error) {
1260
1507
  if (isRequestTimeoutError(error)) {
1261
1508
  return timeoutError(service, error.timeoutMs, error);
@@ -5953,9 +6200,9 @@ function utf8Encode(s) {
5953
6200
  function utf8Decode(b) {
5954
6201
  return new TextDecoder().decode(b);
5955
6202
  }
5956
- function canonicalHashHex(sha256, value) {
6203
+ function canonicalHashHex(sha2562, value) {
5957
6204
  const canonical = canonicalize(value);
5958
- return hexEncode(sha256(utf8Encode(canonical)));
6205
+ return hexEncode(sha2562(utf8Encode(canonical)));
5959
6206
  }
5960
6207
 
5961
6208
  // src/vault/types.ts
@@ -6197,6 +6444,8 @@ function defaultVaultMessage(input) {
6197
6444
  case "STORAGE_QUOTA_EXCEEDED":
6198
6445
  case "STORAGE_LIMIT_REACHED":
6199
6446
  return input.message;
6447
+ default:
6448
+ return input.message ?? input.code;
6200
6449
  }
6201
6450
  }
6202
6451
  function vaultError(input) {
@@ -6248,6 +6497,8 @@ function ifMatchWriteError(key, error) {
6248
6497
  });
6249
6498
  }
6250
6499
  function kvWriteError(context, error) {
6500
+ const routing = routingFailure(error);
6501
+ if (routing) return vaultError(routing);
6251
6502
  if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
6252
6503
  return vaultError({ code: error.code, message: error.message, meta: error.meta });
6253
6504
  }
@@ -6554,6 +6805,8 @@ var DataVaultService = class extends BaseService {
6554
6805
  { aad, metadata }
6555
6806
  );
6556
6807
  if (!envelopeResult.ok) {
6808
+ const routing = routingFailure(envelopeResult.error);
6809
+ if (routing) return vaultError(routing);
6557
6810
  return vaultError({
6558
6811
  code: "STORAGE_ERROR",
6559
6812
  cause: new Error(envelopeResult.error.message)
@@ -6580,6 +6833,8 @@ var DataVaultService = class extends BaseService {
6580
6833
  }
6581
6834
  return { ok: true, data: void 0 };
6582
6835
  } catch (error) {
6836
+ const routing = routingFailure(error);
6837
+ if (routing) return vaultError(routing);
6583
6838
  return vaultError({
6584
6839
  code: "STORAGE_ERROR",
6585
6840
  cause: toError(error)
@@ -6606,6 +6861,8 @@ var DataVaultService = class extends BaseService {
6606
6861
  ...options?.source === void 0 ? {} : { source: options.source }
6607
6862
  });
6608
6863
  if (!valueResult.ok) {
6864
+ const routing = routingFailure(valueResult.error);
6865
+ if (routing) return vaultError(routing);
6609
6866
  return vaultError({ code: "KEY_NOT_FOUND", key });
6610
6867
  }
6611
6868
  const rawEnvelope = unwrapKVData(valueResult.data);
@@ -6626,6 +6883,8 @@ var DataVaultService = class extends BaseService {
6626
6883
  const proof = await this.decryptCapabilityProof();
6627
6884
  const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6628
6885
  if (!plaintextResult.ok) {
6886
+ const routing = routingFailure(plaintextResult.error);
6887
+ if (routing) return vaultError(routing);
6629
6888
  return vaultError({
6630
6889
  code: "DECRYPTION_FAILED",
6631
6890
  message: plaintextResult.error.message
@@ -6645,6 +6904,8 @@ var DataVaultService = class extends BaseService {
6645
6904
  data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6646
6905
  };
6647
6906
  } catch (error) {
6907
+ const routing = routingFailure(error);
6908
+ if (routing) return vaultError(routing);
6648
6909
  return vaultError({
6649
6910
  code: "STORAGE_ERROR",
6650
6911
  cause: toError(error)
@@ -6657,6 +6918,8 @@ var DataVaultService = class extends BaseService {
6657
6918
  * Unlike {@link getNetworkEncrypted}, this is intentionally message-free so
6658
6919
  * an operation layer can distinguish an authorized absence from a failed
6659
6920
  * read without receiving node, envelope, plaintext, or key material.
6921
+ * Typed routing failures reject unchanged rather than becoming an ordinary
6922
+ * read/decrypt failure; the operation boundary projects their safe code.
6660
6923
  */
6661
6924
  async readNetworkEncrypted(key, options) {
6662
6925
  const config = this.networkEncryption;
@@ -6666,10 +6929,12 @@ var DataVaultService = class extends BaseService {
6666
6929
  let valueResult;
6667
6930
  try {
6668
6931
  valueResult = await this.tc.kv.get(`vault/${key}`, { raw: true, source: "network" });
6669
- } catch {
6932
+ } catch (error) {
6933
+ if (routingFailure(error)) throw error;
6670
6934
  return { status: "node_unreachable" };
6671
6935
  }
6672
6936
  if (!valueResult.ok) {
6937
+ if (routingFailure(valueResult.error)) throw valueResult.error;
6673
6938
  const permissionHint = parsePermissionHint(valueResult.error.meta?.permissionHint);
6674
6939
  if (valueResult.error.code === "AUTH_UNAUTHORIZED" && permissionHint !== void 0) {
6675
6940
  return { status: "permission_required", hint: permissionHint };
@@ -6677,7 +6942,7 @@ var DataVaultService = class extends BaseService {
6677
6942
  if ((valueResult.error.code === "NOT_FOUND" || valueResult.error.code === "KV_NOT_FOUND") && !hasHttpResponse(valueResult.error)) {
6678
6943
  return { status: "not_found" };
6679
6944
  }
6680
- if (valueResult.error.code === "NETWORK_ERROR" || valueResult.error.code === "TIMEOUT" || valueResult.error.code === "ABORTED") {
6945
+ if (valueResult.error.code === "NODE_NOT_RESPONDING" || valueResult.error.code === "NETWORK_ERROR" || valueResult.error.code === "TIMEOUT" || valueResult.error.code === "ABORTED") {
6681
6946
  return hasHttpResponse(valueResult.error) ? { status: "read_failed" } : { status: "node_unreachable" };
6682
6947
  }
6683
6948
  return { status: "read_failed" };
@@ -6699,10 +6964,12 @@ var DataVaultService = class extends BaseService {
6699
6964
  try {
6700
6965
  const proof = await this.decryptCapabilityProof();
6701
6966
  plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6702
- } catch {
6967
+ } catch (error) {
6968
+ if (routingFailure(error)) throw error;
6703
6969
  return { status: "decrypt_failed" };
6704
6970
  }
6705
6971
  if (!plaintextResult.ok) {
6972
+ if (routingFailure(plaintextResult.error)) throw plaintextResult.error;
6706
6973
  const permissionHint = parsePermissionHint(
6707
6974
  plaintextResult.error.permissionHint
6708
6975
  );
@@ -6712,7 +6979,7 @@ var DataVaultService = class extends BaseService {
6712
6979
  if (plaintextResult.error.code === "INVALID_ENVELOPE") {
6713
6980
  return { status: "corrupt_envelope" };
6714
6981
  }
6715
- if (plaintextResult.error.code === "TRANSPORT_ERROR") {
6982
+ if (plaintextResult.error.code === "NODE_NOT_RESPONDING" || plaintextResult.error.code === "TRANSPORT_ERROR") {
6716
6983
  return { status: "node_unreachable" };
6717
6984
  }
6718
6985
  return { status: "decrypt_failed" };
@@ -6964,6 +7231,8 @@ var DataVaultService = class extends BaseService {
6964
7231
  if (this.usesNetworkEncryption) {
6965
7232
  const valueDelResult2 = await this.tc.kv.delete(`vault/${key}`);
6966
7233
  if (!valueDelResult2.ok) {
7234
+ const routing = routingFailure(valueDelResult2.error);
7235
+ if (routing) return vaultError(routing);
6967
7236
  return vaultError({ code: "KEY_NOT_FOUND", key });
6968
7237
  }
6969
7238
  return ok(void 0);
@@ -6973,10 +7242,14 @@ var DataVaultService = class extends BaseService {
6973
7242
  this.tc.kv.delete(`vault/${key}`)
6974
7243
  ]);
6975
7244
  if (!keyDelResult.ok && !valueDelResult.ok) {
7245
+ const routing = routingFailure(keyDelResult.error) ?? routingFailure(valueDelResult.error);
7246
+ if (routing) return vaultError(routing);
6976
7247
  return vaultError({ code: "KEY_NOT_FOUND", key });
6977
7248
  }
6978
7249
  return ok(void 0);
6979
7250
  } catch (error) {
7251
+ const routing = routingFailure(error);
7252
+ if (routing) return vaultError(routing);
6980
7253
  return vaultError({
6981
7254
  code: "STORAGE_ERROR",
6982
7255
  cause: toError(error)
@@ -7018,6 +7291,8 @@ var DataVaultService = class extends BaseService {
7018
7291
  ...options?.cursor === void 0 ? {} : { cursor: options.cursor }
7019
7292
  });
7020
7293
  if (!listResult.ok) {
7294
+ const routing = routingFailure(listResult.error);
7295
+ if (routing) return vaultError(routing);
7021
7296
  return vaultError({
7022
7297
  code: "STORAGE_ERROR",
7023
7298
  cause: new Error(
@@ -7038,6 +7313,8 @@ var DataVaultService = class extends BaseService {
7038
7313
  ...listResult.data.nextCursor === void 0 ? {} : { nextCursor: listResult.data.nextCursor }
7039
7314
  });
7040
7315
  } catch (error) {
7316
+ const routing = routingFailure(error);
7317
+ if (routing) return vaultError(routing);
7041
7318
  return vaultError({
7042
7319
  code: "STORAGE_ERROR",
7043
7320
  cause: toError(error)
@@ -7869,6 +8146,8 @@ function defaultEncryptionMessage(input) {
7869
8146
  return input.message ?? input.cause.message;
7870
8147
  case "INVALID_INPUT":
7871
8148
  return input.message;
8149
+ default:
8150
+ return input.message ?? input.code;
7872
8151
  }
7873
8152
  }
7874
8153
  function encryptionError(input) {
@@ -7933,6 +8212,8 @@ async function discoverNetwork(input) {
7933
8212
  return { ok: true, data: { descriptor: validated.data, source: "node" } };
7934
8213
  }
7935
8214
  } catch (err3) {
8215
+ const routing = routingFailure(err3);
8216
+ if (routing) return { ok: false, error: encryptionError(routing) };
7936
8217
  }
7937
8218
  }
7938
8219
  if (input.wellKnown !== void 0) {
@@ -7950,6 +8231,8 @@ async function discoverNetwork(input) {
7950
8231
  };
7951
8232
  }
7952
8233
  } catch (err3) {
8234
+ const routing = routingFailure(err3);
8235
+ if (routing) return { ok: false, error: encryptionError(routing) };
7953
8236
  }
7954
8237
  }
7955
8238
  return {
@@ -8039,6 +8322,152 @@ function ensureNetworkUsableForDecrypt(descriptor) {
8039
8322
  };
8040
8323
  }
8041
8324
 
8325
+ // src/encryption/keyWrap.ts
8326
+ var import_aes = require("@noble/ciphers/aes");
8327
+ var import_ed25519 = require("@noble/curves/ed25519");
8328
+ var import_hkdf = require("@noble/hashes/hkdf");
8329
+ var import_sha256 = require("@noble/hashes/sha256");
8330
+ var import_utils = require("@noble/hashes/utils");
8331
+ var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
8332
+ var MAGIC = utf8Encode("TCW2");
8333
+ var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
8334
+ var MAX_HEADER = 65536;
8335
+ function concat(...parts) {
8336
+ const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
8337
+ let offset = 0;
8338
+ for (const part of parts) {
8339
+ result.set(part, offset);
8340
+ offset += part.length;
8341
+ }
8342
+ return result;
8343
+ }
8344
+ function equal(a, b) {
8345
+ if (a.length !== b.length) return false;
8346
+ let difference = 0;
8347
+ for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
8348
+ return difference === 0;
8349
+ }
8350
+ function decode32(value) {
8351
+ if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
8352
+ const decoded = base64Decode(value);
8353
+ if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
8354
+ return decoded;
8355
+ }
8356
+ function isWellFormed(value) {
8357
+ for (let i = 0; i < value.length; i++) {
8358
+ const unit = value.charCodeAt(i);
8359
+ if (unit >= 55296 && unit <= 56319) {
8360
+ const next = value.charCodeAt(++i);
8361
+ if (!(next >= 56320 && next <= 57343)) return false;
8362
+ } else if (unit >= 56320 && unit <= 57343) return false;
8363
+ }
8364
+ return true;
8365
+ }
8366
+ function validateContext(context) {
8367
+ if (context.purpose !== "network-key" && context.purpose !== "receiver-key" || typeof context.networkId !== "string" || typeof context.keyPath !== "string" || !isWellFormed(context.networkId) || !isWellFormed(context.keyPath) || !Number.isSafeInteger(context.keyVersion) || context.keyVersion <= 0 || !context.metadata || typeof context.metadata !== "object" || Array.isArray(context.metadata) || Object.entries(context.metadata).some(([key, value]) => !isWellFormed(key) || typeof value !== "string" || !isWellFormed(value))) {
8368
+ throw new Error("Invalid key wrap context");
8369
+ }
8370
+ if (context.networkId !== "") {
8371
+ const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
8372
+ if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
8373
+ throw new Error("Invalid key wrap resource");
8374
+ }
8375
+ }
8376
+ }
8377
+ function isKeyWrapV2(wrapped) {
8378
+ return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
8379
+ }
8380
+ function readKeyWrapV2(wrapped) {
8381
+ if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
8382
+ const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
8383
+ if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
8384
+ const bytes = wrapped.subarray(8, 8 + length);
8385
+ const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
8386
+ const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
8387
+ if (!header || typeof header !== "object" || Array.isArray(header) || Object.keys(header).sort().join(",") !== fields.join(",") || header.v !== 2 || header.alg !== KEY_WRAP_V2_ALG || header.kdf !== "hkdf-sha256" || header.aead !== "aes-256-gcm") {
8388
+ throw new Error("Invalid v2 key wrap header");
8389
+ }
8390
+ validateContext(header);
8391
+ decode32(header.salt);
8392
+ decode32(header.ephemeralPublicKey);
8393
+ decode32(header.recipientPublicKey);
8394
+ if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
8395
+ const end = 8 + length;
8396
+ return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
8397
+ }
8398
+ function assertKeyWrapContext(header, expected) {
8399
+ validateContext(expected);
8400
+ for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
8401
+ if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
8402
+ }
8403
+ if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
8404
+ }
8405
+ function deriveKey(secret, ephemeral, recipient, salt) {
8406
+ const shared = import_ed25519.x25519.getSharedSecret(secret, ephemeral);
8407
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
8408
+ try {
8409
+ return (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
8410
+ } finally {
8411
+ shared.fill(0);
8412
+ }
8413
+ }
8414
+ function sealKeyV2(recipientPublicKey, plaintext, context) {
8415
+ validateContext(context);
8416
+ if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
8417
+ const secret = (0, import_utils.randomBytes)(32);
8418
+ const ephemeral = import_ed25519.x25519.getPublicKey(secret);
8419
+ const salt = (0, import_utils.randomBytes)(32);
8420
+ const header = {
8421
+ ...context,
8422
+ v: 2,
8423
+ alg: KEY_WRAP_V2_ALG,
8424
+ kdf: "hkdf-sha256",
8425
+ aead: "aes-256-gcm",
8426
+ salt: base64Encode(salt),
8427
+ ephemeralPublicKey: base64Encode(ephemeral),
8428
+ recipientPublicKey: base64Encode(recipientPublicKey)
8429
+ };
8430
+ const bytes = utf8Encode(canonicalize({ ...header }));
8431
+ if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
8432
+ const prefix = new Uint8Array(8);
8433
+ prefix.set(MAGIC);
8434
+ new DataView(prefix.buffer).setUint32(4, bytes.length);
8435
+ const aad = concat(prefix, bytes);
8436
+ const nonce = (0, import_utils.randomBytes)(12);
8437
+ let key;
8438
+ try {
8439
+ const shared = import_ed25519.x25519.getSharedSecret(secret, recipientPublicKey);
8440
+ try {
8441
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
8442
+ key = (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
8443
+ } finally {
8444
+ shared.fill(0);
8445
+ }
8446
+ return concat(aad, nonce, (0, import_aes.gcm)(key, nonce, aad).encrypt(plaintext));
8447
+ } finally {
8448
+ secret.fill(0);
8449
+ key?.fill(0);
8450
+ }
8451
+ }
8452
+ function openKeyV2(recipientPrivateKey, wrapped, expected) {
8453
+ const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
8454
+ assertKeyWrapContext(header, expected);
8455
+ const recipient = import_ed25519.x25519.getPublicKey(recipientPrivateKey);
8456
+ if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
8457
+ const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
8458
+ try {
8459
+ return (0, import_aes.gcm)(key, nonce, aad).decrypt(ciphertext);
8460
+ } finally {
8461
+ key.fill(0);
8462
+ }
8463
+ }
8464
+ function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
8465
+ if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
8466
+ if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
8467
+ if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
8468
+ if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
8469
+ }
8470
+
8042
8471
  // src/encryption/envelope.ts
8043
8472
  function encryptToNetwork(crypto2, input) {
8044
8473
  const parsedResource = parseEncryptionResource(
@@ -8047,11 +8476,20 @@ function encryptToNetwork(crypto2, input) {
8047
8476
  if (parsedResource.keyPath !== input.keyPath || input.keyPath?.endsWith("/")) {
8048
8477
  throw new NetworkIdError("keyPath must name an exact encryption key");
8049
8478
  }
8050
- const alg = input.alg ?? DEFAULT_ENCRYPTION_ALG;
8479
+ const alg = input.keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG;
8480
+ if (input.alg !== void 0 && input.alg !== alg) {
8481
+ throw new Error("Algorithm does not match keyWrapV2");
8482
+ }
8051
8483
  const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
8052
8484
  const symmetricKey = crypto2.randomBytes(32);
8053
8485
  const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
8054
- const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
8486
+ const wrapped = input.keyWrapV2 ? sealKeyV2(input.networkPublicKey, symmetricKey, {
8487
+ purpose: "network-key",
8488
+ networkId: input.networkId,
8489
+ keyPath: input.keyPath ?? "",
8490
+ keyVersion,
8491
+ metadata: input.metadata ?? {}
8492
+ }) : crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
8055
8493
  const encryptedSymmetricKey = base64Encode(wrapped);
8056
8494
  const encryptedSymmetricKeyHash = canonicalHashHex(
8057
8495
  crypto2.sha256,
@@ -8071,7 +8509,7 @@ function encryptToNetwork(crypto2, input) {
8071
8509
  };
8072
8510
  return { envelope, symmetricKey };
8073
8511
  }
8074
- function validateEnvelope(crypto2, envelope) {
8512
+ function validateEnvelope(crypto2, envelope, requireKeyWrapV2 = false) {
8075
8513
  if (envelope === null || typeof envelope !== "object") {
8076
8514
  return {
8077
8515
  ok: false,
@@ -8148,9 +8586,28 @@ function validateEnvelope(crypto2, envelope) {
8148
8586
  })
8149
8587
  };
8150
8588
  }
8589
+ try {
8590
+ validateEnvelopeKeyWrap(e, requireKeyWrapV2);
8591
+ } catch {
8592
+ return { ok: false, error: encryptionError({ code: "INVALID_ENVELOPE", message: "Envelope key wrap context is invalid" }) };
8593
+ }
8151
8594
  return { ok: true, data: e };
8152
8595
  }
8596
+ function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
8597
+ const wrapped = base64Decode(envelope.encryptedSymmetricKey);
8598
+ assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
8599
+ if (envelope.alg === KEY_WRAP_V2_ALG) {
8600
+ assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
8601
+ purpose: "network-key",
8602
+ networkId: envelope.networkId,
8603
+ keyPath: envelope.keyPath ?? "",
8604
+ keyVersion: envelope.keyVersion,
8605
+ metadata: envelope.metadata ?? {}
8606
+ });
8607
+ }
8608
+ }
8153
8609
  function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
8610
+ validateEnvelopeKeyWrap(envelope);
8154
8611
  const ciphertext = base64Decode(envelope.ciphertext);
8155
8612
  const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
8156
8613
  return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
@@ -8440,7 +8897,7 @@ function verifyDecryptResponse(input) {
8440
8897
  })
8441
8898
  };
8442
8899
  }
8443
- if (response.alg !== request.alg) {
8900
+ if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
8444
8901
  return {
8445
8902
  ok: false,
8446
8903
  error: encryptionError({
@@ -8497,7 +8954,7 @@ function verifyDecryptResponse(input) {
8497
8954
  })
8498
8955
  };
8499
8956
  }
8500
- if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== response.alg || facts.keyVersion !== response.keyVersion) {
8957
+ if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== request.alg || facts.keyVersion !== response.keyVersion) {
8501
8958
  return {
8502
8959
  ok: false,
8503
8960
  error: encryptionError({
@@ -8520,8 +8977,13 @@ function verifyDecryptResponse(input) {
8520
8977
  }
8521
8978
  return { ok: true, data: response };
8522
8979
  }
8523
- function openWrappedKey(crypto2, receiverPrivateKey, response) {
8980
+ function openWrappedKey(crypto2, receiverPrivateKey, response, expected) {
8524
8981
  const wrapped = base64Decode(response.wrappedKey);
8982
+ assertKeyWrapAlgorithm(wrapped, response.alg);
8983
+ if (response.alg === KEY_WRAP_V2_ALG) {
8984
+ if (!expected) throw new Error("V2 response requires expected key context");
8985
+ return openKeyV2(receiverPrivateKey, wrapped, expected);
8986
+ }
8525
8987
  return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
8526
8988
  }
8527
8989
 
@@ -8534,6 +8996,14 @@ function encOk(data) {
8534
8996
  function encErr(error) {
8535
8997
  return { ok: false, error };
8536
8998
  }
8999
+ function decryptionError(input) {
9000
+ const unavailable = input.code === "NODE_NOT_RESPONDING" || input.code === "TRANSPORT_ERROR" && input.cause.name !== "AbortError";
9001
+ return encryptionError(unavailable ? {
9002
+ ...input,
9003
+ code: "DECRYPTION_DEPENDENCY_UNAVAILABLE",
9004
+ message: "The decryption service isn't responding right now. Please try again."
9005
+ } : input);
9006
+ }
8537
9007
  var EncryptionService = class extends BaseService {
8538
9008
  constructor(config) {
8539
9009
  super();
@@ -8575,12 +9045,15 @@ var EncryptionService = class extends BaseService {
8575
9045
  networkPublicKey,
8576
9046
  plaintext,
8577
9047
  ...options?.aad !== void 0 ? { aad: options.aad } : {},
8578
- alg: options?.alg ?? descriptor.alg,
9048
+ keyWrapV2: options?.keyWrapV2 ?? this._config.keyWrapV2 ?? false,
9049
+ ...options?.alg !== void 0 ? { alg: options.alg } : {},
8579
9050
  keyVersion: options?.keyVersion ?? descriptor.keyVersion,
8580
9051
  ...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
8581
9052
  });
8582
9053
  return encOk(result.envelope);
8583
9054
  } catch (error) {
9055
+ const routing = routingFailure(error);
9056
+ if (routing) return encErr(encryptionError(routing));
8584
9057
  return encErr(
8585
9058
  encryptionError({
8586
9059
  code: "TRANSPORT_ERROR",
@@ -8592,7 +9065,7 @@ var EncryptionService = class extends BaseService {
8592
9065
  async decryptEnvelope(envelope, capabilityProof, options) {
8593
9066
  try {
8594
9067
  this.assertActive();
8595
- const validated = validateEnvelope(this.crypto, envelope);
9068
+ const validated = validateEnvelope(this.crypto, envelope, this._config.requireKeyWrapV2 ?? false);
8596
9069
  if (!validated.ok) return validated;
8597
9070
  if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
8598
9071
  return encErr(
@@ -8607,12 +9080,12 @@ var EncryptionService = class extends BaseService {
8607
9080
  descriptor = options.descriptor;
8608
9081
  } else {
8609
9082
  const discovered = await this.discoverNetwork(envelope.networkId);
8610
- if (!discovered.ok) return discovered;
9083
+ if (!discovered.ok) return encErr(decryptionError(discovered.error));
8611
9084
  descriptor = discovered.data;
8612
9085
  }
8613
9086
  const usable = ensureNetworkUsableForDecrypt(descriptor);
8614
9087
  if (!usable.ok) return usable;
8615
- const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
9088
+ const targetNode = this._config.resolveTargetNode ? await this._config.resolveTargetNode(envelope.networkId) : options?.targetNode ?? descriptor.members[0]?.nodeId;
8616
9089
  if (targetNode === void 0) {
8617
9090
  return encErr(
8618
9091
  encryptionError({
@@ -8621,6 +9094,9 @@ var EncryptionService = class extends BaseService {
8621
9094
  })
8622
9095
  );
8623
9096
  }
9097
+ if (this._config.resolveTargetNode && (options?.targetNode !== void 0 && options.targetNode !== targetNode || !descriptor.members.some((member) => member.nodeId === targetNode))) {
9098
+ return encErr(encryptionError({ code: "INVALID_INPUT", message: "Encryption destination does not match the registry" }));
9099
+ }
8624
9100
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
8625
9101
  const receiverPublicKey = base64Encode(receiverKey.publicKey);
8626
9102
  const receiverPublicKeyHash = canonicalHashHex(
@@ -8679,6 +9155,8 @@ var EncryptionService = class extends BaseService {
8679
9155
  });
8680
9156
  this.assertActive();
8681
9157
  } catch (error) {
9158
+ const routing = routingFailure(error);
9159
+ if (routing) return encErr(decryptionError(routing));
8682
9160
  if (error instanceof import_decrypt_transport_response_error.DecryptTransportResponseError) {
8683
9161
  return encErr(
8684
9162
  encryptionError(
@@ -8695,7 +9173,7 @@ var EncryptionService = class extends BaseService {
8695
9173
  );
8696
9174
  }
8697
9175
  return encErr(
8698
- encryptionError({
9176
+ decryptionError({
8699
9177
  code: "TRANSPORT_ERROR",
8700
9178
  cause: toError2(error)
8701
9179
  })
@@ -8713,7 +9191,8 @@ var EncryptionService = class extends BaseService {
8713
9191
  const symmetricKey = openWrappedKey(
8714
9192
  this.crypto,
8715
9193
  receiverKey.privateKey,
8716
- verified.data
9194
+ verified.data,
9195
+ { purpose: "receiver-key", networkId: body.networkId, keyPath: body.keyPath ?? "", keyVersion: body.keyVersion, metadata: {} }
8717
9196
  );
8718
9197
  const plaintext = decryptEnvelopeWithKey(
8719
9198
  this.crypto,
@@ -8722,6 +9201,8 @@ var EncryptionService = class extends BaseService {
8722
9201
  );
8723
9202
  return encOk(plaintext);
8724
9203
  } catch (error) {
9204
+ const routing = routingFailure(error);
9205
+ if (routing) return encErr(decryptionError(routing));
8725
9206
  return encErr(
8726
9207
  encryptionError({
8727
9208
  code: "INVALID_RESPONSE",
@@ -8771,7 +9252,11 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8771
9252
  MAX_SECRET_SCOPE_LENGTH,
8772
9253
  NETWORK_NAME_PATTERN,
8773
9254
  NetworkIdError,
9255
+ NodeIdentityMismatchError,
9256
+ NodeNotRespondingError,
9257
+ PartialNodeWriteError,
8774
9258
  PrefixedKVService,
9259
+ ResourceNotRoutedError,
8775
9260
  RetryPolicySchema,
8776
9261
  SECRET_NAME_RE,
8777
9262
  SECRET_SCOPE_RE,
@@ -8819,6 +9304,8 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8819
9304
  checkDecryptInvocationInput,
8820
9305
  clearTinyCloudDebugLogs,
8821
9306
  createKVResponseSchema,
9307
+ createResourceRoutingFetch,
9308
+ createResourceTransportInvocationFetch,
8822
9309
  createResultSchema,
8823
9310
  createVaultCrypto,
8824
9311
  decryptEnvelopeWithKey,
@@ -8861,6 +9348,9 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8861
9348
  requiresSecretsOptIn,
8862
9349
  resolveSecretListPrefix,
8863
9350
  resolveSecretPath,
9351
+ routeResourceRequest,
9352
+ routingFailure,
9353
+ routingResourceOf,
8864
9354
  serviceError,
8865
9355
  storageLimitReachedError,
8866
9356
  storageQuotaExceededError,
@@ -8881,6 +9371,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8881
9371
  validateTelemetrySpanEvent,
8882
9372
  validatedCapabilityOf,
8883
9373
  verifyDecryptResponse,
9374
+ verifyResourceDestination,
8884
9375
  wrapError
8885
9376
  });
8886
9377
  //# sourceMappingURL=index.cjs.map