@tinycloud/sdk-core 3.8.0-beta.2 → 3.8.0-beta.21

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
@@ -3437,6 +3437,7 @@ import { sha256 as sha2564 } from "@noble/hashes/sha256";
3437
3437
  import { blake3 } from "@noble/hashes/blake3";
3438
3438
  import { ed25519 as ed255193 } from "@noble/curves/ed25519";
3439
3439
  import { z as z2 } from "zod";
3440
+ import { parseEncryptionResource as parseNamedEncryptionResource } from "@tinycloud/sdk-services";
3440
3441
 
3441
3442
  // src/http-error.ts
3442
3443
  import { ErrorCodes, serviceError } from "@tinycloud/sdk-services";
@@ -3705,12 +3706,9 @@ function parseKvResource(resource) {
3705
3706
  return { space, path };
3706
3707
  }
3707
3708
  function parseEncryptionResource(resource) {
3708
- if (!resource.startsWith("urn:tinycloud:encryption:")) throw new Error("encryption capability is invalid");
3709
- const rest = resource.slice("urn:tinycloud:encryption:".length);
3710
- const separator = rest.lastIndexOf(":");
3711
- const ownerDid = separator < 0 ? "" : rest.slice(0, separator);
3712
- const network = separator < 0 ? "" : rest.slice(separator + 1);
3713
- if (!ownerDid.startsWith("did:") || network.length === 0 || network.includes(":") || network.includes("/") || network.includes("%") || network.includes("?") || network.includes("#")) {
3709
+ try {
3710
+ if (parseNamedEncryptionResource(resource).keyPath?.endsWith("/")) throw new Error("prefix");
3711
+ } catch {
3714
3712
  throw new Error("encryption capability is invalid");
3715
3713
  }
3716
3714
  }
@@ -6177,7 +6175,11 @@ var DelegationErrorCodes = {
6177
6175
  KV_SERVICE_UNAVAILABLE: "KV_SERVICE_UNAVAILABLE",
6178
6176
  DATA_FETCH_FAILED: "DATA_FETCH_FAILED",
6179
6177
  VALIDATION_ERROR: "VALIDATION_ERROR",
6180
- UNSUPPORTED_OPERATION: "UNSUPPORTED_OPERATION"
6178
+ UNSUPPORTED_OPERATION: "UNSUPPORTED_OPERATION",
6179
+ /** The node answered 503: the delegation chain is too deep to evaluate right now. Retry later. */
6180
+ CHAIN_TOO_DEEP: "CHAIN_TOO_DEEP",
6181
+ /** The node answered 400 "invalid delegation query cursor": malformed, foreign or from another filter. Start the listing again without it. */
6182
+ INVALID_CURSOR: "INVALID_CURSOR"
6181
6183
  };
6182
6184
  var DelegationSchema = z4.object({
6183
6185
  /** Content identifier (CID) of the delegation */
@@ -6254,7 +6256,7 @@ var AccountDelegationPageSchema = z4.object({
6254
6256
  nextCursor: z4.string().optional()
6255
6257
  }).strict();
6256
6258
  var AccountDelegationQueryOptionsSchema = z4.object({
6257
- direction: z4.enum(["granted", "received", "all"]).optional(),
6259
+ direction: z4.enum(["granted", "received", "all", "issued"]).optional(),
6258
6260
  status: z4.enum(["active", "pending", "expired", "revoked", "ancestor_revoked"]).optional(),
6259
6261
  space: z4.string().trim().min(1).optional(),
6260
6262
  limit: z4.number().int().min(1).max(100).optional(),
@@ -6440,6 +6442,8 @@ var DelegationManagerConfigSchema = z4.object({
6440
6442
  ).optional(),
6441
6443
  /** Optional owner-signed authorization for direct CID revocation */
6442
6444
  signOwnerRevocation: z4.custom().optional(),
6445
+ /** Optional signer for proofless invocations by the session's own did:key (issued-delegation list and revoke) */
6446
+ sessionInvocationSigner: z4.custom().optional(),
6443
6447
  /** Optional custom fetch implementation */
6444
6448
  fetch: z4.unknown().refine(
6445
6449
  (val) => val === void 0 || typeof val === "function",
@@ -6535,7 +6539,7 @@ var CreateDelegationWasmResultSchema = z4.object({
6535
6539
  resources: z4.array(DelegatedResourceSchema)
6536
6540
  });
6537
6541
 
6538
- // src/delegations/DelegationManager.ts
6542
+ // src/delegations/shared.ts
6539
6543
  var DelegationAction = {
6540
6544
  REVOKE: "tinycloud.delegation/revoke",
6541
6545
  STATUS: "tinycloud.delegation/status",
@@ -6552,6 +6556,8 @@ function createError(code3, message, cause, meta) {
6552
6556
  meta
6553
6557
  };
6554
6558
  }
6559
+
6560
+ // src/delegations/DelegationManager.ts
6555
6561
  async function readInvokedJson(response, action) {
6556
6562
  const text3 = await response.text();
6557
6563
  if (text3.trim() === "") {
@@ -6574,11 +6580,13 @@ var DelegationManager = class {
6574
6580
  * @param config - Configuration including hosts, session, and invoke function
6575
6581
  */
6576
6582
  constructor(config) {
6583
+ this.issuedState = { issuedQuerySupported: false };
6577
6584
  this.hosts = config.hosts;
6578
6585
  this.session = config.session;
6579
6586
  this.invoke = config.invoke;
6580
6587
  this.invokeAny = config.invokeAny;
6581
6588
  this.signOwnerRevocation = config.signOwnerRevocation;
6589
+ this.sessionInvocationSigner = config.sessionInvocationSigner;
6582
6590
  this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
6583
6591
  this.accountSpaceId = config.accountSpaceId;
6584
6592
  }
@@ -6801,8 +6809,14 @@ var DelegationManager = class {
6801
6809
  };
6802
6810
  }
6803
6811
  }
6804
- /** Query lifecycle-complete delegation history for the authenticated account. */
6805
- async query(options2 = {}) {
6812
+ /**
6813
+ * Query lifecycle-complete delegation history for the authenticated account.
6814
+ *
6815
+ * `scope.spaces` lists the spaces to sign `tinycloud.delegation/list` for, in order. Omitted, the
6816
+ * account space is tried, then the session's own space. A caller that knows which of them the session
6817
+ * holds passes only those; an empty list sends nothing and answers PERMISSION_DENIED.
6818
+ */
6819
+ async query(options2 = {}, scope = {}) {
6806
6820
  const validated = AccountDelegationQueryOptionsSchema.safeParse(options2);
6807
6821
  if (!validated.success) {
6808
6822
  return {
@@ -6815,6 +6829,7 @@ var DelegationManager = class {
6815
6829
  )
6816
6830
  };
6817
6831
  }
6832
+ if (validated.data.direction === "issued") return this.queryIssued(validated.data);
6818
6833
  if (!this.invokeAny) {
6819
6834
  return {
6820
6835
  ok: false,
@@ -6836,7 +6851,7 @@ var DelegationManager = class {
6836
6851
  }
6837
6852
  try {
6838
6853
  let response;
6839
- for (const spaceId of /* @__PURE__ */ new Set([accountSpaceId, this.session.spaceId])) {
6854
+ for (const spaceId of new Set(scope.spaces ?? [accountSpaceId, this.session.spaceId])) {
6840
6855
  const headers = Object.fromEntries(new Headers(this.invokeAny(this.session, [{
6841
6856
  spaceId,
6842
6857
  service: "delegation",
@@ -6896,6 +6911,45 @@ var DelegationManager = class {
6896
6911
  };
6897
6912
  }
6898
6913
  }
6914
+ /** The state the lazily loaded issued-delegation module reads and updates. */
6915
+ issuedContext() {
6916
+ return {
6917
+ host: this.host,
6918
+ fetchFn: this.fetchFn,
6919
+ sessionInvocationSigner: this.sessionInvocationSigner,
6920
+ state: this.issuedState
6921
+ };
6922
+ }
6923
+ /**
6924
+ * Lists the delegations this session's own key issued (`direction: "issued"`).
6925
+ * The implementation loads on first use.
6926
+ */
6927
+ async queryIssued(options2) {
6928
+ const issued = await import("@tinycloud/sdk-core/delegations/issued");
6929
+ return issued.queryIssued(this.issuedContext(), options2);
6930
+ }
6931
+ /**
6932
+ * Every delegation this session's key issued, following `nextCursor` until
6933
+ * it is absent. `maxItems` stops early once that many are collected;
6934
+ * `untilCid` stops after the page that contains that delegation. Items come
6935
+ * in CID order (each carries `issuedAt`). The implementation loads on first use.
6936
+ */
6937
+ async queryAllIssued(options2 = {}) {
6938
+ const issued = await import("@tinycloud/sdk-core/delegations/issued");
6939
+ return issued.queryAllIssued((page) => this.query(page), options2);
6940
+ }
6941
+ /**
6942
+ * Revokes a delegation this session's key issued, with no proof: a UCAN
6943
+ * signed by the session's own did:key carrying `urn:cid:<cid>` and
6944
+ * `tinycloud.delegation/revoke`. Revoking a delegation that is already revoked
6945
+ * succeeds. A refusal (the key is not the delegator) carries the node's
6946
+ * message; 503 means the chain is too deep right now, retry later.
6947
+ * The implementation loads on first use.
6948
+ */
6949
+ async revokeIssued(cid2) {
6950
+ const issued = await import("@tinycloud/sdk-core/delegations/issued");
6951
+ return issued.revokeIssued(this.issuedContext(), cid2);
6952
+ }
6899
6953
  /**
6900
6954
  * Gets the full delegation chain for a given delegation.
6901
6955
  *
@@ -7202,7 +7256,8 @@ function validateEncodedShareData(data) {
7202
7256
  // src/manifest.ts
7203
7257
  import ms from "ms";
7204
7258
  import { ACCOUNT_MANIFEST_PERMISSIONS } from "@tinycloud/bootstrap";
7205
- import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
7259
+ import { resolveSecretPath, SECRET_NAME_RE, parseNetworkId } from "@tinycloud/sdk-services";
7260
+ import { parseEncryptionResource as parseEncryptionResource2 } from "@tinycloud/sdk-services/encryption/resource";
7206
7261
  var DEFAULT_KNOWLEDGE_ROOT = "knowledge/index.md";
7207
7262
  var ManifestValidationError = class extends Error {
7208
7263
  constructor(message) {
@@ -7325,6 +7380,16 @@ function expandEncryptionPermissionEntry(entry) {
7325
7380
  `tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
7326
7381
  );
7327
7382
  }
7383
+ const sharePrefix = entry.path.endsWith("/share/");
7384
+ if (entry.path.includes("/") && !sharePrefix) {
7385
+ throw new ManifestValidationError("named keys go in manifest.keys");
7386
+ }
7387
+ try {
7388
+ const network = parseNetworkId(sharePrefix ? entry.path.slice(0, -"/share/".length) : entry.path);
7389
+ if (sharePrefix && network.name !== "default") throw new Error("share prefix requires default network");
7390
+ } catch {
7391
+ throw new ManifestValidationError("tinycloud.encryption entries require a valid networkId URN");
7392
+ }
7328
7393
  const normalizedActions = [];
7329
7394
  for (const action of entry.actions) {
7330
7395
  if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
@@ -7356,6 +7421,9 @@ function expandEncryptionPermissionEntry(entry) {
7356
7421
  seen.add(a);
7357
7422
  }
7358
7423
  }
7424
+ if (sharePrefix && dedupedActions.some((action) => action !== "tinycloud.encryption/decrypt")) {
7425
+ throw new ManifestValidationError("share prefix only supports decrypt");
7426
+ }
7359
7427
  return [
7360
7428
  {
7361
7429
  service: ENCRYPTION_PERMISSION_SERVICE,
@@ -7371,6 +7439,55 @@ function expandEncryptionPermissionEntry(entry) {
7371
7439
  function expandPermissionEntries(entries) {
7372
7440
  return entries.flatMap(expandPermissionEntry);
7373
7441
  }
7442
+ function expandRuntimePermissionEntries(entries, ownerDid) {
7443
+ return entries.flatMap((entry) => {
7444
+ if (entry.service !== ENCRYPTION_PERMISSION_SERVICE || typeof entry.path !== "string" || !entry.path.includes("/") || entry.path.endsWith("/share/")) {
7445
+ return expandPermissionEntry(entry);
7446
+ }
7447
+ let resource;
7448
+ let ownerNetwork;
7449
+ try {
7450
+ resource = parseEncryptionResource2(entry.path);
7451
+ if (typeof ownerDid !== "string" || ownerDid.length === 0) throw new Error("missing owner");
7452
+ ownerNetwork = parseEncryptionResource2(`urn:tinycloud:encryption:${ownerDid}:default`);
7453
+ } catch {
7454
+ throw new ManifestValidationError("runtime Share key creation requires a canonical current-owner key resource");
7455
+ }
7456
+ if (resource.kind !== "key" || resource.networkId !== ownerNetwork.networkId || !/^share\/[^/]+$/.test(resource.path) || entry.space !== void 0 && entry.space !== ENCRYPTION_MANIFEST_SPACE || !Array.isArray(entry.actions) || entry.actions.length === 0 || entry.actions.some((action) => action !== "key.create" && action !== "tinycloud.encryption/key.create")) {
7457
+ throw new ManifestValidationError("runtime Share key creation requires the current owner's exact default/share/<id> key and key.create only");
7458
+ }
7459
+ return [{
7460
+ ...entry,
7461
+ space: ENCRYPTION_MANIFEST_SPACE,
7462
+ path: `${resource.networkId}/${resource.path}`,
7463
+ actions: ["tinycloud.encryption/key.create"],
7464
+ skipPrefix: true
7465
+ }];
7466
+ });
7467
+ }
7468
+ function expandDelegationPermissionEntries(entries) {
7469
+ return entries.flatMap((entry) => {
7470
+ if (entry.service !== ENCRYPTION_PERMISSION_SERVICE || typeof entry.path !== "string" || !entry.path.includes("/")) {
7471
+ return expandPermissionEntry(entry);
7472
+ }
7473
+ let resource;
7474
+ try {
7475
+ resource = parseEncryptionResource2(entry.path);
7476
+ } catch {
7477
+ throw new ManifestValidationError("delegation requires a canonical exact named-key resource");
7478
+ }
7479
+ if (resource.kind !== "key" || !Array.isArray(entry.actions) || entry.actions.length === 0 || entry.actions.some((action) => action !== "decrypt" && action !== "tinycloud.encryption/decrypt")) {
7480
+ throw new ManifestValidationError("named-key delegation requires an exact key and decrypt only");
7481
+ }
7482
+ return [{
7483
+ ...entry,
7484
+ space: ENCRYPTION_MANIFEST_SPACE,
7485
+ path: `${resource.networkId}/${resource.path}`,
7486
+ actions: ["tinycloud.encryption/decrypt"],
7487
+ skipPrefix: true
7488
+ }];
7489
+ });
7490
+ }
7374
7491
  function applyPrefix(prefix, path, skipPrefix) {
7375
7492
  if (skipPrefix) {
7376
7493
  return path;
@@ -7449,8 +7566,56 @@ function validateManifest(input) {
7449
7566
  if (m.secrets !== void 0) {
7450
7567
  validateManifestSecrets(m.secrets);
7451
7568
  }
7569
+ if (Object.prototype.hasOwnProperty.call(m, "keys_app_id") && (typeof m.keys_app_id !== "string" || !KEY_APP_ID.test(m.keys_app_id))) {
7570
+ throw new ManifestValidationError("manifest.keys_app_id is invalid");
7571
+ }
7572
+ validateManifestKeys(m.keys_app_id ?? m.app_id, m.keys);
7452
7573
  return m;
7453
7574
  }
7575
+ var KEY_SEGMENT = "[a-z0-9][a-z0-9._-]{0,63}";
7576
+ var KEY_APP_ID = new RegExp(`^${KEY_SEGMENT}$`);
7577
+ var KEY_PATH = new RegExp(`^${KEY_SEGMENT}(?:/${KEY_SEGMENT}){2,7}$`);
7578
+ var KEY_ABILITIES = ["key.create", "decrypt", "key.block", "key.unblock"];
7579
+ function validateManifestKeys(appId, keys) {
7580
+ if (keys === void 0) return;
7581
+ if (!Array.isArray(keys) || keys.length > 32) {
7582
+ throw new ManifestValidationError("manifest.keys must contain at most 32 entries");
7583
+ }
7584
+ if (keys.length === 0) return;
7585
+ if (!KEY_APP_ID.test(appId)) {
7586
+ throw new ManifestValidationError("manifest.keys requires a valid app_id");
7587
+ }
7588
+ const paths = /* @__PURE__ */ new Set();
7589
+ let prefixSeen = false;
7590
+ for (const [index, value] of keys.entries()) {
7591
+ const label = `manifest.keys[${index}]`;
7592
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
7593
+ throw new ManifestValidationError(`${label} must be an object`);
7594
+ }
7595
+ const entry = value;
7596
+ const fields = Object.keys(entry);
7597
+ const hasPath = Object.prototype.hasOwnProperty.call(entry, "path");
7598
+ const hasPrefix = Object.prototype.hasOwnProperty.call(entry, "prefix");
7599
+ if (hasPath === hasPrefix || fields.some((field) => !["path", "prefix", "abilities"].includes(field))) {
7600
+ throw new ManifestValidationError(`${label} must have one path or prefix and abilities`);
7601
+ }
7602
+ if (!Array.isArray(entry.abilities) || entry.abilities.length === 0 || new Set(entry.abilities).size !== entry.abilities.length || entry.abilities.some((ability) => typeof ability !== "string" || !KEY_ABILITIES.includes(ability))) {
7603
+ throw new ManifestValidationError(`${label}.abilities is invalid`);
7604
+ }
7605
+ if (hasPath) {
7606
+ const path = entry.path;
7607
+ if (typeof path !== "string" || path.length > 255 || !KEY_PATH.test(path) || !path.startsWith(`apps/${appId}/`) || paths.has(path)) {
7608
+ throw new ManifestValidationError(`${label}.path is invalid`);
7609
+ }
7610
+ paths.add(path);
7611
+ } else {
7612
+ if (entry.prefix !== `apps/${appId}/` || prefixSeen) {
7613
+ throw new ManifestValidationError(`${label}.prefix is invalid`);
7614
+ }
7615
+ prefixSeen = true;
7616
+ }
7617
+ }
7618
+ }
7454
7619
  function resolveManifestKnowledgeRoot(knowledge) {
7455
7620
  if (knowledge === void 0) {
7456
7621
  return void 0;
@@ -7968,6 +8133,7 @@ function manifestAbilitiesUnion(resolved) {
7968
8133
  }
7969
8134
 
7970
8135
  // src/capabilities.ts
8136
+ import { encryptionResourceContains } from "@tinycloud/sdk-services/encryption/resource";
7971
8137
  var PermissionNotInManifestError = class extends Error {
7972
8138
  constructor(missing, granted) {
7973
8139
  super(
@@ -8019,10 +8185,9 @@ function isCapabilitySubset(requested, granted) {
8019
8185
  const grantedActions = matchingScope.flatMap(
8020
8186
  (entry) => expandActionShortNames(entry.service, entry.actions)
8021
8187
  );
8188
+ const namedKeyRequest = req.service === "tinycloud.encryption" && req.path.startsWith("urn:tinycloud:encryption:") && req.path.includes("/");
8022
8189
  const covered = requestedActions.every(
8023
- (requestedAction) => grantedActions.some(
8024
- (grantedAction) => actionContains(grantedAction, requestedAction)
8025
- )
8190
+ (requestedAction) => grantedActions.some((grantedAction) => namedKeyRequest ? grantedAction === requestedAction : actionContains(grantedAction, requestedAction))
8026
8191
  );
8027
8192
  if (!covered) {
8028
8193
  missing.push(cloneEntry(req));
@@ -8037,7 +8202,9 @@ function canonicalizeEntryScopeMatches(requested, granted) {
8037
8202
  if (normalizeSpace(requested.space ?? DEFAULT_MANIFEST_SPACE) !== normalizeSpace(granted.space ?? DEFAULT_MANIFEST_SPACE)) {
8038
8203
  return false;
8039
8204
  }
8040
- if (!pathContains2(granted.path, requested.path)) {
8205
+ const namedKeyRequest = requested.service === "tinycloud.encryption" && requested.path.startsWith("urn:tinycloud:encryption:") && requested.path.includes("/");
8206
+ const coveredPath = namedKeyRequest ? encryptionResourceContains(granted.path, requested.path) : pathContains2(granted.path, requested.path);
8207
+ if (!coveredPath) {
8041
8208
  return false;
8042
8209
  }
8043
8210
  if (!recapCaveatsEqual(requested.caveats, granted.caveats)) {
@@ -12242,15 +12409,15 @@ function markDelegationTerminal(token, key) {
12242
12409
  // src/networkId.ts
12243
12410
  import {
12244
12411
  buildNetworkId,
12245
- parseNetworkId
12412
+ parseNetworkId as parseNetworkId2
12246
12413
  } from "@tinycloud/sdk-services";
12247
12414
  function canonicalizeNetworkId(networkId) {
12248
- const parsed = parseNetworkId(networkId);
12415
+ const parsed = parseNetworkId2(networkId);
12249
12416
  return buildNetworkId(canonicalizeDid(parsed.ownerDid), parsed.name);
12250
12417
  }
12251
12418
  function parseCanonicalNetworkId(networkId) {
12252
12419
  const canonical = canonicalizeNetworkId(networkId);
12253
- return parseNetworkId(canonical);
12420
+ return parseNetworkId2(canonical);
12254
12421
  }
12255
12422
 
12256
12423
  // src/storage.schema.ts
@@ -15063,7 +15230,7 @@ import {
15063
15230
  resolveSecretPath as resolveSecretPath2,
15064
15231
  EncryptionService,
15065
15232
  DecryptTransportResponseError,
15066
- parseNetworkId as parseNetworkId2,
15233
+ parseNetworkId as parseNetworkId3,
15067
15234
  buildNetworkId as buildNetworkId2,
15068
15235
  isNetworkId,
15069
15236
  networkDiscoveryKey,
@@ -16117,8 +16284,14 @@ function diffImmutableSiweFields(original, signed, options2) {
16117
16284
  function extractRecapAttenuations(siwe) {
16118
16285
  const merged = {};
16119
16286
  const lines = siwe.split(/\r?\n/);
16287
+ let resources = false;
16120
16288
  for (const line of lines) {
16121
- const match = line.match(/urn:recap:([A-Za-z0-9_-]+=*)/);
16289
+ if (line === "Resources:") {
16290
+ resources = true;
16291
+ continue;
16292
+ }
16293
+ if (!resources) continue;
16294
+ const match = line.match(/^- urn:recap:([A-Za-z0-9_-]+=*)$/);
16122
16295
  if (!match) continue;
16123
16296
  const encoded = match[1];
16124
16297
  let decodedJson;
@@ -19833,8 +20006,10 @@ export {
19833
20006
  ensureNetworkUsableForDecrypt,
19834
20007
  err5 as err,
19835
20008
  expandActionShortNames,
20009
+ expandDelegationPermissionEntries,
19836
20010
  expandPermissionEntries,
19837
20011
  expandPermissionEntry,
20012
+ expandRuntimePermissionEntries,
19838
20013
  extractImmutableSiweFields,
19839
20014
  extractRecapAttenuations,
19840
20015
  fetchLocationRecord,
@@ -19888,7 +20063,7 @@ export {
19888
20063
  parseCapabilityResource,
19889
20064
  parseCompactUcanAuthorization,
19890
20065
  parseExpiry,
19891
- parseNetworkId2 as parseNetworkId,
20066
+ parseNetworkId3 as parseNetworkId,
19892
20067
  parseNodeNativePortableDelegation,
19893
20068
  parsePkhDid,
19894
20069
  parsePolicySessionUcan,