@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.
@@ -333,28 +333,6 @@ function withCapabilitiesReadForSpaces(resources) {
333
333
  }))
334
334
  ]);
335
335
  }
336
- function accountRegistryPermissions() {
337
- return [
338
- {
339
- service: "tinycloud.kv",
340
- space: ACCOUNT_REGISTRY_SPACE,
341
- path: ACCOUNT_REGISTRY_PATH,
342
- actions: [KV.GET, KV.PUT, KV.LIST]
343
- },
344
- {
345
- service: "tinycloud.kv",
346
- space: ACCOUNT_REGISTRY_SPACE,
347
- path: "spaces/",
348
- actions: [KV.GET, KV.PUT, KV.LIST]
349
- },
350
- {
351
- service: "tinycloud.sql",
352
- space: ACCOUNT_REGISTRY_SPACE,
353
- path: "account",
354
- actions: [SQL.READ, SQL.WRITE, SQL.SCHEMA]
355
- }
356
- ];
357
- }
358
336
  function composeManifestRequest(inputs, options = {}) {
359
337
  if (!Array.isArray(inputs) || inputs.length === 0) {
360
338
  throw new Error("composeManifestRequest requires at least one manifest");
@@ -368,7 +346,12 @@ function composeManifestRequest(inputs, options = {}) {
368
346
  return explicit.flatMap((entry) => expandPermissionEntry(entry, prefix, space));
369
347
  });
370
348
  if (includeAccountRegistryPermissions) {
371
- resources.push(...accountRegistryPermissions());
349
+ resources.push(
350
+ ...ACCOUNT_MANIFEST_PERMISSIONS.map((resource) => ({
351
+ ...resource,
352
+ actions: [...resource.actions]
353
+ }))
354
+ );
372
355
  }
373
356
  const manifestsByAppId = /* @__PURE__ */ new Map();
374
357
  for (const manifest of manifests) {
@@ -396,7 +379,7 @@ function composeBootstrapSpaceManifest(space) {
396
379
  includeAccountRegistryPermissions: false
397
380
  });
398
381
  }
399
- 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;
382
+ 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;
400
383
  var init_dist = __esm({
401
384
  "../bootstrap/dist/index.js"() {
402
385
  "use strict";
@@ -701,11 +684,60 @@ var init_dist = __esm({
701
684
  name: BOOTSTRAP_ENCRYPTION_NETWORK_NAME
702
685
  }
703
686
  };
687
+ ACCOUNT_MANIFEST_PERMISSIONS = Object.freeze([
688
+ {
689
+ service: "tinycloud.kv",
690
+ space: ACCOUNT_REGISTRY_SPACE,
691
+ path: ACCOUNT_REGISTRY_PATH,
692
+ actions: [KV.GET, KV.PUT, KV.LIST]
693
+ },
694
+ {
695
+ service: "tinycloud.kv",
696
+ space: ACCOUNT_REGISTRY_SPACE,
697
+ path: "spaces/",
698
+ actions: [KV.GET, KV.PUT, KV.LIST]
699
+ },
700
+ {
701
+ service: "tinycloud.kv",
702
+ space: ACCOUNT_REGISTRY_SPACE,
703
+ path: "system/bootstrap/complete",
704
+ actions: [KV.GET, KV.PUT]
705
+ },
706
+ {
707
+ service: "tinycloud.delegation",
708
+ space: ACCOUNT_REGISTRY_SPACE,
709
+ path: "",
710
+ actions: ["tinycloud.delegation/list"]
711
+ },
712
+ {
713
+ service: "tinycloud.sql",
714
+ space: ACCOUNT_REGISTRY_SPACE,
715
+ path: "account",
716
+ actions: [SQL.READ, SQL.WRITE, SQL.SCHEMA]
717
+ },
718
+ {
719
+ service: "tinycloud.capabilities",
720
+ space: ACCOUNT_REGISTRY_SPACE,
721
+ path: "",
722
+ actions: [CAPABILITIES2.READ]
723
+ }
724
+ ]);
725
+ BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST = composeBootstrapSpaceManifest(BOOTSTRAP_DEFAULT_SPACE);
726
+ BOOTSTRAP_WIDENED_DEFAULT_SESSION_REQUEST = {
727
+ ...BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST,
728
+ resources: [
729
+ ...BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST.resources,
730
+ ...ACCOUNT_MANIFEST_PERMISSIONS.map((resource) => ({
731
+ ...resource,
732
+ actions: [...resource.actions]
733
+ }))
734
+ ]
735
+ };
704
736
  BOOTSTRAP_SESSION_REQUESTS = Object.freeze(
705
737
  Object.fromEntries(
706
738
  BOOTSTRAP_SPACE_NAMES.map((space) => [
707
739
  space,
708
- composeBootstrapSpaceManifest(space)
740
+ space === BOOTSTRAP_DEFAULT_SPACE ? BOOTSTRAP_WIDENED_DEFAULT_SESSION_REQUEST : composeBootstrapSpaceManifest(space)
709
741
  ])
710
742
  )
711
743
  );
@@ -760,10 +792,10 @@ var require_decrypt_transport_response_error = __commonJS({
760
792
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
761
793
  var DecryptTransportResponseError_exports = {};
762
794
  __export2(DecryptTransportResponseError_exports, {
763
- DecryptTransportResponseError: () => DecryptTransportResponseError4
795
+ DecryptTransportResponseError: () => DecryptTransportResponseError2
764
796
  });
765
797
  module.exports = __toCommonJS(DecryptTransportResponseError_exports);
766
- var DecryptTransportResponseError4 = class extends Error {
798
+ var DecryptTransportResponseError2 = class extends Error {
767
799
  constructor(status, permissionHint) {
768
800
  super("Node decrypt request failed");
769
801
  this.status = status;
@@ -2396,15 +2428,13 @@ function encOk(data) {
2396
2428
  function encErr(error) {
2397
2429
  return { ok: false, error };
2398
2430
  }
2399
- var 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;
2431
+ var 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;
2400
2432
  var init_dist2 = __esm({
2401
2433
  "../sdk-services/dist/index.js"() {
2402
2434
  "use strict";
2403
2435
  init_dist();
2404
2436
  init_dist();
2405
2437
  import_decrypt_transport_response_error = __toESM(require_decrypt_transport_response_error(), 1);
2406
- import_decrypt_transport_response_error2 = __toESM(require_decrypt_transport_response_error(), 1);
2407
- import_decrypt_transport_response_error3 = __toESM(require_decrypt_transport_response_error(), 1);
2408
2438
  ErrorCodes = {
2409
2439
  // Common errors
2410
2440
  NOT_FOUND: "NOT_FOUND",
@@ -3676,6 +3706,7 @@ var init_dist2 = __esm({
3676
3706
  }
3677
3707
  seen.add(path);
3678
3708
  }
3709
+ let requestMayHaveDispatched = false;
3679
3710
  try {
3680
3711
  const body = new FormData();
3681
3712
  for (let index = 0; index < items.length; index++) {
@@ -3693,14 +3724,31 @@ var init_dist2 = __esm({
3693
3724
  action: KVAction.PUT
3694
3725
  }))
3695
3726
  );
3696
- const response = await this.context.fetch(`${this.host}/invoke`, {
3727
+ const url = `${this.host}/invoke`;
3728
+ const init = {
3697
3729
  method: "POST",
3698
3730
  headers,
3699
3731
  body,
3700
3732
  signal: this.combineSignals(options?.signal)
3701
- });
3733
+ };
3734
+ const fetchFn = this.context.fetch;
3735
+ requestMayHaveDispatched = true;
3736
+ const response = await fetchFn(url, init);
3702
3737
  if (!response.ok) {
3703
- const errorText = await response.text();
3738
+ let errorText;
3739
+ try {
3740
+ errorText = await response.text();
3741
+ } catch (textError) {
3742
+ const cause = textError instanceof Error ? textError : new Error(String(textError));
3743
+ return err(
3744
+ serviceError(
3745
+ ErrorCodes.KV_WRITE_FAILED,
3746
+ `Failed to batch put ${items.length} key(s): ${response.status} - <response body could not be read: ${cause.message}>`,
3747
+ "kv",
3748
+ { cause, meta: { status: response.status, statusText: response.statusText } }
3749
+ )
3750
+ );
3751
+ }
3704
3752
  if (response.status === 401 || response.status === 403) {
3705
3753
  const { resource, action } = parseAuthError(errorText);
3706
3754
  return err(authUnauthorizedError("kv", errorText, {
@@ -3726,19 +3774,73 @@ var init_dist2 = __esm({
3726
3774
  )
3727
3775
  );
3728
3776
  }
3729
- const batchResponse = this.normalizeBatchPutResponse(await response.json());
3730
- if (!batchResponse || batchResponse.count !== batchResponse.written.length) {
3777
+ let rawBody;
3778
+ try {
3779
+ rawBody = await response.json();
3780
+ } catch (jsonError) {
3781
+ const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
3782
+ return err(
3783
+ serviceError(
3784
+ ErrorCodes.NETWORK_ERROR,
3785
+ `KV batchPut response was not valid JSON: ${cause.message}`,
3786
+ "kv",
3787
+ {
3788
+ cause,
3789
+ meta: {
3790
+ requestMayHaveDispatched: true,
3791
+ responseReceived: true,
3792
+ status: response.status,
3793
+ outcome: "batch-unconfirmed"
3794
+ }
3795
+ }
3796
+ )
3797
+ );
3798
+ }
3799
+ const batchResponse = this.normalizeBatchPutResponse(rawBody);
3800
+ if (!batchResponse) {
3731
3801
  return err(
3732
3802
  serviceError(
3733
3803
  ErrorCodes.NETWORK_ERROR,
3734
3804
  "KV batchPut response did not include matching written keys and count",
3735
- "kv"
3805
+ "kv",
3806
+ {
3807
+ meta: {
3808
+ requestMayHaveDispatched: true,
3809
+ responseReceived: true,
3810
+ status: response.status,
3811
+ outcome: "batch-unconfirmed"
3812
+ }
3813
+ }
3814
+ )
3815
+ );
3816
+ }
3817
+ const requestedPaths = new Set(paths);
3818
+ const writtenPaths = new Set(batchResponse.written);
3819
+ const matchesRequest = batchResponse.count === paths.length && batchResponse.written.length === paths.length && writtenPaths.size === paths.length && batchResponse.written.every((key) => requestedPaths.has(key));
3820
+ if (!matchesRequest) {
3821
+ return err(
3822
+ serviceError(
3823
+ ErrorCodes.NETWORK_ERROR,
3824
+ `KV batchPut response did not confirm all ${paths.length} requested key(s) were written`,
3825
+ "kv",
3826
+ {
3827
+ meta: {
3828
+ requestMayHaveDispatched: true,
3829
+ responseReceived: true,
3830
+ status: response.status,
3831
+ outcome: "batch-unconfirmed"
3832
+ }
3833
+ }
3736
3834
  )
3737
3835
  );
3738
3836
  }
3739
3837
  return ok(batchResponse);
3740
3838
  } catch (error) {
3741
- return err(wrapError("kv", error));
3839
+ const wrapped = wrapError("kv", error);
3840
+ return err({
3841
+ ...wrapped,
3842
+ meta: { ...wrapped.meta, requestMayHaveDispatched }
3843
+ });
3742
3844
  }
3743
3845
  });
3744
3846
  }
@@ -6735,15 +6837,28 @@ function normalizeSpace(space) {
6735
6837
  function isCapabilitySubset(requested, granted) {
6736
6838
  const missing = [];
6737
6839
  for (const req of requested) {
6738
- const match = granted.find((g) => canonicalizeEntryMatches(req, g));
6739
- if (match === void 0) {
6840
+ const matchingScope = granted.filter(
6841
+ (entry) => canonicalizeEntryScopeMatches(req, entry)
6842
+ );
6843
+ const requestedActions = expandActionShortNames(
6844
+ req.service,
6845
+ req.actions
6846
+ );
6847
+ const grantedActions = matchingScope.flatMap(
6848
+ (entry) => expandActionShortNames(entry.service, entry.actions)
6849
+ );
6850
+ const covered = requestedActions.every(
6851
+ (requestedAction) => grantedActions.some(
6852
+ (grantedAction) => actionContains(grantedAction, requestedAction)
6853
+ )
6854
+ );
6855
+ if (!covered) {
6740
6856
  missing.push(cloneEntry(req));
6741
- continue;
6742
6857
  }
6743
6858
  }
6744
6859
  return { subset: missing.length === 0, missing };
6745
6860
  }
6746
- function canonicalizeEntryMatches(requested, granted) {
6861
+ function canonicalizeEntryScopeMatches(requested, granted) {
6747
6862
  if (requested.service !== granted.service) {
6748
6863
  return false;
6749
6864
  }
@@ -6753,15 +6868,6 @@ function canonicalizeEntryMatches(requested, granted) {
6753
6868
  if (!pathContains(granted.path, requested.path)) {
6754
6869
  return false;
6755
6870
  }
6756
- const reqActions = new Set(
6757
- expandActionShortNames(requested.service, requested.actions)
6758
- );
6759
- const grantedActions = expandActionShortNames(granted.service, granted.actions);
6760
- for (const a of reqActions) {
6761
- if (!grantedActions.some((grantedAction) => actionContains(grantedAction, a))) {
6762
- return false;
6763
- }
6764
- }
6765
6871
  if (!recapCaveatsEqual(requested.caveats, granted.caveats)) {
6766
6872
  return false;
6767
6873
  }