@tinycloud/operations 0.4.0-beta.10 → 0.4.0-beta.12

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
@@ -42,6 +42,17 @@ function operationError(code, message, options = {}) {
42
42
  ...options.details === void 0 ? {} : { details: options.details }
43
43
  };
44
44
  }
45
+ function storageFullOperationError(error) {
46
+ if (typeof error !== "object" || error === null) return void 0;
47
+ const { code, meta } = error;
48
+ if (code !== "STORAGE_QUOTA_EXCEEDED" && code !== "STORAGE_LIMIT_REACHED") return void 0;
49
+ const account = typeof meta === "object" && meta !== null ? meta.account : void 0;
50
+ return operationError(
51
+ "STORAGE_QUOTA_EXCEEDED",
52
+ code === "STORAGE_LIMIT_REACHED" ? `This write is larger than the TinyCloud storage left, so nothing was written. Reading still works. ${MANAGE_STORAGE}` : `TinyCloud storage is full, so nothing was written. Reading still works. ${MANAGE_STORAGE}`,
53
+ { retryable: false, ...account === void 0 ? {} : { details: { account } } }
54
+ );
55
+ }
45
56
  function isOperationErrorCode(value) {
46
57
  return typeof value === "string" && OPERATION_ERROR_CODES.includes(value);
47
58
  }
@@ -67,7 +78,7 @@ function sanitizeOperationError(error) {
67
78
  ...candidate.details === void 0 ? {} : { details: candidate.details }
68
79
  });
69
80
  }
70
- var OPERATION_ERROR_CODES, OperationInvocationError;
81
+ var OPERATION_ERROR_CODES, MANAGE_STORAGE, OperationInvocationError;
71
82
  var init_errors = __esm({
72
83
  "src/errors.ts"() {
73
84
  "use strict";
@@ -90,6 +101,7 @@ var init_errors = __esm({
90
101
  "ENCRYPTION_NETWORK_UNRESOLVED",
91
102
  "NODE_UNREACHABLE",
92
103
  "NODE_ERROR",
104
+ "STORAGE_QUOTA_EXCEEDED",
93
105
  "KV_NOT_FOUND",
94
106
  "KV_PRECONDITION_FAILED",
95
107
  "KV_CONFLICT",
@@ -103,6 +115,7 @@ var init_errors = __esm({
103
115
  "OUTPUT_INVALID",
104
116
  "INTERNAL_ERROR"
105
117
  ];
118
+ MANAGE_STORAGE = "Do not retry: the owner must free up space or upgrade at https://account.tinycloud.xyz/billing.";
106
119
  OperationInvocationError = class extends Error {
107
120
  constructor(operationError2) {
108
121
  super(operationError2.message);
@@ -2532,6 +2545,8 @@ function kvFailure(error, action) {
2532
2545
  if (error.code === "KV_RESPONSE_TOO_LARGE") {
2533
2546
  return { status: "error", error: operationError("KV_RESPONSE_TOO_LARGE", `The TinyCloud KV value exceeds ${MAX_KV_VALUE_BYTES} bytes.`) };
2534
2547
  }
2548
+ const storageFull = storageFullOperationError(error);
2549
+ if (storageFull) return { status: "error", error: storageFull };
2535
2550
  return nodeFailure(action);
2536
2551
  }
2537
2552
  function nodeFailure(action) {
@@ -3150,7 +3165,7 @@ async function executeSqlDml(context, input) {
3150
3165
  const runtime = runtimeContext2(context);
3151
3166
  const space = resolveSqlSpace(runtime, input.space);
3152
3167
  const result = await runtimeNode2(runtime).sqlForSpace(space).db(input.database).execute(input.sql, input.params.map(decodeSqlInputValue));
3153
- if (!result.ok) return authorizationFailure(result.error, "execute the SQLite statement") ?? sqlMutationFailure();
3168
+ if (!result.ok) return authorizationFailure(result.error, "execute the SQLite statement") ?? sqlMutationFailure(result.error);
3154
3169
  const normalized = normalizeExecuteResult(result.data);
3155
3170
  return {
3156
3171
  status: "ok",
@@ -3164,7 +3179,7 @@ async function executeSqlDml(context, input) {
3164
3179
  };
3165
3180
  } catch (error) {
3166
3181
  if (error instanceof OperationInvocationError) return { status: "error", error: error.operationError };
3167
- return authorizationFailure(error, "execute the SQLite statement") ?? sqlMutationFailure();
3182
+ return authorizationFailure(error, "execute the SQLite statement") ?? sqlMutationFailure(error);
3168
3183
  }
3169
3184
  }
3170
3185
  function isSingleReadQuery(sql) {
@@ -3341,7 +3356,9 @@ function sqlServiceFailure(error, action) {
3341
3356
  }
3342
3357
  return nodeFailure2(action);
3343
3358
  }
3344
- function sqlMutationFailure() {
3359
+ function sqlMutationFailure(error) {
3360
+ const storageFull = storageFullOperationError(error);
3361
+ if (storageFull) return { status: "error", error: storageFull };
3345
3362
  return {
3346
3363
  status: "error",
3347
3364
  error: operationError(
@@ -3598,6 +3615,32 @@ ORDER BY type, name`;
3598
3615
  }
3599
3616
  });
3600
3617
 
3618
+ // src/contract.ts
3619
+ var STORED_GRANT_SKIP_REASONS, DELEGATION_CID_PATTERN;
3620
+ var init_contract = __esm({
3621
+ "src/contract.ts"() {
3622
+ "use strict";
3623
+ STORED_GRANT_SKIP_REASONS = [
3624
+ "malformed",
3625
+ "unbound",
3626
+ "outside_request",
3627
+ "host_mismatch",
3628
+ "expired",
3629
+ "cid_mismatch",
3630
+ "authority_invalid",
3631
+ "proof_missing",
3632
+ "proof_invalid",
3633
+ "owner_mismatch",
3634
+ "audience_mismatch",
3635
+ "resources_mismatch",
3636
+ "unsupported",
3637
+ "activation_rejected",
3638
+ "invalid"
3639
+ ];
3640
+ DELEGATION_CID_PATTERN = "^bafkr4i[a-z2-7]{52}$";
3641
+ }
3642
+ });
3643
+
3601
3644
  // src/delegation-binding.ts
3602
3645
  function bindingMigrationPath(profile) {
3603
3646
  return (0, import_node_path2.join)(profilePath(profile), "delegation-binding-migration.json");
@@ -3621,12 +3664,12 @@ function replayLimit(entry, migrated) {
3621
3664
  if (binding.success) return binding.data.requested;
3622
3665
  return !migrated && kind === "compact" && !("authorityRequest" in entry) ? "signed" : void 0;
3623
3666
  }
3624
- function bindToSignedCapabilities(record, signed, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3667
+ function bindToRequest(record, requested, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3625
3668
  return {
3626
3669
  ...record,
3627
3670
  authorityRequest: DelegationRequestBindingSchema.parse({
3628
3671
  requestId: `${REQUEST_ID_PREFIX[source]}:${record.delegation.cid}`,
3629
- requested: structuredClone(signed)
3672
+ requested: structuredClone(requested)
3630
3673
  }),
3631
3674
  authorityRequestAudit: { source, recordedAt, note: BINDING_NOTES[source] }
3632
3675
  };
@@ -3682,23 +3725,40 @@ async function migratedRecord(node, entry, host, recordedAt) {
3682
3725
  const signed = await signedCapabilities(node, delegation, host);
3683
3726
  if (signed === void 0) return void 0;
3684
3727
  try {
3685
- return bindToSignedCapabilities({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
3728
+ return bindToRequest({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
3686
3729
  } catch {
3687
3730
  return void 0;
3688
3731
  }
3689
3732
  }
3733
+ function skipped(reason) {
3734
+ return { status: "skipped", reason };
3735
+ }
3690
3736
  async function replayStoredDelegation(node, entry, options) {
3737
+ const kind = storedDelegationKind(entry);
3691
3738
  const delegation = normalizeStoredDelegation(entry);
3692
- if (delegation === void 0 || delegation.expiry.getTime() <= Date.now()) return void 0;
3739
+ if (kind === "refused" || delegation === void 0) return skipped("malformed");
3740
+ if (delegation.expiry.getTime() <= Date.now()) return NOT_REPLAYED;
3741
+ if (kind === "other") return skipped("proof_missing");
3693
3742
  const limit = replayLimit(entry, options.migrated);
3694
- if (limit === void 0) return void 0;
3695
- const authorize = limit === "signed" ? () => true : (effective) => delegationWithinRequest(limit, effective, options.resolveSpace);
3743
+ if (limit === void 0) return skipped("unbound");
3744
+ let outsideRequest = false;
3745
+ const authorize = limit === "signed" ? () => true : (effective) => {
3746
+ outsideRequest = !delegationWithinRequest(limit, effective, options.resolveSpace);
3747
+ return !outsideRequest;
3748
+ };
3696
3749
  try {
3697
- return await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize });
3698
- } catch {
3699
- return void 0;
3750
+ return {
3751
+ status: "installed",
3752
+ delegation: await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize })
3753
+ };
3754
+ } catch (error) {
3755
+ return skipped(outsideRequest ? "outside_request" : rejectionReason(error));
3700
3756
  }
3701
3757
  }
3758
+ function rejectionReason(error) {
3759
+ const reason = isRecord4(error) ? error.reason : void 0;
3760
+ return typeof reason === "string" && STORED_GRANT_SKIP_REASONS.includes(reason) ? reason : "invalid";
3761
+ }
3702
3762
  async function bindingMigrationRecorded(profile) {
3703
3763
  try {
3704
3764
  return await readJson(bindingMigrationPath(profile)) !== null;
@@ -3743,7 +3803,7 @@ function recordKey(entry) {
3743
3803
  function isRecord4(value) {
3744
3804
  return typeof value === "object" && value !== null && !Array.isArray(value);
3745
3805
  }
3746
- var import_node_path2, nodeSdk3, REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS;
3806
+ var import_node_path2, nodeSdk3, REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS, NOT_REPLAYED;
3747
3807
  var init_delegation_binding = __esm({
3748
3808
  "src/delegation-binding.ts"() {
3749
3809
  "use strict";
@@ -3751,17 +3811,21 @@ var init_delegation_binding = __esm({
3751
3811
  nodeSdk3 = __toESM(require("@tinycloud/node-sdk"), 1);
3752
3812
  init_artifacts();
3753
3813
  init_authority();
3814
+ init_contract();
3754
3815
  init_state();
3755
3816
  init_secrets();
3756
3817
  REQUEST_ID_PREFIX = {
3757
3818
  migration: "migrated",
3758
- "unbound-import": "unbound-import"
3819
+ "unbound-import": "unbound-import",
3820
+ "cli-grant": "cli-grant"
3759
3821
  };
3760
3822
  BINDING_NOTES = {
3761
3823
  migration: "Stored without a request binding before bindings were required. Bound at migration to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them.",
3762
- "unbound-import": "Imported by `tc auth import` without a stored request. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them."
3824
+ "unbound-import": "Imported by `tc auth import` without a stored request. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them.",
3825
+ "cli-grant": "Granted to this CLI by `tc auth request --grant` or a permission escalation, and checked against the request it answered. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them."
3763
3826
  };
3764
3827
  MIGRATION_LOCK_TIMEOUT_MS = 250;
3828
+ NOT_REPLAYED = { status: "not-replayed" };
3765
3829
  }
3766
3830
  });
3767
3831
 
@@ -4173,7 +4237,18 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4173
4237
  ));
4174
4238
  }
4175
4239
  } else {
4176
- await node.restoreSession(activeSession);
4240
+ try {
4241
+ await node.restoreSession(activeSession);
4242
+ } catch (error) {
4243
+ if (isRecord7(error) && error.code === "AUTH_EXPIRED") {
4244
+ return failed(summary, operationError(
4245
+ "AUTH_REQUIRED",
4246
+ "The stored session has expired or is no longer valid. Sign in again.",
4247
+ { retryable: false }
4248
+ ));
4249
+ }
4250
+ throw error;
4251
+ }
4177
4252
  }
4178
4253
  const activeSessionDid = normalizeDid(node.sessionDid);
4179
4254
  const livePrincipalDid = normalizeDid(node.did);
@@ -4181,6 +4256,7 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4181
4256
  const livePermissions = [...node.getVerifiedSessionCapabilities()];
4182
4257
  const resolveSpace2 = operationSpaceResolver(node, authenticatedSpace ?? summary.space);
4183
4258
  const seenCids = /* @__PURE__ */ new Set();
4259
+ const warnings = [];
4184
4260
  if (!explicitPrivateKeyOverride) {
4185
4261
  const activator = node;
4186
4262
  const migrated = await prepareStoredDelegationReplay(profileName, activator, {
@@ -4190,20 +4266,25 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4190
4266
  migrate: activeSession !== void 0
4191
4267
  });
4192
4268
  for (const entry of await readAdditionalDelegations(profileName)) {
4193
- const activated = await replayStoredDelegation(activator, entry, {
4269
+ const replay = await replayStoredDelegation(activator, entry, {
4194
4270
  host: summary.host,
4195
4271
  migrated,
4196
4272
  resolveSpace: resolveSpace2
4197
4273
  });
4198
- if (activated !== void 0 && !seenCids.has(activated.cid)) {
4199
- seenCids.add(activated.cid);
4200
- livePermissions.push(...activated.effectivePermissions);
4274
+ if (replay.status === "installed") {
4275
+ if (!seenCids.has(replay.delegation.cid)) {
4276
+ seenCids.add(replay.delegation.cid);
4277
+ livePermissions.push(...replay.delegation.effectivePermissions);
4278
+ }
4279
+ } else if (replay.status === "skipped") {
4280
+ warnings.push(skippedGrantWarning(node, entry, replay.reason));
4201
4281
  }
4202
4282
  }
4203
4283
  }
4204
4284
  const runtime = {
4205
4285
  node,
4206
- granted: canonicalizeCapabilities(livePermissions)
4286
+ granted: canonicalizeCapabilities(livePermissions),
4287
+ ...warnings.length === 0 ? {} : { warnings }
4207
4288
  };
4208
4289
  return {
4209
4290
  ok: true,
@@ -4227,6 +4308,24 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4227
4308
  ));
4228
4309
  }
4229
4310
  }
4311
+ function skippedGrantWarning(node, entry, reason) {
4312
+ const grantCid = verifiedStoredCid(node, entry);
4313
+ return { code: "STORED_GRANT_SKIPPED", reason, ...grantCid === void 0 ? {} : { grantCid } };
4314
+ }
4315
+ function verifiedStoredCid(node, entry) {
4316
+ const delegation = isRecord7(entry) && isRecord7(entry.delegation) ? entry.delegation : void 0;
4317
+ const header = isRecord7(delegation?.delegationHeader) ? delegation.delegationHeader : void 0;
4318
+ const cid = delegation?.cid;
4319
+ const computeDelegationCid = isRecord7(node) ? node.computeDelegationCid : void 0;
4320
+ if (typeof cid !== "string" || typeof header?.Authorization !== "string" || typeof computeDelegationCid !== "function") {
4321
+ return void 0;
4322
+ }
4323
+ try {
4324
+ return computeDelegationCid.call(node, header.Authorization) === cid && DELEGATION_CID.test(cid) ? cid : void 0;
4325
+ } catch {
4326
+ return void 0;
4327
+ }
4328
+ }
4230
4329
  function spaceForAuthenticatedPrincipal(principal2) {
4231
4330
  if (principal2 === void 0) return void 0;
4232
4331
  try {
@@ -4275,17 +4374,19 @@ function unresolvedSummary(target) {
4275
4374
  posture: "unauthenticated"
4276
4375
  };
4277
4376
  }
4278
- var nodeSdk4;
4377
+ var nodeSdk4, DELEGATION_CID;
4279
4378
  var init_runtime = __esm({
4280
4379
  "src/runtime.ts"() {
4281
4380
  "use strict";
4282
4381
  nodeSdk4 = __toESM(require("@tinycloud/node-sdk"), 1);
4382
+ init_contract();
4283
4383
  init_authority();
4284
4384
  init_errors();
4285
4385
  init_profile();
4286
4386
  init_secrets();
4287
4387
  init_state();
4288
4388
  init_delegation_binding();
4389
+ DELEGATION_CID = new RegExp(DELEGATION_CID_PATTERN);
4289
4390
  }
4290
4391
  });
4291
4392
 
@@ -4590,8 +4691,19 @@ var SAFE_ERROR_DETAIL_POLICIES = {
4590
4691
  DELEGATION_HOST_MISMATCH: {
4591
4692
  expectedHost: safeOriginHost,
4592
4693
  artifactHost: safeOriginHost
4694
+ },
4695
+ STORAGE_QUOTA_EXCEEDED: {
4696
+ account: safeStorageAccount
4593
4697
  }
4594
4698
  };
4699
+ function safeStorageAccount(value) {
4700
+ if (!isRecord(value)) return void 0;
4701
+ const { usedBytes, limitBytes, plan } = value;
4702
+ if (!Number.isSafeInteger(usedBytes) || usedBytes < 0 || !Number.isSafeInteger(limitBytes) || limitBytes <= 0) {
4703
+ return void 0;
4704
+ }
4705
+ return typeof plan === "string" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan) ? { usedBytes, limitBytes, plan } : { usedBytes, limitBytes };
4706
+ }
4595
4707
  function redactSafeErrorDetails(code, details) {
4596
4708
  const policy = SAFE_ERROR_DETAIL_POLICIES[code];
4597
4709
  if (policy === void 0) return void 0;
@@ -4657,9 +4769,13 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
4657
4769
  unknownInput
4658
4770
  );
4659
4771
  if (isPreparedError(prepared)) return prepared.result;
4660
- return executePreparedInvocation(prepared);
4772
+ return withRuntimeWarnings(await executePreparedInvocation(prepared), prepared);
4661
4773
  });
4662
4774
  }
4775
+ function withRuntimeWarnings(result, prepared) {
4776
+ const warnings = prepared.runtimeResolution.context.runtime?.warnings;
4777
+ return warnings === void 0 || warnings.length === 0 ? result : { ...result, warnings: [...warnings] };
4778
+ }
4663
4779
  function isPreparedError(prepared) {
4664
4780
  return "status" in prepared;
4665
4781
  }