@tinycloud/node-sdk 2.11.0-beta.10 → 2.11.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.
@@ -239,7 +239,7 @@ interface NodeUserAuthorizationConfig {
239
239
  manifest?: Manifest | Manifest[];
240
240
  /** Pre-composed manifest request. Takes precedence over `manifest`. */
241
241
  capabilityRequest?: ComposedManifestRequest;
242
- /** Include implicit account registry permissions when composing `manifest`. Default true. */
242
+ /** Include canonical account registry read/create-update/list permissions when composing `manifest` and plain sign-in. Default true. */
243
243
  includeAccountRegistryPermissions?: boolean;
244
244
  }
245
245
  interface CreateBootstrapSessionOptions {
@@ -975,6 +975,9 @@ declare class DelegatedAccess {
975
975
  * ```
976
976
  */
977
977
 
978
+ declare const BOOTSTRAP_COMPLETION_MARKER_KEY = "system/bootstrap/complete";
979
+ declare const BOOTSTRAP_COMPLETION_MARKER_VERSION = 1;
980
+ declare const ACCEPTED_MARKER_VERSIONS: readonly number[];
978
981
  /** Input for {@link TinyCloudNode.readSecret}. The target space is required. */
979
982
  interface SecretReadInput {
980
983
  /** Explicit full space URI or an owned-space name. */
@@ -1104,7 +1107,7 @@ interface TinyCloudNodeConfig {
1104
1107
  manifest?: Manifest | Manifest[];
1105
1108
  /** Pre-composed manifest request. Takes precedence over `manifest`. */
1106
1109
  capabilityRequest?: ComposedManifestRequest;
1107
- /** Include implicit account registry permissions when composing `manifest`. Default true. */
1110
+ /** Include account bootstrap support in plain sessions and implicit manifest permissions. Default true. */
1108
1111
  includeAccountRegistryPermissions?: boolean;
1109
1112
  /** Run canonical first-account bootstrap when fresh account state is detected. Default true. */
1110
1113
  autoBootstrapAccount?: boolean;
@@ -1190,6 +1193,12 @@ interface BootstrapWarning {
1190
1193
  /** Human-readable detail. Diagnostics only — never parse this. */
1191
1194
  message: string;
1192
1195
  }
1196
+ /** Durable attestation that the canonical account bootstrap ceremony completed. */
1197
+ interface BootstrapCompletionMarker {
1198
+ v: number;
1199
+ stepIds: string[];
1200
+ completedAt: string;
1201
+ }
1193
1202
  declare class TinyCloudNode {
1194
1203
  /** @internal Registered by importing @tinycloud/node-sdk (not /core) */
1195
1204
  private static nodeDefaults?;
@@ -1258,9 +1267,12 @@ declare class TinyCloudNode {
1258
1267
  private _bootstrapSkipped;
1259
1268
  /**
1260
1269
  * Outcome of the last signIn()'s account-bootstrap attempt. `skipped` is
1261
- * true when bootstrap did not complete (interactive signer, auto-sign
1262
- * denied, or a bootstrap step failed); `reason` carries the cause so apps
1263
- * can surface a "finish account setup" call-to-action. `warnings` is
1270
+ * true when bootstrap did not complete in this invocation (interactive signer,
1271
+ * auto-bootstrap disabled, already provisioned, or a decision/provisioning
1272
+ * step failed). `already-provisioned` means the bootstrap was not run here;
1273
+ * it does not mean a prior provisioning attempt failed to complete.
1274
+ * `reason` carries the cause so apps can surface a "finish account setup"
1275
+ * call-to-action. `warnings` is
1264
1276
  * present when bootstrap completed, but one or more steps recovered from
1265
1277
  * an ambiguous failure (e.g. a KV batch write reconciled via per-space
1266
1278
  * fallback) — clean vs. recovered runs are otherwise byte-identical.
@@ -1396,7 +1408,11 @@ declare class TinyCloudNode {
1396
1408
  signIn(options?: SignInOptions): Promise<void>;
1397
1409
  private ownedSpaceId;
1398
1410
  private bootstrapAccountIfNeeded;
1399
- private isFreshBootstrapAccount;
1411
+ private resolveBootstrapDecision;
1412
+ private readBootstrapCompletionMarker;
1413
+ private writeBootstrapCompletionMarker;
1414
+ private isAcceptedBootstrapCompletionMarker;
1415
+ private markerReadIsUnhostedSpace;
1400
1416
  private runAccountBootstrap;
1401
1417
  private registerBootstrapRuntimeGrant;
1402
1418
  /**
@@ -2301,6 +2317,13 @@ declare class TinyCloudNode {
2301
2317
  disableSubDelegation?: boolean;
2302
2318
  /** Expiration time in milliseconds from now (must be before parent's expiry) */
2303
2319
  expiryMs?: number;
2320
+ /** Explicit multi-resource projection, including encryption networks. */
2321
+ resources?: Array<{
2322
+ service: string;
2323
+ space?: string;
2324
+ path: string;
2325
+ actions: string[];
2326
+ }>;
2304
2327
  }): Promise<PortableDelegation>;
2305
2328
  }
2306
2329
 
@@ -2412,4 +2435,4 @@ declare class WasmKeyProvider implements KeyProvider {
2412
2435
  */
2413
2436
  declare function createWasmKeyProvider(sessionManager: SessionManagerWithListing): WasmKeyProvider;
2414
2437
 
2415
- export { type AuthDelegationArtifact as A, type BootstrapWarning as B, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthRequestArtifact as a, type DelegateToResult as b, DelegatedAccess as c, type DelegationAuthority as d, NodeUserAuthorization as e, type NodeUserAuthorizationConfig as f, type RuntimeDelegationActivator as g, type RuntimePermissionGrantOptions as h, type SecretReadInput as i, type SecretReadResult as j, type SignStrategy as k, type TinyCloudNodeConfig as l, type WasmKeyProviderConfig as m, activateValidatedRuntimeDelegation as n, createWasmKeyProvider as o, defaultSignStrategy as p, deserializeDelegation as q, grantAuthRequest as r, serializeDelegation as s };
2438
+ export { ACCEPTED_MARKER_VERSIONS as A, BOOTSTRAP_COMPLETION_MARKER_KEY as B, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthDelegationArtifact as a, type AuthRequestArtifact as b, BOOTSTRAP_COMPLETION_MARKER_VERSION as c, type BootstrapCompletionMarker as d, type BootstrapWarning as e, type DelegateToResult as f, DelegatedAccess as g, type DelegationAuthority as h, NodeUserAuthorization as i, type NodeUserAuthorizationConfig as j, type RuntimeDelegationActivator as k, type RuntimePermissionGrantOptions as l, type SecretReadInput as m, type SecretReadResult as n, type SignStrategy as o, type TinyCloudNodeConfig as p, type WasmKeyProviderConfig as q, activateValidatedRuntimeDelegation as r, createWasmKeyProvider as s, defaultSignStrategy as t, deserializeDelegation as u, grantAuthRequest as v, serializeDelegation as w };
@@ -239,7 +239,7 @@ interface NodeUserAuthorizationConfig {
239
239
  manifest?: Manifest | Manifest[];
240
240
  /** Pre-composed manifest request. Takes precedence over `manifest`. */
241
241
  capabilityRequest?: ComposedManifestRequest;
242
- /** Include implicit account registry permissions when composing `manifest`. Default true. */
242
+ /** Include canonical account registry read/create-update/list permissions when composing `manifest` and plain sign-in. Default true. */
243
243
  includeAccountRegistryPermissions?: boolean;
244
244
  }
245
245
  interface CreateBootstrapSessionOptions {
@@ -975,6 +975,9 @@ declare class DelegatedAccess {
975
975
  * ```
976
976
  */
977
977
 
978
+ declare const BOOTSTRAP_COMPLETION_MARKER_KEY = "system/bootstrap/complete";
979
+ declare const BOOTSTRAP_COMPLETION_MARKER_VERSION = 1;
980
+ declare const ACCEPTED_MARKER_VERSIONS: readonly number[];
978
981
  /** Input for {@link TinyCloudNode.readSecret}. The target space is required. */
979
982
  interface SecretReadInput {
980
983
  /** Explicit full space URI or an owned-space name. */
@@ -1104,7 +1107,7 @@ interface TinyCloudNodeConfig {
1104
1107
  manifest?: Manifest | Manifest[];
1105
1108
  /** Pre-composed manifest request. Takes precedence over `manifest`. */
1106
1109
  capabilityRequest?: ComposedManifestRequest;
1107
- /** Include implicit account registry permissions when composing `manifest`. Default true. */
1110
+ /** Include account bootstrap support in plain sessions and implicit manifest permissions. Default true. */
1108
1111
  includeAccountRegistryPermissions?: boolean;
1109
1112
  /** Run canonical first-account bootstrap when fresh account state is detected. Default true. */
1110
1113
  autoBootstrapAccount?: boolean;
@@ -1190,6 +1193,12 @@ interface BootstrapWarning {
1190
1193
  /** Human-readable detail. Diagnostics only — never parse this. */
1191
1194
  message: string;
1192
1195
  }
1196
+ /** Durable attestation that the canonical account bootstrap ceremony completed. */
1197
+ interface BootstrapCompletionMarker {
1198
+ v: number;
1199
+ stepIds: string[];
1200
+ completedAt: string;
1201
+ }
1193
1202
  declare class TinyCloudNode {
1194
1203
  /** @internal Registered by importing @tinycloud/node-sdk (not /core) */
1195
1204
  private static nodeDefaults?;
@@ -1258,9 +1267,12 @@ declare class TinyCloudNode {
1258
1267
  private _bootstrapSkipped;
1259
1268
  /**
1260
1269
  * Outcome of the last signIn()'s account-bootstrap attempt. `skipped` is
1261
- * true when bootstrap did not complete (interactive signer, auto-sign
1262
- * denied, or a bootstrap step failed); `reason` carries the cause so apps
1263
- * can surface a "finish account setup" call-to-action. `warnings` is
1270
+ * true when bootstrap did not complete in this invocation (interactive signer,
1271
+ * auto-bootstrap disabled, already provisioned, or a decision/provisioning
1272
+ * step failed). `already-provisioned` means the bootstrap was not run here;
1273
+ * it does not mean a prior provisioning attempt failed to complete.
1274
+ * `reason` carries the cause so apps can surface a "finish account setup"
1275
+ * call-to-action. `warnings` is
1264
1276
  * present when bootstrap completed, but one or more steps recovered from
1265
1277
  * an ambiguous failure (e.g. a KV batch write reconciled via per-space
1266
1278
  * fallback) — clean vs. recovered runs are otherwise byte-identical.
@@ -1396,7 +1408,11 @@ declare class TinyCloudNode {
1396
1408
  signIn(options?: SignInOptions): Promise<void>;
1397
1409
  private ownedSpaceId;
1398
1410
  private bootstrapAccountIfNeeded;
1399
- private isFreshBootstrapAccount;
1411
+ private resolveBootstrapDecision;
1412
+ private readBootstrapCompletionMarker;
1413
+ private writeBootstrapCompletionMarker;
1414
+ private isAcceptedBootstrapCompletionMarker;
1415
+ private markerReadIsUnhostedSpace;
1400
1416
  private runAccountBootstrap;
1401
1417
  private registerBootstrapRuntimeGrant;
1402
1418
  /**
@@ -2301,6 +2317,13 @@ declare class TinyCloudNode {
2301
2317
  disableSubDelegation?: boolean;
2302
2318
  /** Expiration time in milliseconds from now (must be before parent's expiry) */
2303
2319
  expiryMs?: number;
2320
+ /** Explicit multi-resource projection, including encryption networks. */
2321
+ resources?: Array<{
2322
+ service: string;
2323
+ space?: string;
2324
+ path: string;
2325
+ actions: string[];
2326
+ }>;
2304
2327
  }): Promise<PortableDelegation>;
2305
2328
  }
2306
2329
 
@@ -2412,4 +2435,4 @@ declare class WasmKeyProvider implements KeyProvider {
2412
2435
  */
2413
2436
  declare function createWasmKeyProvider(sessionManager: SessionManagerWithListing): WasmKeyProvider;
2414
2437
 
2415
- export { type AuthDelegationArtifact as A, type BootstrapWarning as B, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthRequestArtifact as a, type DelegateToResult as b, DelegatedAccess as c, type DelegationAuthority as d, NodeUserAuthorization as e, type NodeUserAuthorizationConfig as f, type RuntimeDelegationActivator as g, type RuntimePermissionGrantOptions as h, type SecretReadInput as i, type SecretReadResult as j, type SignStrategy as k, type TinyCloudNodeConfig as l, type WasmKeyProviderConfig as m, activateValidatedRuntimeDelegation as n, createWasmKeyProvider as o, defaultSignStrategy as p, deserializeDelegation as q, grantAuthRequest as r, serializeDelegation as s };
2438
+ export { ACCEPTED_MARKER_VERSIONS as A, BOOTSTRAP_COMPLETION_MARKER_KEY as B, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthDelegationArtifact as a, type AuthRequestArtifact as b, BOOTSTRAP_COMPLETION_MARKER_VERSION as c, type BootstrapCompletionMarker as d, type BootstrapWarning as e, type DelegateToResult as f, DelegatedAccess as g, type DelegationAuthority as h, NodeUserAuthorization as i, type NodeUserAuthorizationConfig as j, type RuntimeDelegationActivator as k, type RuntimePermissionGrantOptions as l, type SecretReadInput as m, type SecretReadResult as n, type SignStrategy as o, type TinyCloudNodeConfig as p, type WasmKeyProviderConfig as q, activateValidatedRuntimeDelegation as r, createWasmKeyProvider as s, defaultSignStrategy as t, deserializeDelegation as u, grantAuthRequest as v, serializeDelegation as w };
package/dist/core.cjs CHANGED
@@ -371,6 +371,16 @@ function addRawAbility(rawAbilities, resource, action) {
371
371
  actions.push(action);
372
372
  }
373
373
  }
374
+ function cloneAbilitiesMap(actions) {
375
+ return Object.fromEntries(
376
+ Object.entries(actions).map(([service, paths]) => [
377
+ service,
378
+ Object.fromEntries(
379
+ Object.entries(paths).map(([path, pathActions]) => [path, [...pathActions]])
380
+ )
381
+ ])
382
+ );
383
+ }
374
384
  var NodeUserAuthorization = class {
375
385
  constructor(config) {
376
386
  this.activeSessionKeyId = "default";
@@ -642,23 +652,41 @@ var NodeUserAuthorization = class {
642
652
  const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
643
653
  const primarySpaceId2 = (0, import_sdk_core3.makePkhSpaceId)(address, chainId, this.spacePrefix);
644
654
  const secretsSpaceId = (0, import_sdk_core3.makePkhSpaceId)(address, chainId, "secrets");
645
- return {
646
- abilities: this.defaultActions,
647
- spaceId: primarySpaceId2,
648
- spaceAbilities: {
649
- [primarySpaceId2]: this.defaultActions,
650
- [secretsSpaceId]: {
651
- kv: {
652
- "vault/secrets/": [
653
- import_sdk_core3.KV.GET,
654
- import_sdk_core3.KV.PUT,
655
- import_sdk_core3.KV.DEL,
656
- import_sdk_core3.KV.LIST,
657
- import_sdk_core3.KV.METADATA
658
- ]
655
+ const primaryActions = this.includeAccountRegistryPermissions && this.spacePrefix === import_sdk_core3.ACCOUNT_REGISTRY_SPACE ? cloneAbilitiesMap(this.defaultActions) : this.defaultActions;
656
+ const spaceAbilities2 = {
657
+ [primarySpaceId2]: primaryActions,
658
+ [secretsSpaceId]: {
659
+ kv: {
660
+ "vault/secrets/": [
661
+ import_sdk_core3.KV.GET,
662
+ import_sdk_core3.KV.PUT,
663
+ import_sdk_core3.KV.DEL,
664
+ import_sdk_core3.KV.LIST,
665
+ import_sdk_core3.KV.METADATA
666
+ ]
667
+ }
668
+ }
669
+ };
670
+ if (this.includeAccountRegistryPermissions && this.spacePrefix !== import_sdk_core3.ACCOUNT_REGISTRY_SPACE) {
671
+ spaceAbilities2[(0, import_sdk_core3.makePkhSpaceId)(address, chainId, import_sdk_core3.ACCOUNT_REGISTRY_SPACE)] = (0, import_sdk_core3.resourceCapabilitiesToAbilitiesMap)(import_sdk_core3.ACCOUNT_MANIFEST_PERMISSIONS);
672
+ } else if (this.includeAccountRegistryPermissions) {
673
+ const accountAbilities = (0, import_sdk_core3.resourceCapabilitiesToAbilitiesMap)(
674
+ import_sdk_core3.ACCOUNT_MANIFEST_PERMISSIONS
675
+ );
676
+ for (const [service, paths] of Object.entries(accountAbilities)) {
677
+ const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
678
+ for (const [path, actions] of Object.entries(paths)) {
679
+ const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
680
+ for (const action of actions) {
681
+ if (!existingActions.includes(action)) existingActions.push(action);
659
682
  }
660
683
  }
661
- },
684
+ }
685
+ }
686
+ return {
687
+ abilities: primaryActions,
688
+ spaceId: primarySpaceId2,
689
+ spaceAbilities: spaceAbilities2,
662
690
  rawAbilities: {
663
691
  [defaultNetworkId]: [DECRYPT_ACTION, NETWORK_CREATE_ACTION]
664
692
  }
@@ -2125,6 +2153,9 @@ var DEFAULT_HOST = "https://node.tinycloud.xyz";
2125
2153
  var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
2126
2154
  var NETWORK_CREATE_ACTION2 = import_sdk_core8.ENCRYPTION.NETWORK_CREATE;
2127
2155
  var DECRYPT_ACTION2 = import_sdk_core8.ENCRYPTION.DECRYPT;
2156
+ var BOOTSTRAP_COMPLETION_MARKER_KEY = "system/bootstrap/complete";
2157
+ var BOOTSTRAP_COMPLETION_MARKER_VERSION = 1;
2158
+ var ACCEPTED_MARKER_VERSIONS = [BOOTSTRAP_COMPLETION_MARKER_VERSION];
2128
2159
  var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
2129
2160
  function isSecretPayload(value) {
2130
2161
  return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "createdAt" in value && typeof value.createdAt === "string" && "updatedAt" in value && typeof value.updatedAt === "string";
@@ -2532,9 +2563,12 @@ var _TinyCloudNode = class _TinyCloudNode {
2532
2563
  this._bootstrapSkipped = false;
2533
2564
  /**
2534
2565
  * Outcome of the last signIn()'s account-bootstrap attempt. `skipped` is
2535
- * true when bootstrap did not complete (interactive signer, auto-sign
2536
- * denied, or a bootstrap step failed); `reason` carries the cause so apps
2537
- * can surface a "finish account setup" call-to-action. `warnings` is
2566
+ * true when bootstrap did not complete in this invocation (interactive signer,
2567
+ * auto-bootstrap disabled, already provisioned, or a decision/provisioning
2568
+ * step failed). `already-provisioned` means the bootstrap was not run here;
2569
+ * it does not mean a prior provisioning attempt failed to complete.
2570
+ * `reason` carries the cause so apps can surface a "finish account setup"
2571
+ * call-to-action. `warnings` is
2538
2572
  * present when bootstrap completed, but one or more steps recovered from
2539
2573
  * an ambiguous failure (e.g. a KV batch write reconciled via per-space
2540
2574
  * fallback) — clean vs. recovered runs are otherwise byte-identical.
@@ -2717,7 +2751,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2717
2751
  nonce: config.nonce,
2718
2752
  siweConfig: config.siweConfig,
2719
2753
  manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
2720
- capabilityRequest: useBootstrapSignInRequest ? import_sdk_core8.BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
2754
+ capabilityRequest: useBootstrapSignInRequest ? config.includeAccountRegistryPermissions === false ? import_sdk_core8.BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : import_sdk_core8.BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
2721
2755
  includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
2722
2756
  });
2723
2757
  this.tc = new import_sdk_core8.TinyCloud(this.auth, {
@@ -2960,6 +2994,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2960
2994
  this._bootstrapSkipped = false;
2961
2995
  this._bootstrapStatus = { skipped: false };
2962
2996
  if (this.config.autoBootstrapAccount === false) {
2997
+ this._bootstrapStatus = { skipped: true, reason: "auto-bootstrap-disabled" };
2963
2998
  return false;
2964
2999
  }
2965
3000
  if (!this.auth || !this._address) {
@@ -2974,11 +3009,14 @@ var _TinyCloudNode = class _TinyCloudNode {
2974
3009
  return false;
2975
3010
  }
2976
3011
  const steps = (0, import_sdk_core8.bootstrapSteps)(this._address, this._chainId);
2977
- if (!await this.isFreshBootstrapAccount(steps)) {
2978
- return false;
2979
- }
2980
3012
  try {
2981
- const warnings = await this.runAccountBootstrap(steps);
3013
+ const decision = await this.resolveBootstrapDecision(steps);
3014
+ if (decision.action === "skip") {
3015
+ this._bootstrapStatus = { skipped: true, reason: "already-provisioned" };
3016
+ return false;
3017
+ }
3018
+ const warnings = await this.runAccountBootstrap(steps, { mode: decision.mode });
3019
+ await this.writeBootstrapCompletionMarker(steps);
2982
3020
  if (warnings.length > 0) {
2983
3021
  this._bootstrapStatus = { skipped: false, warnings };
2984
3022
  this.notificationHandler.warning(
@@ -2997,32 +3035,92 @@ var _TinyCloudNode = class _TinyCloudNode {
2997
3035
  }
2998
3036
  return true;
2999
3037
  }
3000
- async isFreshBootstrapAccount(steps) {
3038
+ async resolveBootstrapDecision(steps) {
3001
3039
  const enshrinedSpaceIds = /* @__PURE__ */ new Set();
3002
3040
  for (const step of steps) {
3003
3041
  if (step.kind === "session") {
3004
3042
  enshrinedSpaceIds.add(step.spaceId);
3005
3043
  }
3006
3044
  }
3007
- const skipped = this.auth.lastActivationSkippedSpaceIds;
3045
+ const auth = this.auth;
3046
+ if (!auth) {
3047
+ throw new Error("Account bootstrap requires an active wallet session");
3048
+ }
3049
+ const skipped = auth.lastActivationSkippedSpaceIds;
3008
3050
  if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
3009
- return true;
3051
+ return { action: "run", mode: "fresh" };
3010
3052
  }
3011
3053
  try {
3012
- const indexed = await this.account.index.spaces.list();
3013
- if (indexed.ok && indexed.data.length === 0) {
3014
- return true;
3054
+ const accountSpaceId = this.ownedSpaceId(import_sdk_core8.ACCOUNT_REGISTRY_SPACE);
3055
+ const markerPermission = {
3056
+ service: "tinycloud.kv",
3057
+ space: accountSpaceId,
3058
+ path: BOOTSTRAP_COMPLETION_MARKER_KEY,
3059
+ actions: ["get"]
3060
+ };
3061
+ if (!this.hasRuntimePermissions([markerPermission])) {
3062
+ return { action: "run", mode: "repair" };
3015
3063
  }
3016
- } catch {
3064
+ const marker = await this.readBootstrapCompletionMarker();
3065
+ if (marker.ok) {
3066
+ if (this.isAcceptedBootstrapCompletionMarker(marker.data.data)) {
3067
+ return { action: "skip" };
3068
+ }
3069
+ return { action: "run", mode: "repair" };
3070
+ }
3071
+ if (marker.error.code === import_sdk_core8.ErrorCodes.KV_NOT_FOUND && this.markerReadIsUnhostedSpace(marker.error.meta)) {
3072
+ return { action: "run", mode: "fresh" };
3073
+ }
3074
+ } catch (err) {
3075
+ const reason = err instanceof Error ? err.message : String(err);
3076
+ this.notificationHandler.warning(
3077
+ `Account bootstrap decision failed; attempting one bounded repair: ${reason}`
3078
+ );
3079
+ console.warn(`[TinyCloudNode] account bootstrap decision failed: ${reason}`);
3080
+ return { action: "run", mode: "repair" };
3017
3081
  }
3018
- try {
3019
- const spaces = await this.account.spaces.list();
3020
- return spaces.ok && spaces.data.length === 0;
3021
- } catch {
3022
- return false;
3082
+ return { action: "run", mode: "repair" };
3083
+ }
3084
+ async readBootstrapCompletionMarker() {
3085
+ return this.kvForSpace(this.ownedSpaceId(import_sdk_core8.ACCOUNT_REGISTRY_SPACE)).get(
3086
+ BOOTSTRAP_COMPLETION_MARKER_KEY
3087
+ );
3088
+ }
3089
+ async writeBootstrapCompletionMarker(steps) {
3090
+ if (!this._address) {
3091
+ throw new Error("Bootstrap completion marker requires an active wallet session");
3023
3092
  }
3093
+ const canonicalStepIds = new Set(
3094
+ (0, import_sdk_core8.bootstrapSteps)(this._address, this._chainId).map((step) => step.id)
3095
+ );
3096
+ const providedStepIds = new Set(steps.map((step) => step.id));
3097
+ if (canonicalStepIds.size !== providedStepIds.size || [...canonicalStepIds].some((stepId) => !providedStepIds.has(stepId))) {
3098
+ throw new Error("Bootstrap completion marker requires the canonical bootstrap step set");
3099
+ }
3100
+ const marker = {
3101
+ v: BOOTSTRAP_COMPLETION_MARKER_VERSION,
3102
+ stepIds: [...canonicalStepIds],
3103
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
3104
+ };
3105
+ const written = await this.kvForSpace(this.ownedSpaceId(import_sdk_core8.ACCOUNT_REGISTRY_SPACE)).put(
3106
+ BOOTSTRAP_COMPLETION_MARKER_KEY,
3107
+ marker
3108
+ );
3109
+ if (!written.ok) {
3110
+ throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
3111
+ }
3112
+ }
3113
+ isAcceptedBootstrapCompletionMarker(value) {
3114
+ if (typeof value !== "object" || value === null) return false;
3115
+ const entries = Object.entries(value);
3116
+ const markerValue = (name) => entries.find(([key]) => key === name)?.[1];
3117
+ const version = markerValue("v");
3118
+ return typeof version === "number" && Number.isInteger(version) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version);
3119
+ }
3120
+ markerReadIsUnhostedSpace(meta) {
3121
+ return meta?.status === 404;
3024
3122
  }
3025
- async runAccountBootstrap(steps) {
3123
+ async runAccountBootstrap(steps, { mode } = { mode: "fresh" }) {
3026
3124
  if (!this.auth || !this._address) {
3027
3125
  throw new Error("Account bootstrap requires an active wallet session");
3028
3126
  }
@@ -3123,9 +3221,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3123
3221
  if (step.kind === "seed-applications") {
3124
3222
  const registered = await this.account.applications.register(
3125
3223
  step.manifests.length > 0 ? [...step.manifests] : import_sdk_core8.TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
3126
- // Bootstrap only runs on an account with no registry records, so the
3127
- // manifest-hash pre-read is definitionally a miss. Skip it; the write
3128
- // it guards is an INSERT OR REPLACE and is safe to repeat.
3224
+ // The write is an INSERT OR REPLACE and is safe to repeat during a
3225
+ // repair, so skip the manifest-hash pre-read.
3129
3226
  { assumeUnregistered: true }
3130
3227
  );
3131
3228
  if (!registered.ok) {
@@ -3133,7 +3230,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3133
3230
  }
3134
3231
  }
3135
3232
  if (step.kind === "encryption-network-create") {
3136
- await this.ensureEncryptionNetwork(step.networkId, { assumeMissing: true });
3233
+ await this.ensureEncryptionNetwork(step.networkId, { assumeMissing: mode === "fresh" });
3137
3234
  }
3138
3235
  if (step.kind === "secret-records-schema") {
3139
3236
  const db = this.sqlForSpace(step.spaceId).db(step.database);
@@ -4182,7 +4279,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4182
4279
  nonce: this.config.nonce,
4183
4280
  siweConfig: this.config.siweConfig,
4184
4281
  manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
4185
- capabilityRequest: useBootstrapSignInRequest ? import_sdk_core8.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
4282
+ capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? import_sdk_core8.BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : import_sdk_core8.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
4186
4283
  includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
4187
4284
  });
4188
4285
  this.tc = new import_sdk_core8.TinyCloud(this.auth, {
@@ -4235,7 +4332,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4235
4332
  nonce: this.config.nonce,
4236
4333
  siweConfig: this.config.siweConfig,
4237
4334
  manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
4238
- capabilityRequest: useBootstrapSignInRequest ? import_sdk_core8.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
4335
+ capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? import_sdk_core8.BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : import_sdk_core8.BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
4239
4336
  includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
4240
4337
  });
4241
4338
  this.tc = new import_sdk_core8.TinyCloud(this.auth, {
@@ -6984,35 +7081,44 @@ var _TinyCloudNode = class _TinyCloudNode {
6984
7081
  if (parentDelegation.disableSubDelegation) {
6985
7082
  throw new Error("Parent delegation does not allow sub-delegation");
6986
7083
  }
6987
- if (!params.path.startsWith(parentDelegation.path)) {
7084
+ const parentResources = parentDelegation.resources ?? [{
7085
+ service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
7086
+ space: parentDelegation.spaceId,
7087
+ path: parentDelegation.path,
7088
+ actions: parentDelegation.actions
7089
+ }];
7090
+ const requestedResources = params.resources ?? [{
7091
+ service: params.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
7092
+ space: parentDelegation.spaceId,
7093
+ path: params.path,
7094
+ actions: params.actions
7095
+ }];
7096
+ const resourceIsContained = (parent, child) => {
7097
+ const parentEncryption = parent.service === "encryption" || parent.service === "tinycloud.encryption";
7098
+ const childEncryption = child.service === "encryption" || child.service === "tinycloud.encryption";
7099
+ if (parentEncryption || childEncryption) return parentEncryption && childEncryption && parent.path === child.path && child.actions.every((action) => parent.actions.includes(action));
7100
+ const pathContained = parent.path === child.path || parent.path.endsWith("/") && child.path.startsWith(parent.path) || child.path.startsWith(`${parent.path}/`);
7101
+ return pathContained && child.actions.every((action) => parent.actions.includes(action));
7102
+ };
7103
+ if (requestedResources.length === 0 || requestedResources.some((child) => !parentResources.some((parent) => resourceIsContained(parent, child)))) {
6988
7104
  throw new Error(
6989
- `Sub-delegation path "${params.path}" must be within parent path "${parentDelegation.path}"`
7105
+ "Sub-delegation resources exceed the received delegation"
6990
7106
  );
6991
7107
  }
6992
- const parentActions = new Set(parentDelegation.actions);
6993
- for (const action of params.actions) {
6994
- if (!parentActions.has(action)) {
6995
- throw new Error(
6996
- `Sub-delegation action "${action}" is not in parent's actions: ${parentDelegation.actions.join(", ")}`
6997
- );
6998
- }
6999
- }
7000
7108
  const now = /* @__PURE__ */ new Date();
7001
7109
  const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
7002
7110
  const requestedExpiry = new Date(now.getTime() + expiryMs);
7003
7111
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
7004
7112
  const abilities = {};
7005
- const kvActions = params.actions.filter((a) => a.startsWith("tinycloud.kv/"));
7006
- const sqlActions = params.actions.filter((a) => a.startsWith("tinycloud.sql/"));
7007
- const duckdbActions = params.actions.filter((a) => a.startsWith("tinycloud.duckdb/"));
7008
- if (kvActions.length > 0) {
7009
- abilities.kv = { [params.path]: kvActions };
7010
- }
7011
- if (sqlActions.length > 0) {
7012
- abilities.sql = { [params.path]: sqlActions };
7013
- }
7014
- if (duckdbActions.length > 0) {
7015
- abilities.duckdb = { [params.path]: duckdbActions };
7113
+ const rawAbilities = {};
7114
+ for (const resource of requestedResources) {
7115
+ const service = resource.service.replace(/^tinycloud\./, "");
7116
+ if (service === "encryption") {
7117
+ rawAbilities[resource.path] = [...resource.actions];
7118
+ continue;
7119
+ }
7120
+ abilities[service] ?? (abilities[service] = {});
7121
+ abilities[service][resource.path] = [...resource.actions];
7016
7122
  }
7017
7123
  const targetHost = parentDelegation.host ?? this.config.host;
7018
7124
  const prepared = this.wasmBindings.prepareSession({
@@ -7024,7 +7130,8 @@ var _TinyCloudNode = class _TinyCloudNode {
7024
7130
  expirationTime: actualExpiry.toISOString(),
7025
7131
  spaceId: parentDelegation.spaceId,
7026
7132
  delegateUri: params.delegateDID,
7027
- parents: [parentDelegation.cid]
7133
+ parents: [parentDelegation.cid],
7134
+ ...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
7028
7135
  });
7029
7136
  const signature = await this.signer.signMessage(prepared.siwe);
7030
7137
  const subDelegationSession = this.wasmBindings.completeSessionSetup({
@@ -7042,14 +7149,20 @@ var _TinyCloudNode = class _TinyCloudNode {
7042
7149
  cid: subDelegationSession.delegationCid,
7043
7150
  delegationHeader: subDelegationSession.delegationHeader,
7044
7151
  spaceId: parentDelegation.spaceId,
7045
- path: params.path,
7046
- actions: params.actions,
7152
+ path: requestedResources[0].path,
7153
+ actions: requestedResources[0].actions,
7047
7154
  disableSubDelegation: params.disableSubDelegation ?? false,
7048
7155
  expiry: actualExpiry,
7049
7156
  delegateDID: params.delegateDID,
7050
7157
  ownerAddress: parentDelegation.ownerAddress,
7051
7158
  chainId: parentDelegation.chainId,
7052
- host: targetHost
7159
+ host: targetHost,
7160
+ resources: requestedResources.map((resource) => ({
7161
+ service: resource.service.replace(/^tinycloud\./, ""),
7162
+ space: resource.service.replace(/^tinycloud\./, "") === "encryption" ? "encryption" : resource.space ?? parentDelegation.spaceId,
7163
+ path: resource.path,
7164
+ actions: [...resource.actions]
7165
+ }))
7053
7166
  };
7054
7167
  }
7055
7168
  };