@tinycloud/operations 0.4.0-beta.11 → 0.4.0-beta.13

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
@@ -357,8 +357,13 @@ function canonicalizeRecapCaveats(caveats) {
357
357
  }
358
358
  return `[${branches.join(",")}]`;
359
359
  }
360
+ function isExplicitOnlyAction(action) {
361
+ return Object.prototype.hasOwnProperty.call(EXPLICIT_ONLY_ACTIONS, action);
362
+ }
360
363
  function actionContains(grantedAction, requestedAction) {
361
- if (grantedAction === "*" || grantedAction === requestedAction) return true;
364
+ if (grantedAction === requestedAction) return true;
365
+ if (isExplicitOnlyAction(requestedAction)) return false;
366
+ if (grantedAction === "*") return true;
362
367
  if (!grantedAction.endsWith("/*")) return false;
363
368
  const service = grantedAction.slice(0, -2);
364
369
  return requestedAction === service || requestedAction.startsWith(`${service}/`);
@@ -383,10 +388,15 @@ function cloneEntry(entry) {
383
388
  ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {}
384
389
  };
385
390
  }
391
+ var EXPLICIT_ONLY_ACTIONS;
386
392
  var init_capabilities = __esm({
387
393
  "../sdk-core/src/capabilities.ts"() {
388
394
  "use strict";
389
395
  init_manifest();
396
+ EXPLICIT_ONLY_ACTIONS = {
397
+ "tinycloud.kv/sync": true,
398
+ "tinycloud.kv/retain": true
399
+ };
390
400
  }
391
401
  });
392
402
 
@@ -3615,6 +3625,32 @@ ORDER BY type, name`;
3615
3625
  }
3616
3626
  });
3617
3627
 
3628
+ // src/contract.ts
3629
+ var STORED_GRANT_SKIP_REASONS, DELEGATION_CID_PATTERN;
3630
+ var init_contract = __esm({
3631
+ "src/contract.ts"() {
3632
+ "use strict";
3633
+ STORED_GRANT_SKIP_REASONS = [
3634
+ "malformed",
3635
+ "unbound",
3636
+ "outside_request",
3637
+ "host_mismatch",
3638
+ "expired",
3639
+ "cid_mismatch",
3640
+ "authority_invalid",
3641
+ "proof_missing",
3642
+ "proof_invalid",
3643
+ "owner_mismatch",
3644
+ "audience_mismatch",
3645
+ "resources_mismatch",
3646
+ "unsupported",
3647
+ "activation_rejected",
3648
+ "invalid"
3649
+ ];
3650
+ DELEGATION_CID_PATTERN = "^bafkr4i[a-z2-7]{52}$";
3651
+ }
3652
+ });
3653
+
3618
3654
  // src/delegation-binding.ts
3619
3655
  function bindingMigrationPath(profile) {
3620
3656
  return (0, import_node_path2.join)(profilePath(profile), "delegation-binding-migration.json");
@@ -3638,12 +3674,12 @@ function replayLimit(entry, migrated) {
3638
3674
  if (binding.success) return binding.data.requested;
3639
3675
  return !migrated && kind === "compact" && !("authorityRequest" in entry) ? "signed" : void 0;
3640
3676
  }
3641
- function bindToSignedCapabilities(record, signed, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3677
+ function bindToRequest(record, requested, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3642
3678
  return {
3643
3679
  ...record,
3644
3680
  authorityRequest: DelegationRequestBindingSchema.parse({
3645
3681
  requestId: `${REQUEST_ID_PREFIX[source]}:${record.delegation.cid}`,
3646
- requested: structuredClone(signed)
3682
+ requested: structuredClone(requested)
3647
3683
  }),
3648
3684
  authorityRequestAudit: { source, recordedAt, note: BINDING_NOTES[source] }
3649
3685
  };
@@ -3699,23 +3735,40 @@ async function migratedRecord(node, entry, host, recordedAt) {
3699
3735
  const signed = await signedCapabilities(node, delegation, host);
3700
3736
  if (signed === void 0) return void 0;
3701
3737
  try {
3702
- return bindToSignedCapabilities({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
3738
+ return bindToRequest({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
3703
3739
  } catch {
3704
3740
  return void 0;
3705
3741
  }
3706
3742
  }
3743
+ function skipped(reason) {
3744
+ return { status: "skipped", reason };
3745
+ }
3707
3746
  async function replayStoredDelegation(node, entry, options) {
3747
+ const kind = storedDelegationKind(entry);
3708
3748
  const delegation = normalizeStoredDelegation(entry);
3709
- if (delegation === void 0 || delegation.expiry.getTime() <= Date.now()) return void 0;
3749
+ if (kind === "refused" || delegation === void 0) return skipped("malformed");
3750
+ if (delegation.expiry.getTime() <= Date.now()) return NOT_REPLAYED;
3751
+ if (kind === "other") return skipped("proof_missing");
3710
3752
  const limit = replayLimit(entry, options.migrated);
3711
- if (limit === void 0) return void 0;
3712
- const authorize = limit === "signed" ? () => true : (effective) => delegationWithinRequest(limit, effective, options.resolveSpace);
3753
+ if (limit === void 0) return skipped("unbound");
3754
+ let outsideRequest = false;
3755
+ const authorize = limit === "signed" ? () => true : (effective) => {
3756
+ outsideRequest = !delegationWithinRequest(limit, effective, options.resolveSpace);
3757
+ return !outsideRequest;
3758
+ };
3713
3759
  try {
3714
- return await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize });
3715
- } catch {
3716
- return void 0;
3760
+ return {
3761
+ status: "installed",
3762
+ delegation: await nodeSdk3.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize })
3763
+ };
3764
+ } catch (error) {
3765
+ return skipped(outsideRequest ? "outside_request" : rejectionReason(error));
3717
3766
  }
3718
3767
  }
3768
+ function rejectionReason(error) {
3769
+ const reason = isRecord4(error) ? error.reason : void 0;
3770
+ return typeof reason === "string" && STORED_GRANT_SKIP_REASONS.includes(reason) ? reason : "invalid";
3771
+ }
3719
3772
  async function bindingMigrationRecorded(profile) {
3720
3773
  try {
3721
3774
  return await readJson(bindingMigrationPath(profile)) !== null;
@@ -3760,7 +3813,7 @@ function recordKey(entry) {
3760
3813
  function isRecord4(value) {
3761
3814
  return typeof value === "object" && value !== null && !Array.isArray(value);
3762
3815
  }
3763
- var import_node_path2, nodeSdk3, REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS;
3816
+ var import_node_path2, nodeSdk3, REQUEST_ID_PREFIX, BINDING_NOTES, MIGRATION_LOCK_TIMEOUT_MS, NOT_REPLAYED;
3764
3817
  var init_delegation_binding = __esm({
3765
3818
  "src/delegation-binding.ts"() {
3766
3819
  "use strict";
@@ -3768,17 +3821,21 @@ var init_delegation_binding = __esm({
3768
3821
  nodeSdk3 = __toESM(require("@tinycloud/node-sdk"), 1);
3769
3822
  init_artifacts();
3770
3823
  init_authority();
3824
+ init_contract();
3771
3825
  init_state();
3772
3826
  init_secrets();
3773
3827
  REQUEST_ID_PREFIX = {
3774
3828
  migration: "migrated",
3775
- "unbound-import": "unbound-import"
3829
+ "unbound-import": "unbound-import",
3830
+ "cli-grant": "cli-grant"
3776
3831
  };
3777
3832
  BINDING_NOTES = {
3778
3833
  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.",
3779
- "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."
3834
+ "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
+ "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."
3780
3836
  };
3781
3837
  MIGRATION_LOCK_TIMEOUT_MS = 250;
3838
+ NOT_REPLAYED = { status: "not-replayed" };
3782
3839
  }
3783
3840
  });
3784
3841
 
@@ -4190,7 +4247,18 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4190
4247
  ));
4191
4248
  }
4192
4249
  } else {
4193
- await node.restoreSession(activeSession);
4250
+ try {
4251
+ await node.restoreSession(activeSession);
4252
+ } catch (error) {
4253
+ if (isRecord7(error) && error.code === "AUTH_EXPIRED") {
4254
+ return failed(summary, operationError(
4255
+ "AUTH_REQUIRED",
4256
+ "The stored session has expired or is no longer valid. Sign in again.",
4257
+ { retryable: false }
4258
+ ));
4259
+ }
4260
+ throw error;
4261
+ }
4194
4262
  }
4195
4263
  const activeSessionDid = normalizeDid(node.sessionDid);
4196
4264
  const livePrincipalDid = normalizeDid(node.did);
@@ -4198,6 +4266,7 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4198
4266
  const livePermissions = [...node.getVerifiedSessionCapabilities()];
4199
4267
  const resolveSpace2 = operationSpaceResolver(node, authenticatedSpace ?? summary.space);
4200
4268
  const seenCids = /* @__PURE__ */ new Set();
4269
+ const warnings = [];
4201
4270
  if (!explicitPrivateKeyOverride) {
4202
4271
  const activator = node;
4203
4272
  const migrated = await prepareStoredDelegationReplay(profileName, activator, {
@@ -4207,20 +4276,25 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4207
4276
  migrate: activeSession !== void 0
4208
4277
  });
4209
4278
  for (const entry of await readAdditionalDelegations(profileName)) {
4210
- const activated = await replayStoredDelegation(activator, entry, {
4279
+ const replay = await replayStoredDelegation(activator, entry, {
4211
4280
  host: summary.host,
4212
4281
  migrated,
4213
4282
  resolveSpace: resolveSpace2
4214
4283
  });
4215
- if (activated !== void 0 && !seenCids.has(activated.cid)) {
4216
- seenCids.add(activated.cid);
4217
- livePermissions.push(...activated.effectivePermissions);
4284
+ if (replay.status === "installed") {
4285
+ if (!seenCids.has(replay.delegation.cid)) {
4286
+ seenCids.add(replay.delegation.cid);
4287
+ livePermissions.push(...replay.delegation.effectivePermissions);
4288
+ }
4289
+ } else if (replay.status === "skipped") {
4290
+ warnings.push(skippedGrantWarning(node, entry, replay.reason));
4218
4291
  }
4219
4292
  }
4220
4293
  }
4221
4294
  const runtime = {
4222
4295
  node,
4223
- granted: canonicalizeCapabilities(livePermissions)
4296
+ granted: canonicalizeCapabilities(livePermissions),
4297
+ ...warnings.length === 0 ? {} : { warnings }
4224
4298
  };
4225
4299
  return {
4226
4300
  ok: true,
@@ -4244,6 +4318,24 @@ async function createInvocationRuntime(target, requirement = "authenticated") {
4244
4318
  ));
4245
4319
  }
4246
4320
  }
4321
+ function skippedGrantWarning(node, entry, reason) {
4322
+ const grantCid = verifiedStoredCid(node, entry);
4323
+ return { code: "STORED_GRANT_SKIPPED", reason, ...grantCid === void 0 ? {} : { grantCid } };
4324
+ }
4325
+ function verifiedStoredCid(node, entry) {
4326
+ const delegation = isRecord7(entry) && isRecord7(entry.delegation) ? entry.delegation : void 0;
4327
+ const header = isRecord7(delegation?.delegationHeader) ? delegation.delegationHeader : void 0;
4328
+ const cid = delegation?.cid;
4329
+ const computeDelegationCid = isRecord7(node) ? node.computeDelegationCid : void 0;
4330
+ if (typeof cid !== "string" || typeof header?.Authorization !== "string" || typeof computeDelegationCid !== "function") {
4331
+ return void 0;
4332
+ }
4333
+ try {
4334
+ return computeDelegationCid.call(node, header.Authorization) === cid && DELEGATION_CID.test(cid) ? cid : void 0;
4335
+ } catch {
4336
+ return void 0;
4337
+ }
4338
+ }
4247
4339
  function spaceForAuthenticatedPrincipal(principal2) {
4248
4340
  if (principal2 === void 0) return void 0;
4249
4341
  try {
@@ -4292,17 +4384,19 @@ function unresolvedSummary(target) {
4292
4384
  posture: "unauthenticated"
4293
4385
  };
4294
4386
  }
4295
- var nodeSdk4;
4387
+ var nodeSdk4, DELEGATION_CID;
4296
4388
  var init_runtime = __esm({
4297
4389
  "src/runtime.ts"() {
4298
4390
  "use strict";
4299
4391
  nodeSdk4 = __toESM(require("@tinycloud/node-sdk"), 1);
4392
+ init_contract();
4300
4393
  init_authority();
4301
4394
  init_errors();
4302
4395
  init_profile();
4303
4396
  init_secrets();
4304
4397
  init_state();
4305
4398
  init_delegation_binding();
4399
+ DELEGATION_CID = new RegExp(DELEGATION_CID_PATTERN);
4306
4400
  }
4307
4401
  });
4308
4402
 
@@ -4685,9 +4779,13 @@ async function invokeOperation(operationId, operationVersion, invocationTarget,
4685
4779
  unknownInput
4686
4780
  );
4687
4781
  if (isPreparedError(prepared)) return prepared.result;
4688
- return executePreparedInvocation(prepared);
4782
+ return withRuntimeWarnings(await executePreparedInvocation(prepared), prepared);
4689
4783
  });
4690
4784
  }
4785
+ function withRuntimeWarnings(result, prepared) {
4786
+ const warnings = prepared.runtimeResolution.context.runtime?.warnings;
4787
+ return warnings === void 0 || warnings.length === 0 ? result : { ...result, warnings: [...warnings] };
4788
+ }
4691
4789
  function isPreparedError(prepared) {
4692
4790
  return "status" in prepared;
4693
4791
  }