@tinycloud/operations 0.3.2-beta.1 → 0.3.2-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -335,28 +335,6 @@ function withCapabilitiesReadForSpaces(resources) {
335
335
  }))
336
336
  ]);
337
337
  }
338
- function accountRegistryPermissions() {
339
- return [
340
- {
341
- service: "tinycloud.kv",
342
- space: ACCOUNT_REGISTRY_SPACE,
343
- path: ACCOUNT_REGISTRY_PATH,
344
- actions: [KV.GET, KV.PUT, KV.LIST]
345
- },
346
- {
347
- service: "tinycloud.kv",
348
- space: ACCOUNT_REGISTRY_SPACE,
349
- path: "spaces/",
350
- actions: [KV.GET, KV.PUT, KV.LIST]
351
- },
352
- {
353
- service: "tinycloud.sql",
354
- space: ACCOUNT_REGISTRY_SPACE,
355
- path: "account",
356
- actions: [SQL.READ, SQL.WRITE, SQL.SCHEMA]
357
- }
358
- ];
359
- }
360
338
  function composeManifestRequest(inputs, options = {}) {
361
339
  if (!Array.isArray(inputs) || inputs.length === 0) {
362
340
  throw new Error("composeManifestRequest requires at least one manifest");
@@ -370,7 +348,12 @@ function composeManifestRequest(inputs, options = {}) {
370
348
  return explicit.flatMap((entry) => expandPermissionEntry(entry, prefix, space));
371
349
  });
372
350
  if (includeAccountRegistryPermissions) {
373
- resources.push(...accountRegistryPermissions());
351
+ resources.push(
352
+ ...ACCOUNT_MANIFEST_PERMISSIONS.map((resource) => ({
353
+ ...resource,
354
+ actions: [...resource.actions]
355
+ }))
356
+ );
374
357
  }
375
358
  const manifestsByAppId = /* @__PURE__ */ new Map();
376
359
  for (const manifest of manifests) {
@@ -398,7 +381,7 @@ function composeBootstrapSpaceManifest(space) {
398
381
  includeAccountRegistryPermissions: false
399
382
  });
400
383
  }
401
- var CAPABILITIES, KV, SQL, DUCKDB, CAPABILITIES2, HOOKS, ENCRYPTION, SPACE, DEFAULT_MANIFEST_SPACE, ACCOUNT_REGISTRY_SPACE, ACCOUNT_REGISTRY_PATH, SECRETS_SPACE, BOOTSTRAP_DEFAULT_SPACE, BOOTSTRAP_PUBLIC_SPACE, BOOTSTRAP_ENCRYPTION_NETWORK_NAME, BOOTSTRAP_ENCRYPTION_NETWORK_RESOURCE_TEMPLATE, DEFAULT_EXPIRY_MS, VAULT_PERMISSION_SERVICE, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, NETWORK_CREATE_ACTION, BOOTSTRAP_SPACE_NAMES, TINYCLOUD_DEFAULT_SPACE_MANIFEST, TINYCLOUD_APPLICATIONS_SPACE_MANIFEST, TINYCLOUD_ACCOUNT_SPACE_MANIFEST, TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST, TINYCLOUD_PUBLIC_SPACE_MANIFEST, BOOTSTRAP_SPACE_MANIFESTS, BOOTSTRAP_PERSISTED_APPLICATION_MANIFESTS, ACCOUNT_INDEX_SCHEMA, SECRET_RECORDS_SCHEMA, BOOTSTRAP_MANIFEST, BOOTSTRAP_SESSION_REQUESTS, ACCOUNT_SESSION_RAW_ALLOWLIST, BOOTSTRAP_ALLOWLIST;
384
+ var CAPABILITIES, KV, SQL, DUCKDB, CAPABILITIES2, HOOKS, ENCRYPTION, SPACE, DEFAULT_MANIFEST_SPACE, ACCOUNT_REGISTRY_SPACE, ACCOUNT_REGISTRY_PATH, SECRETS_SPACE, BOOTSTRAP_DEFAULT_SPACE, BOOTSTRAP_PUBLIC_SPACE, BOOTSTRAP_ENCRYPTION_NETWORK_NAME, BOOTSTRAP_ENCRYPTION_NETWORK_RESOURCE_TEMPLATE, DEFAULT_EXPIRY_MS, VAULT_PERMISSION_SERVICE, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, NETWORK_CREATE_ACTION, BOOTSTRAP_SPACE_NAMES, TINYCLOUD_DEFAULT_SPACE_MANIFEST, TINYCLOUD_APPLICATIONS_SPACE_MANIFEST, TINYCLOUD_ACCOUNT_SPACE_MANIFEST, TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST, TINYCLOUD_PUBLIC_SPACE_MANIFEST, BOOTSTRAP_SPACE_MANIFESTS, BOOTSTRAP_PERSISTED_APPLICATION_MANIFESTS, ACCOUNT_INDEX_SCHEMA, SECRET_RECORDS_SCHEMA, BOOTSTRAP_MANIFEST, ACCOUNT_MANIFEST_PERMISSIONS, BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST, BOOTSTRAP_WIDENED_DEFAULT_SESSION_REQUEST, BOOTSTRAP_SESSION_REQUESTS, ACCOUNT_SESSION_RAW_ALLOWLIST, BOOTSTRAP_ALLOWLIST;
402
385
  var init_dist = __esm({
403
386
  "../bootstrap/dist/index.js"() {
404
387
  "use strict";
@@ -703,11 +686,60 @@ var init_dist = __esm({
703
686
  name: BOOTSTRAP_ENCRYPTION_NETWORK_NAME
704
687
  }
705
688
  };
689
+ ACCOUNT_MANIFEST_PERMISSIONS = Object.freeze([
690
+ {
691
+ service: "tinycloud.kv",
692
+ space: ACCOUNT_REGISTRY_SPACE,
693
+ path: ACCOUNT_REGISTRY_PATH,
694
+ actions: [KV.GET, KV.PUT, KV.LIST]
695
+ },
696
+ {
697
+ service: "tinycloud.kv",
698
+ space: ACCOUNT_REGISTRY_SPACE,
699
+ path: "spaces/",
700
+ actions: [KV.GET, KV.PUT, KV.LIST]
701
+ },
702
+ {
703
+ service: "tinycloud.kv",
704
+ space: ACCOUNT_REGISTRY_SPACE,
705
+ path: "system/bootstrap/complete",
706
+ actions: [KV.GET, KV.PUT]
707
+ },
708
+ {
709
+ service: "tinycloud.delegation",
710
+ space: ACCOUNT_REGISTRY_SPACE,
711
+ path: "",
712
+ actions: ["tinycloud.delegation/list"]
713
+ },
714
+ {
715
+ service: "tinycloud.sql",
716
+ space: ACCOUNT_REGISTRY_SPACE,
717
+ path: "account",
718
+ actions: [SQL.READ, SQL.WRITE, SQL.SCHEMA]
719
+ },
720
+ {
721
+ service: "tinycloud.capabilities",
722
+ space: ACCOUNT_REGISTRY_SPACE,
723
+ path: "",
724
+ actions: [CAPABILITIES2.READ]
725
+ }
726
+ ]);
727
+ BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST = composeBootstrapSpaceManifest(BOOTSTRAP_DEFAULT_SPACE);
728
+ BOOTSTRAP_WIDENED_DEFAULT_SESSION_REQUEST = {
729
+ ...BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST,
730
+ resources: [
731
+ ...BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST.resources,
732
+ ...ACCOUNT_MANIFEST_PERMISSIONS.map((resource) => ({
733
+ ...resource,
734
+ actions: [...resource.actions]
735
+ }))
736
+ ]
737
+ };
706
738
  BOOTSTRAP_SESSION_REQUESTS = Object.freeze(
707
739
  Object.fromEntries(
708
740
  BOOTSTRAP_SPACE_NAMES.map((space) => [
709
741
  space,
710
- composeBootstrapSpaceManifest(space)
742
+ space === BOOTSTRAP_DEFAULT_SPACE ? BOOTSTRAP_WIDENED_DEFAULT_SESSION_REQUEST : composeBootstrapSpaceManifest(space)
711
743
  ])
712
744
  )
713
745
  );
@@ -762,10 +794,10 @@ var require_decrypt_transport_response_error = __commonJS({
762
794
  var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
763
795
  var DecryptTransportResponseError_exports = {};
764
796
  __export2(DecryptTransportResponseError_exports, {
765
- DecryptTransportResponseError: () => DecryptTransportResponseError4
797
+ DecryptTransportResponseError: () => DecryptTransportResponseError2
766
798
  });
767
799
  module2.exports = __toCommonJS2(DecryptTransportResponseError_exports);
768
- var DecryptTransportResponseError4 = class extends Error {
800
+ var DecryptTransportResponseError2 = class extends Error {
769
801
  constructor(status, permissionHint) {
770
802
  super("Node decrypt request failed");
771
803
  this.status = status;
@@ -2397,7 +2429,7 @@ function encOk(data) {
2397
2429
  function encErr(error) {
2398
2430
  return { ok: false, error };
2399
2431
  }
2400
- var import_zod, import_decrypt_transport_response_error, import_decrypt_transport_response_error2, import_decrypt_transport_response_error3, ErrorCodes, defaultRetryPolicy, TelemetryEvents, REDACTED, SAFE_NUMBER_FIELDS, SAFE_BOOLEAN_FIELDS, DEBUG_FLAG, MAX_EVENTS, TinyCloudDebugLogger, tinyCloudDebugLogger, ServiceErrorSchema, GenericResultSchema, KVResponseHeadersSchema, GenericKVResponseSchema, KVListResponseSchema, KVListResultSchema, ServiceRequestEventSchema, ServiceResponseEventSchema, ServiceErrorEventSchema, ServiceRetryEventSchema, TelemetrySpanEventSchema, RetryPolicySchema, ServiceSessionSchema, BaseService, PrefixedKVService, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, KVAction, MAX_KV_BATCH_READ_ITEMS, KVService, SQLMigrations, DatabaseHandle, SQLAction, DDL_TOKENS, MIGRATIONS_TABLE, MIGRATIONS_SCHEMA, MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID, SQLService, DuckDbDatabaseHandle, DuckDbAction, DuckDbService, AsyncQueue, HooksService, VaultVersionConfig, CURRENT_VAULT_VERSION, VaultHeaders, DB_NAME, DB_VERSION, STORE_NAME, WRAP_KEY_ID, DataVaultService, HEX, URN_PREFIX, NETWORK_NAME_RE, PKH_EIP155_DID_RE, NetworkIdError, DEFAULT_ENCRYPTION_ALG, ENVELOPE_VERSION, DEFAULT_KEY_VERSION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, EncryptionService;
2432
+ var import_zod, import_decrypt_transport_response_error, ErrorCodes, defaultRetryPolicy, TelemetryEvents, REDACTED, SAFE_NUMBER_FIELDS, SAFE_BOOLEAN_FIELDS, DEBUG_FLAG, MAX_EVENTS, TinyCloudDebugLogger, tinyCloudDebugLogger, ServiceErrorSchema, GenericResultSchema, KVResponseHeadersSchema, GenericKVResponseSchema, KVListResponseSchema, KVListResultSchema, ServiceRequestEventSchema, ServiceResponseEventSchema, ServiceErrorEventSchema, ServiceRetryEventSchema, TelemetrySpanEventSchema, RetryPolicySchema, ServiceSessionSchema, BaseService, PrefixedKVService, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, KVAction, MAX_KV_BATCH_READ_ITEMS, KVService, SQLMigrations, DatabaseHandle, SQLAction, DDL_TOKENS, MIGRATIONS_TABLE, MIGRATIONS_SCHEMA, MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID, SQLService, DuckDbDatabaseHandle, DuckDbAction, DuckDbService, AsyncQueue, HooksService, VaultVersionConfig, CURRENT_VAULT_VERSION, VaultHeaders, DB_NAME, DB_VERSION, STORE_NAME, WRAP_KEY_ID, DataVaultService, HEX, URN_PREFIX, NETWORK_NAME_RE, PKH_EIP155_DID_RE, NetworkIdError, DEFAULT_ENCRYPTION_ALG, ENVELOPE_VERSION, DEFAULT_KEY_VERSION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, EncryptionService;
2401
2433
  var init_dist2 = __esm({
2402
2434
  "../sdk-services/dist/index.js"() {
2403
2435
  "use strict";
@@ -2405,8 +2437,6 @@ var init_dist2 = __esm({
2405
2437
  init_dist();
2406
2438
  init_dist();
2407
2439
  import_decrypt_transport_response_error = __toESM(require_decrypt_transport_response_error(), 1);
2408
- import_decrypt_transport_response_error2 = __toESM(require_decrypt_transport_response_error(), 1);
2409
- import_decrypt_transport_response_error3 = __toESM(require_decrypt_transport_response_error(), 1);
2410
2440
  ErrorCodes = {
2411
2441
  // Common errors
2412
2442
  NOT_FOUND: "NOT_FOUND",
@@ -3678,6 +3708,7 @@ var init_dist2 = __esm({
3678
3708
  }
3679
3709
  seen.add(path);
3680
3710
  }
3711
+ let requestMayHaveDispatched = false;
3681
3712
  try {
3682
3713
  const body = new FormData();
3683
3714
  for (let index = 0; index < items.length; index++) {
@@ -3695,14 +3726,31 @@ var init_dist2 = __esm({
3695
3726
  action: KVAction.PUT
3696
3727
  }))
3697
3728
  );
3698
- const response = await this.context.fetch(`${this.host}/invoke`, {
3729
+ const url = `${this.host}/invoke`;
3730
+ const init = {
3699
3731
  method: "POST",
3700
3732
  headers,
3701
3733
  body,
3702
3734
  signal: this.combineSignals(options?.signal)
3703
- });
3735
+ };
3736
+ const fetchFn = this.context.fetch;
3737
+ requestMayHaveDispatched = true;
3738
+ const response = await fetchFn(url, init);
3704
3739
  if (!response.ok) {
3705
- const errorText = await response.text();
3740
+ let errorText;
3741
+ try {
3742
+ errorText = await response.text();
3743
+ } catch (textError) {
3744
+ const cause = textError instanceof Error ? textError : new Error(String(textError));
3745
+ return err(
3746
+ serviceError(
3747
+ ErrorCodes.KV_WRITE_FAILED,
3748
+ `Failed to batch put ${items.length} key(s): ${response.status} - <response body could not be read: ${cause.message}>`,
3749
+ "kv",
3750
+ { cause, meta: { status: response.status, statusText: response.statusText } }
3751
+ )
3752
+ );
3753
+ }
3706
3754
  if (response.status === 401 || response.status === 403) {
3707
3755
  const { resource, action } = parseAuthError(errorText);
3708
3756
  return err(authUnauthorizedError("kv", errorText, {
@@ -3728,19 +3776,73 @@ var init_dist2 = __esm({
3728
3776
  )
3729
3777
  );
3730
3778
  }
3731
- const batchResponse = this.normalizeBatchPutResponse(await response.json());
3732
- if (!batchResponse || batchResponse.count !== batchResponse.written.length) {
3779
+ let rawBody;
3780
+ try {
3781
+ rawBody = await response.json();
3782
+ } catch (jsonError) {
3783
+ const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
3784
+ return err(
3785
+ serviceError(
3786
+ ErrorCodes.NETWORK_ERROR,
3787
+ `KV batchPut response was not valid JSON: ${cause.message}`,
3788
+ "kv",
3789
+ {
3790
+ cause,
3791
+ meta: {
3792
+ requestMayHaveDispatched: true,
3793
+ responseReceived: true,
3794
+ status: response.status,
3795
+ outcome: "batch-unconfirmed"
3796
+ }
3797
+ }
3798
+ )
3799
+ );
3800
+ }
3801
+ const batchResponse = this.normalizeBatchPutResponse(rawBody);
3802
+ if (!batchResponse) {
3733
3803
  return err(
3734
3804
  serviceError(
3735
3805
  ErrorCodes.NETWORK_ERROR,
3736
3806
  "KV batchPut response did not include matching written keys and count",
3737
- "kv"
3807
+ "kv",
3808
+ {
3809
+ meta: {
3810
+ requestMayHaveDispatched: true,
3811
+ responseReceived: true,
3812
+ status: response.status,
3813
+ outcome: "batch-unconfirmed"
3814
+ }
3815
+ }
3816
+ )
3817
+ );
3818
+ }
3819
+ const requestedPaths = new Set(paths);
3820
+ const writtenPaths = new Set(batchResponse.written);
3821
+ const matchesRequest = batchResponse.count === paths.length && batchResponse.written.length === paths.length && writtenPaths.size === paths.length && batchResponse.written.every((key) => requestedPaths.has(key));
3822
+ if (!matchesRequest) {
3823
+ return err(
3824
+ serviceError(
3825
+ ErrorCodes.NETWORK_ERROR,
3826
+ `KV batchPut response did not confirm all ${paths.length} requested key(s) were written`,
3827
+ "kv",
3828
+ {
3829
+ meta: {
3830
+ requestMayHaveDispatched: true,
3831
+ responseReceived: true,
3832
+ status: response.status,
3833
+ outcome: "batch-unconfirmed"
3834
+ }
3835
+ }
3738
3836
  )
3739
3837
  );
3740
3838
  }
3741
3839
  return ok(batchResponse);
3742
3840
  } catch (error) {
3743
- return err(wrapError("kv", error));
3841
+ const wrapped = wrapError("kv", error);
3842
+ return err({
3843
+ ...wrapped,
3844
+ meta: { ...wrapped.meta, requestMayHaveDispatched }
3845
+ });
3744
3846
  }
3745
3847
  });
3746
3848
  }
@@ -6737,15 +6839,28 @@ function normalizeSpace(space) {
6737
6839
  function isCapabilitySubset(requested, granted) {
6738
6840
  const missing = [];
6739
6841
  for (const req of requested) {
6740
- const match = granted.find((g) => canonicalizeEntryMatches(req, g));
6741
- if (match === void 0) {
6842
+ const matchingScope = granted.filter(
6843
+ (entry) => canonicalizeEntryScopeMatches(req, entry)
6844
+ );
6845
+ const requestedActions = expandActionShortNames(
6846
+ req.service,
6847
+ req.actions
6848
+ );
6849
+ const grantedActions = matchingScope.flatMap(
6850
+ (entry) => expandActionShortNames(entry.service, entry.actions)
6851
+ );
6852
+ const covered = requestedActions.every(
6853
+ (requestedAction) => grantedActions.some(
6854
+ (grantedAction) => actionContains(grantedAction, requestedAction)
6855
+ )
6856
+ );
6857
+ if (!covered) {
6742
6858
  missing.push(cloneEntry(req));
6743
- continue;
6744
6859
  }
6745
6860
  }
6746
6861
  return { subset: missing.length === 0, missing };
6747
6862
  }
6748
- function canonicalizeEntryMatches(requested, granted) {
6863
+ function canonicalizeEntryScopeMatches(requested, granted) {
6749
6864
  if (requested.service !== granted.service) {
6750
6865
  return false;
6751
6866
  }
@@ -6755,15 +6870,6 @@ function canonicalizeEntryMatches(requested, granted) {
6755
6870
  if (!pathContains(granted.path, requested.path)) {
6756
6871
  return false;
6757
6872
  }
6758
- const reqActions = new Set(
6759
- expandActionShortNames(requested.service, requested.actions)
6760
- );
6761
- const grantedActions = expandActionShortNames(granted.service, granted.actions);
6762
- for (const a of reqActions) {
6763
- if (!grantedActions.some((grantedAction) => actionContains(grantedAction, a))) {
6764
- return false;
6765
- }
6766
- }
6767
6873
  if (!recapCaveatsEqual(requested.caveats, granted.caveats)) {
6768
6874
  return false;
6769
6875
  }