@tinycloud/sdk-services 3.1.0-beta.2 → 3.1.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -116,6 +116,7 @@ __export(src_exports, {
116
116
  ok: () => ok,
117
117
  openWrappedKey: () => openWrappedKey,
118
118
  parseAuthError: () => parseAuthError,
119
+ parseCapabilityResource: () => parseCapabilityResource,
119
120
  parseNetworkId: () => parseNetworkId,
120
121
  parsePermissionHint: () => parsePermissionHint,
121
122
  parsePermissionHintFromErrorText: () => parsePermissionHintFromErrorText,
@@ -141,6 +142,7 @@ __export(src_exports, {
141
142
  validateServiceResponseEvent: () => validateServiceResponseEvent,
142
143
  validateServiceSession: () => validateServiceSession,
143
144
  validateTelemetrySpanEvent: () => validateTelemetrySpanEvent,
145
+ validatedCapabilityOf: () => validatedCapabilityOf,
144
146
  verifyDecryptResponse: () => verifyDecryptResponse,
145
147
  wrapError: () => wrapError
146
148
  });
@@ -1044,6 +1046,106 @@ var ServiceContext = class {
1044
1046
  }
1045
1047
  };
1046
1048
 
1049
+ // src/errors.ts
1050
+ var import_bootstrap = require("@tinycloud/bootstrap");
1051
+
1052
+ // src/encryption/networkId.ts
1053
+ var URN_PREFIX = "urn:tinycloud:encryption:";
1054
+ var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
1055
+ var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
1056
+ var NetworkIdError = class extends Error {
1057
+ constructor(message) {
1058
+ super(message);
1059
+ this.name = "NetworkIdError";
1060
+ }
1061
+ };
1062
+ function parseNetworkId(networkId) {
1063
+ if (typeof networkId !== "string" || networkId.length === 0) {
1064
+ throw new NetworkIdError("networkId must be a non-empty string");
1065
+ }
1066
+ if (!networkId.startsWith(URN_PREFIX)) {
1067
+ throw new NetworkIdError(
1068
+ `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
1069
+ );
1070
+ }
1071
+ const body = networkId.slice(URN_PREFIX.length);
1072
+ const lastColon = body.lastIndexOf(":");
1073
+ if (lastColon <= 0 || lastColon === body.length - 1) {
1074
+ throw new NetworkIdError(
1075
+ `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
1076
+ );
1077
+ }
1078
+ const ownerDid = body.slice(0, lastColon);
1079
+ const name = body.slice(lastColon + 1);
1080
+ if (!ownerDid.startsWith("did:")) {
1081
+ throw new NetworkIdError(
1082
+ `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
1083
+ );
1084
+ }
1085
+ const didParts = ownerDid.split(":");
1086
+ if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
1087
+ throw new NetworkIdError(
1088
+ `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
1089
+ );
1090
+ }
1091
+ if (!NETWORK_NAME_RE.test(name)) {
1092
+ throw new NetworkIdError(
1093
+ `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1094
+ );
1095
+ }
1096
+ return { networkId, ownerDid, name };
1097
+ }
1098
+ function buildNetworkId(ownerDid, name) {
1099
+ if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
1100
+ throw new NetworkIdError("ownerDid must be a DID");
1101
+ }
1102
+ if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
1103
+ throw new NetworkIdError(
1104
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1105
+ );
1106
+ }
1107
+ const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
1108
+ parseNetworkId(networkId);
1109
+ return networkId;
1110
+ }
1111
+ function isNetworkId(networkId) {
1112
+ if (typeof networkId !== "string") {
1113
+ return false;
1114
+ }
1115
+ try {
1116
+ parseNetworkId(networkId);
1117
+ return true;
1118
+ } catch {
1119
+ return false;
1120
+ }
1121
+ }
1122
+ function parsePkhOwnerDid(ownerDid) {
1123
+ const match = ownerDid.match(PKH_EIP155_DID_RE);
1124
+ if (!match) return null;
1125
+ return {
1126
+ chainId: match[1],
1127
+ address: match[2].toLowerCase()
1128
+ };
1129
+ }
1130
+ function ownerDidMatches(a, b) {
1131
+ const aPkh = parsePkhOwnerDid(a);
1132
+ const bPkh = parsePkhOwnerDid(b);
1133
+ if (aPkh && bPkh) {
1134
+ return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
1135
+ }
1136
+ return a === b;
1137
+ }
1138
+ function networkDiscoveryKey(name) {
1139
+ if (!NETWORK_NAME_RE.test(name)) {
1140
+ throw new NetworkIdError(
1141
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1142
+ );
1143
+ }
1144
+ return `.well-known/encryption/network/${name}`;
1145
+ }
1146
+ var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
1147
+ var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
1148
+
1047
1149
  // src/errors.ts
1048
1150
  function parsePermissionHint(value) {
1049
1151
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -1141,6 +1243,70 @@ function parseAuthError(responseText) {
1141
1243
  function authUnauthorizedError(service, message, meta) {
1142
1244
  return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
1143
1245
  }
1246
+ var grantableActions = new Set(
1247
+ import_bootstrap.CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
1248
+ );
1249
+ var capabilityServices = new Set(
1250
+ import_bootstrap.CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
1251
+ );
1252
+ function parseCapabilityResource(resource, service) {
1253
+ if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
1254
+ const segments = resource.split("/");
1255
+ if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
1256
+ const serviceIndex = segments.findIndex(
1257
+ (segment, index) => index > 0 && capabilityServices.has(segment)
1258
+ );
1259
+ if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
1260
+ if (segments.slice(1, serviceIndex).some(
1261
+ (segment) => segment.length === 0 || segment === "." || segment === ".."
1262
+ )) return void 0;
1263
+ const pathSegments = segments.slice(serviceIndex + 1);
1264
+ if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
1265
+ if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
1266
+ return {
1267
+ space: segments.slice(0, serviceIndex).join("/"),
1268
+ path: pathSegments.join("/")
1269
+ };
1270
+ }
1271
+ function safeCapabilityResource(value, service, action) {
1272
+ if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
1273
+ if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
1274
+ if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
1275
+ try {
1276
+ parseNetworkId(value);
1277
+ return true;
1278
+ } catch {
1279
+ return false;
1280
+ }
1281
+ }
1282
+ const parsed = parseCapabilityResource(value, service);
1283
+ return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
1284
+ }
1285
+ function validatedCapabilityOf(error) {
1286
+ const verdict = authorizationVerdictOf(error);
1287
+ if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
1288
+ const seen = /* @__PURE__ */ new Set();
1289
+ let node = error;
1290
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
1291
+ seen.add(node);
1292
+ const record = node;
1293
+ const status = errorStatusOf(record);
1294
+ const meta = record.meta;
1295
+ if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
1296
+ const metadata = meta;
1297
+ const { resource, requiredAction } = metadata;
1298
+ if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
1299
+ const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
1300
+ const explicitService = record.service === void 0 ? metadata.service : record.service;
1301
+ if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
1302
+ return { resource, requiredAction };
1303
+ }
1304
+ }
1305
+ }
1306
+ node = record.cause;
1307
+ }
1308
+ return void 0;
1309
+ }
1144
1310
  var MAX_CAUSE_DEPTH = 8;
1145
1311
  function errorStatusOf(node) {
1146
1312
  const meta = node.meta;
@@ -1888,12 +2054,14 @@ var KVService = class extends BaseService {
1888
2054
  signal: this.combineSignals(options?.signal)
1889
2055
  });
1890
2056
  if (!response.ok) {
1891
- const errorText = await response.text();
2057
+ const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
1892
2058
  if (response.status === 401 || response.status === 403) {
1893
2059
  return this.authorizationFailure(
1894
2060
  `Failed to batch read ${keys.length} key(s)`,
1895
2061
  response,
1896
- errorText
2062
+ errorText,
2063
+ paths,
2064
+ action
1897
2065
  );
1898
2066
  }
1899
2067
  if (response.status === 413) {
@@ -1983,7 +2151,7 @@ var KVService = class extends BaseService {
1983
2151
  return text;
1984
2152
  }
1985
2153
  }
1986
- async createSignedReadUrlError(response, key) {
2154
+ async createSignedReadUrlError(response, key, path) {
1987
2155
  let errorText = response.statusText;
1988
2156
  try {
1989
2157
  const text = await response.text();
@@ -1996,7 +2164,9 @@ var KVService = class extends BaseService {
1996
2164
  return this.authorizationFailure(
1997
2165
  `Failed to create signed read URL for key ${JSON.stringify(key)}`,
1998
2166
  response,
1999
- errorText
2167
+ errorText,
2168
+ [path],
2169
+ KVAction.GET
2000
2170
  );
2001
2171
  }
2002
2172
  const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
@@ -2009,21 +2179,42 @@ var KVService = class extends BaseService {
2009
2179
  )
2010
2180
  );
2011
2181
  }
2182
+ /** A known 401/403 keeps its typed status even if reading its body fails. */
2183
+ async readAuthorizationText(response) {
2184
+ try {
2185
+ return await response.text();
2186
+ } catch {
2187
+ return "";
2188
+ }
2189
+ }
2012
2190
  /**
2013
2191
  * AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
2014
2192
  * status and the server text (`<context>: <status> - <text>`, the same
2015
2193
  * shape as every other KV failure) so callers that rethrow only the message
2016
2194
  * still see the status; `meta.status` carries it for typed callers.
2017
- * Resource/ability hints are parsed from the raw body, never the message.
2195
+ * Server hints must match the requested path/action (whether the node sends
2196
+ * a relative path or full resource); metadata uses only the request-derived
2197
+ * canonical resource under the authenticated session's space.
2018
2198
  */
2019
- authorizationFailure(context, response, errorText, extraMeta = {}) {
2199
+ authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
2020
2200
  const { resource, action } = parseAuthError(errorText);
2201
+ const requestedPath = paths.find(
2202
+ (path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
2203
+ );
2204
+ const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf({
2205
+ service: "kv",
2206
+ code: ErrorCodes.AUTH_UNAUTHORIZED,
2207
+ meta: {
2208
+ status: response.status,
2209
+ resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
2210
+ requiredAction: action
2211
+ }
2212
+ }) : void 0;
2021
2213
  const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
2022
2214
  return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
2023
2215
  status: response.status,
2024
- ...action && { requiredAction: action },
2025
- ...resource && { resource },
2026
- ...extraMeta
2216
+ ...extraMeta,
2217
+ ...capability
2027
2218
  }));
2028
2219
  }
2029
2220
  normalizeSignedReadUrlResponse(data) {
@@ -2067,13 +2258,24 @@ var KVService = class extends BaseService {
2067
2258
  );
2068
2259
  if (!response.ok) {
2069
2260
  if (response.status === 401 || response.status === 403) {
2070
- const errorText2 = await response.text();
2261
+ const errorText2 = await this.readAuthorizationText(response);
2071
2262
  const permissionHint = parsePermissionHintFromErrorText(errorText2);
2263
+ const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
2264
+ service: "kv",
2265
+ code: ErrorCodes.AUTH_UNAUTHORIZED,
2266
+ meta: {
2267
+ status: response.status,
2268
+ resource: `${this.context.session.spaceId}/kv/${path}`,
2269
+ requiredAction: KVAction.GET
2270
+ }
2271
+ }) : void 0;
2072
2272
  return this.authorizationFailure(
2073
2273
  `Failed to get key ${JSON.stringify(key)}`,
2074
2274
  response,
2075
2275
  errorText2,
2076
- permissionHint === void 0 ? {} : { permissionHint }
2276
+ [path],
2277
+ KVAction.GET,
2278
+ structuredCapability ? { permissionHint, ...structuredCapability } : {}
2077
2279
  );
2078
2280
  }
2079
2281
  if (response.status === 404) {
@@ -2150,7 +2352,9 @@ var KVService = class extends BaseService {
2150
2352
  return this.authorizationFailure(
2151
2353
  `Failed to put key ${JSON.stringify(key)}`,
2152
2354
  response,
2153
- await response.text()
2355
+ await this.readAuthorizationText(response),
2356
+ [path],
2357
+ KVAction.PUT
2154
2358
  );
2155
2359
  }
2156
2360
  if (!response.ok) {
@@ -2279,7 +2483,9 @@ var KVService = class extends BaseService {
2279
2483
  return this.authorizationFailure(
2280
2484
  `Failed to batch put ${items.length} key(s)`,
2281
2485
  response,
2282
- errorText
2486
+ errorText,
2487
+ paths,
2488
+ KVAction.PUT
2283
2489
  );
2284
2490
  }
2285
2491
  const quotaError = this.handleQuotaErrorResponse(
@@ -2400,11 +2606,13 @@ var KVService = class extends BaseService {
2400
2606
  }
2401
2607
  );
2402
2608
  if (!response.ok) {
2403
- if (response.status === 401) {
2609
+ if (response.status === 401 || response.status === 403) {
2404
2610
  return this.authorizationFailure(
2405
2611
  "Failed to list keys",
2406
2612
  response,
2407
- await response.text()
2613
+ await this.readAuthorizationText(response),
2614
+ [listPath],
2615
+ KVAction.LIST
2408
2616
  );
2409
2617
  }
2410
2618
  const errorText = await response.text();
@@ -2453,11 +2661,13 @@ var KVService = class extends BaseService {
2453
2661
  options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
2454
2662
  );
2455
2663
  if (!response.ok) {
2456
- if (response.status === 401) {
2664
+ if (response.status === 401 || response.status === 403) {
2457
2665
  return this.authorizationFailure(
2458
2666
  `Failed to delete key ${JSON.stringify(key)}`,
2459
2667
  response,
2460
- await response.text()
2668
+ await this.readAuthorizationText(response),
2669
+ [path],
2670
+ KVAction.DELETE
2461
2671
  );
2462
2672
  }
2463
2673
  if (response.status === 404) {
@@ -2515,11 +2725,13 @@ var KVService = class extends BaseService {
2515
2725
  options?.signal
2516
2726
  );
2517
2727
  if (!response.ok) {
2518
- if (response.status === 401) {
2728
+ if (response.status === 401 || response.status === 403) {
2519
2729
  return this.authorizationFailure(
2520
2730
  `Failed to get metadata for key ${JSON.stringify(key)}`,
2521
2731
  response,
2522
- await response.text()
2732
+ await this.readAuthorizationText(response),
2733
+ [path],
2734
+ KVAction.HEAD
2523
2735
  );
2524
2736
  }
2525
2737
  if (response.status === 404) {
@@ -2586,7 +2798,7 @@ var KVService = class extends BaseService {
2586
2798
  signal: this.combineSignals(options?.signal)
2587
2799
  });
2588
2800
  if (!response.ok) {
2589
- return this.createSignedReadUrlError(response, key);
2801
+ return this.createSignedReadUrlError(response, key, path);
2590
2802
  }
2591
2803
  const signedUrl = this.normalizeSignedReadUrlResponse(
2592
2804
  await response.json()
@@ -2729,16 +2941,16 @@ var DatabaseHandle = class {
2729
2941
  };
2730
2942
 
2731
2943
  // src/sql/types.ts
2732
- var import_bootstrap = require("@tinycloud/bootstrap");
2944
+ var import_bootstrap2 = require("@tinycloud/bootstrap");
2733
2945
  var SQLAction = {
2734
- READ: import_bootstrap.SQL.READ,
2735
- WRITE: import_bootstrap.SQL.WRITE,
2736
- SCHEMA: import_bootstrap.SQL.SCHEMA,
2737
- ADMIN: import_bootstrap.SQL.ADMIN,
2738
- SELECT: import_bootstrap.SQL.SELECT,
2739
- EXECUTE: import_bootstrap.SQL.EXECUTE,
2740
- EXPORT: import_bootstrap.SQL.EXPORT,
2741
- ALL: import_bootstrap.SQL.ALL
2946
+ READ: import_bootstrap2.SQL.READ,
2947
+ WRITE: import_bootstrap2.SQL.WRITE,
2948
+ SCHEMA: import_bootstrap2.SQL.SCHEMA,
2949
+ ADMIN: import_bootstrap2.SQL.ADMIN,
2950
+ SELECT: import_bootstrap2.SQL.SELECT,
2951
+ EXECUTE: import_bootstrap2.SQL.EXECUTE,
2952
+ EXPORT: import_bootstrap2.SQL.EXPORT,
2953
+ ALL: import_bootstrap2.SQL.ALL
2742
2954
  };
2743
2955
 
2744
2956
  // src/sql/SQLService.ts
@@ -2824,14 +3036,15 @@ var SQLService = class extends BaseService {
2824
3036
  };
2825
3037
  if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
2826
3038
  if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
3039
+ const action = this.actionForSql(sql, SQLAction.READ);
2827
3040
  const response = await this.invokeSQL(
2828
3041
  dbName,
2829
- this.actionForSql(sql, SQLAction.READ),
3042
+ action,
2830
3043
  body,
2831
3044
  options?.signal
2832
3045
  );
2833
3046
  if (!response.ok) {
2834
- return this.handleErrorResponse(response, "query");
3047
+ return this.handleErrorResponse(response, "query", dbName, [action]);
2835
3048
  }
2836
3049
  const data = await response.json();
2837
3050
  return ok(data);
@@ -2860,14 +3073,15 @@ var SQLService = class extends BaseService {
2860
3073
  (statement) => this.actionForSql(statement, SQLAction.SCHEMA)
2861
3074
  )
2862
3075
  ];
3076
+ const requestedActions = this.dedupeActions(actions);
2863
3077
  const response = await this.invokeSQL(
2864
3078
  dbName,
2865
- this.dedupeActions(actions),
3079
+ requestedActions,
2866
3080
  body,
2867
3081
  options?.signal
2868
3082
  );
2869
3083
  if (!response.ok) {
2870
- return this.handleErrorResponse(response, "execute");
3084
+ return this.handleErrorResponse(response, "execute", dbName, requestedActions);
2871
3085
  }
2872
3086
  const data = await response.json();
2873
3087
  return ok(data);
@@ -2882,14 +3096,15 @@ var SQLService = class extends BaseService {
2882
3096
  return err(authRequiredError("sql"));
2883
3097
  }
2884
3098
  try {
3099
+ const requestedActions = this.actionsForSqlBatch(statements);
2885
3100
  const response = await this.invokeSQL(
2886
3101
  dbName,
2887
- this.actionsForSqlBatch(statements),
3102
+ requestedActions,
2888
3103
  { action: "batch", statements },
2889
3104
  options?.signal
2890
3105
  );
2891
3106
  if (!response.ok) {
2892
- return this.handleErrorResponse(response, "batch");
3107
+ return this.handleErrorResponse(response, "batch", dbName, requestedActions);
2893
3108
  }
2894
3109
  const data = await response.json();
2895
3110
  return ok(data);
@@ -2911,7 +3126,7 @@ var SQLService = class extends BaseService {
2911
3126
  options?.signal
2912
3127
  );
2913
3128
  if (!response.ok) {
2914
- return this.handleErrorResponse(response, "executeStatement");
3129
+ return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
2915
3130
  }
2916
3131
  const data = await response.json();
2917
3132
  return ok(data);
@@ -2933,7 +3148,7 @@ var SQLService = class extends BaseService {
2933
3148
  options?.signal
2934
3149
  );
2935
3150
  if (!response.ok) {
2936
- return this.handleErrorResponse(response, "export");
3151
+ return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
2937
3152
  }
2938
3153
  const resp = response;
2939
3154
  if (typeof resp.blob === "function") {
@@ -3067,7 +3282,7 @@ var SQLService = class extends BaseService {
3067
3282
  }))
3068
3283
  );
3069
3284
  }
3070
- async handleErrorResponse(response, operation) {
3285
+ async handleErrorResponse(response, operation, dbName, requestedActions) {
3071
3286
  const errorText = await response.text();
3072
3287
  const meta = responseErrorMeta(response.status, response.statusText, errorText);
3073
3288
  if (response.status === 402) {
@@ -3086,9 +3301,11 @@ var SQLService = class extends BaseService {
3086
3301
  errorBody
3087
3302
  );
3088
3303
  if (response.status === 401) {
3089
- const { resource, action } = parseAuthError(errorText);
3090
- if (action) meta.requiredAction = action;
3091
- if (resource) meta.resource = resource;
3304
+ const { action } = parseAuthError(errorText);
3305
+ if (action && requestedActions.includes(action)) {
3306
+ meta.requiredAction = action;
3307
+ meta.resource = `${this.context.session.spaceId}/sql/${dbName}`;
3308
+ }
3092
3309
  }
3093
3310
  return err(
3094
3311
  serviceError(errorCode, message, "sql", { meta })
@@ -3233,16 +3450,16 @@ var DuckDbDatabaseHandle = class {
3233
3450
  };
3234
3451
 
3235
3452
  // src/duckdb/types.ts
3236
- var import_bootstrap2 = require("@tinycloud/bootstrap");
3453
+ var import_bootstrap3 = require("@tinycloud/bootstrap");
3237
3454
  var DuckDbAction = {
3238
- READ: import_bootstrap2.DUCKDB.READ,
3239
- WRITE: import_bootstrap2.DUCKDB.WRITE,
3240
- ADMIN: import_bootstrap2.DUCKDB.ADMIN,
3241
- DESCRIBE: import_bootstrap2.DUCKDB.DESCRIBE,
3242
- EXPORT: import_bootstrap2.DUCKDB.EXPORT,
3243
- IMPORT: import_bootstrap2.DUCKDB.IMPORT,
3244
- EXECUTE: import_bootstrap2.DUCKDB.EXECUTE,
3245
- ALL: import_bootstrap2.DUCKDB.ALL
3455
+ READ: import_bootstrap3.DUCKDB.READ,
3456
+ WRITE: import_bootstrap3.DUCKDB.WRITE,
3457
+ ADMIN: import_bootstrap3.DUCKDB.ADMIN,
3458
+ DESCRIBE: import_bootstrap3.DUCKDB.DESCRIBE,
3459
+ EXPORT: import_bootstrap3.DUCKDB.EXPORT,
3460
+ IMPORT: import_bootstrap3.DUCKDB.IMPORT,
3461
+ EXECUTE: import_bootstrap3.DUCKDB.EXECUTE,
3462
+ ALL: import_bootstrap3.DUCKDB.ALL
3246
3463
  };
3247
3464
 
3248
3465
  // src/duckdb/DuckDbService.ts
@@ -6067,103 +6284,6 @@ function canonicalHashHex(sha256, value) {
6067
6284
  return hexEncode2(sha256(utf8Encode(canonical)));
6068
6285
  }
6069
6286
 
6070
- // src/encryption/networkId.ts
6071
- var URN_PREFIX = "urn:tinycloud:encryption:";
6072
- var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
6073
- var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
6074
- var NetworkIdError = class extends Error {
6075
- constructor(message) {
6076
- super(message);
6077
- this.name = "NetworkIdError";
6078
- }
6079
- };
6080
- function parseNetworkId(networkId) {
6081
- if (typeof networkId !== "string" || networkId.length === 0) {
6082
- throw new NetworkIdError("networkId must be a non-empty string");
6083
- }
6084
- if (!networkId.startsWith(URN_PREFIX)) {
6085
- throw new NetworkIdError(
6086
- `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
6087
- );
6088
- }
6089
- const body = networkId.slice(URN_PREFIX.length);
6090
- const lastColon = body.lastIndexOf(":");
6091
- if (lastColon <= 0 || lastColon === body.length - 1) {
6092
- throw new NetworkIdError(
6093
- `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
6094
- );
6095
- }
6096
- const ownerDid = body.slice(0, lastColon);
6097
- const name = body.slice(lastColon + 1);
6098
- if (!ownerDid.startsWith("did:")) {
6099
- throw new NetworkIdError(
6100
- `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
6101
- );
6102
- }
6103
- const didParts = ownerDid.split(":");
6104
- if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
6105
- throw new NetworkIdError(
6106
- `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
6107
- );
6108
- }
6109
- if (!NETWORK_NAME_RE.test(name)) {
6110
- throw new NetworkIdError(
6111
- `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
6112
- );
6113
- }
6114
- return { networkId, ownerDid, name };
6115
- }
6116
- function buildNetworkId(ownerDid, name) {
6117
- if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
6118
- throw new NetworkIdError("ownerDid must be a DID");
6119
- }
6120
- if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
6121
- throw new NetworkIdError(
6122
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
6123
- );
6124
- }
6125
- const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
6126
- parseNetworkId(networkId);
6127
- return networkId;
6128
- }
6129
- function isNetworkId(networkId) {
6130
- if (typeof networkId !== "string") {
6131
- return false;
6132
- }
6133
- try {
6134
- parseNetworkId(networkId);
6135
- return true;
6136
- } catch {
6137
- return false;
6138
- }
6139
- }
6140
- function parsePkhOwnerDid(ownerDid) {
6141
- const match = ownerDid.match(PKH_EIP155_DID_RE);
6142
- if (!match) return null;
6143
- return {
6144
- chainId: match[1],
6145
- address: match[2].toLowerCase()
6146
- };
6147
- }
6148
- function ownerDidMatches(a, b) {
6149
- const aPkh = parsePkhOwnerDid(a);
6150
- const bPkh = parsePkhOwnerDid(b);
6151
- if (aPkh && bPkh) {
6152
- return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
6153
- }
6154
- return a === b;
6155
- }
6156
- function networkDiscoveryKey(name) {
6157
- if (!NETWORK_NAME_RE.test(name)) {
6158
- throw new NetworkIdError(
6159
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
6160
- );
6161
- }
6162
- return `.well-known/encryption/network/${name}`;
6163
- }
6164
- var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
6165
- var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
6166
-
6167
6287
  // src/encryption/types.ts
6168
6288
  var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
6169
6289
  var ENVELOPE_VERSION = 1;
@@ -7107,6 +7227,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7107
7227
  ok,
7108
7228
  openWrappedKey,
7109
7229
  parseAuthError,
7230
+ parseCapabilityResource,
7110
7231
  parseNetworkId,
7111
7232
  parsePermissionHint,
7112
7233
  parsePermissionHintFromErrorText,
@@ -7132,6 +7253,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7132
7253
  validateServiceResponseEvent,
7133
7254
  validateServiceSession,
7134
7255
  validateTelemetrySpanEvent,
7256
+ validatedCapabilityOf,
7135
7257
  verifyDecryptResponse,
7136
7258
  wrapError
7137
7259
  });