@tinycloud/node-sdk 2.11.0-beta.9 → 2.11.1-beta.0

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.js CHANGED
@@ -17179,12 +17179,14 @@ import {
17179
17179
  SharingService,
17180
17180
  UnsupportedFeatureError,
17181
17181
  makePublicSpaceId,
17182
- ACCOUNT_REGISTRY_SPACE,
17182
+ ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
17183
+ BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST,
17183
17184
  BOOTSTRAP_SESSION_REQUESTS,
17184
17185
  SECRET_RECORDS_SCHEMA,
17185
17186
  SECRETS_SPACE as SECRETS_SPACE2,
17186
17187
  TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
17187
17188
  bootstrapSteps,
17189
+ ErrorCodes as ErrorCodes2,
17188
17190
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
17189
17191
  CaveatedDelegationUnsupportedError,
17190
17192
  PermissionNotInManifestError,
@@ -17211,13 +17213,17 @@ import {
17211
17213
  parseNetworkId as parseNetworkId2,
17212
17214
  resolveSecretPath as resolveSecretPath2,
17213
17215
  validateOwnerSharePolicyRegistrationBytes,
17214
- validateShareDeliveryAuthorizationBytes
17216
+ validateShareDeliveryAuthorizationBytes,
17217
+ verifyEip191MessageSignature,
17218
+ signCompactUcanRootAuthorization
17215
17219
  } from "@tinycloud/sdk-core";
17216
17220
  import {
17217
17221
  parsePermissionHint
17218
17222
  } from "@tinycloud/sdk-services";
17219
17223
 
17220
17224
  // src/authorization/NodeUserAuthorization.ts
17225
+ import { createHash } from "crypto";
17226
+ import { SiweMessage } from "siwe";
17221
17227
  import {
17222
17228
  fetchPeerId,
17223
17229
  submitHostDelegation,
@@ -17236,6 +17242,13 @@ import {
17236
17242
  canonicalizeAddress,
17237
17243
  makePkhSpaceId,
17238
17244
  pkhDid,
17245
+ extractImmutableSiweFields,
17246
+ diffImmutableSiweFields,
17247
+ extractRecapAttenuations,
17248
+ unauthorizedRecapCapabilities,
17249
+ parseCanonicalRecapResource,
17250
+ ACCOUNT_REGISTRY_SPACE,
17251
+ ACCOUNT_MANIFEST_PERMISSIONS,
17239
17252
  KV,
17240
17253
  SQL,
17241
17254
  DUCKDB,
@@ -17322,6 +17335,27 @@ var MemorySessionStorage = class {
17322
17335
  // src/authorization/NodeUserAuthorization.ts
17323
17336
  var DECRYPT_ACTION = ENCRYPTION.DECRYPT;
17324
17337
  var NETWORK_CREATE_ACTION = ENCRYPTION.NETWORK_CREATE;
17338
+ function canonicalizeOpenKeyManifestJson(value) {
17339
+ const json = JSON.stringify(value);
17340
+ if (json === void 0) {
17341
+ throw new TypeError("manifest is not JSON-serializable");
17342
+ }
17343
+ return canonicalizeJsonValue(JSON.parse(json));
17344
+ }
17345
+ function canonicalizeJsonValue(value) {
17346
+ if (value === null || typeof value !== "object") {
17347
+ return JSON.stringify(value);
17348
+ }
17349
+ if (Array.isArray(value)) {
17350
+ return `[${value.map(canonicalizeJsonValue).join(",")}]`;
17351
+ }
17352
+ return `{${Object.keys(value).sort().map(
17353
+ (key2) => `${JSON.stringify(key2)}:${canonicalizeJsonValue(value[key2])}`
17354
+ ).join(",")}}`;
17355
+ }
17356
+ function canonicalOpenKeyManifestSha256Hex(value) {
17357
+ return createHash("sha256").update(canonicalizeOpenKeyManifestJson(value)).digest("hex");
17358
+ }
17325
17359
  function didPrincipalMatches(actual, expected) {
17326
17360
  try {
17327
17361
  return principalDidEquals(actual, expected);
@@ -17339,6 +17373,16 @@ function addRawAbility(rawAbilities, resource, action) {
17339
17373
  actions.push(action);
17340
17374
  }
17341
17375
  }
17376
+ function cloneAbilitiesMap(actions) {
17377
+ return Object.fromEntries(
17378
+ Object.entries(actions).map(([service, paths]) => [
17379
+ service,
17380
+ Object.fromEntries(
17381
+ Object.entries(paths).map(([path, pathActions]) => [path, [...pathActions]])
17382
+ )
17383
+ ])
17384
+ );
17385
+ }
17342
17386
  var NodeUserAuthorization = class {
17343
17387
  constructor(config) {
17344
17388
  this.activeSessionKeyId = "default";
@@ -17610,23 +17654,41 @@ var NodeUserAuthorization = class {
17610
17654
  const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
17611
17655
  const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
17612
17656
  const secretsSpaceId = makePkhSpaceId(address, chainId, "secrets");
17613
- return {
17614
- abilities: this.defaultActions,
17615
- spaceId: primarySpaceId2,
17616
- spaceAbilities: {
17617
- [primarySpaceId2]: this.defaultActions,
17618
- [secretsSpaceId]: {
17619
- kv: {
17620
- "vault/secrets/": [
17621
- KV.GET,
17622
- KV.PUT,
17623
- KV.DEL,
17624
- KV.LIST,
17625
- KV.METADATA
17626
- ]
17657
+ const primaryActions = this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE ? cloneAbilitiesMap(this.defaultActions) : this.defaultActions;
17658
+ const spaceAbilities2 = {
17659
+ [primarySpaceId2]: primaryActions,
17660
+ [secretsSpaceId]: {
17661
+ kv: {
17662
+ "vault/secrets/": [
17663
+ KV.GET,
17664
+ KV.PUT,
17665
+ KV.DEL,
17666
+ KV.LIST,
17667
+ KV.METADATA
17668
+ ]
17669
+ }
17670
+ }
17671
+ };
17672
+ if (this.includeAccountRegistryPermissions && this.spacePrefix !== ACCOUNT_REGISTRY_SPACE) {
17673
+ spaceAbilities2[makePkhSpaceId(address, chainId, ACCOUNT_REGISTRY_SPACE)] = resourceCapabilitiesToAbilitiesMap(ACCOUNT_MANIFEST_PERMISSIONS);
17674
+ } else if (this.includeAccountRegistryPermissions) {
17675
+ const accountAbilities = resourceCapabilitiesToAbilitiesMap(
17676
+ ACCOUNT_MANIFEST_PERMISSIONS
17677
+ );
17678
+ for (const [service, paths] of Object.entries(accountAbilities)) {
17679
+ const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
17680
+ for (const [path, actions] of Object.entries(paths)) {
17681
+ const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
17682
+ for (const action of actions) {
17683
+ if (!existingActions.includes(action)) existingActions.push(action);
17627
17684
  }
17628
17685
  }
17629
- },
17686
+ }
17687
+ }
17688
+ return {
17689
+ abilities: primaryActions,
17690
+ spaceId: primarySpaceId2,
17691
+ spaceAbilities: spaceAbilities2,
17630
17692
  rawAbilities: {
17631
17693
  [defaultNetworkId]: [DECRYPT_ACTION, NETWORK_CREATE_ACTION]
17632
17694
  }
@@ -18207,6 +18269,398 @@ var NodeUserAuthorization = class {
18207
18269
  await this.ensureSpaceExists();
18208
18270
  return clientSession;
18209
18271
  }
18272
+ /**
18273
+ * End-to-end sign-in via OpenKey `authorizeTinyCloud()` (Sol CRITICAL-4).
18274
+ *
18275
+ * This is the production entry point that wires
18276
+ * {@link prepareSessionForSigning} → OpenKey `authorizeTinyCloud()` →
18277
+ * {@link signInWithOpenKeyResult} into one call. Consumers pass in a
18278
+ * caller-provided `authorizeFn` (typically a thin wrapper around
18279
+ * `openkey.authorizeTinyCloud()`) so this method has no direct
18280
+ * dependency on the OpenKey SDK — it works with any transport that
18281
+ * conforms to the versioned protocol.
18282
+ *
18283
+ * The `authorizeFn` MUST:
18284
+ * 1. Send the prepared SIWE bytes to OpenKey.
18285
+ * 2. Return the exact `TinyCloudAuthorizationResultV1` shape (with
18286
+ * `signedMessage` — the bytes the signature actually verifies
18287
+ * against — plus `selectedActionKeys` and `permissions`).
18288
+ *
18289
+ * NodeUserAuthorization then completes the session with `signedMessage`
18290
+ * (never the prepared bytes), verifying the signature, the immutable
18291
+ * SIWE fields, and every subset invariant enforced by
18292
+ * {@link signInWithOpenKeyResult}. This preserves the exact-byte
18293
+ * guarantee: if the user did NOT narrow, the returned `signedMessage`
18294
+ * equals the prepared SIWE byte-for-byte; if the user narrowed, only
18295
+ * the ReCap block differs and every immutable header field survives.
18296
+ *
18297
+ * Legacy exact-byte {@link signIn} callers are unaffected — this method
18298
+ * is opt-in.
18299
+ *
18300
+ * @param authorizeFn Callback that invokes the versioned OpenKey
18301
+ * `authorizeTinyCloud()` protocol and returns the result. Receives
18302
+ * the prepared SIWE bytes plus a `host` hint the widget should bind
18303
+ * into its /authorize-sign-prepare context.
18304
+ * @param options Optional overrides for the OpenKey request.
18305
+ */
18306
+ async signInWithOpenKey(authorizeFn, options) {
18307
+ if (options?.host !== void 0) {
18308
+ const overrideHost = options.host.trim();
18309
+ if (!overrideHost) {
18310
+ throw new Error("OpenKey authorization host must not be empty");
18311
+ }
18312
+ this.tinycloudHosts = [overrideHost];
18313
+ }
18314
+ const signingAddress = canonicalizeAddress(await this.signer.getAddress());
18315
+ const signingChainId = await this.signer.getChainId();
18316
+ await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
18317
+ const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
18318
+ const presentation = this.buildPresentationEnvelope(options?.reason);
18319
+ const hostHint = this.primaryTinyCloudHost;
18320
+ const result = await authorizeFn({
18321
+ protocolVersion: 1,
18322
+ siwe: prepared.siwe,
18323
+ jwk,
18324
+ host: hostHint,
18325
+ keyId: options?.openkeyKeyId,
18326
+ presentation
18327
+ });
18328
+ return this.signInWithOpenKeyResult(result, prepared, keyId, jwk);
18329
+ }
18330
+ /**
18331
+ * Build a `CapabilityPresentationEnvelopeV1` from `this._manifest`. The
18332
+ * envelope is display-only — the OpenKey widget renders it under an
18333
+ * honest trust label, and the server /authorize-sign-prepare route
18334
+ * decides whether to upgrade the label to `origin-bound` by fetching
18335
+ * the well-known manifest and comparing its digest to
18336
+ * `manifestDigest`.
18337
+ *
18338
+ * Returns `undefined` when no manifest is set — the widget renders as
18339
+ * "no manifest supplied" (unsigned).
18340
+ *
18341
+ * The digest is a deterministic SHA-256 over canonical JSON of the
18342
+ * first manifest payload; OpenKey parses the fetched well-known manifest
18343
+ * and applies the same canonicalization before comparing digests.
18344
+ */
18345
+ buildPresentationEnvelope(reason) {
18346
+ const manifests = Array.isArray(this._manifest) ? this._manifest : this._manifest ? [this._manifest] : [];
18347
+ if (manifests.length === 0 && !reason) {
18348
+ return void 0;
18349
+ }
18350
+ const primary = manifests[0];
18351
+ let displayName;
18352
+ let manifestId;
18353
+ let manifestDigest;
18354
+ const envelopeManifests = [];
18355
+ for (const m of manifests) {
18356
+ envelopeManifests.push({
18357
+ name: m.name,
18358
+ appId: m.app_id,
18359
+ // Deep-copy to prevent later mutations from affecting the digest.
18360
+ payload: JSON.parse(JSON.stringify(m))
18361
+ });
18362
+ }
18363
+ if (primary) {
18364
+ displayName = primary.name;
18365
+ manifestId = primary.app_id;
18366
+ manifestDigest = canonicalOpenKeyManifestSha256Hex(primary);
18367
+ }
18368
+ return {
18369
+ protocolVersion: 1,
18370
+ displayName,
18371
+ reason: reason && reason.length > 0 ? reason : void 0,
18372
+ manifestId,
18373
+ manifestDigest,
18374
+ manifests: envelopeManifests.length > 0 ? envelopeManifests : void 0
18375
+ };
18376
+ }
18377
+ /**
18378
+ * Complete sign-in with a versioned OpenKey authorization result.
18379
+ *
18380
+ * Where `signInWithPreparedSession` completes with a signature over the
18381
+ * ORIGINAL prepared SIWE, this method completes with the SIWE bytes the
18382
+ * OpenKey widget actually presented and signed (`signedMessage`). It
18383
+ * exists because the versioned authorizeTinyCloud protocol lets the user
18384
+ * narrow capabilities; the returned bytes may differ from the original
18385
+ * request and are the only ones the signature actually verifies against.
18386
+ *
18387
+ * The `result` parameter is validated at the trust boundary via
18388
+ * `validateAuthorizationResultV1` (in sdk-core). Callers should validate
18389
+ * BEFORE calling this method so unknown/missing fields are rejected
18390
+ * before any session state is created.
18391
+ *
18392
+ * Validation performed here (defence-in-depth: even a "valid" v1 payload
18393
+ * must clear these checks before any session state is created):
18394
+ * 1. `result.address` matches the local signer's address.
18395
+ * 2. `result.signature` recovers to `result.address` when verified against
18396
+ * `result.signedMessage`.
18397
+ * 3. Every immutable SIWE header field (domain, address, URI, version,
18398
+ * chainId, nonce, issuedAt) in the original prepared SIWE is preserved
18399
+ * byte-for-byte in `result.signedMessage`. `statement` is NOT in this
18400
+ * list because the WASM renders it from the ReCap contents; it MUST
18401
+ * change when the ReCap is narrowed.
18402
+ * 4. Every capability in `result.signedMessage`'s ReCap block was present
18403
+ * in the original prepared SIWE — narrowing OK, broadening rejected.
18404
+ * 5. Every `result.selectedActionKeys` entry corresponds to a
18405
+ * (resource, action) pair in the returned SIWE.
18406
+ */
18407
+ async signInWithOpenKeyResult(result, prepared, keyId, jwk) {
18408
+ if (result.protocolVersion !== 1) {
18409
+ throw new Error(`Unsupported OpenKey protocol version ${result.protocolVersion}`);
18410
+ }
18411
+ if (!result.signedMessage) {
18412
+ throw new Error("OpenKey result must include signedMessage");
18413
+ }
18414
+ if (!prepared.siwe) {
18415
+ throw new Error(
18416
+ "signInWithOpenKeyResult requires prepared.siwe (the SDK-generated SIWE the sign-in was proposed with)"
18417
+ );
18418
+ }
18419
+ const expectedAddress = canonicalizeAddress(await this.signer.getAddress());
18420
+ if (canonicalizeAddress(result.address) !== expectedAddress) {
18421
+ throw new Error(
18422
+ `OpenKey returned signature from ${result.address} but expected ${expectedAddress}`
18423
+ );
18424
+ }
18425
+ let parsedSignedSiwe;
18426
+ try {
18427
+ parsedSignedSiwe = new SiweMessage(result.signedMessage);
18428
+ } catch (err) {
18429
+ throw new Error(
18430
+ `OpenKey signedMessage is not a parseable SIWE: ${err.message}`
18431
+ );
18432
+ }
18433
+ const verifyResult = await parsedSignedSiwe.verify(
18434
+ { signature: result.signature },
18435
+ { suppressExceptions: true }
18436
+ );
18437
+ if (!verifyResult.success) {
18438
+ const errData = verifyResult.error;
18439
+ const type = errData && typeof errData === "object" && "type" in errData ? errData.type : void 0;
18440
+ const details = errData && typeof errData === "object" && "expected" in errData ? ` (expected=${errData.expected}, received=${errData.received})` : "";
18441
+ throw new Error(
18442
+ `OpenKey signature verification failed${type ? `: ${type}` : ""}${details}`
18443
+ );
18444
+ }
18445
+ if (canonicalizeAddress(parsedSignedSiwe.address) !== expectedAddress) {
18446
+ throw new Error(
18447
+ `OpenKey signedMessage.address (${parsedSignedSiwe.address}) does not match local signer address (${expectedAddress})`
18448
+ );
18449
+ }
18450
+ const originalHasRecap = /(?:^|\n)-\s+urn:recap:/i.test(prepared.siwe);
18451
+ const originalFields = extractImmutableSiweFields(prepared.siwe);
18452
+ const signedFields = extractImmutableSiweFields(result.signedMessage);
18453
+ const changedFields = diffImmutableSiweFields(originalFields, signedFields, {
18454
+ originalHasRecap
18455
+ });
18456
+ if (changedFields.length > 0) {
18457
+ throw new Error(
18458
+ `OpenKey signedMessage altered immutable SIWE fields: ${changedFields.join(", ")}`
18459
+ );
18460
+ }
18461
+ let originalCaps;
18462
+ let signedCaps;
18463
+ try {
18464
+ originalCaps = extractRecapAttenuations(prepared.siwe);
18465
+ } catch (err) {
18466
+ throw new Error(
18467
+ `Failed to decode prepared SIWE recap: ${err.message}`
18468
+ );
18469
+ }
18470
+ try {
18471
+ signedCaps = extractRecapAttenuations(result.signedMessage);
18472
+ } catch (err) {
18473
+ throw new Error(
18474
+ `Failed to decode OpenKey signedMessage recap: ${err.message}`
18475
+ );
18476
+ }
18477
+ const unauthorized = unauthorizedRecapCapabilities(signedCaps, originalCaps);
18478
+ if (unauthorized.length > 0) {
18479
+ const previewLimit = 5;
18480
+ const preview = unauthorized.slice(0, previewLimit).map((u) => `${u.resource}::${u.action}`).join(", ");
18481
+ const overflow = unauthorized.length > previewLimit ? ` (and ${unauthorized.length - previewLimit} more)` : "";
18482
+ throw new Error(
18483
+ `OpenKey signedMessage broadened authorization beyond the original request: ${preview}${overflow}`
18484
+ );
18485
+ }
18486
+ if (!result.signedMessage.includes(prepared.spaceId)) {
18487
+ throw new Error(
18488
+ `OpenKey signedMessage does not reference the expected spaceId ${prepared.spaceId}`
18489
+ );
18490
+ }
18491
+ const grantedPairs = /* @__PURE__ */ new Set();
18492
+ const grantedFourPartIndex = /* @__PURE__ */ new Map();
18493
+ for (const [resource, actions] of Object.entries(signedCaps)) {
18494
+ const { space, path } = parseCanonicalRecapResource(resource);
18495
+ for (const action of Object.keys(actions)) {
18496
+ grantedPairs.add(`${resource}\0${action}`);
18497
+ const slashIdx = action.indexOf("/");
18498
+ const service = slashIdx > 0 ? action.slice(0, slashIdx) : "";
18499
+ if (service) {
18500
+ grantedFourPartIndex.set(
18501
+ `${service}\0${space}\0${path}\0${action}`,
18502
+ `${resource}\0${action}`
18503
+ );
18504
+ }
18505
+ }
18506
+ }
18507
+ const isStructurallyRequired = (action) => action === "tinycloud.capabilities/read" || action === "capabilities/read";
18508
+ const nonRequiredGrantedPairs = /* @__PURE__ */ new Set();
18509
+ for (const pair of grantedPairs) {
18510
+ const idx = pair.indexOf("\0");
18511
+ const action = idx >= 0 ? pair.slice(idx + 1) : pair;
18512
+ if (!isStructurallyRequired(action)) {
18513
+ nonRequiredGrantedPairs.add(pair);
18514
+ }
18515
+ }
18516
+ const allSignedGrantedPairs = new Set(grantedPairs);
18517
+ const seenRawKeys = /* @__PURE__ */ new Set();
18518
+ for (const rawKey of result.selectedActionKeys) {
18519
+ if (seenRawKeys.has(rawKey)) {
18520
+ throw new Error(
18521
+ `OpenKey selectedActionKeys contains duplicate entry ${rawKey}`
18522
+ );
18523
+ }
18524
+ seenRawKeys.add(rawKey);
18525
+ }
18526
+ const selectedResourceActionPairs = /* @__PURE__ */ new Set();
18527
+ for (const rawKey of result.selectedActionKeys) {
18528
+ const parts = rawKey.split("\0");
18529
+ if (parts.length !== 4) {
18530
+ throw new Error(
18531
+ `OpenKey selectedActionKeys entry is malformed (expected canonical 4-part service\\0space\\0path\\0ability): ${rawKey}`
18532
+ );
18533
+ }
18534
+ const canonical = grantedFourPartIndex.get(rawKey);
18535
+ if (!canonical) {
18536
+ throw new Error(
18537
+ `OpenKey selectedActionKeys entry ${rawKey} is not covered by any granted capability`
18538
+ );
18539
+ }
18540
+ selectedResourceActionPairs.add(canonical);
18541
+ }
18542
+ if (nonRequiredGrantedPairs.size > 0) {
18543
+ const missingPairs = [];
18544
+ for (const pair of nonRequiredGrantedPairs) {
18545
+ if (!selectedResourceActionPairs.has(pair)) {
18546
+ missingPairs.push(pair);
18547
+ }
18548
+ }
18549
+ if (missingPairs.length > 0) {
18550
+ const previewLimit = 5;
18551
+ const preview = missingPairs.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
18552
+ const overflow = missingPairs.length > previewLimit ? ` (and ${missingPairs.length - previewLimit} more)` : "";
18553
+ throw new Error(
18554
+ `OpenKey selectedActionKeys is missing entries for signedMessage capabilities: ${preview}${overflow}`
18555
+ );
18556
+ }
18557
+ }
18558
+ if (allSignedGrantedPairs.size > 0 && result.permissions.length === 0) {
18559
+ throw new Error(
18560
+ "OpenKey permissions is empty but signedMessage carries capabilities \u2014 refusing to accept a session without an authoritative grant list"
18561
+ );
18562
+ }
18563
+ const permissionCanonicalKeys = /* @__PURE__ */ new Set();
18564
+ const permissionPairsCovered = /* @__PURE__ */ new Set();
18565
+ for (const perm of result.permissions) {
18566
+ const { service, space, path, actions } = perm;
18567
+ if (typeof service !== "string" || typeof space !== "string" || typeof path !== "string") {
18568
+ throw new Error(
18569
+ `OpenKey permissions entry has non-string service/space/path (service=${service}, space=${space}, path=${path})`
18570
+ );
18571
+ }
18572
+ if (!Array.isArray(actions) || actions.length === 0) {
18573
+ throw new Error(
18574
+ `OpenKey permissions entry (${service} ${space} ${path}) has empty actions \u2014 a capability-bearing entry must list at least one action`
18575
+ );
18576
+ }
18577
+ const canonicalKey = `${service}\0${space}\0${path}`;
18578
+ if (permissionCanonicalKeys.has(canonicalKey)) {
18579
+ throw new Error(
18580
+ `OpenKey permissions has duplicate entry for (${service} ${space} ${path})`
18581
+ );
18582
+ }
18583
+ permissionCanonicalKeys.add(canonicalKey);
18584
+ let expectedResource;
18585
+ if (space.startsWith("tinycloud:")) {
18586
+ const shortService = service.startsWith("tinycloud.") ? service.slice("tinycloud.".length) : service;
18587
+ expectedResource = path ? `${space}/${shortService}/${path}` : `${space}/${shortService}`;
18588
+ } else {
18589
+ if (path) {
18590
+ throw new Error(
18591
+ `OpenKey permissions entry (${service} ${space} ${path}) carries a path on a non-tinycloud: resource URI, which is not a recognized encoding`
18592
+ );
18593
+ }
18594
+ expectedResource = space;
18595
+ }
18596
+ let matchedResource;
18597
+ if (signedCaps[expectedResource] !== void 0) {
18598
+ matchedResource = expectedResource;
18599
+ } else {
18600
+ throw new Error(
18601
+ `OpenKey permissions entry (${service} ${space} ${path}) claims grants not confirmed in signedMessage capabilities`
18602
+ );
18603
+ }
18604
+ const grantedActionsForResource = new Set(
18605
+ Object.keys(signedCaps[matchedResource])
18606
+ );
18607
+ for (const action of actions) {
18608
+ if (typeof action !== "string" || !action) {
18609
+ throw new Error(
18610
+ `OpenKey permissions entry (${service} ${space} ${path}) has non-string action ${String(action)}`
18611
+ );
18612
+ }
18613
+ if (!grantedActionsForResource.has(action)) {
18614
+ throw new Error(
18615
+ `OpenKey permissions entry (${service} ${space} ${path}) claims action ${action} not present in signedMessage capabilities`
18616
+ );
18617
+ }
18618
+ permissionPairsCovered.add(`${matchedResource}\0${action}`);
18619
+ }
18620
+ }
18621
+ if (allSignedGrantedPairs.size > 0) {
18622
+ const missingFromPerms = [];
18623
+ for (const pair of allSignedGrantedPairs) {
18624
+ if (!permissionPairsCovered.has(pair)) {
18625
+ missingFromPerms.push(pair);
18626
+ }
18627
+ }
18628
+ if (missingFromPerms.length > 0) {
18629
+ const previewLimit = 5;
18630
+ const preview = missingFromPerms.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
18631
+ const overflow = missingFromPerms.length > previewLimit ? ` (and ${missingFromPerms.length - previewLimit} more)` : "";
18632
+ throw new Error(
18633
+ `OpenKey permissions is missing entries for signedMessage capabilities: ${preview}${overflow}`
18634
+ );
18635
+ }
18636
+ const extrasInPerms = [];
18637
+ for (const pair of permissionPairsCovered) {
18638
+ if (!allSignedGrantedPairs.has(pair)) {
18639
+ extrasInPerms.push(pair);
18640
+ }
18641
+ }
18642
+ if (extrasInPerms.length > 0) {
18643
+ const previewLimit = 5;
18644
+ const preview = extrasInPerms.slice(0, previewLimit).map((p) => p.replace("\0", "::")).join(", ");
18645
+ const overflow = extrasInPerms.length > previewLimit ? ` (and ${extrasInPerms.length - previewLimit} more)` : "";
18646
+ throw new Error(
18647
+ `OpenKey permissions contains entries not present in the signed authority: ${preview}${overflow}`
18648
+ );
18649
+ }
18650
+ }
18651
+ void nonRequiredGrantedPairs;
18652
+ return this.signInWithPreparedSession(
18653
+ {
18654
+ siwe: result.signedMessage,
18655
+ jwk: prepared.jwk,
18656
+ spaceId: prepared.spaceId,
18657
+ verificationMethod: prepared.verificationMethod
18658
+ },
18659
+ result.signature,
18660
+ keyId,
18661
+ jwk
18662
+ );
18663
+ }
18210
18664
  /**
18211
18665
  * Clear persisted session data.
18212
18666
  */
@@ -18462,7 +18916,7 @@ function createWasmKeyProvider(sessionManager) {
18462
18916
  // src/delegateToHelpers.ts
18463
18917
  import {
18464
18918
  parseExpiry,
18465
- SiweMessage,
18919
+ SiweMessage as SiweMessage2,
18466
18920
  EXPIRY as EXPIRY2
18467
18921
  } from "@tinycloud/sdk-core";
18468
18922
  function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
@@ -18511,7 +18965,7 @@ function resolveExpiryMs(expiry) {
18511
18965
  return parseExpiry(expiry);
18512
18966
  }
18513
18967
  function extractSiweExpiration(siwe) {
18514
- const parsed = new SiweMessage(siwe);
18968
+ const parsed = new SiweMessage2(siwe);
18515
18969
  if (parsed.expirationTime === void 0 || parsed.expirationTime === null) {
18516
18970
  return void 0;
18517
18971
  }
@@ -18713,6 +19167,9 @@ var DEFAULT_HOST = "https://node.tinycloud.xyz";
18713
19167
  var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
18714
19168
  var NETWORK_CREATE_ACTION2 = ENCRYPTION2.NETWORK_CREATE;
18715
19169
  var DECRYPT_ACTION2 = ENCRYPTION2.DECRYPT;
19170
+ var BOOTSTRAP_COMPLETION_MARKER_KEY = "system/bootstrap/complete";
19171
+ var BOOTSTRAP_COMPLETION_MARKER_VERSION = 1;
19172
+ var ACCEPTED_MARKER_VERSIONS = [BOOTSTRAP_COMPLETION_MARKER_VERSION];
18716
19173
  var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
18717
19174
  function isSecretPayload(value) {
18718
19175
  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";
@@ -18736,6 +19193,18 @@ var ROOT_DELEGATION_ACTIONS = [
18736
19193
  DUCKDB2.ALL
18737
19194
  ];
18738
19195
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
19196
+ function unifiedRootAttenuation(capabilities) {
19197
+ const attenuation = {};
19198
+ for (const capability of capabilities) {
19199
+ if (attenuation[capability.resource] !== void 0) throw new Error("unified owner root contains a duplicate resource");
19200
+ attenuation[capability.resource] = capability.kind === "encryption" ? { [capability.action]: [{}] } : Object.fromEntries(capability.actions.map((action) => [action, [{
19201
+ type: "xyz.tinycloud.resource/selector",
19202
+ kind: capability.selector,
19203
+ value: capability.resource
19204
+ }]]));
19205
+ }
19206
+ return attenuation;
19207
+ }
18739
19208
  function decodeAuthorizationBytes(authorization) {
18740
19209
  const encoded = authorization.replace(/^Bearer /i, "");
18741
19210
  const match = /^([A-Za-z0-9_-]+)(={1,2})?$/.exec(encoded);
@@ -18755,6 +19224,9 @@ function decodeAuthorizationBytes(authorization) {
18755
19224
  function isOpenKeyAutoSignStrategy(strategy) {
18756
19225
  return strategy?.openKeyAutoSign === true;
18757
19226
  }
19227
+ function openKeyApproval(strategy) {
19228
+ return strategy?.openKeyRequestApproval;
19229
+ }
18758
19230
  function isInteractiveSigner(config) {
18759
19231
  if (config.privateKey) {
18760
19232
  return false;
@@ -19112,6 +19584,8 @@ var _TinyCloudNode = class _TinyCloudNode {
19112
19584
  * hosting is confirmed against the session that was active at the time.
19113
19585
  */
19114
19586
  this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
19587
+ /** Serializes account-registry writes for the active node instance. */
19588
+ this.accountRegistryTail = Promise.resolve();
19115
19589
  /**
19116
19590
  * True when the last signIn() detected an interactive signer and skipped
19117
19591
  * client-side bootstrap. Apps can read this to know whether bootstrap was
@@ -19120,9 +19594,12 @@ var _TinyCloudNode = class _TinyCloudNode {
19120
19594
  this._bootstrapSkipped = false;
19121
19595
  /**
19122
19596
  * Outcome of the last signIn()'s account-bootstrap attempt. `skipped` is
19123
- * true when bootstrap did not complete (interactive signer, auto-sign
19124
- * denied, or a bootstrap step failed); `reason` carries the cause so apps
19125
- * can surface a "finish account setup" call-to-action. `warnings` is
19597
+ * true when bootstrap did not complete in this invocation (interactive signer,
19598
+ * auto-bootstrap disabled, already provisioned, or a decision/provisioning
19599
+ * step failed). `already-provisioned` means the bootstrap was not run here;
19600
+ * it does not mean a prior provisioning attempt failed to complete.
19601
+ * `reason` carries the cause so apps can surface a "finish account setup"
19602
+ * call-to-action. `warnings` is
19126
19603
  * present when bootstrap completed, but one or more steps recovered from
19127
19604
  * an ambiguous failure (e.g. a KV batch write reconciled via per-space
19128
19605
  * fallback) — clean vs. recovered runs are otherwise byte-identical.
@@ -19305,7 +19782,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19305
19782
  nonce: config.nonce,
19306
19783
  siweConfig: config.siweConfig,
19307
19784
  manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
19308
- capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
19785
+ capabilityRequest: useBootstrapSignInRequest ? config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
19309
19786
  includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
19310
19787
  });
19311
19788
  this.tc = new TinyCloud(this.auth, {
@@ -19395,6 +19872,15 @@ var _TinyCloudNode = class _TinyCloudNode {
19395
19872
  get sessionDid() {
19396
19873
  return this.sessionManager.getDID(this.sessionKeyId);
19397
19874
  }
19875
+ /** Bare did:key principal used as the credential subject and holder binding. */
19876
+ get credentialHolderDid() {
19877
+ return this.sessionDid.split("#", 1)[0];
19878
+ }
19879
+ /** Canonical verification method for the active credential holder key. */
19880
+ get credentialHolderKid() {
19881
+ const holder = this.credentialHolderDid;
19882
+ return `${holder}#${holder.slice("did:key:".length)}`;
19883
+ }
19398
19884
  /**
19399
19885
  * Return the current session's signed ReCap capabilities after the session
19400
19886
  * has been authenticated or restored. This is intentionally distinct from
@@ -19445,7 +19931,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19445
19931
  if (!this._address) {
19446
19932
  return void 0;
19447
19933
  }
19448
- return this.wasmBindings.makeSpaceId(this._address, this._chainId, ACCOUNT_REGISTRY_SPACE);
19934
+ return this.wasmBindings.makeSpaceId(this._address, this._chainId, ACCOUNT_REGISTRY_SPACE2);
19449
19935
  }
19450
19936
  /**
19451
19937
  * Account-level application and delegation helpers.
@@ -19496,6 +19982,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19496
19982
  "Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
19497
19983
  );
19498
19984
  }
19985
+ await this.accountRegistryTail;
19499
19986
  await this.wasmBindings.ensureInitialized?.();
19500
19987
  this._address = canonicalizeAddress2(await this.signer.getAddress());
19501
19988
  this._chainId = await this.signer.getChainId();
@@ -19548,6 +20035,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19548
20035
  this._bootstrapSkipped = false;
19549
20036
  this._bootstrapStatus = { skipped: false };
19550
20037
  if (this.config.autoBootstrapAccount === false) {
20038
+ this._bootstrapStatus = { skipped: true, reason: "auto-bootstrap-disabled" };
19551
20039
  return false;
19552
20040
  }
19553
20041
  if (!this.auth || !this._address) {
@@ -19562,11 +20050,14 @@ var _TinyCloudNode = class _TinyCloudNode {
19562
20050
  return false;
19563
20051
  }
19564
20052
  const steps = bootstrapSteps(this._address, this._chainId);
19565
- if (!await this.isFreshBootstrapAccount(steps)) {
19566
- return false;
19567
- }
19568
20053
  try {
19569
- const warnings = await this.runAccountBootstrap(steps);
20054
+ const decision = await this.resolveBootstrapDecision(steps);
20055
+ if (decision.action === "skip") {
20056
+ this._bootstrapStatus = { skipped: true, reason: "already-provisioned" };
20057
+ return false;
20058
+ }
20059
+ const warnings = await this.runAccountBootstrap(steps, { mode: decision.mode });
20060
+ await this.writeBootstrapCompletionMarker(steps);
19570
20061
  if (warnings.length > 0) {
19571
20062
  this._bootstrapStatus = { skipped: false, warnings };
19572
20063
  this.notificationHandler.warning(
@@ -19585,32 +20076,92 @@ var _TinyCloudNode = class _TinyCloudNode {
19585
20076
  }
19586
20077
  return true;
19587
20078
  }
19588
- async isFreshBootstrapAccount(steps) {
20079
+ async resolveBootstrapDecision(steps) {
19589
20080
  const enshrinedSpaceIds = /* @__PURE__ */ new Set();
19590
20081
  for (const step of steps) {
19591
20082
  if (step.kind === "session") {
19592
20083
  enshrinedSpaceIds.add(step.spaceId);
19593
20084
  }
19594
20085
  }
19595
- const skipped = this.auth.lastActivationSkippedSpaceIds;
20086
+ const auth = this.auth;
20087
+ if (!auth) {
20088
+ throw new Error("Account bootstrap requires an active wallet session");
20089
+ }
20090
+ const skipped = auth.lastActivationSkippedSpaceIds;
19596
20091
  if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
19597
- return true;
20092
+ return { action: "run", mode: "fresh" };
19598
20093
  }
19599
20094
  try {
19600
- const indexed = await this.account.index.spaces.list();
19601
- if (indexed.ok && indexed.data.length === 0) {
19602
- return true;
20095
+ const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
20096
+ const markerPermission = {
20097
+ service: "tinycloud.kv",
20098
+ space: accountSpaceId,
20099
+ path: BOOTSTRAP_COMPLETION_MARKER_KEY,
20100
+ actions: ["get"]
20101
+ };
20102
+ if (!this.hasRuntimePermissions([markerPermission])) {
20103
+ return { action: "run", mode: "repair" };
19603
20104
  }
19604
- } catch {
20105
+ const marker = await this.readBootstrapCompletionMarker();
20106
+ if (marker.ok) {
20107
+ if (this.isAcceptedBootstrapCompletionMarker(marker.data.data)) {
20108
+ return { action: "skip" };
20109
+ }
20110
+ return { action: "run", mode: "repair" };
20111
+ }
20112
+ if (marker.error.code === ErrorCodes2.KV_NOT_FOUND && this.markerReadIsUnhostedSpace(marker.error.meta)) {
20113
+ return { action: "run", mode: "fresh" };
20114
+ }
20115
+ } catch (err) {
20116
+ const reason = err instanceof Error ? err.message : String(err);
20117
+ this.notificationHandler.warning(
20118
+ `Account bootstrap decision failed; attempting one bounded repair: ${reason}`
20119
+ );
20120
+ console.warn(`[TinyCloudNode] account bootstrap decision failed: ${reason}`);
20121
+ return { action: "run", mode: "repair" };
19605
20122
  }
19606
- try {
19607
- const spaces = await this.account.spaces.list();
19608
- return spaces.ok && spaces.data.length === 0;
19609
- } catch {
19610
- return false;
20123
+ return { action: "run", mode: "repair" };
20124
+ }
20125
+ async readBootstrapCompletionMarker() {
20126
+ return this.kvForSpace(this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2)).get(
20127
+ BOOTSTRAP_COMPLETION_MARKER_KEY
20128
+ );
20129
+ }
20130
+ async writeBootstrapCompletionMarker(steps) {
20131
+ if (!this._address) {
20132
+ throw new Error("Bootstrap completion marker requires an active wallet session");
19611
20133
  }
20134
+ const canonicalStepIds = new Set(
20135
+ bootstrapSteps(this._address, this._chainId).map((step) => step.id)
20136
+ );
20137
+ const providedStepIds = new Set(steps.map((step) => step.id));
20138
+ if (canonicalStepIds.size !== providedStepIds.size || [...canonicalStepIds].some((stepId) => !providedStepIds.has(stepId))) {
20139
+ throw new Error("Bootstrap completion marker requires the canonical bootstrap step set");
20140
+ }
20141
+ const marker = {
20142
+ v: BOOTSTRAP_COMPLETION_MARKER_VERSION,
20143
+ stepIds: [...canonicalStepIds],
20144
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
20145
+ };
20146
+ const written = await this.kvForSpace(this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2)).put(
20147
+ BOOTSTRAP_COMPLETION_MARKER_KEY,
20148
+ marker
20149
+ );
20150
+ if (!written.ok) {
20151
+ throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
20152
+ }
20153
+ }
20154
+ isAcceptedBootstrapCompletionMarker(value) {
20155
+ if (typeof value !== "object" || value === null) return false;
20156
+ const entries = Object.entries(value);
20157
+ const markerValue = (name) => entries.find(([key2]) => key2 === name)?.[1];
20158
+ const version23 = markerValue("v");
20159
+ return typeof version23 === "number" && Number.isInteger(version23) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version23);
20160
+ }
20161
+ markerReadIsUnhostedSpace(meta) {
20162
+ return meta?.status === 404;
19612
20163
  }
19613
- async runAccountBootstrap(steps) {
20164
+ async runAccountBootstrap(steps, { mode } = { mode: "fresh" }) {
19614
20165
  if (!this.auth || !this._address) {
19615
20166
  throw new Error("Account bootstrap requires an active wallet session");
19616
20167
  }
@@ -19711,9 +20262,8 @@ var _TinyCloudNode = class _TinyCloudNode {
19711
20262
  if (step.kind === "seed-applications") {
19712
20263
  const registered = await this.account.applications.register(
19713
20264
  step.manifests.length > 0 ? [...step.manifests] : TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
19714
- // Bootstrap only runs on an account with no registry records, so the
19715
- // manifest-hash pre-read is definitionally a miss. Skip it; the write
19716
- // it guards is an INSERT OR REPLACE and is safe to repeat.
20265
+ // The write is an INSERT OR REPLACE and is safe to repeat during a
20266
+ // repair, so skip the manifest-hash pre-read.
19717
20267
  { assumeUnregistered: true }
19718
20268
  );
19719
20269
  if (!registered.ok) {
@@ -19721,7 +20271,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19721
20271
  }
19722
20272
  }
19723
20273
  if (step.kind === "encryption-network-create") {
19724
- await this.ensureEncryptionNetwork(step.networkId, { assumeMissing: true });
20274
+ await this.ensureEncryptionNetwork(step.networkId, { assumeMissing: mode === "fresh" });
19725
20275
  }
19726
20276
  if (step.kind === "secret-records-schema") {
19727
20277
  const db = this.sqlForSpace(step.spaceId).db(step.database);
@@ -19904,7 +20454,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19904
20454
  if (!this.auth || !this.signer) {
19905
20455
  throw new Error("Manifest registry write requires wallet mode");
19906
20456
  }
19907
- const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE);
20457
+ const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
19908
20458
  await this.ensureOwnedSpaceHostedById(accountSpaceId);
19909
20459
  const result = await this.account.applications.register(request.manifests);
19910
20460
  if (!result.ok) {
@@ -19914,16 +20464,48 @@ var _TinyCloudNode = class _TinyCloudNode {
19914
20464
  }
19915
20465
  }
19916
20466
  scheduleAccountRegistrySync() {
19917
- void this.withAccountRegistryRetry(async () => {
19918
- void this.account.index.ensure();
19919
- await this.writeManifestRegistryRecords();
19920
- if (this.currentSessionCanListSpaces()) {
19921
- const spaces = await this.account.spaces.syncAccessible();
19922
- if (!spaces.ok) {
19923
- throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
20467
+ const session = this.currentTinyCloudSession();
20468
+ if (!session || this.pendingAccountRegistrySync?.session === session) {
20469
+ return;
20470
+ }
20471
+ const promise = this.enqueueAccountRegistryOperation(session, async () => {
20472
+ await this.withAccountRegistryRetry(async () => {
20473
+ if (this.currentTinyCloudSession() !== session) {
20474
+ return;
19924
20475
  }
20476
+ await this.account.index.ensure();
20477
+ if (this.currentTinyCloudSession() !== session) {
20478
+ return;
20479
+ }
20480
+ await this.writeManifestRegistryRecords();
20481
+ if (this.currentTinyCloudSession() !== session) {
20482
+ return;
20483
+ }
20484
+ if (this.currentSessionCanListSpaces()) {
20485
+ const spaces = await this.account.spaces.syncAccessible();
20486
+ if (!spaces.ok) {
20487
+ throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
20488
+ }
20489
+ }
20490
+ });
20491
+ });
20492
+ this.pendingAccountRegistrySync = { session, promise };
20493
+ const clearPendingSync = () => {
20494
+ if (this.pendingAccountRegistrySync?.promise === promise) {
20495
+ this.pendingAccountRegistrySync = void 0;
20496
+ }
20497
+ };
20498
+ void promise.then(clearPendingSync, clearPendingSync);
20499
+ }
20500
+ enqueueAccountRegistryOperation(session, task) {
20501
+ const operation = this.accountRegistryTail.then(async () => {
20502
+ if (this.currentTinyCloudSession() === session) {
20503
+ await task();
19925
20504
  }
19926
20505
  });
20506
+ this.accountRegistryTail = operation.catch(() => {
20507
+ });
20508
+ return operation;
19927
20509
  }
19928
20510
  /**
19929
20511
  * Whether the current primary session may invoke `tinycloud.space/list`.
@@ -20076,7 +20658,8 @@ var _TinyCloudNode = class _TinyCloudNode {
20076
20658
  * @returns The hosted space URI.
20077
20659
  */
20078
20660
  async hostOwnedSpace(name) {
20079
- if (!this.auth || !this.auth.tinyCloudSession) {
20661
+ const session = this.auth?.tinyCloudSession;
20662
+ if (!this.auth || !session) {
20080
20663
  throw new Error("Not signed in. Call signIn() first.");
20081
20664
  }
20082
20665
  const spaceId = this.ownedSpaceId(name);
@@ -20092,22 +20675,26 @@ var _TinyCloudNode = class _TinyCloudNode {
20092
20675
  }
20093
20676
  const activation = await activateSessionWithHost2(
20094
20677
  host,
20095
- this.auth.tinyCloudSession.delegationHeader
20678
+ session.delegationHeader
20096
20679
  );
20097
20680
  if (!activation.success || activation.skipped?.includes(spaceId)) {
20098
20681
  throw new Error(
20099
20682
  `Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
20100
20683
  );
20101
20684
  }
20102
- void this.account.spaces.register({
20103
- spaceId,
20104
- name,
20105
- ownerDid: this.did,
20106
- type: "owned",
20107
- permissions: ["*"],
20108
- status: "active"
20109
- }).catch(() => {
20110
- });
20685
+ try {
20686
+ await this.enqueueAccountRegistryOperation(session, async () => {
20687
+ await this.account.spaces.register({
20688
+ spaceId,
20689
+ name,
20690
+ ownerDid: this.did,
20691
+ type: "owned",
20692
+ permissions: ["*"],
20693
+ status: "active"
20694
+ });
20695
+ });
20696
+ } catch {
20697
+ }
20111
20698
  return spaceId;
20112
20699
  }
20113
20700
  /**
@@ -20142,23 +20729,20 @@ var _TinyCloudNode = class _TinyCloudNode {
20142
20729
  if (!this.auth || !this.auth.tinyCloudSession) {
20143
20730
  throw new Error("Not signed in. Call signIn() first.");
20144
20731
  }
20732
+ await this.accountRegistryTail;
20145
20733
  const spaceId = this.ownedSpaceId(name);
20146
20734
  if (await this.isOwnedSpaceRegistered(spaceId)) {
20147
20735
  return spaceId;
20148
20736
  }
20149
- const hosted = await this.hostOwnedSpace(name);
20150
- try {
20151
- await this.account.spaces.register({
20152
- spaceId,
20153
- name,
20154
- ownerDid: this.did,
20155
- type: "owned",
20156
- permissions: ["*"],
20157
- status: "active"
20158
- });
20159
- } catch {
20737
+ return this.hostOwnedSpace(name);
20738
+ }
20739
+ /** Resolve and authenticate the owner encoded by an owned-space URI. */
20740
+ credentialSpaceOwnerDid(spaceId) {
20741
+ const ownerDid = this.ownerDidFromSpaceId(spaceId);
20742
+ if (ownerDid === void 0 || !didPrincipalMatches2(ownerDid, this.did)) {
20743
+ throw new Error("Credential space owner does not match the active TinyCloud owner");
20160
20744
  }
20161
- return hosted;
20745
+ return ownerDid;
20162
20746
  }
20163
20747
  /**
20164
20748
  * Check whether an owned space is already registered/hosted by consulting the
@@ -20703,6 +21287,106 @@ var _TinyCloudNode = class _TinyCloudNode {
20703
21287
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
20704
21288
  return signBytesWithJwk(bytes, session.jwk);
20705
21289
  }
21290
+ /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
21291
+ async createUnifiedOwnerRoot(input) {
21292
+ const ownerDid = this.credentialHolderDid;
21293
+ if (input.ownerDid !== ownerDid) throw new Error("unified owner root signer does not match owner DID");
21294
+ const facts = {
21295
+ role: input.role,
21296
+ mode: input.role === "policy-authority" ? "policy-source" : "conditional-mint",
21297
+ ownerDid,
21298
+ policyId: input.policyId,
21299
+ policyDigestHex: input.policyDigestHex,
21300
+ policyCid: input.policyCid,
21301
+ contentSourceDigestHex: input.contentSourceDigestHex,
21302
+ capabilityCeilingHashHex: input.capabilityCeilingHashHex,
21303
+ nativeProjectionHashHex: input.nativeProjectionHashHex,
21304
+ nodeAudience: input.nodeAudience,
21305
+ ...input.role === "policy-enforcement" ? { enforcerDid: input.audienceDid } : {}
21306
+ };
21307
+ const root = await signCompactUcanRootAuthorization({
21308
+ issuerDid: ownerDid,
21309
+ audienceDid: input.audienceDid,
21310
+ attenuation: unifiedRootAttenuation(input.capabilities),
21311
+ facts: [facts],
21312
+ notBefore: Math.floor(input.notBefore.getTime() / 1e3),
21313
+ expiresAt: Math.floor(input.expiresAt.getTime() / 1e3),
21314
+ nonce: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
21315
+ sign: (bytes) => this.signSessionBytes(bytes)
21316
+ });
21317
+ const kv = input.capabilities.find((capability) => capability.kind === "kv");
21318
+ const resource = kv?.resource ?? "";
21319
+ const marker = resource.indexOf("/kv/");
21320
+ return {
21321
+ cid: root.cid,
21322
+ delegationHeader: { Authorization: root.authorization },
21323
+ delegateDID: input.audienceDid,
21324
+ spaceId: marker === -1 ? "" : resource.slice("tinycloud://".length, marker),
21325
+ path: marker === -1 ? resource : resource.slice(marker + 4),
21326
+ actions: kv?.actions ?? [],
21327
+ expiry: input.expiresAt
21328
+ };
21329
+ }
21330
+ /** Apply only an already-enabled signing policy to the exact credential request. */
21331
+ async autoSignCredentialBytes(bytes) {
21332
+ const strategy = this.config.signStrategy;
21333
+ if (strategy?.type === "auto-sign") return this.signSessionBytes(bytes);
21334
+ if (strategy?.type !== "callback" || !isOpenKeyAutoSignStrategy(strategy)) return void 0;
21335
+ const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
21336
+ if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
21337
+ const request = {
21338
+ address: this.address ?? "",
21339
+ chainId: this.session?.chainId ?? this._chainId,
21340
+ message: new TextDecoder().decode(bytes),
21341
+ type: "message",
21342
+ purpose: "message"
21343
+ };
21344
+ const decision = await strategy.handler(request);
21345
+ if (!decision.approved) {
21346
+ if (decision.needsApproval) return void 0;
21347
+ throw new Error(decision.reason ?? "OpenKey automatic credential signing was rejected");
21348
+ }
21349
+ if (typeof decision.signature !== "string" || !await verifyEip191MessageSignature(
21350
+ request.message,
21351
+ decision.signature,
21352
+ request.address
21353
+ )) {
21354
+ throw new Error("OpenKey credential signature evidence is invalid");
21355
+ }
21356
+ return this.signSessionBytes(bytes);
21357
+ }
21358
+ /** Invoke the configured interactive approval strategy before session signing. */
21359
+ async approveCredentialBytes(bytes) {
21360
+ const strategy = this.config.signStrategy;
21361
+ const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
21362
+ if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
21363
+ const request = {
21364
+ address: this.address ?? "",
21365
+ chainId: this.session?.chainId ?? this._chainId,
21366
+ message: new TextDecoder().decode(bytes),
21367
+ type: "message",
21368
+ purpose: "message"
21369
+ };
21370
+ const approval = isOpenKeyAutoSignStrategy(strategy) ? openKeyApproval(strategy) : strategy?.type === "callback" ? strategy.handler : void 0;
21371
+ if (approval) {
21372
+ const decision = await approval(request);
21373
+ if (!decision.approved) throw new Error(decision.reason ?? "Credential signing was rejected");
21374
+ if (decision.signature !== void 0 && !await verifyEip191MessageSignature(
21375
+ request.message,
21376
+ decision.signature,
21377
+ request.address
21378
+ )) {
21379
+ throw new Error("OpenKey credential signature evidence is invalid");
21380
+ }
21381
+ return this.signSessionBytes(bytes);
21382
+ }
21383
+ if (strategy?.type === "auto-reject") throw new Error("Credential signing was rejected");
21384
+ if (this.config.signer) {
21385
+ await this.config.signer.signMessage(request.message);
21386
+ return this.signSessionBytes(bytes);
21387
+ }
21388
+ throw new Error("Interactive OpenKey approval is not configured");
21389
+ }
20706
21390
  /** Bind a session-signed invocation to the canonical service audience. */
20707
21391
  async bindInvocationAudience(authorization, audience) {
20708
21392
  const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
@@ -20770,7 +21454,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20770
21454
  nonce: this.config.nonce,
20771
21455
  siweConfig: this.config.siweConfig,
20772
21456
  manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
20773
- capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
21457
+ capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
20774
21458
  includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
20775
21459
  });
20776
21460
  this.tc = new TinyCloud(this.auth, {
@@ -20823,7 +21507,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20823
21507
  nonce: this.config.nonce,
20824
21508
  siweConfig: this.config.siweConfig,
20825
21509
  manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
20826
- capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
21510
+ capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
20827
21511
  includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
20828
21512
  });
20829
21513
  this.tc = new TinyCloud(this.auth, {
@@ -23572,35 +24256,44 @@ var _TinyCloudNode = class _TinyCloudNode {
23572
24256
  if (parentDelegation.disableSubDelegation) {
23573
24257
  throw new Error("Parent delegation does not allow sub-delegation");
23574
24258
  }
23575
- if (!params.path.startsWith(parentDelegation.path)) {
24259
+ const parentResources = parentDelegation.resources ?? [{
24260
+ service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
24261
+ space: parentDelegation.spaceId,
24262
+ path: parentDelegation.path,
24263
+ actions: parentDelegation.actions
24264
+ }];
24265
+ const requestedResources = params.resources ?? [{
24266
+ service: params.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
24267
+ space: parentDelegation.spaceId,
24268
+ path: params.path,
24269
+ actions: params.actions
24270
+ }];
24271
+ const resourceIsContained = (parent, child) => {
24272
+ const parentEncryption = parent.service === "encryption" || parent.service === "tinycloud.encryption";
24273
+ const childEncryption = child.service === "encryption" || child.service === "tinycloud.encryption";
24274
+ if (parentEncryption || childEncryption) return parentEncryption && childEncryption && parent.path === child.path && child.actions.every((action) => parent.actions.includes(action));
24275
+ const pathContained = parent.path === child.path || parent.path.endsWith("/") && child.path.startsWith(parent.path) || child.path.startsWith(`${parent.path}/`);
24276
+ return pathContained && child.actions.every((action) => parent.actions.includes(action));
24277
+ };
24278
+ if (requestedResources.length === 0 || requestedResources.some((child) => !parentResources.some((parent) => resourceIsContained(parent, child)))) {
23576
24279
  throw new Error(
23577
- `Sub-delegation path "${params.path}" must be within parent path "${parentDelegation.path}"`
24280
+ "Sub-delegation resources exceed the received delegation"
23578
24281
  );
23579
24282
  }
23580
- const parentActions = new Set(parentDelegation.actions);
23581
- for (const action of params.actions) {
23582
- if (!parentActions.has(action)) {
23583
- throw new Error(
23584
- `Sub-delegation action "${action}" is not in parent's actions: ${parentDelegation.actions.join(", ")}`
23585
- );
23586
- }
23587
- }
23588
24283
  const now = /* @__PURE__ */ new Date();
23589
24284
  const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
23590
24285
  const requestedExpiry = new Date(now.getTime() + expiryMs);
23591
24286
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
23592
24287
  const abilities = {};
23593
- const kvActions = params.actions.filter((a) => a.startsWith("tinycloud.kv/"));
23594
- const sqlActions = params.actions.filter((a) => a.startsWith("tinycloud.sql/"));
23595
- const duckdbActions = params.actions.filter((a) => a.startsWith("tinycloud.duckdb/"));
23596
- if (kvActions.length > 0) {
23597
- abilities.kv = { [params.path]: kvActions };
23598
- }
23599
- if (sqlActions.length > 0) {
23600
- abilities.sql = { [params.path]: sqlActions };
23601
- }
23602
- if (duckdbActions.length > 0) {
23603
- abilities.duckdb = { [params.path]: duckdbActions };
24288
+ const rawAbilities = {};
24289
+ for (const resource of requestedResources) {
24290
+ const service = resource.service.replace(/^tinycloud\./, "");
24291
+ if (service === "encryption") {
24292
+ rawAbilities[resource.path] = [...resource.actions];
24293
+ continue;
24294
+ }
24295
+ abilities[service] ?? (abilities[service] = {});
24296
+ abilities[service][resource.path] = [...resource.actions];
23604
24297
  }
23605
24298
  const targetHost = parentDelegation.host ?? this.config.host;
23606
24299
  const prepared = this.wasmBindings.prepareSession({
@@ -23612,7 +24305,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23612
24305
  expirationTime: actualExpiry.toISOString(),
23613
24306
  spaceId: parentDelegation.spaceId,
23614
24307
  delegateUri: params.delegateDID,
23615
- parents: [parentDelegation.cid]
24308
+ parents: [parentDelegation.cid],
24309
+ ...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
23616
24310
  });
23617
24311
  const signature2 = await this.signer.signMessage(prepared.siwe);
23618
24312
  const subDelegationSession = this.wasmBindings.completeSessionSetup({
@@ -23630,14 +24324,20 @@ var _TinyCloudNode = class _TinyCloudNode {
23630
24324
  cid: subDelegationSession.delegationCid,
23631
24325
  delegationHeader: subDelegationSession.delegationHeader,
23632
24326
  spaceId: parentDelegation.spaceId,
23633
- path: params.path,
23634
- actions: params.actions,
24327
+ path: requestedResources[0].path,
24328
+ actions: requestedResources[0].actions,
23635
24329
  disableSubDelegation: params.disableSubDelegation ?? false,
23636
24330
  expiry: actualExpiry,
23637
24331
  delegateDID: params.delegateDID,
23638
24332
  ownerAddress: parentDelegation.ownerAddress,
23639
24333
  chainId: parentDelegation.chainId,
23640
- host: targetHost
24334
+ host: targetHost,
24335
+ resources: requestedResources.map((resource) => ({
24336
+ service: resource.service.replace(/^tinycloud\./, ""),
24337
+ space: resource.service.replace(/^tinycloud\./, "") === "encryption" ? "encryption" : resource.space ?? parentDelegation.spaceId,
24338
+ path: resource.path,
24339
+ actions: [...resource.actions]
24340
+ }))
23641
24341
  };
23642
24342
  }
23643
24343
  };
@@ -23810,10 +24510,54 @@ var FileSessionStorage = class {
23810
24510
  }
23811
24511
  };
23812
24512
 
24513
+ // src/authorization/openKeyBridge.ts
24514
+ function wireOpenKeyAuthorize(openkey) {
24515
+ return async (input) => {
24516
+ const result = await openkey.authorizeTinyCloud({
24517
+ protocolVersion: 1,
24518
+ siwe: input.siwe,
24519
+ keyId: input.keyId,
24520
+ jwk: input.jwk,
24521
+ host: input.host,
24522
+ // Forward the presentation envelope verbatim — the OpenKey SDK
24523
+ // validates and size-bounds it before use.
24524
+ presentation: input.presentation
24525
+ });
24526
+ if (result.protocolVersion !== 1) {
24527
+ throw new Error(
24528
+ `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
24529
+ );
24530
+ }
24531
+ if (typeof result.signedMessage !== "string" || !result.signedMessage) {
24532
+ throw new Error(
24533
+ "wireOpenKeyAuthorize: OpenKey returned no signedMessage \u2014 cannot complete a TinyCloud session"
24534
+ );
24535
+ }
24536
+ if (!Array.isArray(result.selectedActionKeys)) {
24537
+ throw new Error(
24538
+ "wireOpenKeyAuthorize: OpenKey returned no selectedActionKeys"
24539
+ );
24540
+ }
24541
+ if (!Array.isArray(result.permissions)) {
24542
+ throw new Error(
24543
+ "wireOpenKeyAuthorize: OpenKey returned no permissions"
24544
+ );
24545
+ }
24546
+ return {
24547
+ protocolVersion: 1,
24548
+ address: result.address,
24549
+ signature: result.signature,
24550
+ signedMessage: result.signedMessage,
24551
+ selectedActionKeys: result.selectedActionKeys,
24552
+ permissions: result.permissions
24553
+ };
24554
+ };
24555
+ }
24556
+
23813
24557
  // src/index.ts
23814
24558
  import {
23815
24559
  ACCOUNT_REGISTRY_PATH,
23816
- ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
24560
+ ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE3,
23817
24561
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
23818
24562
  DEFAULT_MANIFEST_VERSION,
23819
24563
  VAULT_PERMISSION_SERVICE,
@@ -23836,6 +24580,7 @@ import {
23836
24580
  // src/delegation.ts
23837
24581
  import {
23838
24582
  canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
24583
+ decodeBase64Url,
23839
24584
  principalDidEquals as principalDidEquals3
23840
24585
  } from "@tinycloud/sdk-core";
23841
24586
  var SERVICE_BY_SHORT_NAME = {
@@ -23870,7 +24615,7 @@ function compactUcanPayload(authorization) {
23870
24615
  }
23871
24616
  try {
23872
24617
  const payload = JSON.parse(
23873
- Buffer.from(parts[1], "base64url").toString("utf8")
24618
+ new TextDecoder("utf-8", { fatal: true }).decode(decodeBase64Url(parts[1]))
23874
24619
  );
23875
24620
  if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
23876
24621
  throw new Error("payload is not an object");
@@ -23921,6 +24666,14 @@ function signedAuthorityFromCompactUcan(authorization) {
23921
24666
  if (typeof payload.aud !== "string" || payload.aud.length === 0) {
23922
24667
  throw new Error("Validated runtime delegation is missing a signed audience.");
23923
24668
  }
24669
+ if (typeof payload.iss !== "string" || payload.iss.length === 0) {
24670
+ throw new Error("Validated runtime delegation is missing a signed issuer.");
24671
+ }
24672
+ if (!Array.isArray(payload.prf) || payload.prf.some(
24673
+ (proof) => typeof proof !== "string" || proof.length === 0
24674
+ )) {
24675
+ throw new Error("Validated runtime delegation has malformed signed proofs.");
24676
+ }
23924
24677
  if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
23925
24678
  throw new Error("Validated runtime delegation is missing a signed expiry.");
23926
24679
  }
@@ -23931,6 +24684,11 @@ function signedAuthorityFromCompactUcan(authorization) {
23931
24684
  if (payload.att === null || typeof payload.att !== "object" || Array.isArray(payload.att)) {
23932
24685
  throw new Error("Validated runtime delegation is missing a signed attenuation.");
23933
24686
  }
24687
+ const fact = Array.isArray(payload.fct) && payload.fct.length > 0 && payload.fct[0] !== null && typeof payload.fct[0] === "object" && !Array.isArray(payload.fct[0]) ? payload.fct[0] : void 0;
24688
+ const remainingRedelegationDepth = fact?.remainingRedelegationDepth;
24689
+ if (remainingRedelegationDepth !== void 0 && (!Number.isInteger(remainingRedelegationDepth) || remainingRedelegationDepth < 0 || remainingRedelegationDepth > 8)) {
24690
+ throw new Error("Validated runtime delegation has invalid signed redelegation depth.");
24691
+ }
23934
24692
  const permissions = [];
23935
24693
  for (const [resource, abilities] of Object.entries(payload.att)) {
23936
24694
  if (abilities === null || typeof abilities !== "object" || Array.isArray(abilities)) {
@@ -23949,7 +24707,9 @@ function signedAuthorityFromCompactUcan(authorization) {
23949
24707
  ));
23950
24708
  continue;
23951
24709
  }
23952
- const match = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
24710
+ const nativeMatch = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
24711
+ const policyMatch = /^tinycloud:\/\/([^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
24712
+ const match = nativeMatch ?? policyMatch;
23953
24713
  if (!match) {
23954
24714
  throw new Error(
23955
24715
  `Validated runtime delegation has an unsupported signed resource '${resource}'.`
@@ -23981,8 +24741,11 @@ function signedAuthorityFromCompactUcan(authorization) {
23981
24741
  ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
23982
24742
  }));
23983
24743
  return {
24744
+ issuer: payload.iss,
23984
24745
  audience: payload.aud,
23985
24746
  expiry,
24747
+ proofs: [...payload.prf],
24748
+ ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
23986
24749
  permissions: canonicalPermissions,
23987
24750
  resources: canonicalResources
23988
24751
  };
@@ -24073,6 +24836,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
24073
24836
  delegationHeader: { Authorization: authorization },
24074
24837
  ownerAddress: delegation.ownerAddress,
24075
24838
  chainId: delegation.chainId,
24839
+ delegatorDID: signed2.issuer,
24840
+ ...signed2.proofs.length === 1 ? { parentCid: signed2.proofs[0] } : {},
24841
+ ...signed2.remainingRedelegationDepth === void 0 ? {} : {
24842
+ allowSubDelegation: signed2.remainingRedelegationDepth > 0,
24843
+ disableSubDelegation: signed2.remainingRedelegationDepth === 0
24844
+ },
24845
+ authHeader: authorization,
24076
24846
  spaceId: signedSpace ?? delegation.spaceId,
24077
24847
  path: primary.path,
24078
24848
  actions: [...primary.actions],
@@ -24111,6 +24881,40 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
24111
24881
  host: normalizedHost(installed.host ?? host)
24112
24882
  };
24113
24883
  }
24884
+ async function activateCompactRuntimeDelegation(node, input) {
24885
+ const authorization = input.authorization;
24886
+ const cid = node.computeDelegationCid(authorization);
24887
+ if (cid !== input.cid) {
24888
+ throw new Error("Compact runtime delegation CID does not match authorization bytes.");
24889
+ }
24890
+ const signed2 = signedAuthorityFromCompactUcan(authorization);
24891
+ const primary = signed2.resources[0];
24892
+ const signedSpace = signed2.permissions.find(
24893
+ (permission) => permission.service !== "tinycloud.encryption"
24894
+ )?.space;
24895
+ const delegation = {
24896
+ cid,
24897
+ delegationHeader: { Authorization: authorization },
24898
+ ownerAddress: input.ownerAddress,
24899
+ chainId: input.chainId,
24900
+ delegatorDID: signed2.issuer,
24901
+ authHeader: authorization,
24902
+ spaceId: signedSpace ?? primary.space,
24903
+ path: primary.path,
24904
+ actions: [...primary.actions],
24905
+ ...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
24906
+ resources: signed2.resources.map((resource) => ({
24907
+ ...resource,
24908
+ actions: [...resource.actions]
24909
+ })),
24910
+ expiry: new Date(signed2.expiry),
24911
+ delegateDID: signed2.audience,
24912
+ host: normalizedHost(input.host)
24913
+ };
24914
+ return activateValidatedRuntimeDelegation(node, delegation, {
24915
+ host: input.host
24916
+ });
24917
+ }
24114
24918
  async function grantAuthRequest(authority, request, options) {
24115
24919
  if (request.kind !== "tinycloud.auth.request") {
24116
24920
  throw new Error(
@@ -24247,10 +25051,13 @@ import {
24247
25051
  } from "@tinycloud/sdk-core";
24248
25052
  import { ServiceContext as ServiceContext3 } from "@tinycloud/sdk-core";
24249
25053
  export {
25054
+ ACCEPTED_MARKER_VERSIONS,
24250
25055
  ACCOUNT_REGISTRY_PATH,
24251
- ACCOUNT_REGISTRY_SPACE2 as ACCOUNT_REGISTRY_SPACE,
25056
+ ACCOUNT_REGISTRY_SPACE3 as ACCOUNT_REGISTRY_SPACE,
24252
25057
  AccountService,
24253
25058
  AutoApproveSpaceCreationHandler2 as AutoApproveSpaceCreationHandler,
25059
+ BOOTSTRAP_COMPLETION_MARKER_KEY,
25060
+ BOOTSTRAP_COMPLETION_MARKER_VERSION,
24254
25061
  CapabilityKeyRegistry2 as CapabilityKeyRegistry,
24255
25062
  CapabilityKeyRegistryErrorCodes,
24256
25063
  CaveatedDelegationUnsupportedError2 as CaveatedDelegationUnsupportedError,
@@ -24311,6 +25118,7 @@ export {
24311
25118
  VaultPublicSpaceKVActions,
24312
25119
  VersionCheckError,
24313
25120
  WasmKeyProvider,
25121
+ activateCompactRuntimeDelegation,
24314
25122
  activateValidatedRuntimeDelegation,
24315
25123
  addressStorageKey,
24316
25124
  buildCanonicalDecryptRequest,
@@ -24387,7 +25195,8 @@ export {
24387
25195
  tinyCloudDebugLogger,
24388
25196
  validateEnvelope,
24389
25197
  validateManifest,
24390
- verifyDecryptResponse
25198
+ verifyDecryptResponse,
25199
+ wireOpenKeyAuthorize
24391
25200
  };
24392
25201
  /*! Bundled license information:
24393
25202