@tinycloud/node-sdk 3.1.0-beta.10 → 3.1.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.
@@ -1504,6 +1504,9 @@ declare class TinyCloudNode {
1504
1504
  private resolveBootstrapDecision;
1505
1505
  private readBootstrapCompletionMarker;
1506
1506
  private writeBootstrapCompletionMarker;
1507
+ /**
1508
+ * A marker written before a canonical space was added triggers one bounded repair.
1509
+ */
1507
1510
  private isAcceptedBootstrapCompletionMarker;
1508
1511
  private markerReadIsUnhostedSpace;
1509
1512
  private runAccountBootstrap;
@@ -2300,6 +2303,7 @@ declare class TinyCloudNode {
2300
2303
  private shortServiceName;
2301
2304
  private permissionsToAbilities;
2302
2305
  private isEncryptionPermissionEntry;
2306
+ private isRawRevocationPermissionEntry;
2303
2307
  private permissionsToRawAbilities;
2304
2308
  private permissionOperations;
2305
2309
  /** Raw network authority cannot be covered by a nested encryption space. */
@@ -1504,6 +1504,9 @@ declare class TinyCloudNode {
1504
1504
  private resolveBootstrapDecision;
1505
1505
  private readBootstrapCompletionMarker;
1506
1506
  private writeBootstrapCompletionMarker;
1507
+ /**
1508
+ * A marker written before a canonical space was added triggers one bounded repair.
1509
+ */
1507
1510
  private isAcceptedBootstrapCompletionMarker;
1508
1511
  private markerReadIsUnhostedSpace;
1509
1512
  private runAccountBootstrap;
@@ -2300,6 +2303,7 @@ declare class TinyCloudNode {
2300
2303
  private shortServiceName;
2301
2304
  private permissionsToAbilities;
2302
2305
  private isEncryptionPermissionEntry;
2306
+ private isRawRevocationPermissionEntry;
2303
2307
  private permissionsToRawAbilities;
2304
2308
  private permissionOperations;
2305
2309
  /** Raw network authority cannot be covered by a nested encryption space. */
package/dist/core.cjs CHANGED
@@ -2699,15 +2699,21 @@ var _TinyCloudNode = class _TinyCloudNode {
2699
2699
  if (!this.wasmBindings.invokeAny) {
2700
2700
  throw new Error("WASM binding does not support invokeAny");
2701
2701
  }
2702
- const operations = entries.flatMap((entry) => {
2702
+ const routedEntries = entries;
2703
+ const operations = routedEntries.flatMap((entry) => {
2703
2704
  const operation = this.operationFromInvokeAnyEntry(entry);
2704
2705
  return operation ? [operation] : [];
2705
2706
  });
2706
- const grant = this.findGrantForOperations(operations);
2707
- const invocationSession = !grant || grant.provenance === "primary" ? session : grant.session;
2708
- const caveatPreservingEntries = grant ? entries.map((entry) => {
2707
+ const authorityCid = routedEntries.length === 1 ? routedEntries[0]?.revokeAuthorityCid : void 0;
2708
+ const explicitGrant = authorityCid === void 0 ? void 0 : this.runtimePermissionGrants.find((grant2) => grant2.delegation.cid === authorityCid);
2709
+ if (authorityCid !== void 0 && !explicitGrant) {
2710
+ throw new Error(`Explicit revocation authority ${authorityCid} is not installed.`);
2711
+ }
2712
+ const grant = explicitGrant ?? this.findGrantForOperations(operations);
2713
+ const invocationSession = explicitGrant ? explicitGrant.session : !grant || grant.provenance === "primary" ? session : grant.session;
2714
+ const caveatPreservingEntries = routedEntries.map(({ revokeAuthorityCid: _cid, ...entry }) => {
2709
2715
  const requested = this.operationFromInvokeAnyEntry(entry);
2710
- const granted = requested && grant.operations.find(
2716
+ const granted = grant && requested && grant.operations.find(
2711
2717
  (candidate) => this.operationCovers(candidate, requested)
2712
2718
  );
2713
2719
  if (!granted?.caveats?.length) {
@@ -2717,7 +2723,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2717
2723
  throw new Error("Invocation caveats do not match signed ReCap authority.");
2718
2724
  }
2719
2725
  return { ...entry, caveats: cloneRecapCaveats(granted.caveats) };
2720
- }) : entries;
2726
+ });
2721
2727
  return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
2722
2728
  };
2723
2729
  this.explicitHost = config.host;
@@ -3182,12 +3188,13 @@ var _TinyCloudNode = class _TinyCloudNode {
3182
3188
  if (!auth) {
3183
3189
  throw new Error("Account bootstrap requires an active wallet session");
3184
3190
  }
3191
+ const accountSpaceId = this.ownedSpaceId(import_sdk_core8.ACCOUNT_REGISTRY_SPACE);
3185
3192
  const skipped = auth.lastActivationSkippedSpaceIds;
3193
+ if (skipped.includes(accountSpaceId)) return { action: "run", mode: "fresh" };
3186
3194
  if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
3187
- return { action: "run", mode: "fresh" };
3195
+ return { action: "run", mode: "repair" };
3188
3196
  }
3189
3197
  try {
3190
- const accountSpaceId = this.ownedSpaceId(import_sdk_core8.ACCOUNT_REGISTRY_SPACE);
3191
3198
  const markerPermission = {
3192
3199
  service: "tinycloud.kv",
3193
3200
  space: accountSpaceId,
@@ -3199,7 +3206,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3199
3206
  }
3200
3207
  const marker = await this.readBootstrapCompletionMarker();
3201
3208
  if (marker.ok) {
3202
- if (this.isAcceptedBootstrapCompletionMarker(marker.data.data)) {
3209
+ if (this.isAcceptedBootstrapCompletionMarker(marker.data.data, steps.map((step) => step.id))) {
3203
3210
  return { action: "skip" };
3204
3211
  }
3205
3212
  return { action: "run", mode: "repair" };
@@ -3249,12 +3256,16 @@ var _TinyCloudNode = class _TinyCloudNode {
3249
3256
  );
3250
3257
  }
3251
3258
  }
3252
- isAcceptedBootstrapCompletionMarker(value) {
3259
+ /**
3260
+ * A marker written before a canonical space was added triggers one bounded repair.
3261
+ */
3262
+ isAcceptedBootstrapCompletionMarker(value, canonicalStepIds) {
3253
3263
  if (typeof value !== "object" || value === null) return false;
3254
3264
  const entries = Object.entries(value);
3255
3265
  const markerValue = (name) => entries.find(([key]) => key === name)?.[1];
3256
3266
  const version = markerValue("v");
3257
- return typeof version === "number" && Number.isInteger(version) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version);
3267
+ const stepIds = markerValue("stepIds");
3268
+ return typeof version === "number" && Number.isInteger(version) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version) && Array.isArray(stepIds) && canonicalStepIds.every((stepId) => stepIds.includes(stepId));
3258
3269
  }
3259
3270
  markerReadIsUnhostedSpace(meta) {
3260
3271
  return meta?.status === 404;
@@ -3341,30 +3352,39 @@ var _TinyCloudNode = class _TinyCloudNode {
3341
3352
  }
3342
3353
  }
3343
3354
  if (step.kind === "seed-spaces") {
3344
- const registered = await this.account.spaces.registerBatch(
3345
- step.spaces.map((space) => ({
3346
- spaceId: space.spaceId,
3347
- name: space.name,
3348
- ownerDid: this.did,
3349
- type: "owned",
3350
- permissions: ["*"],
3351
- status: "active"
3352
- }))
3353
- );
3354
- if (!registered.ok) {
3355
- throw Object.assign(
3356
- new Error(`Failed to seed account spaces: ${registered.error.message}`),
3357
- { cause: registered.error }
3358
- );
3359
- }
3360
- const batchError = registered.data.recoveredFromBatchError;
3361
- if (batchError) {
3362
- warnings.push({
3363
- stepId: step.id,
3364
- kind: "batch-write-reconciled",
3365
- code: batchError.code,
3366
- message: batchError.message
3367
- });
3355
+ const inputs = step.spaces.map((space) => ({
3356
+ spaceId: space.spaceId,
3357
+ name: space.name,
3358
+ ownerDid: this.did,
3359
+ type: "owned",
3360
+ permissions: ["*"],
3361
+ status: "active"
3362
+ }));
3363
+ if (mode === "fresh") {
3364
+ const registered = await this.account.spaces.registerBatch(inputs);
3365
+ if (!registered.ok) {
3366
+ throw Object.assign(
3367
+ new Error(`Failed to seed account spaces: ${registered.error.message}`),
3368
+ { cause: registered.error }
3369
+ );
3370
+ }
3371
+ const batchError = registered.data.recoveredFromBatchError;
3372
+ if (batchError) {
3373
+ warnings.push({
3374
+ stepId: step.id,
3375
+ kind: "batch-write-reconciled",
3376
+ code: batchError.code,
3377
+ message: batchError.message
3378
+ });
3379
+ }
3380
+ } else {
3381
+ const ensured = await this.account.spaces.registerMissing(inputs);
3382
+ if (!ensured.ok) {
3383
+ throw Object.assign(
3384
+ new Error(`Failed to seed account spaces: ${ensured.error.message}`),
3385
+ { cause: ensured.error }
3386
+ );
3387
+ }
3368
3388
  }
3369
3389
  }
3370
3390
  if (step.kind === "seed-applications") {
@@ -4049,6 +4069,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4049
4069
  stagedSessionExpiry,
4050
4070
  stagedRecap
4051
4071
  );
4072
+ const stagedAccountSpaceId = stagedAddress ? this.wasmBindings.makeSpaceId(stagedAddress, stagedChainId, import_sdk_core8.ACCOUNT_REGISTRY_SPACE) : void 0;
4052
4073
  const stagedGraph = this.stageRestoredServiceGraph({
4053
4074
  host: stagedHost,
4054
4075
  manager: stagedManager,
@@ -4057,6 +4078,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4057
4078
  nodeDid: stagedNodeDid,
4058
4079
  address: stagedAddress,
4059
4080
  chainId: stagedChainId,
4081
+ accountSpaceId: stagedAccountSpaceId,
4060
4082
  tinyCloudSession: stagedTcSession,
4061
4083
  sessionExpiry: stagedSessionExpiry,
4062
4084
  recap: stagedRecap
@@ -4222,6 +4244,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4222
4244
  }
4223
4245
  const delegationManager = new import_sdk_core8.DelegationManager({
4224
4246
  hosts: [input.host],
4247
+ accountSpaceId: input.accountSpaceId,
4225
4248
  session: input.serviceSession,
4226
4249
  invoke: graph.invoke,
4227
4250
  invokeAny: graph.invokeAny,
@@ -5926,6 +5949,14 @@ var _TinyCloudNode = class _TinyCloudNode {
5926
5949
  delegation.delegationHeader
5927
5950
  );
5928
5951
  if (!activateResult.success) {
5952
+ if (this.operationsFromDelegation(delegation).some(
5953
+ (operation) => operation.resource?.startsWith("urn:cid:") === true && operation.service === "delegation" && operation.action === "tinycloud.delegation/revoke"
5954
+ ) && activateResult.status === 401 && /Cannot find parent delegation/i.test(activateResult.error ?? "")) {
5955
+ throw Object.assign(
5956
+ new Error("The host cannot activate a root raw-CID revoke authority without a parent delegation."),
5957
+ { code: "RAW_RECAP_RESOURCE_UNSUPPORTED", cause: activateResult }
5958
+ );
5959
+ }
5929
5960
  throw Object.assign(
5930
5961
  new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
5931
5962
  { cause: activateResult }
@@ -5973,10 +6004,10 @@ var _TinyCloudNode = class _TinyCloudNode {
5973
6004
  }
5974
6005
  this.assertDelegationCaveatsPreservable(expanded);
5975
6006
  const rawEntries = expanded.filter(
5976
- (entry) => this.isEncryptionPermissionEntry(entry)
6007
+ (entry) => this.isEncryptionPermissionEntry(entry) || this.isRawRevocationPermissionEntry(entry)
5977
6008
  );
5978
6009
  const spaceEntries = expanded.filter(
5979
- (entry) => !this.isEncryptionPermissionEntry(entry)
6010
+ (entry) => !this.isEncryptionPermissionEntry(entry) && !this.isRawRevocationPermissionEntry(entry)
5980
6011
  );
5981
6012
  const bySpace = /* @__PURE__ */ new Map();
5982
6013
  for (const entry of spaceEntries) {
@@ -6003,17 +6034,29 @@ var _TinyCloudNode = class _TinyCloudNode {
6003
6034
  }
6004
6035
  const delegatedEntries = [...entries, ...rawForDelegation];
6005
6036
  const abilities = this.permissionsToAbilities(entries);
6006
- const prepared = this.wasmBindings.prepareSession({
6007
- abilities,
6008
- ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
6009
- address: this.wasmBindings.ensureEip55(session.address),
6010
- chainId: session.chainId,
6011
- domain: this.siweDomain,
6012
- issuedAt: now.toISOString(),
6013
- expirationTime: expiresAt.toISOString(),
6014
- spaceId,
6015
- jwk: session.jwk
6016
- });
6037
+ const prepared = (() => {
6038
+ try {
6039
+ return this.wasmBindings.prepareSession({
6040
+ abilities,
6041
+ ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
6042
+ address: this.wasmBindings.ensureEip55(session.address),
6043
+ chainId: session.chainId,
6044
+ domain: this.siweDomain,
6045
+ issuedAt: now.toISOString(),
6046
+ expirationTime: expiresAt.toISOString(),
6047
+ spaceId,
6048
+ jwk: session.jwk
6049
+ });
6050
+ } catch (error) {
6051
+ if (rawForDelegation.some((entry) => this.isRawRevocationPermissionEntry(entry)) && /invalid ReCap resource URI/i.test(error instanceof Error ? error.message : String(error))) {
6052
+ throw Object.assign(
6053
+ new Error("This WASM build cannot encode raw urn:cid ReCap resources."),
6054
+ { code: "RAW_RECAP_RESOURCE_UNSUPPORTED", cause: error }
6055
+ );
6056
+ }
6057
+ throw error;
6058
+ }
6059
+ })();
6017
6060
  const signature = await this.signer.signMessage(prepared.siwe);
6018
6061
  const delegatedSession = this.wasmBindings.completeSessionSetup({
6019
6062
  ...prepared,
@@ -6024,6 +6067,12 @@ var _TinyCloudNode = class _TinyCloudNode {
6024
6067
  delegatedSession.delegationHeader
6025
6068
  );
6026
6069
  if (!activateResult.success) {
6070
+ if (rawForDelegation.some((entry) => this.isRawRevocationPermissionEntry(entry)) && activateResult.status === 401 && /Cannot find parent delegation/i.test(activateResult.error ?? "")) {
6071
+ throw Object.assign(
6072
+ new Error("The host cannot activate a root raw-CID revoke authority without a parent delegation."),
6073
+ { code: "RAW_RECAP_RESOURCE_UNSUPPORTED", cause: activateResult }
6074
+ );
6075
+ }
6027
6076
  throw Object.assign(
6028
6077
  new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
6029
6078
  { cause: activateResult }
@@ -6725,6 +6774,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6725
6774
  permissionsToAbilities(entries) {
6726
6775
  const abilities = {};
6727
6776
  for (const entry of entries) {
6777
+ if (this.isRawRevocationPermissionEntry(entry)) continue;
6728
6778
  const service = this.shortServiceName(entry.service);
6729
6779
  abilities[service] ?? (abilities[service] = {});
6730
6780
  const existing = abilities[service][entry.path] ?? [];
@@ -6742,9 +6792,16 @@ var _TinyCloudNode = class _TinyCloudNode {
6742
6792
  isEncryptionPermissionEntry(entry) {
6743
6793
  return entry.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
6744
6794
  }
6795
+ isRawRevocationPermissionEntry(entry) {
6796
+ return entry.service === "tinycloud.delegation" && entry.path === "" && entry.space?.startsWith("urn:cid:") === true && entry.actions.includes("tinycloud.delegation/revoke");
6797
+ }
6745
6798
  permissionsToRawAbilities(entries) {
6746
6799
  const rawAbilities = {};
6747
6800
  for (const entry of entries) {
6801
+ if (this.isRawRevocationPermissionEntry(entry)) {
6802
+ rawAbilities[entry.space] = [...new Set(entry.actions)];
6803
+ continue;
6804
+ }
6748
6805
  if (!this.isEncryptionPermissionEntry(entry)) {
6749
6806
  continue;
6750
6807
  }
@@ -6764,7 +6821,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6764
6821
  return entries.flatMap((entry) => {
6765
6822
  const service = this.shortServiceName(entry.service);
6766
6823
  return entry.actions.map((action) => ({
6767
- ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6824
+ ...this.isRawRevocationPermissionEntry(entry) ? { resource: entry.space } : this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6768
6825
  service,
6769
6826
  path: entry.path,
6770
6827
  action,
@@ -6774,11 +6831,13 @@ var _TinyCloudNode = class _TinyCloudNode {
6774
6831
  }
6775
6832
  /** Raw network authority cannot be covered by a nested encryption space. */
6776
6833
  signedCapabilitySubset(requested, granted) {
6777
- const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
6778
- const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
6834
+ const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry) || this.isRawRevocationPermissionEntry(entry)).map((entry) => this.isEncryptionPermissionEntry(entry) ? { ...entry, space: "encryption" } : entry);
6835
+ const spaceGranted = granted.filter(
6836
+ (entry) => !this.isEncryptionPermissionEntry(entry) && !this.isRawRevocationPermissionEntry(entry)
6837
+ );
6779
6838
  const missing = requested.filter((entry) => {
6780
- const raw = this.isEncryptionPermissionEntry(entry);
6781
- const comparable = raw ? { ...entry, space: "encryption" } : entry;
6839
+ const raw = this.isEncryptionPermissionEntry(entry) || this.isRawRevocationPermissionEntry(entry);
6840
+ const comparable = raw && this.isEncryptionPermissionEntry(entry) ? { ...entry, space: "encryption" } : entry;
6782
6841
  return !(0, import_sdk_core8.isCapabilitySubset)([comparable], raw ? rawGranted : spaceGranted).subset;
6783
6842
  });
6784
6843
  return { subset: missing.length === 0, missing };
@@ -6793,10 +6852,11 @@ var _TinyCloudNode = class _TinyCloudNode {
6793
6852
  }
6794
6853
  permissionEntriesToOperations(entries, session) {
6795
6854
  return entries.flatMap((entry) => {
6796
- const spaceId = this.resolvePermissionSpace(entry.space, session);
6855
+ const rawRevocation = this.isRawRevocationPermissionEntry(entry);
6856
+ const spaceId = rawRevocation ? void 0 : this.resolvePermissionSpace(entry.space, session);
6797
6857
  const service = this.shortServiceName(entry.service);
6798
6858
  return entry.actions.map((action) => ({
6799
- ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6859
+ ...rawRevocation ? { resource: entry.space } : this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6800
6860
  service,
6801
6861
  path: entry.path,
6802
6862
  action,
@@ -6875,7 +6935,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6875
6935
  delegatedResourcesForEntries(entries, spaceId) {
6876
6936
  return entries.map((entry) => ({
6877
6937
  service: this.shortServiceName(entry.service),
6878
- space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
6938
+ space: this.isRawRevocationPermissionEntry(entry) ? entry.space : this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
6879
6939
  path: entry.path,
6880
6940
  actions: [...entry.actions],
6881
6941
  ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
@@ -6885,8 +6945,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6885
6945
  const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
6886
6946
  return resources.flatMap((resource) => {
6887
6947
  const service = this.invocationServiceName(resource.service);
6948
+ const rawRevocation = service === "delegation" && resource.space.startsWith("urn:cid:") && resource.path === "";
6888
6949
  return resource.actions.map((action) => ({
6889
- ...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
6950
+ ...rawRevocation ? { resource: resource.space } : this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
6890
6951
  service,
6891
6952
  path: resource.path,
6892
6953
  action,
@@ -6932,7 +6993,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6932
6993
  * @internal
6933
6994
  */
6934
6995
  buildActivationAbilities(delegation) {
6935
- var _a, _b, _c;
6996
+ var _a, _b, _c, _d;
6936
6997
  const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
6937
6998
  const abilities = {};
6938
6999
  const rawAbilities = {};
@@ -6952,13 +7013,18 @@ var _TinyCloudNode = class _TinyCloudNode {
6952
7013
  addActions(rawAbilities[resource.path], resource.actions);
6953
7014
  continue;
6954
7015
  }
7016
+ if (service === "delegation" && resource.space.startsWith("urn:cid:") && resource.path === "") {
7017
+ rawAbilities[_b = resource.space] ?? (rawAbilities[_b] = []);
7018
+ addActions(rawAbilities[resource.space], resource.actions);
7019
+ continue;
7020
+ }
6955
7021
  if (resource.space !== delegation.spaceId) {
6956
7022
  throw new Error(
6957
7023
  `useDelegation: resource targets space '${resource.space}' but the delegation activates space '${delegation.spaceId}'. Multi-space delegations cannot be activated in a single useDelegation call.`
6958
7024
  );
6959
7025
  }
6960
7026
  abilities[service] ?? (abilities[service] = {});
6961
- (_b = abilities[service])[_c = resource.path] ?? (_b[_c] = []);
7027
+ (_c = abilities[service])[_d = resource.path] ?? (_c[_d] = []);
6962
7028
  addActions(abilities[service][resource.path], resource.actions);
6963
7029
  }
6964
7030
  return { abilities, rawAbilities };