@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.
@@ -1,4 +1,4 @@
1
- import { I as InvocationTarget, a as OperationResult } from './contract-CjuEjE-M.cjs';
1
+ import { I as InvocationTarget, a as OperationResult } from './contract-BRwWJpPs.cjs';
2
2
 
3
3
  /**
4
4
  * The only published CLI acquisition helper. Its fixed operation identity is
@@ -1,4 +1,4 @@
1
- import { I as InvocationTarget, a as OperationResult } from './contract-CjuEjE-M.js';
1
+ import { I as InvocationTarget, a as OperationResult } from './contract-BRwWJpPs.js';
2
2
 
3
3
  /**
4
4
  * The only published CLI acquisition helper. Its fixed operation identity is
@@ -40,6 +40,17 @@ function operationError(code, message, options = {}) {
40
40
  ...options.details === void 0 ? {} : { details: options.details }
41
41
  };
42
42
  }
43
+ function storageFullOperationError(error) {
44
+ if (typeof error !== "object" || error === null) return void 0;
45
+ const { code, meta } = error;
46
+ if (code !== "STORAGE_QUOTA_EXCEEDED" && code !== "STORAGE_LIMIT_REACHED") return void 0;
47
+ const account = typeof meta === "object" && meta !== null ? meta.account : void 0;
48
+ return operationError(
49
+ "STORAGE_QUOTA_EXCEEDED",
50
+ 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}`,
51
+ { retryable: false, ...account === void 0 ? {} : { details: { account } } }
52
+ );
53
+ }
43
54
  function isOperationErrorCode(value) {
44
55
  return typeof value === "string" && OPERATION_ERROR_CODES.includes(value);
45
56
  }
@@ -65,7 +76,7 @@ function sanitizeOperationError(error) {
65
76
  ...candidate.details === void 0 ? {} : { details: candidate.details }
66
77
  });
67
78
  }
68
- var OPERATION_ERROR_CODES, OperationInvocationError;
79
+ var OPERATION_ERROR_CODES, MANAGE_STORAGE, OperationInvocationError;
69
80
  var init_errors = __esm({
70
81
  "src/errors.ts"() {
71
82
  "use strict";
@@ -88,6 +99,7 @@ var init_errors = __esm({
88
99
  "ENCRYPTION_NETWORK_UNRESOLVED",
89
100
  "NODE_UNREACHABLE",
90
101
  "NODE_ERROR",
102
+ "STORAGE_QUOTA_EXCEEDED",
91
103
  "KV_NOT_FOUND",
92
104
  "KV_PRECONDITION_FAILED",
93
105
  "KV_CONFLICT",
@@ -101,6 +113,7 @@ var init_errors = __esm({
101
113
  "OUTPUT_INVALID",
102
114
  "INTERNAL_ERROR"
103
115
  ];
116
+ MANAGE_STORAGE = "Do not retry: the owner must free up space or upgrade at https://account.tinycloud.xyz/billing.";
104
117
  OperationInvocationError = class extends Error {
105
118
  constructor(operationError2) {
106
119
  super(operationError2.message);
@@ -2545,6 +2558,8 @@ function kvFailure(error, action) {
2545
2558
  if (error.code === "KV_RESPONSE_TOO_LARGE") {
2546
2559
  return { status: "error", error: operationError("KV_RESPONSE_TOO_LARGE", `The TinyCloud KV value exceeds ${MAX_KV_VALUE_BYTES} bytes.`) };
2547
2560
  }
2561
+ const storageFull = storageFullOperationError(error);
2562
+ if (storageFull) return { status: "error", error: storageFull };
2548
2563
  return nodeFailure(action);
2549
2564
  }
2550
2565
  function nodeFailure(action) {
@@ -3163,7 +3178,7 @@ async function executeSqlDml(context, input) {
3163
3178
  const runtime = runtimeContext2(context);
3164
3179
  const space = resolveSqlSpace(runtime, input.space);
3165
3180
  const result = await runtimeNode2(runtime).sqlForSpace(space).db(input.database).execute(input.sql, input.params.map(decodeSqlInputValue));
3166
- if (!result.ok) return authorizationFailure(result.error, "execute the SQLite statement") ?? sqlMutationFailure();
3181
+ if (!result.ok) return authorizationFailure(result.error, "execute the SQLite statement") ?? sqlMutationFailure(result.error);
3167
3182
  const normalized = normalizeExecuteResult(result.data);
3168
3183
  return {
3169
3184
  status: "ok",
@@ -3177,7 +3192,7 @@ async function executeSqlDml(context, input) {
3177
3192
  };
3178
3193
  } catch (error) {
3179
3194
  if (error instanceof OperationInvocationError) return { status: "error", error: error.operationError };
3180
- return authorizationFailure(error, "execute the SQLite statement") ?? sqlMutationFailure();
3195
+ return authorizationFailure(error, "execute the SQLite statement") ?? sqlMutationFailure(error);
3181
3196
  }
3182
3197
  }
3183
3198
  function isSingleReadQuery(sql) {
@@ -3354,7 +3369,9 @@ function sqlServiceFailure(error, action) {
3354
3369
  }
3355
3370
  return nodeFailure2(action);
3356
3371
  }
3357
- function sqlMutationFailure() {
3372
+ function sqlMutationFailure(error) {
3373
+ const storageFull = storageFullOperationError(error);
3374
+ if (storageFull) return { status: "error", error: storageFull };
3358
3375
  return {
3359
3376
  status: "error",
3360
3377
  error: operationError(
@@ -3609,6 +3626,32 @@ ORDER BY type, name`;
3609
3626
  }
3610
3627
  });
3611
3628
 
3629
+ // src/contract.ts
3630
+ var STORED_GRANT_SKIP_REASONS, DELEGATION_CID_PATTERN;
3631
+ var init_contract = __esm({
3632
+ "src/contract.ts"() {
3633
+ "use strict";
3634
+ STORED_GRANT_SKIP_REASONS = [
3635
+ "malformed",
3636
+ "unbound",
3637
+ "outside_request",
3638
+ "host_mismatch",
3639
+ "expired",
3640
+ "cid_mismatch",
3641
+ "authority_invalid",
3642
+ "proof_missing",
3643
+ "proof_invalid",
3644
+ "owner_mismatch",
3645
+ "audience_mismatch",
3646
+ "resources_mismatch",
3647
+ "unsupported",
3648
+ "activation_rejected",
3649
+ "invalid"
3650
+ ];
3651
+ DELEGATION_CID_PATTERN = "^bafkr4i[a-z2-7]{52}$";
3652
+ }
3653
+ });
3654
+
3612
3655
  // src/delegation-binding.ts
3613
3656
  import { join as join2 } from "path";
3614
3657
  import * as nodeSdk3 from "@tinycloud/node-sdk";
@@ -3634,12 +3677,12 @@ function replayLimit(entry, migrated) {
3634
3677
  if (binding.success) return binding.data.requested;
3635
3678
  return !migrated && kind === "compact" && !("authorityRequest" in entry) ? "signed" : void 0;
3636
3679
  }
3637
- function bindToSignedCapabilities(record, signed, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3680
+ function bindToRequest(record, requested, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3638
3681
  return {
3639
3682
  ...record,
3640
3683
  authorityRequest: DelegationRequestBindingSchema.parse({
3641
3684
  requestId: `${REQUEST_ID_PREFIX[source]}:${record.delegation.cid}`,
3642
- requested: structuredClone(signed)
3685
+ requested: structuredClone(requested)
3643
3686
  }),
3644
3687
  authorityRequestAudit: { source, recordedAt, note: BINDING_NOTES[source] }
3645
3688
  };
@@ -3695,23 +3738,40 @@ async function migratedRecord(node, entry, host, recordedAt) {
3695
3738
  const signed = await signedCapabilities(node, delegation, host);
3696
3739
  if (signed === void 0) return void 0;
3697
3740
  try {
3698
- return bindToSignedCapabilities({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
3741
+ return bindToRequest({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
3699
3742
  } catch {
3700
3743
  return void 0;
3701
3744
  }
3702
3745
  }
3746
+ function skipped(reason) {
3747
+ return { status: "skipped", reason };
3748
+ }
3703
3749
  async function replayStoredDelegation(node, entry, options) {
3750
+ const kind = storedDelegationKind(entry);
3704
3751
  const delegation = normalizeStoredDelegation(entry);
3705
- if (delegation === void 0 || delegation.expiry.getTime() <= Date.now()) return void 0;
3752
+ if (kind === "refused" || delegation === void 0) return skipped("malformed");
3753
+ if (delegation.expiry.getTime() <= Date.now()) return NOT_REPLAYED;
3754
+ if (kind === "other") return skipped("proof_missing");
3706
3755
  const limit = replayLimit(entry, options.migrated);
3707
- if (limit === void 0) return void 0;
3708
- const authorize = limit === "signed" ? () => true : (effective) => delegationWithinRequest(limit, effective, options.resolveSpace);
3756
+ if (limit === void 0) return skipped("unbound");
3757
+ let outsideRequest = false;
3758
+ const authorize = limit === "signed" ? () => true : (effective) => {
3759
+ outsideRequest = !delegationWithinRequest(limit, effective, options.resolveSpace);
3760
+ return !outsideRequest;
3761
+ };
3709
3762
  try {
3710
- return await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize });
3711
- } catch {
3712
- return void 0;
3763
+ return {
3764
+ status: "installed",
3765
+ delegation: await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize })
3766
+ };
3767
+ } catch (error) {
3768
+ return skipped(outsideRequest ? "outside_request" : rejectionReason(error));
3713
3769
  }
3714
3770
  }
3771
+ function rejectionReason(error) {
3772
+ const reason = isRecord4(error) ? error.reason : void 0;
3773
+ return typeof reason === "string" && STORED_GRANT_SKIP_REASONS.includes(reason) ? reason : "invalid";
3774
+ }
3715
3775
  async function bindingMigrationRecorded(profile) {
3716
3776
  try {
3717
3777
  return await readJson(bindingMigrationPath(profile)) !== null;
@@ -3756,23 +3816,27 @@ function recordKey(entry) {
3756
3816
  function isRecord4(value) {
3757
3817
  return typeof value === "object" && value !== null && !Array.isArray(value);
3758
3818
  }
3759
- var REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS;
3819
+ var REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS, NOT_REPLAYED;
3760
3820
  var init_delegation_binding = __esm({
3761
3821
  "src/delegation-binding.ts"() {
3762
3822
  "use strict";
3763
3823
  init_artifacts();
3764
3824
  init_authority();
3825
+ init_contract();
3765
3826
  init_state();
3766
3827
  init_secrets();
3767
3828
  REQUEST_ID_PREFIX = {
3768
3829
  migration: "migrated",
3769
- "unbound-import": "unbound-import"
3830
+ "unbound-import": "unbound-import",
3831
+ "cli-grant": "cli-grant"
3770
3832
  };
3771
3833
  BINDING_NOTES = {
3772
3834
  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.",
3773
- "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."
3835
+ "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.",
3836
+ "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."
3774
3837
  };
3775
3838
  MIGRATION_LOCK_TIMEOUT_MS = 250;
3839
+ NOT_REPLAYED = { status: "not-replayed" };
3776
3840
  }
3777
3841
  });
3778
3842
 
@@ -4185,7 +4249,18 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4185
4249
  ));
4186
4250
  }
4187
4251
  } else {
4188
- await node.restoreSession(activeSession);
4252
+ try {
4253
+ await node.restoreSession(activeSession);
4254
+ } catch (error) {
4255
+ if (isRecord7(error) && error.code === "AUTH_EXPIRED") {
4256
+ return failed(summary, operationError(
4257
+ "AUTH_REQUIRED",
4258
+ "The stored session has expired or is no longer valid. Sign in again.",
4259
+ { retryable: false }
4260
+ ));
4261
+ }
4262
+ throw error;
4263
+ }
4189
4264
  }
4190
4265
  const activeSessionDid = normalizeDid(node.sessionDid);
4191
4266
  const livePrincipalDid = normalizeDid(node.did);
@@ -4193,6 +4268,7 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4193
4268
  const livePermissions = [...node.getVerifiedSessionCapabilities()];
4194
4269
  const resolveSpace2 = operationSpaceResolver(node, authenticatedSpace ?? summary.space);
4195
4270
  const seenCids = /* @__PURE__ */ new Set();
4271
+ const warnings = [];
4196
4272
  if (!explicitPrivateKeyOverride) {
4197
4273
  const activator = node;
4198
4274
  const migrated = await prepareStoredDelegationReplay(profileName, activator, {
@@ -4202,20 +4278,25 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4202
4278
  migrate: activeSession !== void 0
4203
4279
  });
4204
4280
  for (const entry of await readAdditionalDelegations(profileName)) {
4205
- const activated = await replayStoredDelegation(activator, entry, {
4281
+ const replay = await replayStoredDelegation(activator, entry, {
4206
4282
  host: summary.host,
4207
4283
  migrated,
4208
4284
  resolveSpace: resolveSpace2
4209
4285
  });
4210
- if (activated !== void 0 && !seenCids.has(activated.cid)) {
4211
- seenCids.add(activated.cid);
4212
- livePermissions.push(...activated.effectivePermissions);
4286
+ if (replay.status === "installed") {
4287
+ if (!seenCids.has(replay.delegation.cid)) {
4288
+ seenCids.add(replay.delegation.cid);
4289
+ livePermissions.push(...replay.delegation.effectivePermissions);
4290
+ }
4291
+ } else if (replay.status === "skipped") {
4292
+ warnings.push(skippedGrantWarning(node, entry, replay.reason));
4213
4293
  }
4214
4294
  }
4215
4295
  }
4216
4296
  const runtime = {
4217
4297
  node,
4218
- granted: canonicalizeCapabilities(livePermissions)
4298
+ granted: canonicalizeCapabilities(livePermissions),
4299
+ ...warnings.length === 0 ? {} : { warnings }
4219
4300
  };
4220
4301
  return {
4221
4302
  ok: true,
@@ -4239,6 +4320,24 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4239
4320
  ));
4240
4321
  }
4241
4322
  }
4323
+ function skippedGrantWarning(node, entry, reason) {
4324
+ const grantCid = verifiedStoredCid(node, entry);
4325
+ return { code: "STORED_GRANT_SKIPPED", reason, ...grantCid === void 0 ? {} : { grantCid } };
4326
+ }
4327
+ function verifiedStoredCid(node, entry) {
4328
+ const delegation = isRecord7(entry) && isRecord7(entry.delegation) ? entry.delegation : void 0;
4329
+ const header = isRecord7(delegation?.delegationHeader) ? delegation.delegationHeader : void 0;
4330
+ const cid = delegation?.cid;
4331
+ const computeDelegationCid = isRecord7(node) ? node.computeDelegationCid : void 0;
4332
+ if (typeof cid !== "string" || typeof header?.Authorization !== "string" || typeof computeDelegationCid !== "function") {
4333
+ return void 0;
4334
+ }
4335
+ try {
4336
+ return computeDelegationCid.call(node, header.Authorization) === cid && DELEGATION_CID.test(cid) ? cid : void 0;
4337
+ } catch {
4338
+ return void 0;
4339
+ }
4340
+ }
4242
4341
  function spaceForAuthenticatedPrincipal(principal2) {
4243
4342
  if (principal2 === void 0) return void 0;
4244
4343
  try {
@@ -4287,15 +4386,18 @@ function unresolvedSummary(target) {
4287
4386
  posture: "unauthenticated"
4288
4387
  };
4289
4388
  }
4389
+ var DELEGATION_CID;
4290
4390
  var init_runtime = __esm({
4291
4391
  "src/runtime.ts"() {
4292
4392
  "use strict";
4393
+ init_contract();
4293
4394
  init_authority();
4294
4395
  init_errors();
4295
4396
  init_profile();
4296
4397
  init_secrets();
4297
4398
  init_state();
4298
4399
  init_delegation_binding();
4400
+ DELEGATION_CID = new RegExp(DELEGATION_CID_PATTERN);
4299
4401
  }
4300
4402
  });
4301
4403
 
@@ -4597,8 +4699,19 @@ var SAFE_ERROR_DETAIL_POLICIES = {
4597
4699
  DELEGATION_HOST_MISMATCH: {
4598
4700
  expectedHost: safeOriginHost,
4599
4701
  artifactHost: safeOriginHost
4702
+ },
4703
+ STORAGE_QUOTA_EXCEEDED: {
4704
+ account: safeStorageAccount
4600
4705
  }
4601
4706
  };
4707
+ function safeStorageAccount(value) {
4708
+ if (!isRecord(value)) return void 0;
4709
+ const { usedBytes, limitBytes, plan } = value;
4710
+ if (!Number.isSafeInteger(usedBytes) || usedBytes < 0 || !Number.isSafeInteger(limitBytes) || limitBytes <= 0) {
4711
+ return void 0;
4712
+ }
4713
+ return typeof plan === "string" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan) ? { usedBytes, limitBytes, plan } : { usedBytes, limitBytes };
4714
+ }
4602
4715
  function redactSafeErrorDetails(code, details) {
4603
4716
  const policy = SAFE_ERROR_DETAIL_POLICIES[code];
4604
4717
  if (policy === void 0) return void 0;
@@ -4684,6 +4797,9 @@ async function invokeLocalAuthorityRetry(operationId, operationVersion, target,
4684
4797
  unknownInput
4685
4798
  );
4686
4799
  if (isPreparedError(prepared)) return prepared.result;
4800
+ return withRuntimeWarnings(await retryWithLocalAuthority(prepared), prepared);
4801
+ }
4802
+ async function retryWithLocalAuthority(prepared) {
4687
4803
  if (prepared.operation.operationId !== "tinycloud.secrets.get" || prepared.operation.operationVersion !== 1) {
4688
4804
  return errorResult(
4689
4805
  prepared.operation,
@@ -4782,6 +4898,10 @@ async function invokeLocalAuthorityRetry(operationId, operationVersion, target,
4782
4898
  return localAuthorityFailure(prepared);
4783
4899
  }
4784
4900
  }
4901
+ function withRuntimeWarnings(result, prepared) {
4902
+ const warnings = prepared.runtimeResolution.context.runtime?.warnings;
4903
+ return warnings === void 0 || warnings.length === 0 ? result : { ...result, warnings: [...warnings] };
4904
+ }
4785
4905
  function isPreparedError(prepared) {
4786
4906
  return "status" in prepared;
4787
4907
  }