@tinycloud/cli 1.1.0-beta.18 → 1.1.0-beta.19

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.
@@ -13804,6 +13804,9 @@ function permissionRequestsPath(profile) {
13804
13804
  function grantHistoryPath(profile) {
13805
13805
  return join4(PROFILES_DIR, profile, "auth-grants.jsonl");
13806
13806
  }
13807
+ function localGrantArtifactsPath(profile) {
13808
+ return join4(PROFILES_DIR, profile, "auth-grant-artifacts.json");
13809
+ }
13807
13810
  function createPermissionRequestArtifact(params) {
13808
13811
  return buildPermissionRequestArtifact({
13809
13812
  profile: params.profileName,
@@ -13828,6 +13831,32 @@ function didWithoutFragment(did) {
13828
13831
  async function loadAdditionalDelegations(profile) {
13829
13832
  return readAdditionalDelegations(profile);
13830
13833
  }
13834
+ async function loadLocalGrantArtifacts(profile) {
13835
+ const path = localGrantArtifactsPath(profile);
13836
+ if (!await fileExists(path)) return [];
13837
+ let parsed;
13838
+ try {
13839
+ parsed = JSON.parse(await readFile2(path, "utf8"));
13840
+ } catch {
13841
+ return [];
13842
+ }
13843
+ if (!Array.isArray(parsed)) return [];
13844
+ return parsed.filter(
13845
+ (value) => value !== null && typeof value === "object" && typeof value.delegationCid === "string" && value.delegation !== null && typeof value.delegation === "object" && value.delegation.cid === value.delegationCid && typeof value.delegation.delegationHeader?.Authorization === "string"
13846
+ );
13847
+ }
13848
+ async function appendLocalGrantArtifact(profile, delegation) {
13849
+ await ProfileManager.ensureProfileDir(profile);
13850
+ await withProfileLock2(profile, async () => {
13851
+ await refuseWriteToDeletedProfile2(profile);
13852
+ const records = await loadLocalGrantArtifacts(profile);
13853
+ if (records.some((record) => record.delegationCid === delegation.cid)) return;
13854
+ await writeJsonAtomic(localGrantArtifactsPath(profile), [
13855
+ ...records,
13856
+ { delegationCid: delegation.cid, delegation }
13857
+ ]);
13858
+ });
13859
+ }
13831
13860
  async function appendAdditionalDelegations(profile, entries) {
13832
13861
  const { mergeDelegationsWithoutRequest } = await import("@tinycloud/operations/delegation-binding");
13833
13862
  await updateProfileStore(profile, "additional-delegations", (stored) => ({
@@ -15246,11 +15275,15 @@ function ownerSpaceId(space, ownerDid) {
15246
15275
  function permissionTuples(permissions, ownerDid) {
15247
15276
  return new Set(permissions.flatMap((permission) => actionTuples(permission, ownerDid)));
15248
15277
  }
15278
+ function isRawCidRevocationPermission(permission) {
15279
+ return permission.service === "tinycloud.delegation" && permission.space?.startsWith("urn:cid:") === true && permission.path === "" && permission.actions.includes("tinycloud.delegation/revoke");
15280
+ }
15249
15281
  function actionTuples(permission, ownerDid) {
15250
15282
  const service = permission.service.startsWith("tinycloud.") ? permission.service : `tinycloud.${permission.service}`;
15251
- const raw = isVerifiedRawEncryptionPermission(permission);
15252
- const space = raw ? ENCRYPTION_MANIFEST_SPACE2 : normalizePkhIdentifier(ownerSpaceId(permission.space ?? "", ownerDid));
15253
- const path = raw ? normalizePkhIdentifier(permission.path) : permission.path;
15283
+ const rawEncryption = isVerifiedRawEncryptionPermission(permission);
15284
+ const rawCidRevocation = isRawCidRevocationPermission(permission);
15285
+ const space = rawEncryption ? ENCRYPTION_MANIFEST_SPACE2 : rawCidRevocation ? permission.space : normalizePkhIdentifier(ownerSpaceId(permission.space ?? "", ownerDid));
15286
+ const path = rawEncryption ? normalizePkhIdentifier(permission.path) : permission.path;
15254
15287
  return permission.actions.map((action) => JSON.stringify([service, space, path, action.includes("/") ? action : `${service}/${action}`]));
15255
15288
  }
15256
15289
  function isLegacyNestedDecrypt(permission, requested, ownerDid, spaceId) {
@@ -15321,7 +15354,7 @@ function declinedPermissions(requested, signed, ownerDid) {
15321
15354
  const missing = [...permissionTuples(requested, ownerDid)].filter((tuple) => !granted.has(tuple));
15322
15355
  const requestedScopes = /* @__PURE__ */ new Map();
15323
15356
  for (const entry of requested) {
15324
- const space = isVerifiedRawEncryptionPermission(entry) ? entry.space ?? ENCRYPTION_MANIFEST_SPACE2 : ownerSpaceId(entry.space ?? "", ownerDid);
15357
+ const space = isVerifiedRawEncryptionPermission(entry) ? entry.space ?? ENCRYPTION_MANIFEST_SPACE2 : isRawCidRevocationPermission(entry) ? entry.space : ownerSpaceId(entry.space ?? "", ownerDid);
15325
15358
  for (const tuple of actionTuples(entry, ownerDid)) {
15326
15359
  requestedScopes.set(tuple, { space, path: entry.path });
15327
15360
  }
@@ -15344,7 +15377,10 @@ function scopedLoginPermissions(permissions) {
15344
15377
  }
15345
15378
  function grantRequestPermissions(group, anchorSpace) {
15346
15379
  const request = group.map((p) => isRawEncryptionPermission(p) ? { ...p, space: ENCRYPTION_MANIFEST_SPACE2, path: canonicalNetworkUrn(p.path) } : p);
15347
- return withCapabilitiesRead(request, request.find((p) => !isRawEncryptionPermission(p))?.space ?? anchorSpace);
15380
+ const capabilitySpace = request.find(
15381
+ (permission) => !isRawEncryptionPermission(permission) && !isRawCidRevocationPermission(permission)
15382
+ )?.space ?? anchorSpace;
15383
+ return withCapabilitiesRead(request, capabilitySpace);
15348
15384
  }
15349
15385
  function withCapabilitiesRead(permissions, space) {
15350
15386
  const hasRead = permissions.some((p) => p.service === "tinycloud.capabilities" && p.path === "" && p.actions.includes(CAPABILITIES_READ) && normalizePkhIdentifier(p.space ?? "") === normalizePkhIdentifier(space));
@@ -15378,7 +15414,10 @@ function verifySignedSession(data, key, sessionDid, expected = {}) {
15378
15414
  if (/expir/i.test(error instanceof Error ? error.message : String(error))) {
15379
15415
  throw new CLIError("AUTH_EXPIRED", `The approved session has expired. ${notStored}`, ExitCode.AUTH_REQUIRED);
15380
15416
  }
15381
- throw new CLIError("OPENKEY_PROOF_INVALID", `OpenKey did not return a complete, verifiable session proof. ${notStored}`, ExitCode.AUTH_REQUIRED);
15417
+ throw Object.assign(
15418
+ new CLIError("OPENKEY_PROOF_INVALID", `OpenKey did not return a complete, verifiable session proof. ${notStored}`, ExitCode.AUTH_REQUIRED),
15419
+ { cause: error }
15420
+ );
15382
15421
  }
15383
15422
  const signedExpiry = Date.parse(expiresAt);
15384
15423
  if (signedExpiry <= Date.now()) {
@@ -15395,10 +15434,23 @@ function verifySignedSession(data, key, sessionDid, expected = {}) {
15395
15434
  }
15396
15435
  function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
15397
15436
  const notStored = expected.purpose === "grant" ? "No grant was stored." : "No scoped session was saved.";
15398
- const signed = verifySignedSession(data, key, sessionDid, expected);
15437
+ let signed;
15438
+ try {
15439
+ signed = verifySignedSession(data, key, sessionDid, expected);
15440
+ } catch (error) {
15441
+ const cause = error instanceof Error ? error.cause : void 0;
15442
+ if (requested.some(isRawCidRevocationPermission) && /invalid ReCap resource URI/i.test(cause instanceof Error ? cause.message : String(cause ?? ""))) {
15443
+ throw new CLIError(
15444
+ "RAW_RECAP_RESOURCE_UNSUPPORTED",
15445
+ "This WASM build cannot verify raw urn:cid ReCap resources.",
15446
+ ExitCode.PERMISSION_DENIED
15447
+ );
15448
+ }
15449
+ throw error;
15450
+ }
15399
15451
  const spaceId = data.spaceId;
15400
15452
  for (const permission of requested) {
15401
- if (isRawEncryptionPermission(permission)) continue;
15453
+ if (isRawEncryptionPermission(permission) || isRawCidRevocationPermission(permission)) continue;
15402
15454
  if (normalizePkhIdentifier(ownerSpaceId(permission.space ?? "", signed.ownerDid)) !== normalizePkhIdentifier(spaceId)) {
15403
15455
  throw new CLIError("OPENKEY_SCOPE_MISMATCH", `The approved space differs from the requested space. ${notStored}`, ExitCode.PERMISSION_DENIED);
15404
15456
  }
@@ -16439,6 +16491,13 @@ function registerAuthCommand(program) {
16439
16491
  yes: options.yes === true
16440
16492
  });
16441
16493
  const grant = await grantAuthRequest(node, resolvedRequest);
16494
+ await appendLocalGrantArtifact(ctx.profile, grant.delegation);
16495
+ await appendGrantHistory(ctx.profile, {
16496
+ addedCaps: grant.permissions,
16497
+ source: "cli",
16498
+ delegationCid: grant.delegationCid,
16499
+ expiry: grant.expiry
16500
+ });
16442
16501
  outputJson(grant);
16443
16502
  } catch (error) {
16444
16503
  handleError(error);
@@ -16795,7 +16854,10 @@ async function activateAndStoreOpenKeyGrants(profileName2, host, node, grants, s
16795
16854
  });
16796
16855
  }
16797
16856
  async function ensureDelegationAuthority(params) {
16798
- if (!params.force && params.node.hasRuntimePermissions(params.requested)) return;
16857
+ if (!params.force && params.node.hasRuntimePermissions(params.requested)) return void 0;
16858
+ if (!params.consentAlreadyGiven && (params.persist === false || params.profile.authMethod !== "openkey")) {
16859
+ await requirePermissionConsent(params.requested, params.yes);
16860
+ }
16799
16861
  if (params.profile.authMethod === "openkey") {
16800
16862
  const key = await ProfileManager.getKey(params.ctx.profile);
16801
16863
  if (!key) {
@@ -16815,6 +16877,37 @@ async function ensureDelegationAuthority(params) {
16815
16877
  expiry: expiryCap
16816
16878
  };
16817
16879
  const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
16880
+ if (params.persist === false) {
16881
+ const request = grantRequestPermissions(params.requested, anchorSpace);
16882
+ let delegationData;
16883
+ try {
16884
+ delegationData = await acquireOpenKey(params.profile.did, {
16885
+ jwk: key,
16886
+ host: params.ctx.host,
16887
+ permissions: request,
16888
+ reason: permissionGrantReason(params.reason, request),
16889
+ openkeyHost,
16890
+ expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
16891
+ });
16892
+ } catch (error) {
16893
+ if (params.requested.some(isRawCidRevocationPermission) && /invalid ReCap resource URI/i.test(error instanceof Error ? error.message : String(error))) {
16894
+ throw new CLIError(
16895
+ "RAW_RECAP_RESOURCE_UNSUPPORTED",
16896
+ "The OpenKey/WASM ReCap encoder does not support raw urn:cid revocation resources.",
16897
+ ExitCode.PERMISSION_DENIED
16898
+ );
16899
+ }
16900
+ throw error;
16901
+ }
16902
+ const delegation = portableFromOpenKeyDelegation(
16903
+ delegationData,
16904
+ request,
16905
+ params.ctx.host,
16906
+ proof
16907
+ );
16908
+ await params.node.useRuntimeDelegation(delegation);
16909
+ return delegation;
16910
+ }
16818
16911
  const grants = [];
16819
16912
  for (const group of groupPermissionsBySpace(params.requested)) {
16820
16913
  const request = grantRequestPermissions(group, anchorSpace);
@@ -16832,21 +16925,11 @@ async function ensureDelegationAuthority(params) {
16832
16925
  await activateAndStoreOpenKeyGrants(params.ctx.profile, params.ctx.host, params.node, grants, "cli");
16833
16926
  return;
16834
16927
  }
16835
- if (isInteractive()) {
16836
- if (!params.yes) {
16837
- await confirmPermissionRequest(params.requested);
16838
- }
16839
- } else if (!params.yes) {
16840
- throw new CLIError(
16841
- "CONFIRMATION_REQUIRED",
16842
- "Local-key auth grants in non-interactive mode require --yes.",
16843
- ExitCode.USAGE_ERROR
16844
- );
16845
- }
16846
16928
  const delegations = await params.node.grantRuntimePermissions(
16847
16929
  params.requested,
16848
16930
  params.expiryOption !== void 0 ? { expiry: params.expiryOption } : void 0
16849
16931
  );
16932
+ if (params.persist === false) return delegations[0];
16850
16933
  for (const delegation of delegations) {
16851
16934
  const covering = permissionsFromDelegation(delegation);
16852
16935
  await appendAdditionalDelegation(
@@ -16924,6 +17007,17 @@ async function confirmPermissionRequest(permissions) {
16924
17007
  throw new CLIError("REQUEST_CANCELLED", "Permission request cancelled.", ExitCode.ERROR);
16925
17008
  }
16926
17009
  }
17010
+ async function requirePermissionConsent(permissions, yes) {
17011
+ if (isInteractive()) {
17012
+ if (!yes) await confirmPermissionRequest(permissions);
17013
+ } else if (!yes) {
17014
+ throw new CLIError(
17015
+ "CONFIRMATION_REQUIRED",
17016
+ "Permission grants in non-interactive mode require --yes.",
17017
+ ExitCode.USAGE_ERROR
17018
+ );
17019
+ }
17020
+ }
16927
17021
  function isDangerousPermission(permission) {
16928
17022
  if (permission.path === "" || permission.path === "/") return true;
16929
17023
  return permission.actions.some(
@@ -16952,7 +17046,7 @@ function groupPermissionsBySpace(permissions) {
16952
17046
  const groups = /* @__PURE__ */ new Map();
16953
17047
  const rawEntries = [];
16954
17048
  for (const permission of permissions) {
16955
- if (isRawEncryptionPermission(permission)) {
17049
+ if (isRawEncryptionPermission(permission) || isRawCidRevocationPermission(permission)) {
16956
17050
  rawEntries.push(permission);
16957
17051
  continue;
16958
17052
  }
@@ -16983,11 +17077,13 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
16983
17077
  }
16984
17078
  const returnedSpace = data.spaceId;
16985
17079
  const effective = session.permissions;
16986
- const spaced = effective.filter((permission) => !isRawEncryptionPermission(permission));
17080
+ const spaced = effective.filter(
17081
+ (permission) => !isRawEncryptionPermission(permission) && !isRawCidRevocationPermission(permission)
17082
+ );
16987
17083
  const primary = spaced.find((permission) => permission.service !== "tinycloud.capabilities") ?? spaced[0] ?? effective[0];
16988
17084
  const resources = effective.map((permission) => ({
16989
17085
  service: permission.service.slice("tinycloud.".length),
16990
- space: isVerifiedRawEncryptionPermission(permission) ? "encryption" : returnedSpace,
17086
+ space: isRawCidRevocationPermission(permission) ? permission.space : isVerifiedRawEncryptionPermission(permission) ? "encryption" : returnedSpace,
16991
17087
  path: permission.path,
16992
17088
  actions: [...permission.actions],
16993
17089
  ...permission.caveats === void 0 ? {} : { caveats: structuredClone(permission.caveats) }
@@ -17463,6 +17559,7 @@ init_profiles();
17463
17559
  init_formatter();
17464
17560
  init_errors();
17465
17561
  init_constants();
17562
+ import { parseSignedCompactUcanAttenuation } from "@tinycloud/sdk-core";
17466
17563
  function normalizeDid(input) {
17467
17564
  const normalized = input.trim();
17468
17565
  const fragmentIndex = normalized.indexOf("#");
@@ -17470,11 +17567,35 @@ function normalizeDid(input) {
17470
17567
  }
17471
17568
  function didMatches(actual, expected) {
17472
17569
  if (!actual) return false;
17473
- try {
17474
- return normalizeDid(actual) === normalizeDid(expected);
17475
- } catch {
17476
- return actual === expected;
17570
+ return normalizeDid(actual) === normalizeDid(expected);
17571
+ }
17572
+ function ownerDidFromSpace(spaceId) {
17573
+ const owner = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40})(?::|\/|$)/.exec(spaceId ?? "")?.[1];
17574
+ if (!owner) return void 0;
17575
+ return owner.startsWith("did:") ? owner : `did:${owner}`;
17576
+ }
17577
+ async function resolveCidBoundTargetSpace(params) {
17578
+ if (!params.ownerDid) return void 0;
17579
+ const grants = await loadLocalGrantArtifacts(params.profileName);
17580
+ for (const grant of grants) {
17581
+ const delegation = grant.delegation;
17582
+ if (grant.delegationCid !== params.cid || delegation.cid !== params.cid) continue;
17583
+ try {
17584
+ const authorization = delegation.delegationHeader.Authorization.replace(/^Bearer /i, "");
17585
+ const signed = parseSignedCompactUcanAttenuation(authorization, params.cid);
17586
+ for (const resource of Object.keys(signed.payload.att)) {
17587
+ const space = /^(tinycloud:[^/]+)\/[^/]+\/.*$/.exec(resource)?.[1];
17588
+ if (!space) continue;
17589
+ const ownerMatch = /^tinycloud:((?:did:)?pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):/.exec(space);
17590
+ const owner = ownerMatch?.[1];
17591
+ const ownerDid = owner?.startsWith("did:") ? owner : owner ? `did:${owner}` : void 0;
17592
+ if (ownerDid && normalizeDid(ownerDid) === normalizeDid(params.ownerDid)) return space;
17593
+ }
17594
+ } catch {
17595
+ continue;
17596
+ }
17477
17597
  }
17598
+ return void 0;
17478
17599
  }
17479
17600
  function registerDelegationCommand(program) {
17480
17601
  const delegation = program.command("delegation").description("Manage delegations");
@@ -17554,16 +17675,158 @@ function registerDelegationCommand(program) {
17554
17675
  handleError(error);
17555
17676
  }
17556
17677
  });
17557
- delegation.command("revoke <cid>").description("Revoke a delegation").action(async (cid, _options, cmd) => {
17678
+ delegation.command("revoke <cid>").description("Revoke a delegation").option("--yes", "Skip local-key TTY confirmation", false).action(async (cid, options, cmd) => {
17558
17679
  try {
17559
17680
  const globalOpts = cmd.optsWithGlobals();
17560
17681
  const ctx = await ProfileManager.resolveContext(globalOpts);
17561
17682
  const node = await ensureAuthenticated(ctx);
17562
- const result = await node.delegationManager.revoke(cid);
17683
+ const profile = await ProfileManager.getProfile(ctx.profile);
17684
+ const revokePermission = {
17685
+ service: "tinycloud.delegation",
17686
+ space: `urn:cid:${cid}`,
17687
+ path: "",
17688
+ actions: ["tinycloud.delegation/revoke"]
17689
+ };
17690
+ const rawAuthorityCovered = node.hasRuntimePermissions([revokePermission]);
17691
+ if (!rawAuthorityCovered) await requirePermissionConsent([revokePermission], options.yes);
17692
+ let targetFound = false;
17693
+ let targetSpaceSource;
17694
+ let queryDenied = false;
17695
+ let boundSpace;
17696
+ const accountSpaceId = node.accountSpaceId;
17697
+ if (!accountSpaceId) {
17698
+ throw new CLIError(
17699
+ "DELEGATION_QUERY_AUTHORITY_UNAVAILABLE",
17700
+ "Cannot determine the account space needed to query delegation records.",
17701
+ ExitCode.AUTH_REQUIRED
17702
+ );
17703
+ }
17704
+ let cursor;
17705
+ do {
17706
+ const query = await node.delegationManager.query({
17707
+ direction: "all",
17708
+ limit: 100,
17709
+ ...cursor === void 0 ? {} : { cursor }
17710
+ });
17711
+ if (!query.ok) {
17712
+ if (query.error.meta?.status === 403) {
17713
+ queryDenied = true;
17714
+ break;
17715
+ }
17716
+ throw cliErrorFromService(query.error);
17717
+ }
17718
+ targetFound = query.data.items.some((delegation2) => delegation2.cid === cid);
17719
+ cursor = targetFound ? void 0 : query.data.nextCursor;
17720
+ } while (cursor !== void 0);
17721
+ targetSpaceSource = "node";
17722
+ if (!targetFound) {
17723
+ const grantHistory = await readGrantHistory(ctx.profile);
17724
+ const recordedGrant = [...grantHistory].reverse().find((entry) => entry.delegationCid === cid);
17725
+ if (recordedGrant) {
17726
+ targetSpaceSource = "local-grant-history";
17727
+ } else if (queryDenied) {
17728
+ boundSpace = await resolveCidBoundTargetSpace({
17729
+ profileName: ctx.profile,
17730
+ ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
17731
+ cid
17732
+ });
17733
+ if (boundSpace) {
17734
+ targetSpaceSource = "local-signed-grant-artifact";
17735
+ } else {
17736
+ throw new CLIError(
17737
+ "TARGET_NOT_FOUND",
17738
+ `Delegation "${cid}" was not found by the node or local grant sources.`,
17739
+ ExitCode.NOT_FOUND
17740
+ );
17741
+ }
17742
+ } else {
17743
+ throw new CLIError(
17744
+ "TARGET_NOT_FOUND",
17745
+ `Delegation "${cid}" was not found by the node or local grant history.`,
17746
+ ExitCode.NOT_FOUND
17747
+ );
17748
+ }
17749
+ }
17750
+ let authorityScopeSource = "cid-resource";
17751
+ let authorityScopeReason = "The revoke authority is scoped to the exact delegation CID.";
17752
+ let fallbackSpaceId;
17753
+ let commandAuthorityCid;
17754
+ const authorizeFallbackSpace = async (spaceId) => {
17755
+ fallbackSpaceId = spaceId;
17756
+ authorityScopeSource = "local-signed-grant-artifact";
17757
+ authorityScopeReason = "Raw-CID revoke authority could not be issued; the fallback space came from an owner-signed local grant whose Authorization recomputes to this CID.";
17758
+ targetSpaceSource = "local-signed-grant-artifact";
17759
+ const acquired = await ensureDelegationAuthority({
17760
+ ctx,
17761
+ profile,
17762
+ node,
17763
+ requested: [{
17764
+ service: "tinycloud.delegation",
17765
+ space: spaceId === node.accountSpaceId ? "default" : spaceId,
17766
+ path: "",
17767
+ actions: ["tinycloud.delegation/revoke"]
17768
+ }],
17769
+ expiryOption: void 0,
17770
+ reason: `Revoke delegation ${cid} using its CID-bound owner-signed grant artifact`,
17771
+ yes: options.yes,
17772
+ consentAlreadyGiven: true,
17773
+ persist: false
17774
+ });
17775
+ commandAuthorityCid = acquired?.cid;
17776
+ };
17777
+ if (!rawAuthorityCovered) {
17778
+ try {
17779
+ const acquired = await ensureDelegationAuthority({
17780
+ ctx,
17781
+ profile,
17782
+ node,
17783
+ requested: [revokePermission],
17784
+ expiryOption: void 0,
17785
+ reason: `Revoke delegation ${cid}`,
17786
+ yes: options.yes,
17787
+ consentAlreadyGiven: true,
17788
+ persist: false
17789
+ });
17790
+ commandAuthorityCid = acquired?.cid;
17791
+ } catch (error) {
17792
+ if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "RAW_RECAP_RESOURCE_UNSUPPORTED") {
17793
+ throw error;
17794
+ }
17795
+ const fallbackSpace = boundSpace ?? await resolveCidBoundTargetSpace({
17796
+ profileName: ctx.profile,
17797
+ ownerDid: profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId),
17798
+ cid
17799
+ });
17800
+ if (!fallbackSpace) {
17801
+ throw new CLIError(
17802
+ "RAW_RECAP_RESOURCE_UNSUPPORTED",
17803
+ `Cannot safely revoke "${cid}": raw CID ReCap resources are unavailable and no matching owner-signed local grant artifact binds its space to the CID.`,
17804
+ ExitCode.PERMISSION_DENIED
17805
+ );
17806
+ }
17807
+ await authorizeFallbackSpace(fallbackSpace);
17808
+ }
17809
+ }
17810
+ const result = fallbackSpaceId ? await node.delegationManager.revoke(cid, {
17811
+ targetSpaceId: fallbackSpaceId,
17812
+ ...commandAuthorityCid === void 0 ? {} : { authorityCid: commandAuthorityCid }
17813
+ }) : commandAuthorityCid === void 0 ? await node.revokeDelegation(cid) : await node.delegationManager.revoke(cid, { authorityCid: commandAuthorityCid });
17563
17814
  if (!result.ok) {
17815
+ if (result.error.meta?.status === 403 && result.error.message === "Failed to revoke delegation: 403 - Unauthorized Revoker") {
17816
+ const profileDid = profile.ownerDid ?? ownerDidFromSpace(node.accountSpaceId) ?? ownerDidFromSpace(profile.spaceId) ?? "unknown";
17817
+ throw new CLIError(
17818
+ "REVOKE_UNAUTHORIZED",
17819
+ "The node rejected this revocation: Unauthorized Revoker.",
17820
+ ExitCode.PERMISSION_DENIED,
17821
+ {
17822
+ status: 403,
17823
+ hint: `Use a different profile belonging to the delegation's grantor, its recipient, or the owner of a space it covers; the rejected profile was "${ctx.profile}" (DID ${profileDid}). Retry with \`tc --profile <profile-name> delegation revoke ${cid}\`.`
17824
+ }
17825
+ );
17826
+ }
17564
17827
  throw cliErrorFromService(result.error);
17565
17828
  }
17566
- outputJson({ cid, revoked: true });
17829
+ outputJson({ cid, revoked: true, targetSpaceSource, authorityScopeSource, authorityScopeReason });
17567
17830
  } catch (error) {
17568
17831
  handleError(error);
17569
17832
  }