@tinycloud/sdk-services 3.11.0 → 3.12.0-beta.16

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 +523 -29
  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 +513 -29
  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 +48 -11
  42. package/dist/kv/index.cjs.map +1 -1
  43. package/dist/kv/index.d.cts +5 -5
  44. package/dist/kv/index.d.ts +5 -5
  45. package/dist/kv/index.js +48 -11
  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);
@@ -2157,11 +2404,15 @@ var KVService = class extends BaseService {
2157
2404
  }
2158
2405
  /** Delay KV routing until an asynchronously loaded read-through is attached. */
2159
2406
  setReadThroughReady(ready) {
2160
- const settled = ready.then(() => void 0, () => void 0);
2161
- this.readThroughReady = settled;
2162
- void settled.then(() => {
2163
- if (this.readThroughReady === settled) this.readThroughReady = null;
2164
- });
2407
+ const start = () => {
2408
+ if (typeof ready === "function") ready = Promise.resolve().then(ready);
2409
+ const finish = () => {
2410
+ if (this.readThroughReady === start) this.readThroughReady = null;
2411
+ };
2412
+ return ready.then(finish, finish);
2413
+ };
2414
+ this.readThroughReady = start;
2415
+ if (typeof ready !== "function") void start();
2165
2416
  }
2166
2417
  /**
2167
2418
  * Get the service configuration.
@@ -2408,7 +2659,6 @@ var KVService = class extends BaseService {
2408
2659
  return aligned;
2409
2660
  }
2410
2661
  async batchRead(keys, action, options) {
2411
- if (this.readThroughReady !== null) await this.readThroughReady;
2412
2662
  if (!this.requireAuth()) return err(authRequiredError("kv"));
2413
2663
  if (keys.length === 0) return ok({ results: [], count: 0 });
2414
2664
  if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
@@ -2659,7 +2909,7 @@ var KVService = class extends BaseService {
2659
2909
  */
2660
2910
  async get(key, options) {
2661
2911
  return this.withTelemetry("get", key, async () => {
2662
- if (this.readThroughReady !== null) await this.readThroughReady;
2912
+ if (options?.source !== "network" && this.readThroughReady !== null) await this.readThroughReady();
2663
2913
  if (!this.requireAuth()) {
2664
2914
  return err(authRequiredError("kv"));
2665
2915
  }
@@ -2778,7 +3028,7 @@ var KVService = class extends BaseService {
2778
3028
  */
2779
3029
  async put(key, value, options) {
2780
3030
  return this.withTelemetry("put", key, async () => {
2781
- if (this.readThroughReady !== null) await this.readThroughReady;
3031
+ if (this.readThroughReady !== null) await this.readThroughReady();
2782
3032
  if (!this.requireAuth()) {
2783
3033
  return err(authRequiredError("kv"));
2784
3034
  }
@@ -2863,7 +3113,7 @@ var KVService = class extends BaseService {
2863
3113
  */
2864
3114
  async batchPut(items, options) {
2865
3115
  return this.withTelemetry("batchPut", String(items.length), async () => {
2866
- if (this.readThroughReady !== null) await this.readThroughReady;
3116
+ if (this.readThroughReady !== null) await this.readThroughReady();
2867
3117
  if (!this.requireAuth()) {
2868
3118
  return err(authRequiredError("kv"));
2869
3119
  }
@@ -3062,7 +3312,7 @@ var KVService = class extends BaseService {
3062
3312
  */
3063
3313
  async list(options) {
3064
3314
  return this.withTelemetry("list", options?.prefix, async () => {
3065
- if (this.readThroughReady !== null) await this.readThroughReady;
3315
+ if (options?.source !== "network" && this.readThroughReady !== null) await this.readThroughReady();
3066
3316
  if (!this.requireAuth()) {
3067
3317
  return err(authRequiredError("kv"));
3068
3318
  }
@@ -3350,7 +3600,7 @@ var KVService = class extends BaseService {
3350
3600
  */
3351
3601
  async delete(key, options) {
3352
3602
  return this.withTelemetry("delete", key, async () => {
3353
- if (this.readThroughReady !== null) await this.readThroughReady;
3603
+ if (this.readThroughReady !== null) await this.readThroughReady();
3354
3604
  if (!this.requireAuth()) {
3355
3605
  return err(authRequiredError("kv"));
3356
3606
  }
@@ -5953,9 +6203,9 @@ function utf8Encode(s) {
5953
6203
  function utf8Decode(b) {
5954
6204
  return new TextDecoder().decode(b);
5955
6205
  }
5956
- function canonicalHashHex(sha256, value) {
6206
+ function canonicalHashHex(sha2562, value) {
5957
6207
  const canonical = canonicalize(value);
5958
- return hexEncode(sha256(utf8Encode(canonical)));
6208
+ return hexEncode(sha2562(utf8Encode(canonical)));
5959
6209
  }
5960
6210
 
5961
6211
  // src/vault/types.ts
@@ -6197,6 +6447,8 @@ function defaultVaultMessage(input) {
6197
6447
  case "STORAGE_QUOTA_EXCEEDED":
6198
6448
  case "STORAGE_LIMIT_REACHED":
6199
6449
  return input.message;
6450
+ default:
6451
+ return input.message ?? input.code;
6200
6452
  }
6201
6453
  }
6202
6454
  function vaultError(input) {
@@ -6248,6 +6500,8 @@ function ifMatchWriteError(key, error) {
6248
6500
  });
6249
6501
  }
6250
6502
  function kvWriteError(context, error) {
6503
+ const routing = routingFailure(error);
6504
+ if (routing) return vaultError(routing);
6251
6505
  if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
6252
6506
  return vaultError({ code: error.code, message: error.message, meta: error.meta });
6253
6507
  }
@@ -6554,6 +6808,8 @@ var DataVaultService = class extends BaseService {
6554
6808
  { aad, metadata }
6555
6809
  );
6556
6810
  if (!envelopeResult.ok) {
6811
+ const routing = routingFailure(envelopeResult.error);
6812
+ if (routing) return vaultError(routing);
6557
6813
  return vaultError({
6558
6814
  code: "STORAGE_ERROR",
6559
6815
  cause: new Error(envelopeResult.error.message)
@@ -6580,6 +6836,8 @@ var DataVaultService = class extends BaseService {
6580
6836
  }
6581
6837
  return { ok: true, data: void 0 };
6582
6838
  } catch (error) {
6839
+ const routing = routingFailure(error);
6840
+ if (routing) return vaultError(routing);
6583
6841
  return vaultError({
6584
6842
  code: "STORAGE_ERROR",
6585
6843
  cause: toError(error)
@@ -6606,6 +6864,8 @@ var DataVaultService = class extends BaseService {
6606
6864
  ...options?.source === void 0 ? {} : { source: options.source }
6607
6865
  });
6608
6866
  if (!valueResult.ok) {
6867
+ const routing = routingFailure(valueResult.error);
6868
+ if (routing) return vaultError(routing);
6609
6869
  return vaultError({ code: "KEY_NOT_FOUND", key });
6610
6870
  }
6611
6871
  const rawEnvelope = unwrapKVData(valueResult.data);
@@ -6626,6 +6886,8 @@ var DataVaultService = class extends BaseService {
6626
6886
  const proof = await this.decryptCapabilityProof();
6627
6887
  const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6628
6888
  if (!plaintextResult.ok) {
6889
+ const routing = routingFailure(plaintextResult.error);
6890
+ if (routing) return vaultError(routing);
6629
6891
  return vaultError({
6630
6892
  code: "DECRYPTION_FAILED",
6631
6893
  message: plaintextResult.error.message
@@ -6645,6 +6907,8 @@ var DataVaultService = class extends BaseService {
6645
6907
  data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6646
6908
  };
6647
6909
  } catch (error) {
6910
+ const routing = routingFailure(error);
6911
+ if (routing) return vaultError(routing);
6648
6912
  return vaultError({
6649
6913
  code: "STORAGE_ERROR",
6650
6914
  cause: toError(error)
@@ -6657,6 +6921,8 @@ var DataVaultService = class extends BaseService {
6657
6921
  * Unlike {@link getNetworkEncrypted}, this is intentionally message-free so
6658
6922
  * an operation layer can distinguish an authorized absence from a failed
6659
6923
  * read without receiving node, envelope, plaintext, or key material.
6924
+ * Typed routing failures reject unchanged rather than becoming an ordinary
6925
+ * read/decrypt failure; the operation boundary projects their safe code.
6660
6926
  */
6661
6927
  async readNetworkEncrypted(key, options) {
6662
6928
  const config = this.networkEncryption;
@@ -6666,10 +6932,12 @@ var DataVaultService = class extends BaseService {
6666
6932
  let valueResult;
6667
6933
  try {
6668
6934
  valueResult = await this.tc.kv.get(`vault/${key}`, { raw: true, source: "network" });
6669
- } catch {
6935
+ } catch (error) {
6936
+ if (routingFailure(error)) throw error;
6670
6937
  return { status: "node_unreachable" };
6671
6938
  }
6672
6939
  if (!valueResult.ok) {
6940
+ if (routingFailure(valueResult.error)) throw valueResult.error;
6673
6941
  const permissionHint = parsePermissionHint(valueResult.error.meta?.permissionHint);
6674
6942
  if (valueResult.error.code === "AUTH_UNAUTHORIZED" && permissionHint !== void 0) {
6675
6943
  return { status: "permission_required", hint: permissionHint };
@@ -6677,7 +6945,7 @@ var DataVaultService = class extends BaseService {
6677
6945
  if ((valueResult.error.code === "NOT_FOUND" || valueResult.error.code === "KV_NOT_FOUND") && !hasHttpResponse(valueResult.error)) {
6678
6946
  return { status: "not_found" };
6679
6947
  }
6680
- if (valueResult.error.code === "NETWORK_ERROR" || valueResult.error.code === "TIMEOUT" || valueResult.error.code === "ABORTED") {
6948
+ if (valueResult.error.code === "NODE_NOT_RESPONDING" || valueResult.error.code === "NETWORK_ERROR" || valueResult.error.code === "TIMEOUT" || valueResult.error.code === "ABORTED") {
6681
6949
  return hasHttpResponse(valueResult.error) ? { status: "read_failed" } : { status: "node_unreachable" };
6682
6950
  }
6683
6951
  return { status: "read_failed" };
@@ -6699,10 +6967,12 @@ var DataVaultService = class extends BaseService {
6699
6967
  try {
6700
6968
  const proof = await this.decryptCapabilityProof();
6701
6969
  plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6702
- } catch {
6970
+ } catch (error) {
6971
+ if (routingFailure(error)) throw error;
6703
6972
  return { status: "decrypt_failed" };
6704
6973
  }
6705
6974
  if (!plaintextResult.ok) {
6975
+ if (routingFailure(plaintextResult.error)) throw plaintextResult.error;
6706
6976
  const permissionHint = parsePermissionHint(
6707
6977
  plaintextResult.error.permissionHint
6708
6978
  );
@@ -6712,7 +6982,7 @@ var DataVaultService = class extends BaseService {
6712
6982
  if (plaintextResult.error.code === "INVALID_ENVELOPE") {
6713
6983
  return { status: "corrupt_envelope" };
6714
6984
  }
6715
- if (plaintextResult.error.code === "TRANSPORT_ERROR") {
6985
+ if (plaintextResult.error.code === "NODE_NOT_RESPONDING" || plaintextResult.error.code === "TRANSPORT_ERROR") {
6716
6986
  return { status: "node_unreachable" };
6717
6987
  }
6718
6988
  return { status: "decrypt_failed" };
@@ -6964,6 +7234,8 @@ var DataVaultService = class extends BaseService {
6964
7234
  if (this.usesNetworkEncryption) {
6965
7235
  const valueDelResult2 = await this.tc.kv.delete(`vault/${key}`);
6966
7236
  if (!valueDelResult2.ok) {
7237
+ const routing = routingFailure(valueDelResult2.error);
7238
+ if (routing) return vaultError(routing);
6967
7239
  return vaultError({ code: "KEY_NOT_FOUND", key });
6968
7240
  }
6969
7241
  return ok(void 0);
@@ -6973,10 +7245,14 @@ var DataVaultService = class extends BaseService {
6973
7245
  this.tc.kv.delete(`vault/${key}`)
6974
7246
  ]);
6975
7247
  if (!keyDelResult.ok && !valueDelResult.ok) {
7248
+ const routing = routingFailure(keyDelResult.error) ?? routingFailure(valueDelResult.error);
7249
+ if (routing) return vaultError(routing);
6976
7250
  return vaultError({ code: "KEY_NOT_FOUND", key });
6977
7251
  }
6978
7252
  return ok(void 0);
6979
7253
  } catch (error) {
7254
+ const routing = routingFailure(error);
7255
+ if (routing) return vaultError(routing);
6980
7256
  return vaultError({
6981
7257
  code: "STORAGE_ERROR",
6982
7258
  cause: toError(error)
@@ -7018,6 +7294,8 @@ var DataVaultService = class extends BaseService {
7018
7294
  ...options?.cursor === void 0 ? {} : { cursor: options.cursor }
7019
7295
  });
7020
7296
  if (!listResult.ok) {
7297
+ const routing = routingFailure(listResult.error);
7298
+ if (routing) return vaultError(routing);
7021
7299
  return vaultError({
7022
7300
  code: "STORAGE_ERROR",
7023
7301
  cause: new Error(
@@ -7038,6 +7316,8 @@ var DataVaultService = class extends BaseService {
7038
7316
  ...listResult.data.nextCursor === void 0 ? {} : { nextCursor: listResult.data.nextCursor }
7039
7317
  });
7040
7318
  } catch (error) {
7319
+ const routing = routingFailure(error);
7320
+ if (routing) return vaultError(routing);
7041
7321
  return vaultError({
7042
7322
  code: "STORAGE_ERROR",
7043
7323
  cause: toError(error)
@@ -7869,6 +8149,8 @@ function defaultEncryptionMessage(input) {
7869
8149
  return input.message ?? input.cause.message;
7870
8150
  case "INVALID_INPUT":
7871
8151
  return input.message;
8152
+ default:
8153
+ return input.message ?? input.code;
7872
8154
  }
7873
8155
  }
7874
8156
  function encryptionError(input) {
@@ -7933,6 +8215,8 @@ async function discoverNetwork(input) {
7933
8215
  return { ok: true, data: { descriptor: validated.data, source: "node" } };
7934
8216
  }
7935
8217
  } catch (err3) {
8218
+ const routing = routingFailure(err3);
8219
+ if (routing) return { ok: false, error: encryptionError(routing) };
7936
8220
  }
7937
8221
  }
7938
8222
  if (input.wellKnown !== void 0) {
@@ -7950,6 +8234,8 @@ async function discoverNetwork(input) {
7950
8234
  };
7951
8235
  }
7952
8236
  } catch (err3) {
8237
+ const routing = routingFailure(err3);
8238
+ if (routing) return { ok: false, error: encryptionError(routing) };
7953
8239
  }
7954
8240
  }
7955
8241
  return {
@@ -8039,6 +8325,152 @@ function ensureNetworkUsableForDecrypt(descriptor) {
8039
8325
  };
8040
8326
  }
8041
8327
 
8328
+ // src/encryption/keyWrap.ts
8329
+ var import_aes = require("@noble/ciphers/aes");
8330
+ var import_ed25519 = require("@noble/curves/ed25519");
8331
+ var import_hkdf = require("@noble/hashes/hkdf");
8332
+ var import_sha256 = require("@noble/hashes/sha256");
8333
+ var import_utils = require("@noble/hashes/utils");
8334
+ var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
8335
+ var MAGIC = utf8Encode("TCW2");
8336
+ var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
8337
+ var MAX_HEADER = 65536;
8338
+ function concat(...parts) {
8339
+ const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
8340
+ let offset = 0;
8341
+ for (const part of parts) {
8342
+ result.set(part, offset);
8343
+ offset += part.length;
8344
+ }
8345
+ return result;
8346
+ }
8347
+ function equal(a, b) {
8348
+ if (a.length !== b.length) return false;
8349
+ let difference = 0;
8350
+ for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
8351
+ return difference === 0;
8352
+ }
8353
+ function decode32(value) {
8354
+ if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
8355
+ const decoded = base64Decode(value);
8356
+ if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
8357
+ return decoded;
8358
+ }
8359
+ function isWellFormed(value) {
8360
+ for (let i = 0; i < value.length; i++) {
8361
+ const unit = value.charCodeAt(i);
8362
+ if (unit >= 55296 && unit <= 56319) {
8363
+ const next = value.charCodeAt(++i);
8364
+ if (!(next >= 56320 && next <= 57343)) return false;
8365
+ } else if (unit >= 56320 && unit <= 57343) return false;
8366
+ }
8367
+ return true;
8368
+ }
8369
+ function validateContext(context) {
8370
+ 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))) {
8371
+ throw new Error("Invalid key wrap context");
8372
+ }
8373
+ if (context.networkId !== "") {
8374
+ const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
8375
+ if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
8376
+ throw new Error("Invalid key wrap resource");
8377
+ }
8378
+ }
8379
+ }
8380
+ function isKeyWrapV2(wrapped) {
8381
+ return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
8382
+ }
8383
+ function readKeyWrapV2(wrapped) {
8384
+ if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
8385
+ const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
8386
+ if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
8387
+ const bytes = wrapped.subarray(8, 8 + length);
8388
+ const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
8389
+ const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
8390
+ 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") {
8391
+ throw new Error("Invalid v2 key wrap header");
8392
+ }
8393
+ validateContext(header);
8394
+ decode32(header.salt);
8395
+ decode32(header.ephemeralPublicKey);
8396
+ decode32(header.recipientPublicKey);
8397
+ if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
8398
+ const end = 8 + length;
8399
+ return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
8400
+ }
8401
+ function assertKeyWrapContext(header, expected) {
8402
+ validateContext(expected);
8403
+ for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
8404
+ if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
8405
+ }
8406
+ if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
8407
+ }
8408
+ function deriveKey(secret, ephemeral, recipient, salt) {
8409
+ const shared = import_ed25519.x25519.getSharedSecret(secret, ephemeral);
8410
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
8411
+ try {
8412
+ return (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
8413
+ } finally {
8414
+ shared.fill(0);
8415
+ }
8416
+ }
8417
+ function sealKeyV2(recipientPublicKey, plaintext, context) {
8418
+ validateContext(context);
8419
+ if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
8420
+ const secret = (0, import_utils.randomBytes)(32);
8421
+ const ephemeral = import_ed25519.x25519.getPublicKey(secret);
8422
+ const salt = (0, import_utils.randomBytes)(32);
8423
+ const header = {
8424
+ ...context,
8425
+ v: 2,
8426
+ alg: KEY_WRAP_V2_ALG,
8427
+ kdf: "hkdf-sha256",
8428
+ aead: "aes-256-gcm",
8429
+ salt: base64Encode(salt),
8430
+ ephemeralPublicKey: base64Encode(ephemeral),
8431
+ recipientPublicKey: base64Encode(recipientPublicKey)
8432
+ };
8433
+ const bytes = utf8Encode(canonicalize({ ...header }));
8434
+ if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
8435
+ const prefix = new Uint8Array(8);
8436
+ prefix.set(MAGIC);
8437
+ new DataView(prefix.buffer).setUint32(4, bytes.length);
8438
+ const aad = concat(prefix, bytes);
8439
+ const nonce = (0, import_utils.randomBytes)(12);
8440
+ let key;
8441
+ try {
8442
+ const shared = import_ed25519.x25519.getSharedSecret(secret, recipientPublicKey);
8443
+ try {
8444
+ if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
8445
+ key = (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
8446
+ } finally {
8447
+ shared.fill(0);
8448
+ }
8449
+ return concat(aad, nonce, (0, import_aes.gcm)(key, nonce, aad).encrypt(plaintext));
8450
+ } finally {
8451
+ secret.fill(0);
8452
+ key?.fill(0);
8453
+ }
8454
+ }
8455
+ function openKeyV2(recipientPrivateKey, wrapped, expected) {
8456
+ const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
8457
+ assertKeyWrapContext(header, expected);
8458
+ const recipient = import_ed25519.x25519.getPublicKey(recipientPrivateKey);
8459
+ if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
8460
+ const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
8461
+ try {
8462
+ return (0, import_aes.gcm)(key, nonce, aad).decrypt(ciphertext);
8463
+ } finally {
8464
+ key.fill(0);
8465
+ }
8466
+ }
8467
+ function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
8468
+ if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
8469
+ if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
8470
+ if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
8471
+ if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
8472
+ }
8473
+
8042
8474
  // src/encryption/envelope.ts
8043
8475
  function encryptToNetwork(crypto2, input) {
8044
8476
  const parsedResource = parseEncryptionResource(
@@ -8047,11 +8479,20 @@ function encryptToNetwork(crypto2, input) {
8047
8479
  if (parsedResource.keyPath !== input.keyPath || input.keyPath?.endsWith("/")) {
8048
8480
  throw new NetworkIdError("keyPath must name an exact encryption key");
8049
8481
  }
8050
- const alg = input.alg ?? DEFAULT_ENCRYPTION_ALG;
8482
+ const alg = input.keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG;
8483
+ if (input.alg !== void 0 && input.alg !== alg) {
8484
+ throw new Error("Algorithm does not match keyWrapV2");
8485
+ }
8051
8486
  const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
8052
8487
  const symmetricKey = crypto2.randomBytes(32);
8053
8488
  const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
8054
- const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
8489
+ const wrapped = input.keyWrapV2 ? sealKeyV2(input.networkPublicKey, symmetricKey, {
8490
+ purpose: "network-key",
8491
+ networkId: input.networkId,
8492
+ keyPath: input.keyPath ?? "",
8493
+ keyVersion,
8494
+ metadata: input.metadata ?? {}
8495
+ }) : crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
8055
8496
  const encryptedSymmetricKey = base64Encode(wrapped);
8056
8497
  const encryptedSymmetricKeyHash = canonicalHashHex(
8057
8498
  crypto2.sha256,
@@ -8071,7 +8512,7 @@ function encryptToNetwork(crypto2, input) {
8071
8512
  };
8072
8513
  return { envelope, symmetricKey };
8073
8514
  }
8074
- function validateEnvelope(crypto2, envelope) {
8515
+ function validateEnvelope(crypto2, envelope, requireKeyWrapV2 = false) {
8075
8516
  if (envelope === null || typeof envelope !== "object") {
8076
8517
  return {
8077
8518
  ok: false,
@@ -8148,9 +8589,28 @@ function validateEnvelope(crypto2, envelope) {
8148
8589
  })
8149
8590
  };
8150
8591
  }
8592
+ try {
8593
+ validateEnvelopeKeyWrap(e, requireKeyWrapV2);
8594
+ } catch {
8595
+ return { ok: false, error: encryptionError({ code: "INVALID_ENVELOPE", message: "Envelope key wrap context is invalid" }) };
8596
+ }
8151
8597
  return { ok: true, data: e };
8152
8598
  }
8599
+ function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
8600
+ const wrapped = base64Decode(envelope.encryptedSymmetricKey);
8601
+ assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
8602
+ if (envelope.alg === KEY_WRAP_V2_ALG) {
8603
+ assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
8604
+ purpose: "network-key",
8605
+ networkId: envelope.networkId,
8606
+ keyPath: envelope.keyPath ?? "",
8607
+ keyVersion: envelope.keyVersion,
8608
+ metadata: envelope.metadata ?? {}
8609
+ });
8610
+ }
8611
+ }
8153
8612
  function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
8613
+ validateEnvelopeKeyWrap(envelope);
8154
8614
  const ciphertext = base64Decode(envelope.ciphertext);
8155
8615
  const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
8156
8616
  return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
@@ -8440,7 +8900,7 @@ function verifyDecryptResponse(input) {
8440
8900
  })
8441
8901
  };
8442
8902
  }
8443
- if (response.alg !== request.alg) {
8903
+ if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
8444
8904
  return {
8445
8905
  ok: false,
8446
8906
  error: encryptionError({
@@ -8497,7 +8957,7 @@ function verifyDecryptResponse(input) {
8497
8957
  })
8498
8958
  };
8499
8959
  }
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) {
8960
+ 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
8961
  return {
8502
8962
  ok: false,
8503
8963
  error: encryptionError({
@@ -8520,8 +8980,13 @@ function verifyDecryptResponse(input) {
8520
8980
  }
8521
8981
  return { ok: true, data: response };
8522
8982
  }
8523
- function openWrappedKey(crypto2, receiverPrivateKey, response) {
8983
+ function openWrappedKey(crypto2, receiverPrivateKey, response, expected) {
8524
8984
  const wrapped = base64Decode(response.wrappedKey);
8985
+ assertKeyWrapAlgorithm(wrapped, response.alg);
8986
+ if (response.alg === KEY_WRAP_V2_ALG) {
8987
+ if (!expected) throw new Error("V2 response requires expected key context");
8988
+ return openKeyV2(receiverPrivateKey, wrapped, expected);
8989
+ }
8525
8990
  return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
8526
8991
  }
8527
8992
 
@@ -8534,6 +8999,14 @@ function encOk(data) {
8534
8999
  function encErr(error) {
8535
9000
  return { ok: false, error };
8536
9001
  }
9002
+ function decryptionError(input) {
9003
+ const unavailable = input.code === "NODE_NOT_RESPONDING" || input.code === "TRANSPORT_ERROR" && input.cause.name !== "AbortError";
9004
+ return encryptionError(unavailable ? {
9005
+ ...input,
9006
+ code: "DECRYPTION_DEPENDENCY_UNAVAILABLE",
9007
+ message: "The decryption service isn't responding right now. Please try again."
9008
+ } : input);
9009
+ }
8537
9010
  var EncryptionService = class extends BaseService {
8538
9011
  constructor(config) {
8539
9012
  super();
@@ -8575,12 +9048,15 @@ var EncryptionService = class extends BaseService {
8575
9048
  networkPublicKey,
8576
9049
  plaintext,
8577
9050
  ...options?.aad !== void 0 ? { aad: options.aad } : {},
8578
- alg: options?.alg ?? descriptor.alg,
9051
+ keyWrapV2: options?.keyWrapV2 ?? this._config.keyWrapV2 ?? false,
9052
+ ...options?.alg !== void 0 ? { alg: options.alg } : {},
8579
9053
  keyVersion: options?.keyVersion ?? descriptor.keyVersion,
8580
9054
  ...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
8581
9055
  });
8582
9056
  return encOk(result.envelope);
8583
9057
  } catch (error) {
9058
+ const routing = routingFailure(error);
9059
+ if (routing) return encErr(encryptionError(routing));
8584
9060
  return encErr(
8585
9061
  encryptionError({
8586
9062
  code: "TRANSPORT_ERROR",
@@ -8592,7 +9068,7 @@ var EncryptionService = class extends BaseService {
8592
9068
  async decryptEnvelope(envelope, capabilityProof, options) {
8593
9069
  try {
8594
9070
  this.assertActive();
8595
- const validated = validateEnvelope(this.crypto, envelope);
9071
+ const validated = validateEnvelope(this.crypto, envelope, this._config.requireKeyWrapV2 ?? false);
8596
9072
  if (!validated.ok) return validated;
8597
9073
  if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
8598
9074
  return encErr(
@@ -8607,12 +9083,12 @@ var EncryptionService = class extends BaseService {
8607
9083
  descriptor = options.descriptor;
8608
9084
  } else {
8609
9085
  const discovered = await this.discoverNetwork(envelope.networkId);
8610
- if (!discovered.ok) return discovered;
9086
+ if (!discovered.ok) return encErr(decryptionError(discovered.error));
8611
9087
  descriptor = discovered.data;
8612
9088
  }
8613
9089
  const usable = ensureNetworkUsableForDecrypt(descriptor);
8614
9090
  if (!usable.ok) return usable;
8615
- const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
9091
+ const targetNode = this._config.resolveTargetNode ? await this._config.resolveTargetNode(envelope.networkId) : options?.targetNode ?? descriptor.members[0]?.nodeId;
8616
9092
  if (targetNode === void 0) {
8617
9093
  return encErr(
8618
9094
  encryptionError({
@@ -8621,6 +9097,9 @@ var EncryptionService = class extends BaseService {
8621
9097
  })
8622
9098
  );
8623
9099
  }
9100
+ if (this._config.resolveTargetNode && (options?.targetNode !== void 0 && options.targetNode !== targetNode || !descriptor.members.some((member) => member.nodeId === targetNode))) {
9101
+ return encErr(encryptionError({ code: "INVALID_INPUT", message: "Encryption destination does not match the registry" }));
9102
+ }
8624
9103
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
8625
9104
  const receiverPublicKey = base64Encode(receiverKey.publicKey);
8626
9105
  const receiverPublicKeyHash = canonicalHashHex(
@@ -8679,6 +9158,8 @@ var EncryptionService = class extends BaseService {
8679
9158
  });
8680
9159
  this.assertActive();
8681
9160
  } catch (error) {
9161
+ const routing = routingFailure(error);
9162
+ if (routing) return encErr(decryptionError(routing));
8682
9163
  if (error instanceof import_decrypt_transport_response_error.DecryptTransportResponseError) {
8683
9164
  return encErr(
8684
9165
  encryptionError(
@@ -8695,7 +9176,7 @@ var EncryptionService = class extends BaseService {
8695
9176
  );
8696
9177
  }
8697
9178
  return encErr(
8698
- encryptionError({
9179
+ decryptionError({
8699
9180
  code: "TRANSPORT_ERROR",
8700
9181
  cause: toError2(error)
8701
9182
  })
@@ -8713,7 +9194,8 @@ var EncryptionService = class extends BaseService {
8713
9194
  const symmetricKey = openWrappedKey(
8714
9195
  this.crypto,
8715
9196
  receiverKey.privateKey,
8716
- verified.data
9197
+ verified.data,
9198
+ { purpose: "receiver-key", networkId: body.networkId, keyPath: body.keyPath ?? "", keyVersion: body.keyVersion, metadata: {} }
8717
9199
  );
8718
9200
  const plaintext = decryptEnvelopeWithKey(
8719
9201
  this.crypto,
@@ -8722,6 +9204,8 @@ var EncryptionService = class extends BaseService {
8722
9204
  );
8723
9205
  return encOk(plaintext);
8724
9206
  } catch (error) {
9207
+ const routing = routingFailure(error);
9208
+ if (routing) return encErr(decryptionError(routing));
8725
9209
  return encErr(
8726
9210
  encryptionError({
8727
9211
  code: "INVALID_RESPONSE",
@@ -8771,7 +9255,11 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8771
9255
  MAX_SECRET_SCOPE_LENGTH,
8772
9256
  NETWORK_NAME_PATTERN,
8773
9257
  NetworkIdError,
9258
+ NodeIdentityMismatchError,
9259
+ NodeNotRespondingError,
9260
+ PartialNodeWriteError,
8774
9261
  PrefixedKVService,
9262
+ ResourceNotRoutedError,
8775
9263
  RetryPolicySchema,
8776
9264
  SECRET_NAME_RE,
8777
9265
  SECRET_SCOPE_RE,
@@ -8819,6 +9307,8 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8819
9307
  checkDecryptInvocationInput,
8820
9308
  clearTinyCloudDebugLogs,
8821
9309
  createKVResponseSchema,
9310
+ createResourceRoutingFetch,
9311
+ createResourceTransportInvocationFetch,
8822
9312
  createResultSchema,
8823
9313
  createVaultCrypto,
8824
9314
  decryptEnvelopeWithKey,
@@ -8861,6 +9351,9 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8861
9351
  requiresSecretsOptIn,
8862
9352
  resolveSecretListPrefix,
8863
9353
  resolveSecretPath,
9354
+ routeResourceRequest,
9355
+ routingFailure,
9356
+ routingResourceOf,
8864
9357
  serviceError,
8865
9358
  storageLimitReachedError,
8866
9359
  storageQuotaExceededError,
@@ -8881,6 +9374,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8881
9374
  validateTelemetrySpanEvent,
8882
9375
  validatedCapabilityOf,
8883
9376
  verifyDecryptResponse,
9377
+ verifyResourceDestination,
8884
9378
  wrapError
8885
9379
  });
8886
9380
  //# sourceMappingURL=index.cjs.map