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

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/artifacts.js CHANGED
@@ -164,10 +164,10 @@ var require_decrypt_transport_response_error = __commonJS({
164
164
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
165
165
  var DecryptTransportResponseError_exports = {};
166
166
  __export(DecryptTransportResponseError_exports, {
167
- DecryptTransportResponseError: () => DecryptTransportResponseError4
167
+ DecryptTransportResponseError: () => DecryptTransportResponseError2
168
168
  });
169
169
  module.exports = __toCommonJS(DecryptTransportResponseError_exports);
170
- var DecryptTransportResponseError4 = class extends Error {
170
+ var DecryptTransportResponseError2 = class extends Error {
171
171
  constructor(status, permissionHint) {
172
172
  super("Node decrypt request failed");
173
173
  this.status = status;
@@ -185,9 +185,6 @@ import { z as z2 } from "zod";
185
185
  // ../sdk-core/src/manifest.ts
186
186
  var import_ms = __toESM(require_ms(), 1);
187
187
 
188
- // ../sdk-services/dist/index.js
189
- import { z } from "zod";
190
-
191
188
  // ../bootstrap/dist/index.js
192
189
  var CAPABILITIES = [
193
190
  { urn: "tinycloud.kv/get", service: "tinycloud.kv", status: "active" },
@@ -596,28 +593,6 @@ function withCapabilitiesReadForSpaces(resources) {
596
593
  }))
597
594
  ]);
598
595
  }
599
- function accountRegistryPermissions() {
600
- return [
601
- {
602
- service: "tinycloud.kv",
603
- space: ACCOUNT_REGISTRY_SPACE,
604
- path: ACCOUNT_REGISTRY_PATH,
605
- actions: [KV.GET, KV.PUT, KV.LIST]
606
- },
607
- {
608
- service: "tinycloud.kv",
609
- space: ACCOUNT_REGISTRY_SPACE,
610
- path: "spaces/",
611
- actions: [KV.GET, KV.PUT, KV.LIST]
612
- },
613
- {
614
- service: "tinycloud.sql",
615
- space: ACCOUNT_REGISTRY_SPACE,
616
- path: "account",
617
- actions: [SQL.READ, SQL.WRITE, SQL.SCHEMA]
618
- }
619
- ];
620
- }
621
596
  function composeManifestRequest(inputs, options = {}) {
622
597
  if (!Array.isArray(inputs) || inputs.length === 0) {
623
598
  throw new Error("composeManifestRequest requires at least one manifest");
@@ -631,7 +606,12 @@ function composeManifestRequest(inputs, options = {}) {
631
606
  return explicit.flatMap((entry) => expandPermissionEntry(entry, prefix, space));
632
607
  });
633
608
  if (includeAccountRegistryPermissions) {
634
- resources.push(...accountRegistryPermissions());
609
+ resources.push(
610
+ ...ACCOUNT_MANIFEST_PERMISSIONS.map((resource) => ({
611
+ ...resource,
612
+ actions: [...resource.actions]
613
+ }))
614
+ );
635
615
  }
636
616
  const manifestsByAppId = /* @__PURE__ */ new Map();
637
617
  for (const manifest of manifests) {
@@ -659,11 +639,60 @@ function composeBootstrapSpaceManifest(space) {
659
639
  includeAccountRegistryPermissions: false
660
640
  });
661
641
  }
642
+ var ACCOUNT_MANIFEST_PERMISSIONS = Object.freeze([
643
+ {
644
+ service: "tinycloud.kv",
645
+ space: ACCOUNT_REGISTRY_SPACE,
646
+ path: ACCOUNT_REGISTRY_PATH,
647
+ actions: [KV.GET, KV.PUT, KV.LIST]
648
+ },
649
+ {
650
+ service: "tinycloud.kv",
651
+ space: ACCOUNT_REGISTRY_SPACE,
652
+ path: "spaces/",
653
+ actions: [KV.GET, KV.PUT, KV.LIST]
654
+ },
655
+ {
656
+ service: "tinycloud.kv",
657
+ space: ACCOUNT_REGISTRY_SPACE,
658
+ path: "system/bootstrap/complete",
659
+ actions: [KV.GET, KV.PUT]
660
+ },
661
+ {
662
+ service: "tinycloud.delegation",
663
+ space: ACCOUNT_REGISTRY_SPACE,
664
+ path: "",
665
+ actions: ["tinycloud.delegation/list"]
666
+ },
667
+ {
668
+ service: "tinycloud.sql",
669
+ space: ACCOUNT_REGISTRY_SPACE,
670
+ path: "account",
671
+ actions: [SQL.READ, SQL.WRITE, SQL.SCHEMA]
672
+ },
673
+ {
674
+ service: "tinycloud.capabilities",
675
+ space: ACCOUNT_REGISTRY_SPACE,
676
+ path: "",
677
+ actions: [CAPABILITIES2.READ]
678
+ }
679
+ ]);
680
+ var BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST = composeBootstrapSpaceManifest(BOOTSTRAP_DEFAULT_SPACE);
681
+ var BOOTSTRAP_WIDENED_DEFAULT_SESSION_REQUEST = {
682
+ ...BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST,
683
+ resources: [
684
+ ...BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST.resources,
685
+ ...ACCOUNT_MANIFEST_PERMISSIONS.map((resource) => ({
686
+ ...resource,
687
+ actions: [...resource.actions]
688
+ }))
689
+ ]
690
+ };
662
691
  var BOOTSTRAP_SESSION_REQUESTS = Object.freeze(
663
692
  Object.fromEntries(
664
693
  BOOTSTRAP_SPACE_NAMES.map((space) => [
665
694
  space,
666
- composeBootstrapSpaceManifest(space)
695
+ space === BOOTSTRAP_DEFAULT_SPACE ? BOOTSTRAP_WIDENED_DEFAULT_SESSION_REQUEST : composeBootstrapSpaceManifest(space)
667
696
  ])
668
697
  )
669
698
  );
@@ -694,9 +723,8 @@ var BOOTSTRAP_ALLOWLIST = Object.freeze(
694
723
  );
695
724
 
696
725
  // ../sdk-services/dist/index.js
726
+ import { z } from "zod";
697
727
  var import_decrypt_transport_response_error = __toESM(require_decrypt_transport_response_error(), 1);
698
- var import_decrypt_transport_response_error2 = __toESM(require_decrypt_transport_response_error(), 1);
699
- var import_decrypt_transport_response_error3 = __toESM(require_decrypt_transport_response_error(), 1);
700
728
  var ErrorCodes = {
701
729
  // Common errors
702
730
  NOT_FOUND: "NOT_FOUND",
@@ -2237,6 +2265,7 @@ var KVService = class extends BaseService {
2237
2265
  }
2238
2266
  seen.add(path);
2239
2267
  }
2268
+ let requestMayHaveDispatched = false;
2240
2269
  try {
2241
2270
  const body = new FormData();
2242
2271
  for (let index = 0; index < items.length; index++) {
@@ -2254,14 +2283,31 @@ var KVService = class extends BaseService {
2254
2283
  action: KVAction.PUT
2255
2284
  }))
2256
2285
  );
2257
- const response = await this.context.fetch(`${this.host}/invoke`, {
2286
+ const url = `${this.host}/invoke`;
2287
+ const init = {
2258
2288
  method: "POST",
2259
2289
  headers,
2260
2290
  body,
2261
2291
  signal: this.combineSignals(options?.signal)
2262
- });
2292
+ };
2293
+ const fetchFn = this.context.fetch;
2294
+ requestMayHaveDispatched = true;
2295
+ const response = await fetchFn(url, init);
2263
2296
  if (!response.ok) {
2264
- const errorText = await response.text();
2297
+ let errorText;
2298
+ try {
2299
+ errorText = await response.text();
2300
+ } catch (textError) {
2301
+ const cause = textError instanceof Error ? textError : new Error(String(textError));
2302
+ return err(
2303
+ serviceError(
2304
+ ErrorCodes.KV_WRITE_FAILED,
2305
+ `Failed to batch put ${items.length} key(s): ${response.status} - <response body could not be read: ${cause.message}>`,
2306
+ "kv",
2307
+ { cause, meta: { status: response.status, statusText: response.statusText } }
2308
+ )
2309
+ );
2310
+ }
2265
2311
  if (response.status === 401 || response.status === 403) {
2266
2312
  const { resource, action } = parseAuthError(errorText);
2267
2313
  return err(authUnauthorizedError("kv", errorText, {
@@ -2287,19 +2333,73 @@ var KVService = class extends BaseService {
2287
2333
  )
2288
2334
  );
2289
2335
  }
2290
- const batchResponse = this.normalizeBatchPutResponse(await response.json());
2291
- if (!batchResponse || batchResponse.count !== batchResponse.written.length) {
2336
+ let rawBody;
2337
+ try {
2338
+ rawBody = await response.json();
2339
+ } catch (jsonError) {
2340
+ const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
2341
+ return err(
2342
+ serviceError(
2343
+ ErrorCodes.NETWORK_ERROR,
2344
+ `KV batchPut response was not valid JSON: ${cause.message}`,
2345
+ "kv",
2346
+ {
2347
+ cause,
2348
+ meta: {
2349
+ requestMayHaveDispatched: true,
2350
+ responseReceived: true,
2351
+ status: response.status,
2352
+ outcome: "batch-unconfirmed"
2353
+ }
2354
+ }
2355
+ )
2356
+ );
2357
+ }
2358
+ const batchResponse = this.normalizeBatchPutResponse(rawBody);
2359
+ if (!batchResponse) {
2292
2360
  return err(
2293
2361
  serviceError(
2294
2362
  ErrorCodes.NETWORK_ERROR,
2295
2363
  "KV batchPut response did not include matching written keys and count",
2296
- "kv"
2364
+ "kv",
2365
+ {
2366
+ meta: {
2367
+ requestMayHaveDispatched: true,
2368
+ responseReceived: true,
2369
+ status: response.status,
2370
+ outcome: "batch-unconfirmed"
2371
+ }
2372
+ }
2373
+ )
2374
+ );
2375
+ }
2376
+ const requestedPaths = new Set(paths);
2377
+ const writtenPaths = new Set(batchResponse.written);
2378
+ const matchesRequest = batchResponse.count === paths.length && batchResponse.written.length === paths.length && writtenPaths.size === paths.length && batchResponse.written.every((key) => requestedPaths.has(key));
2379
+ if (!matchesRequest) {
2380
+ return err(
2381
+ serviceError(
2382
+ ErrorCodes.NETWORK_ERROR,
2383
+ `KV batchPut response did not confirm all ${paths.length} requested key(s) were written`,
2384
+ "kv",
2385
+ {
2386
+ meta: {
2387
+ requestMayHaveDispatched: true,
2388
+ responseReceived: true,
2389
+ status: response.status,
2390
+ outcome: "batch-unconfirmed"
2391
+ }
2392
+ }
2297
2393
  )
2298
2394
  );
2299
2395
  }
2300
2396
  return ok(batchResponse);
2301
2397
  } catch (error) {
2302
- return err(wrapError("kv", error));
2398
+ const wrapped = wrapError("kv", error);
2399
+ return err({
2400
+ ...wrapped,
2401
+ meta: { ...wrapped.meta, requestMayHaveDispatched }
2402
+ });
2303
2403
  }
2304
2404
  });
2305
2405
  }
@@ -6637,15 +6737,28 @@ function normalizeSpace(space) {
6637
6737
  function isCapabilitySubset(requested, granted) {
6638
6738
  const missing = [];
6639
6739
  for (const req of requested) {
6640
- const match = granted.find((g) => canonicalizeEntryMatches(req, g));
6641
- if (match === void 0) {
6740
+ const matchingScope = granted.filter(
6741
+ (entry) => canonicalizeEntryScopeMatches(req, entry)
6742
+ );
6743
+ const requestedActions = expandActionShortNames(
6744
+ req.service,
6745
+ req.actions
6746
+ );
6747
+ const grantedActions = matchingScope.flatMap(
6748
+ (entry) => expandActionShortNames(entry.service, entry.actions)
6749
+ );
6750
+ const covered = requestedActions.every(
6751
+ (requestedAction) => grantedActions.some(
6752
+ (grantedAction) => actionContains(grantedAction, requestedAction)
6753
+ )
6754
+ );
6755
+ if (!covered) {
6642
6756
  missing.push(cloneEntry(req));
6643
- continue;
6644
6757
  }
6645
6758
  }
6646
6759
  return { subset: missing.length === 0, missing };
6647
6760
  }
6648
- function canonicalizeEntryMatches(requested, granted) {
6761
+ function canonicalizeEntryScopeMatches(requested, granted) {
6649
6762
  if (requested.service !== granted.service) {
6650
6763
  return false;
6651
6764
  }
@@ -6655,15 +6768,6 @@ function canonicalizeEntryMatches(requested, granted) {
6655
6768
  if (!pathContains(granted.path, requested.path)) {
6656
6769
  return false;
6657
6770
  }
6658
- const reqActions = new Set(
6659
- expandActionShortNames(requested.service, requested.actions)
6660
- );
6661
- const grantedActions = expandActionShortNames(granted.service, granted.actions);
6662
- for (const a of reqActions) {
6663
- if (!grantedActions.some((grantedAction) => actionContains(grantedAction, a))) {
6664
- return false;
6665
- }
6666
- }
6667
6771
  if (!recapCaveatsEqual(requested.caveats, granted.caveats)) {
6668
6772
  return false;
6669
6773
  }