@calimero-network/mero-js 22.0.0 → 22.2.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.mjs CHANGED
@@ -5256,6 +5256,15 @@ function base642(bytes) {
5256
5256
 
5257
5257
  // src/cloud/cloud-client.ts
5258
5258
  var DEFAULT_CLOUD_BASE_URL = "https://cloud.calimero.network";
5259
+ function canExecuteOf(row, legacy) {
5260
+ return typeof row.can_execute === "boolean" ? row.can_execute : legacy;
5261
+ }
5262
+ function teeRoleOf(row) {
5263
+ return typeof row.tee_role === "string" && row.tee_role !== "" ? row.tee_role : null;
5264
+ }
5265
+ function pickExecutingRelay(relays) {
5266
+ return relays.find((r) => r.canExecute && r.relayUrl !== null && r.executorAccount !== null) ?? null;
5267
+ }
5259
5268
  var REFRESH_HEADER = "x-mdma-session-refresh";
5260
5269
  var REFRESH_EXPIRES_HEADER = "x-mdma-session-expires";
5261
5270
  var CloudClient = class {
@@ -5404,37 +5413,48 @@ var CloudClient = class {
5404
5413
  * The relays serving one namespace: where to present an intent, and as whom.
5405
5414
  *
5406
5415
  * Everything a client needs in order to mint a warrant it can actually
5407
- * spend. Filter on `authorshipReady` before signing — a warrant presented to
5408
- * a relay with no grant is refused *after* the author has burned a nonce from
5409
- * its monotonic sequence on it.
5416
+ * spend. Filter on `canExecute` before signing — a warrant presented to a
5417
+ * relay that does not relay (a `ReadOnlyTee` replica, or a node with no role
5418
+ * and no grant) is refused *after* the author has burned a nonce from its
5419
+ * monotonic sequence on it.
5410
5420
  */
5411
5421
  async getNamespaceRelays(namespaceId) {
5412
5422
  const body = await this.request(
5413
5423
  "GET",
5414
5424
  `/api/cloud/me/namespaces/${encodeURIComponent(namespaceId)}/relays`
5415
5425
  );
5416
- return (body.relays ?? []).map((row) => ({
5417
- peerId: String(row.peer_id ?? ""),
5418
- relayUrl: row.relay_url ?? null,
5419
- executorAccount: row.executor_account ?? null,
5420
- status: String(row.status ?? ""),
5421
- authorshipReady: row.authorship_ready === true,
5422
- lastSeenAt: row.last_seen_at ?? null,
5423
- confirmedAt: row.confirmed_at ?? null
5424
- }));
5426
+ return (body.relays ?? []).map((row) => {
5427
+ const relayUrl = row.relay_url ?? null;
5428
+ const executorAccount = row.executor_account ?? null;
5429
+ const authorshipReady = row.authorship_ready === true;
5430
+ return {
5431
+ peerId: String(row.peer_id ?? ""),
5432
+ relayUrl,
5433
+ executorAccount,
5434
+ status: String(row.status ?? ""),
5435
+ authorshipReady,
5436
+ teeRole: teeRoleOf(row),
5437
+ canExecute: canExecuteOf(
5438
+ row,
5439
+ authorshipReady && relayUrl !== null && executorAccount !== null
5440
+ ),
5441
+ lastSeenAt: row.last_seen_at ?? null,
5442
+ confirmedAt: row.confirmed_at ?? null
5443
+ };
5444
+ });
5425
5445
  }
5426
5446
  /**
5427
5447
  * The first relay for `namespaceId` that can actually run an intent, or
5428
5448
  * `null`.
5429
5449
  *
5430
- * "Can actually run one" is three conditions, and a client that checks fewer
5431
- * gets a refusal it cannot explain: the cloud must know a URL, the relay must
5432
- * have reported the executor account a warrant has to name, and the grant
5433
- * must be in place.
5450
+ * "Can actually run one" is several conditions, and a client that checks
5451
+ * fewer gets a refusal it cannot explain: the cloud must know a URL, the
5452
+ * relay must have reported the executor account a warrant has to name, and
5453
+ * the node must relay — which the cloud folds into `canExecute`.
5434
5454
  */
5435
5455
  async findExecutingRelay(namespaceId) {
5436
5456
  const relays = await this.getNamespaceRelays(namespaceId);
5437
- return relays.find((r) => r.authorshipReady && r.relayUrl !== null && r.executorAccount !== null) ?? null;
5457
+ return pickExecutingRelay(relays);
5438
5458
  }
5439
5459
  /**
5440
5460
  * Where to reach a namespace without owning it: one read, both verbs.
@@ -5462,17 +5482,27 @@ var CloudClient = class {
5462
5482
  });
5463
5483
  return {
5464
5484
  namespaceId: String(body.namespace_id ?? namespaceId),
5465
- nodes: (body.admitters ?? []).map((row) => ({
5466
- peerId: String(row.peer_id ?? ""),
5467
- account: row.account ?? null,
5468
- relayUrl: row.relay_url ?? null,
5469
- admitUrl: row.admit_url ?? null,
5470
- status: String(row.status ?? ""),
5471
- fresh: row.fresh === true,
5472
- canAdmit: row.can_admit === true,
5473
- authorshipReady: row.authorship_ready === true,
5474
- canExecute: row.can_execute === true
5475
- })),
5485
+ nodes: (body.admitters ?? []).map((row) => {
5486
+ const account = row.account ?? null;
5487
+ const relayUrl = row.relay_url ?? null;
5488
+ const fresh = row.fresh === true;
5489
+ const authorshipReady = row.authorship_ready === true;
5490
+ return {
5491
+ peerId: String(row.peer_id ?? ""),
5492
+ account,
5493
+ relayUrl,
5494
+ admitUrl: row.admit_url ?? null,
5495
+ status: String(row.status ?? ""),
5496
+ fresh,
5497
+ canAdmit: row.can_admit === true,
5498
+ authorshipReady,
5499
+ teeRole: teeRoleOf(row),
5500
+ canExecute: canExecuteOf(
5501
+ row,
5502
+ authorshipReady && relayUrl !== null && account !== null && fresh
5503
+ )
5504
+ };
5505
+ }),
5476
5506
  servable: body.servable === true,
5477
5507
  writable: body.writable === true
5478
5508
  };
@@ -5603,23 +5633,32 @@ var CloudClient = class {
5603
5633
  "GET",
5604
5634
  "/api/cloud/me/machines"
5605
5635
  );
5606
- return (body.machines ?? []).map((row) => ({
5607
- peerId: String(row.peer_id ?? ""),
5608
- relayUrl: row.relay_url ?? null,
5609
- executorAccount: row.executor_account ?? null,
5610
- canExecute: row.can_execute === true,
5611
- namespaces: (Array.isArray(row.namespaces) ? row.namespaces : []).map((n) => {
5636
+ return (body.machines ?? []).map((row) => {
5637
+ const relayUrl = row.relay_url ?? null;
5638
+ const executorAccount = row.executor_account ?? null;
5639
+ const namespaces = (Array.isArray(row.namespaces) ? row.namespaces : []).map((n) => {
5612
5640
  const ns = n;
5613
5641
  return {
5614
5642
  namespaceId: String(ns.namespace_id ?? ""),
5615
5643
  status: String(ns.status ?? ""),
5616
5644
  authorshipReady: ns.authorship_ready === true,
5645
+ teeRole: teeRoleOf(ns),
5617
5646
  fresh: ns.fresh === true,
5618
5647
  confirmedAt: ns.confirmed_at ?? null,
5619
5648
  lastSeenAt: ns.last_seen_at ?? null
5620
5649
  };
5621
- })
5622
- }));
5650
+ });
5651
+ return {
5652
+ peerId: String(row.peer_id ?? ""),
5653
+ relayUrl,
5654
+ executorAccount,
5655
+ canExecute: canExecuteOf(
5656
+ row,
5657
+ relayUrl !== null && executorAccount !== null && namespaces.some((n) => n.authorshipReady)
5658
+ ),
5659
+ namespaces
5660
+ };
5661
+ });
5623
5662
  }
5624
5663
  /**
5625
5664
  * Claim a namespace under this account, proving ownership.
@@ -7159,12 +7198,12 @@ async function connectCloud(options) {
7159
7198
  const namespaces = await cloud.getMyNamespaces();
7160
7199
  const namespace = pickNamespace(namespaces, options.namespaceId);
7161
7200
  const relays = await cloud.getNamespaceRelays(namespace.namespaceId);
7162
- const relayInfo = relays.find((r) => r.authorshipReady && r.relayUrl !== null && r.executorAccount !== null) ?? null;
7201
+ const relayInfo = pickExecutingRelay(relays);
7163
7202
  if (!relayInfo) {
7164
7203
  throw new Error(explainNoRelay(namespace, relays));
7165
7204
  }
7166
7205
  const relay = new RelayClient({
7167
- // Checked by `findExecutingRelay`, which is the only thing that returns a
7206
+ // Checked by `pickExecutingRelay`, which is the only thing that returns a
7168
7207
  // relay — narrowed here rather than re-validated, so the invariant lives in
7169
7208
  // one place.
7170
7209
  relayUrl: relayInfo.relayUrl,
@@ -7218,11 +7257,15 @@ function explainNoRelay(namespace, relays) {
7218
7257
  if (relays.length === 0) {
7219
7258
  return namespace.haStatus === "enabled" ? `namespace ${namespace.namespaceId} has HA enabled but no relay has been assigned to it yet \u2014 retry shortly` : `namespace ${namespace.namespaceId} has no relays: enable HA on it in the cloud to have an attested node assigned`;
7220
7259
  }
7221
- const waitingOnGrant = relays.filter((r) => !r.authorshipReady);
7222
- if (waitingOnGrant.length === relays.length) {
7260
+ const replicas = relays.filter((r) => r.teeRole !== null && r.teeRole !== "RelayTee");
7261
+ if (replicas.length === relays.length) {
7262
+ return `every node on namespace ${namespace.namespaceId} is a read-only TEE replica (ReadOnlyTee), which never relays delegated writes: an admin of the namespace must set its TEE admission policy with mode: 'relay' \u2014 AdminClient.setTeeAdmissionPolicy(namespaceId, { ..., mode: 'relay' }) \u2014 so fleet nodes are admitted as RelayTee`;
7263
+ }
7264
+ const waitingOnGrant = relays.filter((r) => r.teeRole === null && !r.authorshipReady);
7265
+ if (waitingOnGrant.length > 0 && waitingOnGrant.length + replicas.length === relays.length) {
7223
7266
  return `every relay on namespace ${namespace.namespaceId} is waiting for the authorship grant: an admin of the namespace must grant CAN_AUTHOR_ON_BEHALF to ${waitingOnGrant.map((r) => r.executorAccount ?? r.peerId).join(", ") || "the relay account"} \u2014 with AdminClient.grantAuthorship(namespaceId, account) for these relays, and AdminClient.openToDelegatedExecution(namespaceId) so relays assigned later need no further op`;
7224
7267
  }
7225
- return `namespace ${namespace.namespaceId} has ${relays.length} relay(s), but none reported both a URL and an executor account yet \u2014 retry shortly`;
7268
+ return `namespace ${namespace.namespaceId} has ${relays.length} relay(s), but none is ready to relay yet (a URL, an executor account and a recent heartbeat) \u2014 retry shortly`;
7226
7269
  }
7227
7270
  async function defaultNonceSource(deviceSecret) {
7228
7271
  const key = `mero-js:warrant-nonce:${hex(await derivePublicKey(deviceSecret))}`;
@@ -7894,7 +7937,7 @@ function splitVersion2(version) {
7894
7937
  }
7895
7938
 
7896
7939
  // src/namespace-op/namespace-op.ts
7897
- var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 11;
7940
+ var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 12;
7898
7941
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
7899
7942
  var NAMESPACE_OP_ROOT = 0;
7900
7943
  var ROOT_OP = {