@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.d.cts CHANGED
@@ -1083,6 +1083,24 @@ declare function authUnauthorizedError(service: string, message: string, meta?:
1083
1083
  * - `"other"`: a non-authorization HTTP error status (4xx/5xx).
1084
1084
  */
1085
1085
  type AuthorizationVerdict = "unauthenticated" | "forbidden" | "other";
1086
+ /**
1087
+ * Parse a canonical TinyCloud resource, keeping nested space names intact.
1088
+ * The first registered service segment is authoritative: a later matching
1089
+ * segment in the path cannot turn a different service into grant advice.
1090
+ */
1091
+ declare function parseCapabilityResource(resource: string, service: string): {
1092
+ space: string;
1093
+ path: string;
1094
+ } | undefined;
1095
+ /**
1096
+ * Return a grantable missing capability only when typed authorization evidence
1097
+ * and both safe, service-matched capability fields are present. Status alone,
1098
+ * message text, wildcard actions and unrecognized actions cannot grant scope.
1099
+ */
1100
+ declare function validatedCapabilityOf(error: unknown): {
1101
+ resource: string;
1102
+ requiredAction: string;
1103
+ } | undefined;
1086
1104
  /**
1087
1105
  * Classify an error from its typed HTTP error status (`status`, `statusCode`,
1088
1106
  * or `meta.status`, 4xx/5xx only) and `AUTH_UNAUTHORIZED` code, following the
@@ -2297,4 +2315,4 @@ declare class SecretsService implements ISecretsService {
2297
2315
  listAll(): Promise<Result<SecretCatalogEntry[], SecretsError>>;
2298
2316
  }
2299
2317
 
2300
- export { type AuthorizationVerdict, BaseService, type BaseServiceOptions, type ColumnInfo, type DataVaultConfig, DataVaultService, DecryptCapabilityProof, DuckDbAction, type DuckDbActionType, type DuckDbBatchOptions, type BatchResponse as DuckDbBatchResponse, DuckDbDatabaseHandle, type DuckDbExecuteOptions, type ExecuteResponse as DuckDbExecuteResponse, type DuckDbOptions, type DuckDbQueryOptions, type QueryResponse as DuckDbQueryResponse, DuckDbService, type DuckDbServiceConfig, type DuckDbStatement, type DuckDbValue, EventHandler, FetchFunction, GenericKVResponseSchema, type GenericKVResponseType, GenericResultSchema, type HookEvent, type HookServiceName, type HookStreamEvent, type HookSubscription, type HookWebhookListOptions, type HookWebhookRecord, type HookWebhookRegistration, type HookWebhookScope, type HookWebhookUnregisterOptions, HooksService, type HooksServiceConfig, type IDataVaultService, type IDuckDbDatabaseHandle, type IDuckDbService, IEncryptionService, type IHooksService, IKVService, type ISecretsService, IService, IServiceContext, InvokeAnyFunction, InvokeFunction, KVListResponseSchema, type KVListResponseType, KVListResultSchema, type KVListResultType, KVResponseHeadersSchema, type KVResponseHeadersType, type ParsedSecretCatalogKey, PermissionHint, type QuotaConfig, type QuotaStatus, type ResolvedSecretPath, Result, RetryPolicy, RetryPolicySchema, type RetryPolicyType, SECRET_NAME_RE, STORAGE_FULL_MESSAGE, STORAGE_WRITE_TOO_LARGE_MESSAGE, type SchemaInfo, type SecretCatalogEntry, type SecretPayload, type SecretScopeOptions, type SecretsError, SecretsService, type ServiceConstructor, ServiceContext, type ServiceContextConfig, ServiceError, ServiceErrorEventSchema, type ServiceErrorEventType, ServiceErrorSchema, type ServiceErrorType, type ServiceRegistration, ServiceRequestEventSchema, type ServiceRequestEventType, ServiceResponseEventSchema, type ServiceResponseEventType, ServiceRetryEventSchema, type ServiceRetryEventType, ServiceSession, ServiceSessionSchema, type ServiceSessionType, StorageQuotaInfo, type SubscribeOptions, type TableInfo, TelemetryConfig, TelemetrySpanEventSchema, type TelemetrySpanEventType, type TinyCloudDebugEnableOptions, type TinyCloudDebugEvent, type TinyCloudDebugLevel, TinyCloudDebugLogger, type TinyCloudDebugTimer, TinyCloudQuota, type ValidationError, type VaultCrypto, type VaultEntry, type VaultError, type VaultGetOptions, type VaultGrantOptions, VaultHeaders, type VaultListOptions, type VaultListPage, type VaultNetworkReadResult, VaultPublicSpaceKVActions, type VaultPutOptions, type ViewInfo, type WasmVaultFunctions, abortedError, authExpiredError, authRequiredError, authUnauthorizedError, authorizationVerdictOf, canonicalizeSecretScope, clearTinyCloudDebugLogs, createKVResponseSchema, createResultSchema, createVaultCrypto, disableTinyCloudDebug, enableTinyCloudDebug, errorResult, getTinyCloudDebugLogs, installTinyCloudDebugGlobals, isStorageFullError, networkError, notFoundError, parseAuthError, parsePermissionHint, parsePermissionHintFromErrorText, parseSecretCatalogKey, parseStorageQuotaBytes, permissionDeniedError, resolveSecretListPrefix, resolveSecretPath, storageLimitReachedError, storageQuotaExceededError, storageRejectionError, timeoutError, tinyCloudDebugLogger, validateKVListResponse, validateKVResponseHeaders, validateRetryPolicy, validateServiceError, validateServiceRequestEvent, validateServiceResponseEvent, validateServiceSession, validateTelemetrySpanEvent, wrapError };
2318
+ export { type AuthorizationVerdict, BaseService, type BaseServiceOptions, type ColumnInfo, type DataVaultConfig, DataVaultService, DecryptCapabilityProof, DuckDbAction, type DuckDbActionType, type DuckDbBatchOptions, type BatchResponse as DuckDbBatchResponse, DuckDbDatabaseHandle, type DuckDbExecuteOptions, type ExecuteResponse as DuckDbExecuteResponse, type DuckDbOptions, type DuckDbQueryOptions, type QueryResponse as DuckDbQueryResponse, DuckDbService, type DuckDbServiceConfig, type DuckDbStatement, type DuckDbValue, EventHandler, FetchFunction, GenericKVResponseSchema, type GenericKVResponseType, GenericResultSchema, type HookEvent, type HookServiceName, type HookStreamEvent, type HookSubscription, type HookWebhookListOptions, type HookWebhookRecord, type HookWebhookRegistration, type HookWebhookScope, type HookWebhookUnregisterOptions, HooksService, type HooksServiceConfig, type IDataVaultService, type IDuckDbDatabaseHandle, type IDuckDbService, IEncryptionService, type IHooksService, IKVService, type ISecretsService, IService, IServiceContext, InvokeAnyFunction, InvokeFunction, KVListResponseSchema, type KVListResponseType, KVListResultSchema, type KVListResultType, KVResponseHeadersSchema, type KVResponseHeadersType, type ParsedSecretCatalogKey, PermissionHint, type QuotaConfig, type QuotaStatus, type ResolvedSecretPath, Result, RetryPolicy, RetryPolicySchema, type RetryPolicyType, SECRET_NAME_RE, STORAGE_FULL_MESSAGE, STORAGE_WRITE_TOO_LARGE_MESSAGE, type SchemaInfo, type SecretCatalogEntry, type SecretPayload, type SecretScopeOptions, type SecretsError, SecretsService, type ServiceConstructor, ServiceContext, type ServiceContextConfig, ServiceError, ServiceErrorEventSchema, type ServiceErrorEventType, ServiceErrorSchema, type ServiceErrorType, type ServiceRegistration, ServiceRequestEventSchema, type ServiceRequestEventType, ServiceResponseEventSchema, type ServiceResponseEventType, ServiceRetryEventSchema, type ServiceRetryEventType, ServiceSession, ServiceSessionSchema, type ServiceSessionType, StorageQuotaInfo, type SubscribeOptions, type TableInfo, TelemetryConfig, TelemetrySpanEventSchema, type TelemetrySpanEventType, type TinyCloudDebugEnableOptions, type TinyCloudDebugEvent, type TinyCloudDebugLevel, TinyCloudDebugLogger, type TinyCloudDebugTimer, TinyCloudQuota, type ValidationError, type VaultCrypto, type VaultEntry, type VaultError, type VaultGetOptions, type VaultGrantOptions, VaultHeaders, type VaultListOptions, type VaultListPage, type VaultNetworkReadResult, VaultPublicSpaceKVActions, type VaultPutOptions, type ViewInfo, type WasmVaultFunctions, abortedError, authExpiredError, authRequiredError, authUnauthorizedError, authorizationVerdictOf, canonicalizeSecretScope, clearTinyCloudDebugLogs, createKVResponseSchema, createResultSchema, createVaultCrypto, disableTinyCloudDebug, enableTinyCloudDebug, errorResult, getTinyCloudDebugLogs, installTinyCloudDebugGlobals, isStorageFullError, networkError, notFoundError, parseAuthError, parseCapabilityResource, parsePermissionHint, parsePermissionHintFromErrorText, parseSecretCatalogKey, parseStorageQuotaBytes, permissionDeniedError, resolveSecretListPrefix, resolveSecretPath, storageLimitReachedError, storageQuotaExceededError, storageRejectionError, timeoutError, tinyCloudDebugLogger, validateKVListResponse, validateKVResponseHeaders, validateRetryPolicy, validateServiceError, validateServiceRequestEvent, validateServiceResponseEvent, validateServiceSession, validateTelemetrySpanEvent, validatedCapabilityOf, wrapError };
package/dist/index.d.ts CHANGED
@@ -1083,6 +1083,24 @@ declare function authUnauthorizedError(service: string, message: string, meta?:
1083
1083
  * - `"other"`: a non-authorization HTTP error status (4xx/5xx).
1084
1084
  */
1085
1085
  type AuthorizationVerdict = "unauthenticated" | "forbidden" | "other";
1086
+ /**
1087
+ * Parse a canonical TinyCloud resource, keeping nested space names intact.
1088
+ * The first registered service segment is authoritative: a later matching
1089
+ * segment in the path cannot turn a different service into grant advice.
1090
+ */
1091
+ declare function parseCapabilityResource(resource: string, service: string): {
1092
+ space: string;
1093
+ path: string;
1094
+ } | undefined;
1095
+ /**
1096
+ * Return a grantable missing capability only when typed authorization evidence
1097
+ * and both safe, service-matched capability fields are present. Status alone,
1098
+ * message text, wildcard actions and unrecognized actions cannot grant scope.
1099
+ */
1100
+ declare function validatedCapabilityOf(error: unknown): {
1101
+ resource: string;
1102
+ requiredAction: string;
1103
+ } | undefined;
1086
1104
  /**
1087
1105
  * Classify an error from its typed HTTP error status (`status`, `statusCode`,
1088
1106
  * or `meta.status`, 4xx/5xx only) and `AUTH_UNAUTHORIZED` code, following the
@@ -2297,4 +2315,4 @@ declare class SecretsService implements ISecretsService {
2297
2315
  listAll(): Promise<Result<SecretCatalogEntry[], SecretsError>>;
2298
2316
  }
2299
2317
 
2300
- export { type AuthorizationVerdict, BaseService, type BaseServiceOptions, type ColumnInfo, type DataVaultConfig, DataVaultService, DecryptCapabilityProof, DuckDbAction, type DuckDbActionType, type DuckDbBatchOptions, type BatchResponse as DuckDbBatchResponse, DuckDbDatabaseHandle, type DuckDbExecuteOptions, type ExecuteResponse as DuckDbExecuteResponse, type DuckDbOptions, type DuckDbQueryOptions, type QueryResponse as DuckDbQueryResponse, DuckDbService, type DuckDbServiceConfig, type DuckDbStatement, type DuckDbValue, EventHandler, FetchFunction, GenericKVResponseSchema, type GenericKVResponseType, GenericResultSchema, type HookEvent, type HookServiceName, type HookStreamEvent, type HookSubscription, type HookWebhookListOptions, type HookWebhookRecord, type HookWebhookRegistration, type HookWebhookScope, type HookWebhookUnregisterOptions, HooksService, type HooksServiceConfig, type IDataVaultService, type IDuckDbDatabaseHandle, type IDuckDbService, IEncryptionService, type IHooksService, IKVService, type ISecretsService, IService, IServiceContext, InvokeAnyFunction, InvokeFunction, KVListResponseSchema, type KVListResponseType, KVListResultSchema, type KVListResultType, KVResponseHeadersSchema, type KVResponseHeadersType, type ParsedSecretCatalogKey, PermissionHint, type QuotaConfig, type QuotaStatus, type ResolvedSecretPath, Result, RetryPolicy, RetryPolicySchema, type RetryPolicyType, SECRET_NAME_RE, STORAGE_FULL_MESSAGE, STORAGE_WRITE_TOO_LARGE_MESSAGE, type SchemaInfo, type SecretCatalogEntry, type SecretPayload, type SecretScopeOptions, type SecretsError, SecretsService, type ServiceConstructor, ServiceContext, type ServiceContextConfig, ServiceError, ServiceErrorEventSchema, type ServiceErrorEventType, ServiceErrorSchema, type ServiceErrorType, type ServiceRegistration, ServiceRequestEventSchema, type ServiceRequestEventType, ServiceResponseEventSchema, type ServiceResponseEventType, ServiceRetryEventSchema, type ServiceRetryEventType, ServiceSession, ServiceSessionSchema, type ServiceSessionType, StorageQuotaInfo, type SubscribeOptions, type TableInfo, TelemetryConfig, TelemetrySpanEventSchema, type TelemetrySpanEventType, type TinyCloudDebugEnableOptions, type TinyCloudDebugEvent, type TinyCloudDebugLevel, TinyCloudDebugLogger, type TinyCloudDebugTimer, TinyCloudQuota, type ValidationError, type VaultCrypto, type VaultEntry, type VaultError, type VaultGetOptions, type VaultGrantOptions, VaultHeaders, type VaultListOptions, type VaultListPage, type VaultNetworkReadResult, VaultPublicSpaceKVActions, type VaultPutOptions, type ViewInfo, type WasmVaultFunctions, abortedError, authExpiredError, authRequiredError, authUnauthorizedError, authorizationVerdictOf, canonicalizeSecretScope, clearTinyCloudDebugLogs, createKVResponseSchema, createResultSchema, createVaultCrypto, disableTinyCloudDebug, enableTinyCloudDebug, errorResult, getTinyCloudDebugLogs, installTinyCloudDebugGlobals, isStorageFullError, networkError, notFoundError, parseAuthError, parsePermissionHint, parsePermissionHintFromErrorText, parseSecretCatalogKey, parseStorageQuotaBytes, permissionDeniedError, resolveSecretListPrefix, resolveSecretPath, storageLimitReachedError, storageQuotaExceededError, storageRejectionError, timeoutError, tinyCloudDebugLogger, validateKVListResponse, validateKVResponseHeaders, validateRetryPolicy, validateServiceError, validateServiceRequestEvent, validateServiceResponseEvent, validateServiceSession, validateTelemetrySpanEvent, wrapError };
2318
+ export { type AuthorizationVerdict, BaseService, type BaseServiceOptions, type ColumnInfo, type DataVaultConfig, DataVaultService, DecryptCapabilityProof, DuckDbAction, type DuckDbActionType, type DuckDbBatchOptions, type BatchResponse as DuckDbBatchResponse, DuckDbDatabaseHandle, type DuckDbExecuteOptions, type ExecuteResponse as DuckDbExecuteResponse, type DuckDbOptions, type DuckDbQueryOptions, type QueryResponse as DuckDbQueryResponse, DuckDbService, type DuckDbServiceConfig, type DuckDbStatement, type DuckDbValue, EventHandler, FetchFunction, GenericKVResponseSchema, type GenericKVResponseType, GenericResultSchema, type HookEvent, type HookServiceName, type HookStreamEvent, type HookSubscription, type HookWebhookListOptions, type HookWebhookRecord, type HookWebhookRegistration, type HookWebhookScope, type HookWebhookUnregisterOptions, HooksService, type HooksServiceConfig, type IDataVaultService, type IDuckDbDatabaseHandle, type IDuckDbService, IEncryptionService, type IHooksService, IKVService, type ISecretsService, IService, IServiceContext, InvokeAnyFunction, InvokeFunction, KVListResponseSchema, type KVListResponseType, KVListResultSchema, type KVListResultType, KVResponseHeadersSchema, type KVResponseHeadersType, type ParsedSecretCatalogKey, PermissionHint, type QuotaConfig, type QuotaStatus, type ResolvedSecretPath, Result, RetryPolicy, RetryPolicySchema, type RetryPolicyType, SECRET_NAME_RE, STORAGE_FULL_MESSAGE, STORAGE_WRITE_TOO_LARGE_MESSAGE, type SchemaInfo, type SecretCatalogEntry, type SecretPayload, type SecretScopeOptions, type SecretsError, SecretsService, type ServiceConstructor, ServiceContext, type ServiceContextConfig, ServiceError, ServiceErrorEventSchema, type ServiceErrorEventType, ServiceErrorSchema, type ServiceErrorType, type ServiceRegistration, ServiceRequestEventSchema, type ServiceRequestEventType, ServiceResponseEventSchema, type ServiceResponseEventType, ServiceRetryEventSchema, type ServiceRetryEventType, ServiceSession, ServiceSessionSchema, type ServiceSessionType, StorageQuotaInfo, type SubscribeOptions, type TableInfo, TelemetryConfig, TelemetrySpanEventSchema, type TelemetrySpanEventType, type TinyCloudDebugEnableOptions, type TinyCloudDebugEvent, type TinyCloudDebugLevel, TinyCloudDebugLogger, type TinyCloudDebugTimer, TinyCloudQuota, type ValidationError, type VaultCrypto, type VaultEntry, type VaultError, type VaultGetOptions, type VaultGrantOptions, VaultHeaders, type VaultListOptions, type VaultListPage, type VaultNetworkReadResult, VaultPublicSpaceKVActions, type VaultPutOptions, type ViewInfo, type WasmVaultFunctions, abortedError, authExpiredError, authRequiredError, authUnauthorizedError, authorizationVerdictOf, canonicalizeSecretScope, clearTinyCloudDebugLogs, createKVResponseSchema, createResultSchema, createVaultCrypto, disableTinyCloudDebug, enableTinyCloudDebug, errorResult, getTinyCloudDebugLogs, installTinyCloudDebugGlobals, isStorageFullError, networkError, notFoundError, parseAuthError, parseCapabilityResource, parsePermissionHint, parsePermissionHintFromErrorText, parseSecretCatalogKey, parseStorageQuotaBytes, permissionDeniedError, resolveSecretListPrefix, resolveSecretPath, storageLimitReachedError, storageQuotaExceededError, storageRejectionError, timeoutError, tinyCloudDebugLogger, validateKVListResponse, validateKVResponseHeaders, validateRetryPolicy, validateServiceError, validateServiceRequestEvent, validateServiceResponseEvent, validateServiceSession, validateTelemetrySpanEvent, validatedCapabilityOf, wrapError };
package/dist/index.js CHANGED
@@ -896,6 +896,106 @@ var ServiceContext = class {
896
896
  }
897
897
  };
898
898
 
899
+ // src/errors.ts
900
+ import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
901
+
902
+ // src/encryption/networkId.ts
903
+ var URN_PREFIX = "urn:tinycloud:encryption:";
904
+ var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
905
+ var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
906
+ var NetworkIdError = class extends Error {
907
+ constructor(message) {
908
+ super(message);
909
+ this.name = "NetworkIdError";
910
+ }
911
+ };
912
+ function parseNetworkId(networkId) {
913
+ if (typeof networkId !== "string" || networkId.length === 0) {
914
+ throw new NetworkIdError("networkId must be a non-empty string");
915
+ }
916
+ if (!networkId.startsWith(URN_PREFIX)) {
917
+ throw new NetworkIdError(
918
+ `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
919
+ );
920
+ }
921
+ const body = networkId.slice(URN_PREFIX.length);
922
+ const lastColon = body.lastIndexOf(":");
923
+ if (lastColon <= 0 || lastColon === body.length - 1) {
924
+ throw new NetworkIdError(
925
+ `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
926
+ );
927
+ }
928
+ const ownerDid = body.slice(0, lastColon);
929
+ const name = body.slice(lastColon + 1);
930
+ if (!ownerDid.startsWith("did:")) {
931
+ throw new NetworkIdError(
932
+ `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
933
+ );
934
+ }
935
+ const didParts = ownerDid.split(":");
936
+ if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
937
+ throw new NetworkIdError(
938
+ `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
939
+ );
940
+ }
941
+ if (!NETWORK_NAME_RE.test(name)) {
942
+ throw new NetworkIdError(
943
+ `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
944
+ );
945
+ }
946
+ return { networkId, ownerDid, name };
947
+ }
948
+ function buildNetworkId(ownerDid, name) {
949
+ if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
950
+ throw new NetworkIdError("ownerDid must be a DID");
951
+ }
952
+ if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
953
+ throw new NetworkIdError(
954
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
955
+ );
956
+ }
957
+ const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
958
+ parseNetworkId(networkId);
959
+ return networkId;
960
+ }
961
+ function isNetworkId(networkId) {
962
+ if (typeof networkId !== "string") {
963
+ return false;
964
+ }
965
+ try {
966
+ parseNetworkId(networkId);
967
+ return true;
968
+ } catch {
969
+ return false;
970
+ }
971
+ }
972
+ function parsePkhOwnerDid(ownerDid) {
973
+ const match = ownerDid.match(PKH_EIP155_DID_RE);
974
+ if (!match) return null;
975
+ return {
976
+ chainId: match[1],
977
+ address: match[2].toLowerCase()
978
+ };
979
+ }
980
+ function ownerDidMatches(a, b) {
981
+ const aPkh = parsePkhOwnerDid(a);
982
+ const bPkh = parsePkhOwnerDid(b);
983
+ if (aPkh && bPkh) {
984
+ return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
985
+ }
986
+ return a === b;
987
+ }
988
+ function networkDiscoveryKey(name) {
989
+ if (!NETWORK_NAME_RE.test(name)) {
990
+ throw new NetworkIdError(
991
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
992
+ );
993
+ }
994
+ return `.well-known/encryption/network/${name}`;
995
+ }
996
+ var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
997
+ var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
998
+
899
999
  // src/errors.ts
900
1000
  function parsePermissionHint(value) {
901
1001
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -993,6 +1093,70 @@ function parseAuthError(responseText) {
993
1093
  function authUnauthorizedError(service, message, meta) {
994
1094
  return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
995
1095
  }
1096
+ var grantableActions = new Set(
1097
+ CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
1098
+ );
1099
+ var capabilityServices = new Set(
1100
+ CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
1101
+ );
1102
+ function parseCapabilityResource(resource, service) {
1103
+ if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
1104
+ const segments = resource.split("/");
1105
+ if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
1106
+ const serviceIndex = segments.findIndex(
1107
+ (segment, index) => index > 0 && capabilityServices.has(segment)
1108
+ );
1109
+ if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
1110
+ if (segments.slice(1, serviceIndex).some(
1111
+ (segment) => segment.length === 0 || segment === "." || segment === ".."
1112
+ )) return void 0;
1113
+ const pathSegments = segments.slice(serviceIndex + 1);
1114
+ if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
1115
+ if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
1116
+ return {
1117
+ space: segments.slice(0, serviceIndex).join("/"),
1118
+ path: pathSegments.join("/")
1119
+ };
1120
+ }
1121
+ function safeCapabilityResource(value, service, action) {
1122
+ if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
1123
+ if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
1124
+ if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
1125
+ try {
1126
+ parseNetworkId(value);
1127
+ return true;
1128
+ } catch {
1129
+ return false;
1130
+ }
1131
+ }
1132
+ const parsed = parseCapabilityResource(value, service);
1133
+ return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
1134
+ }
1135
+ function validatedCapabilityOf(error) {
1136
+ const verdict = authorizationVerdictOf(error);
1137
+ if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
1138
+ const seen = /* @__PURE__ */ new Set();
1139
+ let node = error;
1140
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
1141
+ seen.add(node);
1142
+ const record = node;
1143
+ const status = errorStatusOf(record);
1144
+ const meta = record.meta;
1145
+ if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
1146
+ const metadata = meta;
1147
+ const { resource, requiredAction } = metadata;
1148
+ if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
1149
+ const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
1150
+ const explicitService = record.service === void 0 ? metadata.service : record.service;
1151
+ if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
1152
+ return { resource, requiredAction };
1153
+ }
1154
+ }
1155
+ }
1156
+ node = record.cause;
1157
+ }
1158
+ return void 0;
1159
+ }
996
1160
  var MAX_CAUSE_DEPTH = 8;
997
1161
  function errorStatusOf(node) {
998
1162
  const meta = node.meta;
@@ -1740,12 +1904,14 @@ var KVService = class extends BaseService {
1740
1904
  signal: this.combineSignals(options?.signal)
1741
1905
  });
1742
1906
  if (!response.ok) {
1743
- const errorText = await response.text();
1907
+ const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
1744
1908
  if (response.status === 401 || response.status === 403) {
1745
1909
  return this.authorizationFailure(
1746
1910
  `Failed to batch read ${keys.length} key(s)`,
1747
1911
  response,
1748
- errorText
1912
+ errorText,
1913
+ paths,
1914
+ action
1749
1915
  );
1750
1916
  }
1751
1917
  if (response.status === 413) {
@@ -1835,7 +2001,7 @@ var KVService = class extends BaseService {
1835
2001
  return text;
1836
2002
  }
1837
2003
  }
1838
- async createSignedReadUrlError(response, key) {
2004
+ async createSignedReadUrlError(response, key, path) {
1839
2005
  let errorText = response.statusText;
1840
2006
  try {
1841
2007
  const text = await response.text();
@@ -1848,7 +2014,9 @@ var KVService = class extends BaseService {
1848
2014
  return this.authorizationFailure(
1849
2015
  `Failed to create signed read URL for key ${JSON.stringify(key)}`,
1850
2016
  response,
1851
- errorText
2017
+ errorText,
2018
+ [path],
2019
+ KVAction.GET
1852
2020
  );
1853
2021
  }
1854
2022
  const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
@@ -1861,21 +2029,42 @@ var KVService = class extends BaseService {
1861
2029
  )
1862
2030
  );
1863
2031
  }
2032
+ /** A known 401/403 keeps its typed status even if reading its body fails. */
2033
+ async readAuthorizationText(response) {
2034
+ try {
2035
+ return await response.text();
2036
+ } catch {
2037
+ return "";
2038
+ }
2039
+ }
1864
2040
  /**
1865
2041
  * AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
1866
2042
  * status and the server text (`<context>: <status> - <text>`, the same
1867
2043
  * shape as every other KV failure) so callers that rethrow only the message
1868
2044
  * still see the status; `meta.status` carries it for typed callers.
1869
- * Resource/ability hints are parsed from the raw body, never the message.
2045
+ * Server hints must match the requested path/action (whether the node sends
2046
+ * a relative path or full resource); metadata uses only the request-derived
2047
+ * canonical resource under the authenticated session's space.
1870
2048
  */
1871
- authorizationFailure(context, response, errorText, extraMeta = {}) {
2049
+ authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
1872
2050
  const { resource, action } = parseAuthError(errorText);
2051
+ const requestedPath = paths.find(
2052
+ (path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
2053
+ );
2054
+ const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf({
2055
+ service: "kv",
2056
+ code: ErrorCodes.AUTH_UNAUTHORIZED,
2057
+ meta: {
2058
+ status: response.status,
2059
+ resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
2060
+ requiredAction: action
2061
+ }
2062
+ }) : void 0;
1873
2063
  const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
1874
2064
  return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
1875
2065
  status: response.status,
1876
- ...action && { requiredAction: action },
1877
- ...resource && { resource },
1878
- ...extraMeta
2066
+ ...extraMeta,
2067
+ ...capability
1879
2068
  }));
1880
2069
  }
1881
2070
  normalizeSignedReadUrlResponse(data) {
@@ -1919,13 +2108,24 @@ var KVService = class extends BaseService {
1919
2108
  );
1920
2109
  if (!response.ok) {
1921
2110
  if (response.status === 401 || response.status === 403) {
1922
- const errorText2 = await response.text();
2111
+ const errorText2 = await this.readAuthorizationText(response);
1923
2112
  const permissionHint = parsePermissionHintFromErrorText(errorText2);
2113
+ const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
2114
+ service: "kv",
2115
+ code: ErrorCodes.AUTH_UNAUTHORIZED,
2116
+ meta: {
2117
+ status: response.status,
2118
+ resource: `${this.context.session.spaceId}/kv/${path}`,
2119
+ requiredAction: KVAction.GET
2120
+ }
2121
+ }) : void 0;
1924
2122
  return this.authorizationFailure(
1925
2123
  `Failed to get key ${JSON.stringify(key)}`,
1926
2124
  response,
1927
2125
  errorText2,
1928
- permissionHint === void 0 ? {} : { permissionHint }
2126
+ [path],
2127
+ KVAction.GET,
2128
+ structuredCapability ? { permissionHint, ...structuredCapability } : {}
1929
2129
  );
1930
2130
  }
1931
2131
  if (response.status === 404) {
@@ -2002,7 +2202,9 @@ var KVService = class extends BaseService {
2002
2202
  return this.authorizationFailure(
2003
2203
  `Failed to put key ${JSON.stringify(key)}`,
2004
2204
  response,
2005
- await response.text()
2205
+ await this.readAuthorizationText(response),
2206
+ [path],
2207
+ KVAction.PUT
2006
2208
  );
2007
2209
  }
2008
2210
  if (!response.ok) {
@@ -2131,7 +2333,9 @@ var KVService = class extends BaseService {
2131
2333
  return this.authorizationFailure(
2132
2334
  `Failed to batch put ${items.length} key(s)`,
2133
2335
  response,
2134
- errorText
2336
+ errorText,
2337
+ paths,
2338
+ KVAction.PUT
2135
2339
  );
2136
2340
  }
2137
2341
  const quotaError = this.handleQuotaErrorResponse(
@@ -2252,11 +2456,13 @@ var KVService = class extends BaseService {
2252
2456
  }
2253
2457
  );
2254
2458
  if (!response.ok) {
2255
- if (response.status === 401) {
2459
+ if (response.status === 401 || response.status === 403) {
2256
2460
  return this.authorizationFailure(
2257
2461
  "Failed to list keys",
2258
2462
  response,
2259
- await response.text()
2463
+ await this.readAuthorizationText(response),
2464
+ [listPath],
2465
+ KVAction.LIST
2260
2466
  );
2261
2467
  }
2262
2468
  const errorText = await response.text();
@@ -2305,11 +2511,13 @@ var KVService = class extends BaseService {
2305
2511
  options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
2306
2512
  );
2307
2513
  if (!response.ok) {
2308
- if (response.status === 401) {
2514
+ if (response.status === 401 || response.status === 403) {
2309
2515
  return this.authorizationFailure(
2310
2516
  `Failed to delete key ${JSON.stringify(key)}`,
2311
2517
  response,
2312
- await response.text()
2518
+ await this.readAuthorizationText(response),
2519
+ [path],
2520
+ KVAction.DELETE
2313
2521
  );
2314
2522
  }
2315
2523
  if (response.status === 404) {
@@ -2367,11 +2575,13 @@ var KVService = class extends BaseService {
2367
2575
  options?.signal
2368
2576
  );
2369
2577
  if (!response.ok) {
2370
- if (response.status === 401) {
2578
+ if (response.status === 401 || response.status === 403) {
2371
2579
  return this.authorizationFailure(
2372
2580
  `Failed to get metadata for key ${JSON.stringify(key)}`,
2373
2581
  response,
2374
- await response.text()
2582
+ await this.readAuthorizationText(response),
2583
+ [path],
2584
+ KVAction.HEAD
2375
2585
  );
2376
2586
  }
2377
2587
  if (response.status === 404) {
@@ -2438,7 +2648,7 @@ var KVService = class extends BaseService {
2438
2648
  signal: this.combineSignals(options?.signal)
2439
2649
  });
2440
2650
  if (!response.ok) {
2441
- return this.createSignedReadUrlError(response, key);
2651
+ return this.createSignedReadUrlError(response, key, path);
2442
2652
  }
2443
2653
  const signedUrl = this.normalizeSignedReadUrlResponse(
2444
2654
  await response.json()
@@ -2676,14 +2886,15 @@ var SQLService = class extends BaseService {
2676
2886
  };
2677
2887
  if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
2678
2888
  if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
2889
+ const action = this.actionForSql(sql, SQLAction.READ);
2679
2890
  const response = await this.invokeSQL(
2680
2891
  dbName,
2681
- this.actionForSql(sql, SQLAction.READ),
2892
+ action,
2682
2893
  body,
2683
2894
  options?.signal
2684
2895
  );
2685
2896
  if (!response.ok) {
2686
- return this.handleErrorResponse(response, "query");
2897
+ return this.handleErrorResponse(response, "query", dbName, [action]);
2687
2898
  }
2688
2899
  const data = await response.json();
2689
2900
  return ok(data);
@@ -2712,14 +2923,15 @@ var SQLService = class extends BaseService {
2712
2923
  (statement) => this.actionForSql(statement, SQLAction.SCHEMA)
2713
2924
  )
2714
2925
  ];
2926
+ const requestedActions = this.dedupeActions(actions);
2715
2927
  const response = await this.invokeSQL(
2716
2928
  dbName,
2717
- this.dedupeActions(actions),
2929
+ requestedActions,
2718
2930
  body,
2719
2931
  options?.signal
2720
2932
  );
2721
2933
  if (!response.ok) {
2722
- return this.handleErrorResponse(response, "execute");
2934
+ return this.handleErrorResponse(response, "execute", dbName, requestedActions);
2723
2935
  }
2724
2936
  const data = await response.json();
2725
2937
  return ok(data);
@@ -2734,14 +2946,15 @@ var SQLService = class extends BaseService {
2734
2946
  return err(authRequiredError("sql"));
2735
2947
  }
2736
2948
  try {
2949
+ const requestedActions = this.actionsForSqlBatch(statements);
2737
2950
  const response = await this.invokeSQL(
2738
2951
  dbName,
2739
- this.actionsForSqlBatch(statements),
2952
+ requestedActions,
2740
2953
  { action: "batch", statements },
2741
2954
  options?.signal
2742
2955
  );
2743
2956
  if (!response.ok) {
2744
- return this.handleErrorResponse(response, "batch");
2957
+ return this.handleErrorResponse(response, "batch", dbName, requestedActions);
2745
2958
  }
2746
2959
  const data = await response.json();
2747
2960
  return ok(data);
@@ -2763,7 +2976,7 @@ var SQLService = class extends BaseService {
2763
2976
  options?.signal
2764
2977
  );
2765
2978
  if (!response.ok) {
2766
- return this.handleErrorResponse(response, "executeStatement");
2979
+ return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
2767
2980
  }
2768
2981
  const data = await response.json();
2769
2982
  return ok(data);
@@ -2785,7 +2998,7 @@ var SQLService = class extends BaseService {
2785
2998
  options?.signal
2786
2999
  );
2787
3000
  if (!response.ok) {
2788
- return this.handleErrorResponse(response, "export");
3001
+ return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
2789
3002
  }
2790
3003
  const resp = response;
2791
3004
  if (typeof resp.blob === "function") {
@@ -2919,7 +3132,7 @@ var SQLService = class extends BaseService {
2919
3132
  }))
2920
3133
  );
2921
3134
  }
2922
- async handleErrorResponse(response, operation) {
3135
+ async handleErrorResponse(response, operation, dbName, requestedActions) {
2923
3136
  const errorText = await response.text();
2924
3137
  const meta = responseErrorMeta(response.status, response.statusText, errorText);
2925
3138
  if (response.status === 402) {
@@ -2938,9 +3151,11 @@ var SQLService = class extends BaseService {
2938
3151
  errorBody
2939
3152
  );
2940
3153
  if (response.status === 401) {
2941
- const { resource, action } = parseAuthError(errorText);
2942
- if (action) meta.requiredAction = action;
2943
- if (resource) meta.resource = resource;
3154
+ const { action } = parseAuthError(errorText);
3155
+ if (action && requestedActions.includes(action)) {
3156
+ meta.requiredAction = action;
3157
+ meta.resource = `${this.context.session.spaceId}/sql/${dbName}`;
3158
+ }
2944
3159
  }
2945
3160
  return err(
2946
3161
  serviceError(errorCode, message, "sql", { meta })
@@ -5919,103 +6134,6 @@ function canonicalHashHex(sha256, value) {
5919
6134
  return hexEncode2(sha256(utf8Encode(canonical)));
5920
6135
  }
5921
6136
 
5922
- // src/encryption/networkId.ts
5923
- var URN_PREFIX = "urn:tinycloud:encryption:";
5924
- var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
5925
- var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
5926
- var NetworkIdError = class extends Error {
5927
- constructor(message) {
5928
- super(message);
5929
- this.name = "NetworkIdError";
5930
- }
5931
- };
5932
- function parseNetworkId(networkId) {
5933
- if (typeof networkId !== "string" || networkId.length === 0) {
5934
- throw new NetworkIdError("networkId must be a non-empty string");
5935
- }
5936
- if (!networkId.startsWith(URN_PREFIX)) {
5937
- throw new NetworkIdError(
5938
- `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
5939
- );
5940
- }
5941
- const body = networkId.slice(URN_PREFIX.length);
5942
- const lastColon = body.lastIndexOf(":");
5943
- if (lastColon <= 0 || lastColon === body.length - 1) {
5944
- throw new NetworkIdError(
5945
- `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
5946
- );
5947
- }
5948
- const ownerDid = body.slice(0, lastColon);
5949
- const name = body.slice(lastColon + 1);
5950
- if (!ownerDid.startsWith("did:")) {
5951
- throw new NetworkIdError(
5952
- `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
5953
- );
5954
- }
5955
- const didParts = ownerDid.split(":");
5956
- if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
5957
- throw new NetworkIdError(
5958
- `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
5959
- );
5960
- }
5961
- if (!NETWORK_NAME_RE.test(name)) {
5962
- throw new NetworkIdError(
5963
- `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
5964
- );
5965
- }
5966
- return { networkId, ownerDid, name };
5967
- }
5968
- function buildNetworkId(ownerDid, name) {
5969
- if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
5970
- throw new NetworkIdError("ownerDid must be a DID");
5971
- }
5972
- if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
5973
- throw new NetworkIdError(
5974
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
5975
- );
5976
- }
5977
- const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
5978
- parseNetworkId(networkId);
5979
- return networkId;
5980
- }
5981
- function isNetworkId(networkId) {
5982
- if (typeof networkId !== "string") {
5983
- return false;
5984
- }
5985
- try {
5986
- parseNetworkId(networkId);
5987
- return true;
5988
- } catch {
5989
- return false;
5990
- }
5991
- }
5992
- function parsePkhOwnerDid(ownerDid) {
5993
- const match = ownerDid.match(PKH_EIP155_DID_RE);
5994
- if (!match) return null;
5995
- return {
5996
- chainId: match[1],
5997
- address: match[2].toLowerCase()
5998
- };
5999
- }
6000
- function ownerDidMatches(a, b) {
6001
- const aPkh = parsePkhOwnerDid(a);
6002
- const bPkh = parsePkhOwnerDid(b);
6003
- if (aPkh && bPkh) {
6004
- return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
6005
- }
6006
- return a === b;
6007
- }
6008
- function networkDiscoveryKey(name) {
6009
- if (!NETWORK_NAME_RE.test(name)) {
6010
- throw new NetworkIdError(
6011
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
6012
- );
6013
- }
6014
- return `.well-known/encryption/network/${name}`;
6015
- }
6016
- var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
6017
- var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
6018
-
6019
6137
  // src/encryption/types.ts
6020
6138
  var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
6021
6139
  var ENVELOPE_VERSION = 1;
@@ -6958,6 +7076,7 @@ export {
6958
7076
  ok,
6959
7077
  openWrappedKey,
6960
7078
  parseAuthError,
7079
+ parseCapabilityResource,
6961
7080
  parseNetworkId,
6962
7081
  parsePermissionHint,
6963
7082
  parsePermissionHintFromErrorText,
@@ -6983,6 +7102,7 @@ export {
6983
7102
  validateServiceResponseEvent,
6984
7103
  validateServiceSession,
6985
7104
  validateTelemetrySpanEvent,
7105
+ validatedCapabilityOf,
6986
7106
  verifyDecryptResponse,
6987
7107
  wrapError
6988
7108
  };