wire-mesh-core 3.4.0 → 3.5.1

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.
Files changed (31) hide show
  1. package/dist/adapters/threshold-identity.cjs +2 -2
  2. package/dist/adapters/threshold-identity.mjs +2 -2
  3. package/dist/domain/capability-grant.cjs +1 -1
  4. package/dist/domain/capability-grant.mjs +1 -1
  5. package/dist/domain/capability-request.cjs +1 -1
  6. package/dist/domain/capability-request.mjs +1 -1
  7. package/dist/domain/coordinator-election.cjs +81 -0
  8. package/dist/domain/coordinator-election.d.cts +44 -0
  9. package/dist/domain/coordinator-election.d.mts +44 -0
  10. package/dist/domain/coordinator-election.mjs +79 -0
  11. package/dist/domain/mesh-session.cjs +20 -4
  12. package/dist/domain/mesh-session.d.cts +5 -1
  13. package/dist/domain/mesh-session.d.mts +5 -1
  14. package/dist/domain/mesh-session.mjs +20 -4
  15. package/dist/domain/relay-advert.cjs +30 -0
  16. package/dist/domain/relay-advert.d.cts +13 -0
  17. package/dist/domain/relay-advert.d.mts +13 -0
  18. package/dist/domain/relay-advert.mjs +27 -0
  19. package/dist/domain/revocation-view.cjs +1 -1
  20. package/dist/domain/revocation-view.mjs +1 -1
  21. package/dist/domain/room-token-verification.cjs +1 -1
  22. package/dist/domain/room-token-verification.mjs +1 -1
  23. package/dist/domain/threshold-subject.cjs +1 -1
  24. package/dist/domain/threshold-subject.mjs +1 -1
  25. package/dist/domain/tokens.cjs +1 -1
  26. package/dist/domain/tokens.mjs +1 -1
  27. package/dist/domain/webrtc-signaling.cjs +1 -1
  28. package/dist/domain/webrtc-signaling.mjs +1 -1
  29. package/package.json +9 -1
  30. package/dist/{tokens-B0dpTLhS.mjs → tokens-CMdWiRBb.mjs} +1 -1
  31. package/dist/{tokens-Dh6mG6p_.cjs → tokens-DSG3SDr8.cjs} +1 -1
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_domain_threshold_subject = require("../domain/threshold-subject.cjs");
3
- const require_adapters_threshold_wasm = require("./threshold-wasm.cjs");
4
2
  const require_adapters_node_identity = require("./node-identity.cjs");
3
+ const require_adapters_threshold_wasm = require("./threshold-wasm.cjs");
4
+ const require_domain_threshold_subject = require("../domain/threshold-subject.cjs");
5
5
  //#region src/adapters/threshold-identity.ts
6
6
  const ALG_ED25519 = -8;
7
7
  const FIRST_SESSION_ID = 1n;
@@ -1,6 +1,6 @@
1
- import { toBeSigned } from "../domain/threshold-subject.mjs";
2
- import { signingAggregate, signingBuildPackage } from "./threshold-wasm.mjs";
3
1
  import { deriveDeviceId, verifyWithPublicKey } from "./node-identity.mjs";
2
+ import { signingAggregate, signingBuildPackage } from "./threshold-wasm.mjs";
3
+ import { toBeSigned } from "../domain/threshold-subject.mjs";
4
4
  //#region src/adapters/threshold-identity.ts
5
5
  const ALG_ED25519 = -8;
6
6
  const FIRST_SESSION_ID = 1n;
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_generated_protocol = require("../generated/protocol.cjs");
3
3
  const require_token_scope = require("../token-scope-CxHXTT3u.cjs");
4
- const require_tokens = require("../tokens-Dh6mG6p_.cjs");
4
+ const require_tokens = require("../tokens-DSG3SDr8.cjs");
5
5
  //#region src/domain/capability-grant.ts
6
6
  /**
7
7
  * The generic, capability-agnostic capability-grant primitive (wire-mesh#117, spec/management.cddl) -- the unsolicited push counterpart to capability-request.ts's own ask/response primitive. Where capability-request lifts core/room's room.join shape (a pull: the requester asks, the owner mints and returns a grant on the same response) out of that one domain, capability-grant lifts room.invite's own shape (a push: the owner mints unprompted and delivers the grant in the request itself, since there is no approval round-trip to carry it back) the same way. core/room's own room-client.ts is the reference consumer: it builds sendRoomInvite as a thin wrapper over sendCapabilityGrant below, and wires createRoomRouter's dispatch onto createCapabilityGrantHandler for the receiving side.
@@ -1,6 +1,6 @@
1
1
  import { capabilityGrantSchema } from "../generated/protocol.mjs";
2
2
  import { n as scopeNarrows } from "../token-scope-Z4bmci4M.mjs";
3
- import { a as verifyCapabilityToken } from "../tokens-B0dpTLhS.mjs";
3
+ import { a as verifyCapabilityToken } from "../tokens-CMdWiRBb.mjs";
4
4
  //#region src/domain/capability-grant.ts
5
5
  /**
6
6
  * The generic, capability-agnostic capability-grant primitive (wire-mesh#117, spec/management.cddl) -- the unsolicited push counterpart to capability-request.ts's own ask/response primitive. Where capability-request lifts core/room's room.join shape (a pull: the requester asks, the owner mints and returns a grant on the same response) out of that one domain, capability-grant lifts room.invite's own shape (a push: the owner mints unprompted and delivers the grant in the request itself, since there is no approval round-trip to carry it back) the same way. core/room's own room-client.ts is the reference consumer: it builds sendRoomInvite as a thin wrapper over sendCapabilityGrant below, and wires createRoomRouter's dispatch onto createCapabilityGrantHandler for the receiving side.
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_generated_protocol = require("../generated/protocol.cjs");
3
- const require_tokens = require("../tokens-Dh6mG6p_.cjs");
3
+ const require_tokens = require("../tokens-DSG3SDr8.cjs");
4
4
  //#region src/domain/capability-request.ts
5
5
  /**
6
6
  * The generic, capability-agnostic capability-request / capability-grant-ok primitive (wire-mesh#78, spec/management.cddl). Lifts core/room's own room.join shape -- an ungated ask, held open, a human (or any other domain-supplied decision) accepts or refuses, acceptance mints and returns a token on the same response -- out of that one domain so any capability can reuse it, not just room:member. core/room's own room-client.ts is the reference consumer: it rewires requestToJoin/createRoomRouter's room.join handling onto requestCapability/createCapabilityRequestHandler below, supplying room-specific extension fields (its member list) through this module's own extensions mechanism rather than this module knowing anything about rooms.
@@ -1,5 +1,5 @@
1
1
  import { capabilityGrantOkSchema, capabilityRequestSchema } from "../generated/protocol.mjs";
2
- import { n as mintCapabilityToken } from "../tokens-B0dpTLhS.mjs";
2
+ import { n as mintCapabilityToken } from "../tokens-CMdWiRBb.mjs";
3
3
  //#region src/domain/capability-request.ts
4
4
  /**
5
5
  * The generic, capability-agnostic capability-request / capability-grant-ok primitive (wire-mesh#78, spec/management.cddl). Lifts core/room's own room.join shape -- an ungated ask, held open, a human (or any other domain-supplied decision) accepts or refuses, acceptance mints and returns a token on the same response -- out of that one domain so any capability can reuse it, not just room:member. core/room's own room-client.ts is the reference consumer: it rewires requestToJoin/createRoomRouter's room.join handling onto requestCapability/createCapabilityRequestHandler below, supplying room-specific extension fields (its member list) through this module's own extensions mechanism rather than this module knowing anything about rooms.
@@ -0,0 +1,81 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/domain/coordinator-election.ts
3
+ /** Bytewise "lowest device-id" comparison, the equal-term tiebreak the spec names: shorter is lower when one id is a prefix of the other, and equal ids compare equal. Device-ids are a fixed 32 bytes today, so the prefix branch is completeness rather than a live case. */
4
+ function compareDeviceIds(a, b) {
5
+ const length = Math.min(a.length, b.length);
6
+ for (let i = 0; i < length; i++) {
7
+ const left = a[i];
8
+ const right = b[i];
9
+ if (left === void 0 || right === void 0) break;
10
+ if (left !== right) return left < right ? -1 : 1;
11
+ }
12
+ return a.length - b.length;
13
+ }
14
+ var CoordinatorElection = class {
15
+ ownDevice;
16
+ incumbent;
17
+ constructor(options) {
18
+ this.ownDevice = options.ownDevice;
19
+ }
20
+ /** The claim this side currently accepts, or undefined before any claim has been made or heard. */
21
+ current() {
22
+ return this.incumbent;
23
+ }
24
+ /** Whether this side's own device is the incumbent. */
25
+ isSelf() {
26
+ return this.incumbent !== void 0 && compareDeviceIds(this.incumbent.coordinator, this.ownDevice) === 0;
27
+ }
28
+ /**
29
+ * Claims the role for this side's own device at a term above every term seen so far (lastTerm + 1, so a first-ever claim is term 0), and returns the frame to gossip. Claiming over a claim this side already holds is a deliberate takeover: it raises the term, which is exactly what a peer recovering the role after losing track of the mesh should do, and what a routine refresh should not (announceCurrent exists for that).
30
+ */
31
+ claim(capacityHint) {
32
+ const frame = {
33
+ type: "coordinator",
34
+ term: (this.incumbent?.term ?? -1) + 1,
35
+ coordinator: this.ownDevice,
36
+ ...capacityHint !== void 0 ? { "capacity-hint": capacityHint } : {}
37
+ };
38
+ this.incumbent = {
39
+ term: frame.term,
40
+ coordinator: this.ownDevice,
41
+ ...capacityHint !== void 0 ? { capacityHint } : {}
42
+ };
43
+ return frame;
44
+ }
45
+ /**
46
+ * The incumbent claim as a frame to re-gossip, for a holder refreshing what late joiners see (and for answering a stale claim, which costs the incumbent nothing to squash): the term is unchanged, because a refresh is not a takeover. Undefined when no claim has been made or heard yet.
47
+ */
48
+ announceCurrent() {
49
+ if (this.incumbent === void 0) return void 0;
50
+ return {
51
+ type: "coordinator",
52
+ term: this.incumbent.term,
53
+ coordinator: this.incumbent.coordinator,
54
+ ...this.incumbent.capacityHint !== void 0 ? { "capacity-hint": this.incumbent.capacityHint } : {}
55
+ };
56
+ }
57
+ /**
58
+ * Evaluates an incoming claim against the one this side currently accepts: a strictly higher term always wins; an equal term breaks by lowest device-id, so an incoming claim naming a lower device-id than the incumbent's takes the role and one naming a higher device-id loses; a lower term never wins. "accepted" means the incumbent changed (the caller should gossip the new incumbent onward so the supersession propagates); "retained" means it did not (the caller may answer a stale claim by re-gossiping announceCurrent(), and should drop a lost equal-term claim it originated, since the tiebreak has settled it).
59
+ */
60
+ evaluate(frame) {
61
+ const previous = this.incumbent;
62
+ if (previous === void 0 || frame.term > previous.term || frame.term === previous.term && compareDeviceIds(frame.coordinator, previous.coordinator) < 0) {
63
+ this.incumbent = {
64
+ term: frame.term,
65
+ coordinator: frame.coordinator,
66
+ ...frame["capacity-hint"] !== void 0 ? { capacityHint: frame["capacity-hint"] } : {}
67
+ };
68
+ return {
69
+ outcome: "accepted",
70
+ incumbent: this.incumbent
71
+ };
72
+ }
73
+ return {
74
+ outcome: "retained",
75
+ incumbent: previous
76
+ };
77
+ }
78
+ };
79
+ //#endregion
80
+ exports.CoordinatorElection = CoordinatorElection;
81
+ exports.compareDeviceIds = compareDeviceIds;
@@ -0,0 +1,44 @@
1
+ import { D as DeviceId, g as CoordinatorFrame } from "../protocol-DCDae3z4.cjs";
2
+ //#region src/domain/coordinator-election.d.ts
3
+ /** The claim this side currently accepts: one coordinator, at the term that put it there. */
4
+ export interface CoordinatorClaim {
5
+ term: number;
6
+ coordinator: DeviceId;
7
+ capacityHint?: number | undefined;
8
+ }
9
+ /** Bytewise "lowest device-id" comparison, the equal-term tiebreak the spec names: shorter is lower when one id is a prefix of the other, and equal ids compare equal. Device-ids are a fixed 32 bytes today, so the prefix branch is completeness rather than a live case. */
10
+ export declare function compareDeviceIds(a: DeviceId, b: DeviceId): number;
11
+ /** What evaluating an incoming claim concluded, so the caller can react without re-deriving the comparison: "accepted" means this claim is now the incumbent (it superseded a previous one, or there was none), "retained" means the incumbent survived (the incoming claim was stale, or lost the equal-term tiebreak), and the incumbent is returned either way so a caller squashing a stale claim re-announces exactly what it already holds. */
12
+ export type EvaluationOutcome = {
13
+ outcome: "accepted";
14
+ incumbent: CoordinatorClaim;
15
+ } | {
16
+ outcome: "retained";
17
+ incumbent: CoordinatorClaim;
18
+ };
19
+ export interface CoordinatorElectionOptions {
20
+ /** This device's own id: the coordinator named by a claim this side mints. */
21
+ ownDevice: DeviceId;
22
+ }
23
+ export declare class CoordinatorElection {
24
+ private readonly ownDevice;
25
+ private incumbent;
26
+ constructor(options: Readonly<CoordinatorElectionOptions>);
27
+ /** The claim this side currently accepts, or undefined before any claim has been made or heard. */
28
+ current(): CoordinatorClaim | undefined;
29
+ /** Whether this side's own device is the incumbent. */
30
+ isSelf(): boolean;
31
+ /**
32
+ * Claims the role for this side's own device at a term above every term seen so far (lastTerm + 1, so a first-ever claim is term 0), and returns the frame to gossip. Claiming over a claim this side already holds is a deliberate takeover: it raises the term, which is exactly what a peer recovering the role after losing track of the mesh should do, and what a routine refresh should not (announceCurrent exists for that).
33
+ */
34
+ claim(capacityHint?: number): CoordinatorFrame;
35
+ /**
36
+ * The incumbent claim as a frame to re-gossip, for a holder refreshing what late joiners see (and for answering a stale claim, which costs the incumbent nothing to squash): the term is unchanged, because a refresh is not a takeover. Undefined when no claim has been made or heard yet.
37
+ */
38
+ announceCurrent(): CoordinatorFrame | undefined;
39
+ /**
40
+ * Evaluates an incoming claim against the one this side currently accepts: a strictly higher term always wins; an equal term breaks by lowest device-id, so an incoming claim naming a lower device-id than the incumbent's takes the role and one naming a higher device-id loses; a lower term never wins. "accepted" means the incumbent changed (the caller should gossip the new incumbent onward so the supersession propagates); "retained" means it did not (the caller may answer a stale claim by re-gossiping announceCurrent(), and should drop a lost equal-term claim it originated, since the tiebreak has settled it).
41
+ */
42
+ evaluate(frame: Readonly<CoordinatorFrame>): EvaluationOutcome;
43
+ }
44
+ //#endregion
@@ -0,0 +1,44 @@
1
+ import { D as DeviceId, g as CoordinatorFrame } from "../protocol-DCDae3z4.mjs";
2
+ //#region src/domain/coordinator-election.d.ts
3
+ /** The claim this side currently accepts: one coordinator, at the term that put it there. */
4
+ export interface CoordinatorClaim {
5
+ term: number;
6
+ coordinator: DeviceId;
7
+ capacityHint?: number | undefined;
8
+ }
9
+ /** Bytewise "lowest device-id" comparison, the equal-term tiebreak the spec names: shorter is lower when one id is a prefix of the other, and equal ids compare equal. Device-ids are a fixed 32 bytes today, so the prefix branch is completeness rather than a live case. */
10
+ export declare function compareDeviceIds(a: DeviceId, b: DeviceId): number;
11
+ /** What evaluating an incoming claim concluded, so the caller can react without re-deriving the comparison: "accepted" means this claim is now the incumbent (it superseded a previous one, or there was none), "retained" means the incumbent survived (the incoming claim was stale, or lost the equal-term tiebreak), and the incumbent is returned either way so a caller squashing a stale claim re-announces exactly what it already holds. */
12
+ export type EvaluationOutcome = {
13
+ outcome: "accepted";
14
+ incumbent: CoordinatorClaim;
15
+ } | {
16
+ outcome: "retained";
17
+ incumbent: CoordinatorClaim;
18
+ };
19
+ export interface CoordinatorElectionOptions {
20
+ /** This device's own id: the coordinator named by a claim this side mints. */
21
+ ownDevice: DeviceId;
22
+ }
23
+ export declare class CoordinatorElection {
24
+ private readonly ownDevice;
25
+ private incumbent;
26
+ constructor(options: Readonly<CoordinatorElectionOptions>);
27
+ /** The claim this side currently accepts, or undefined before any claim has been made or heard. */
28
+ current(): CoordinatorClaim | undefined;
29
+ /** Whether this side's own device is the incumbent. */
30
+ isSelf(): boolean;
31
+ /**
32
+ * Claims the role for this side's own device at a term above every term seen so far (lastTerm + 1, so a first-ever claim is term 0), and returns the frame to gossip. Claiming over a claim this side already holds is a deliberate takeover: it raises the term, which is exactly what a peer recovering the role after losing track of the mesh should do, and what a routine refresh should not (announceCurrent exists for that).
33
+ */
34
+ claim(capacityHint?: number): CoordinatorFrame;
35
+ /**
36
+ * The incumbent claim as a frame to re-gossip, for a holder refreshing what late joiners see (and for answering a stale claim, which costs the incumbent nothing to squash): the term is unchanged, because a refresh is not a takeover. Undefined when no claim has been made or heard yet.
37
+ */
38
+ announceCurrent(): CoordinatorFrame | undefined;
39
+ /**
40
+ * Evaluates an incoming claim against the one this side currently accepts: a strictly higher term always wins; an equal term breaks by lowest device-id, so an incoming claim naming a lower device-id than the incumbent's takes the role and one naming a higher device-id loses; a lower term never wins. "accepted" means the incumbent changed (the caller should gossip the new incumbent onward so the supersession propagates); "retained" means it did not (the caller may answer a stale claim by re-gossiping announceCurrent(), and should drop a lost equal-term claim it originated, since the tiebreak has settled it).
41
+ */
42
+ evaluate(frame: Readonly<CoordinatorFrame>): EvaluationOutcome;
43
+ }
44
+ //#endregion
@@ -0,0 +1,79 @@
1
+ //#region src/domain/coordinator-election.ts
2
+ /** Bytewise "lowest device-id" comparison, the equal-term tiebreak the spec names: shorter is lower when one id is a prefix of the other, and equal ids compare equal. Device-ids are a fixed 32 bytes today, so the prefix branch is completeness rather than a live case. */
3
+ function compareDeviceIds(a, b) {
4
+ const length = Math.min(a.length, b.length);
5
+ for (let i = 0; i < length; i++) {
6
+ const left = a[i];
7
+ const right = b[i];
8
+ if (left === void 0 || right === void 0) break;
9
+ if (left !== right) return left < right ? -1 : 1;
10
+ }
11
+ return a.length - b.length;
12
+ }
13
+ var CoordinatorElection = class {
14
+ ownDevice;
15
+ incumbent;
16
+ constructor(options) {
17
+ this.ownDevice = options.ownDevice;
18
+ }
19
+ /** The claim this side currently accepts, or undefined before any claim has been made or heard. */
20
+ current() {
21
+ return this.incumbent;
22
+ }
23
+ /** Whether this side's own device is the incumbent. */
24
+ isSelf() {
25
+ return this.incumbent !== void 0 && compareDeviceIds(this.incumbent.coordinator, this.ownDevice) === 0;
26
+ }
27
+ /**
28
+ * Claims the role for this side's own device at a term above every term seen so far (lastTerm + 1, so a first-ever claim is term 0), and returns the frame to gossip. Claiming over a claim this side already holds is a deliberate takeover: it raises the term, which is exactly what a peer recovering the role after losing track of the mesh should do, and what a routine refresh should not (announceCurrent exists for that).
29
+ */
30
+ claim(capacityHint) {
31
+ const frame = {
32
+ type: "coordinator",
33
+ term: (this.incumbent?.term ?? -1) + 1,
34
+ coordinator: this.ownDevice,
35
+ ...capacityHint !== void 0 ? { "capacity-hint": capacityHint } : {}
36
+ };
37
+ this.incumbent = {
38
+ term: frame.term,
39
+ coordinator: this.ownDevice,
40
+ ...capacityHint !== void 0 ? { capacityHint } : {}
41
+ };
42
+ return frame;
43
+ }
44
+ /**
45
+ * The incumbent claim as a frame to re-gossip, for a holder refreshing what late joiners see (and for answering a stale claim, which costs the incumbent nothing to squash): the term is unchanged, because a refresh is not a takeover. Undefined when no claim has been made or heard yet.
46
+ */
47
+ announceCurrent() {
48
+ if (this.incumbent === void 0) return void 0;
49
+ return {
50
+ type: "coordinator",
51
+ term: this.incumbent.term,
52
+ coordinator: this.incumbent.coordinator,
53
+ ...this.incumbent.capacityHint !== void 0 ? { "capacity-hint": this.incumbent.capacityHint } : {}
54
+ };
55
+ }
56
+ /**
57
+ * Evaluates an incoming claim against the one this side currently accepts: a strictly higher term always wins; an equal term breaks by lowest device-id, so an incoming claim naming a lower device-id than the incumbent's takes the role and one naming a higher device-id loses; a lower term never wins. "accepted" means the incumbent changed (the caller should gossip the new incumbent onward so the supersession propagates); "retained" means it did not (the caller may answer a stale claim by re-gossiping announceCurrent(), and should drop a lost equal-term claim it originated, since the tiebreak has settled it).
58
+ */
59
+ evaluate(frame) {
60
+ const previous = this.incumbent;
61
+ if (previous === void 0 || frame.term > previous.term || frame.term === previous.term && compareDeviceIds(frame.coordinator, previous.coordinator) < 0) {
62
+ this.incumbent = {
63
+ term: frame.term,
64
+ coordinator: frame.coordinator,
65
+ ...frame["capacity-hint"] !== void 0 ? { capacityHint: frame["capacity-hint"] } : {}
66
+ };
67
+ return {
68
+ outcome: "accepted",
69
+ incumbent: this.incumbent
70
+ };
71
+ }
72
+ return {
73
+ outcome: "retained",
74
+ incumbent: previous
75
+ };
76
+ }
77
+ };
78
+ //#endregion
79
+ export { CoordinatorElection, compareDeviceIds };
@@ -1,13 +1,13 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_gossip_extensions = require("../gossip-extensions-BBYgMZ2N.cjs");
2
+ const require_adapters_frame_codec = require("../adapters/frame-codec.cjs");
3
3
  const require_token_scope = require("../token-scope-CxHXTT3u.cjs");
4
+ const require_gossip_extensions = require("../gossip-extensions-BBYgMZ2N.cjs");
4
5
  const require_domain_peer_advert = require("./peer-advert.cjs");
6
+ const require_domain_async_queue = require("./async-queue.cjs");
5
7
  const require_domain_device_id = require("./device-id.cjs");
6
8
  const require_domain_handshake = require("./handshake.cjs");
7
- const require_domain_async_queue = require("./async-queue.cjs");
8
9
  const require_domain_ping_round_trips = require("./ping-round-trips.cjs");
9
10
  const require_domain_topology_snapshot = require("./topology-snapshot.cjs");
10
- const require_adapters_frame_codec = require("../adapters/frame-codec.cjs");
11
11
  //#region src/domain/relay-pairing.ts
12
12
  function createRelayPairings() {
13
13
  const established = /* @__PURE__ */ new Map();
@@ -325,6 +325,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
325
325
  });
326
326
  const incomingQueue = require_domain_async_queue.createAsyncQueue();
327
327
  const revocationQueue = require_domain_async_queue.createAsyncQueue();
328
+ const coordinatorQueue = require_domain_async_queue.createAsyncQueue();
328
329
  const pingRoundTrips = require_domain_ping_round_trips.createPingRoundTrips();
329
330
  function snapshot() {
330
331
  return {
@@ -466,6 +467,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
466
467
  else if (frame.type === "manage-request") applyManageRequest(frame);
467
468
  else if (frame.type === "pong") pingRoundTrips.resolveOldest(clock.now());
468
469
  else if (frame.type === "revocation-announce") for (const entry of frame.entries) revocationQueue.push(entry);
470
+ else if (frame.type === "coordinator") coordinatorQueue.push(frame);
469
471
  }
470
472
  /** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). Pairing with a new target never tears down an existing pairing with a different one: this connection can hold several simultaneously (wire-mesh#30's own multiplexed adjacency map), so a later request back to an already-paired target must not re-send relay-connect for it. relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
471
473
  async function ensureRelayPairing(targetDevice) {
@@ -632,10 +634,14 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
632
634
  events: eventQueue.stream,
633
635
  incomingManageRequests: incomingQueue.stream,
634
636
  revocationAnnouncements: revocationQueue.stream,
637
+ coordinatorFrames: coordinatorQueue.stream,
635
638
  async connect(address, localDomains) {
636
639
  if (connection !== null) throw new Error("a session connects once; create a new one to reconnect");
637
640
  attempt = 0;
638
- await doConnect(address, localDomains);
641
+ await doConnect(address, localDomains).catch((error) => {
642
+ handleDisconnect(error instanceof Error ? error.message : String(error), address, localDomains);
643
+ throw error;
644
+ });
639
645
  },
640
646
  async sendPing() {
641
647
  const link = requireConnectedLink();
@@ -718,6 +724,16 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
718
724
  await transmit(frame, false);
719
725
  emit();
720
726
  },
727
+ async sendCoordinatorClaim(frame) {
728
+ requireConnectedLink();
729
+ const claim = { ...frame };
730
+ frameLog.push({
731
+ direction: "sent",
732
+ frame: claim
733
+ });
734
+ await transmit(claim, false);
735
+ emit();
736
+ },
721
737
  async sendGossipUpdate(extensions) {
722
738
  requireConnectedLink();
723
739
  const frame = await buildSelfAdvert(extensions);
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, nn as TopologyPeers, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-DCDae3z4.cjs";
1
+ import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, g as CoordinatorFrame, lt as ProtocolVersion, nn as TopologyPeers, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-DCDae3z4.cjs";
2
2
  import { r as Transport, t as Connection } from "../transport-DIXjoQKn.cjs";
3
3
  import { t as IdentityPort } from "../identity-DVCNuFLG.cjs";
4
4
  import { t as Clock } from "../clock-DiSx-WKM.cjs";
@@ -77,6 +77,8 @@ export interface MeshSession {
77
77
  readonly incomingManageRequests: AsyncIterable<IncomingManageRequest>;
78
78
  /** Every `revocation-entry` received from the peer, in arrival order -- a `revocation-announce` frame's own `entries` array is flattened to one item per entry, since each entry is independently verifiable and independently meaningful regardless of which frame carried it. A consumer typically feeds each one into a RevocationView's own `record`. */
79
79
  readonly revocationAnnouncements: AsyncIterable<RevocationEntry>;
80
+ /** Every `coordinator-frame` received from the peer, in arrival order: a gossiped, term-based claim to the rendezvous role (spec/transport.cddl), surfaced raw exactly like revocationAnnouncements because what a claim means is coordinator-election.ts's own business, not the session's. A consumer feeds each one into a CoordinatorElection's evaluate, and sends its own claims with sendCoordinatorClaim. */
81
+ readonly coordinatorFrames: AsyncIterable<CoordinatorFrame>;
80
82
  connect: (address: string, localDomains: readonly string[]) => Promise<void>;
81
83
  sendPing: () => Promise<void>;
82
84
  /** Sends a ping-frame and resolves with the round-trip time in milliseconds once the correlated pong-frame arrives -- FIFO-paired against this call's own ping, since ping-frame carries no correlation id of its own (spec/transport.cddl): the Nth call's own promise resolves against the Nth pong received after it, never matched by any other means. Rejects if the connection closes, or (when timeoutMs is given) if no pong arrives within timeoutMs, rather than resolving a sentinel value the way sendManageRequest's own timeout does -- there is no natural "no answer" value for a bare millisecond count to double as. Unlike sendPing (fire-and-forget, answered by nothing on an ordinary peer connection), this is answered only by a peer that replies to ping with pong -- today, relay-hub's own frame handling (wire-mesh#181) -- so calling this against a connection to a plain peer that never sends pong hangs until timeoutMs (if given) or forever. Exists to isolate the sender-to-hub leg of a relayed path.trace round trip: time this over the same connection a relayed manage-request travelled, then subtract it from path.trace's own end-to-end RTT to recover the hub-to-target leg. */
@@ -85,6 +87,8 @@ export interface MeshSession {
85
87
  setToken: (token: CapabilityToken) => void;
86
88
  /** Announces one or more already-minted revocation-entries to the peer. Sent directly over the connection, never relay-wrapped -- revocation-announce is a gossiped broadcast, not a request addressed to a specific peer, so it has no targetDevice/token parameters the way sendManageRequest does. */
87
89
  sendRevocationAnnounce: (entries: readonly RevocationEntry[]) => Promise<void>;
90
+ /** Sends one coordinator-frame directly over this session's own connection, never relay-wrapped: the claim is a gossiped broadcast like revocation-announce, not a request addressed to a specific peer. The frame itself comes from a CoordinatorElection (claim, or announceCurrent for a refresh), so the session never invents a term on its own. */
91
+ sendCoordinatorClaim: (frame: Readonly<CoordinatorFrame>) => Promise<void>;
88
92
  /** Re-sends this side's own self-advert with a fresh snapshot-seconds and, when given, extensions merged onto peer-advert's own open `* tstr => any` tail -- the mechanism a caller uses to keep gossiped presence status (or any other advertised fact) live over a connection's lifetime, since the initial self-advert wireUpConnection sends at connect time is otherwise never repeated. Callers own their own re-advertisement cadence (there is no timer inside MeshSession itself, matching its own DOM-free, fully unit-testable design); a caller not calling this again after connecting is exactly today's existing gossip-once-on-connect behaviour. */
89
93
  sendGossipUpdate: (extensions?: Record<string, unknown>) => Promise<void>;
90
94
  /** This session's own current topology snapshot -- the identical `topology/peers` value buildSelfAdvert merges into every gossiped self-advert (wire-mesh#180), read back directly rather than only from a possibly-stale gossiped copy elsewhere in the mesh. The live, cache-bust half of the cached-vs-live split `topology.get` itself establishes: a caller answering an incoming topology.get manage-request (see topology.ts's createTopologyGetHandler) calls this to build the response. Synchronous and side-effect-free -- unlike every other method here, it sends nothing and works even before this session has ever connected (an unconnected session simply has no direct peer and no relay pairings yet, both honestly empty). */
@@ -1,4 +1,4 @@
1
- import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, nn as TopologyPeers, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-DCDae3z4.mjs";
1
+ import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, g as CoordinatorFrame, lt as ProtocolVersion, nn as TopologyPeers, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-DCDae3z4.mjs";
2
2
  import { r as Transport, t as Connection } from "../transport-B0GywEzG.mjs";
3
3
  import { t as IdentityPort } from "../identity-DcaYgjXT.mjs";
4
4
  import { t as Clock } from "../clock-DiSx-WKM.mjs";
@@ -77,6 +77,8 @@ export interface MeshSession {
77
77
  readonly incomingManageRequests: AsyncIterable<IncomingManageRequest>;
78
78
  /** Every `revocation-entry` received from the peer, in arrival order -- a `revocation-announce` frame's own `entries` array is flattened to one item per entry, since each entry is independently verifiable and independently meaningful regardless of which frame carried it. A consumer typically feeds each one into a RevocationView's own `record`. */
79
79
  readonly revocationAnnouncements: AsyncIterable<RevocationEntry>;
80
+ /** Every `coordinator-frame` received from the peer, in arrival order: a gossiped, term-based claim to the rendezvous role (spec/transport.cddl), surfaced raw exactly like revocationAnnouncements because what a claim means is coordinator-election.ts's own business, not the session's. A consumer feeds each one into a CoordinatorElection's evaluate, and sends its own claims with sendCoordinatorClaim. */
81
+ readonly coordinatorFrames: AsyncIterable<CoordinatorFrame>;
80
82
  connect: (address: string, localDomains: readonly string[]) => Promise<void>;
81
83
  sendPing: () => Promise<void>;
82
84
  /** Sends a ping-frame and resolves with the round-trip time in milliseconds once the correlated pong-frame arrives -- FIFO-paired against this call's own ping, since ping-frame carries no correlation id of its own (spec/transport.cddl): the Nth call's own promise resolves against the Nth pong received after it, never matched by any other means. Rejects if the connection closes, or (when timeoutMs is given) if no pong arrives within timeoutMs, rather than resolving a sentinel value the way sendManageRequest's own timeout does -- there is no natural "no answer" value for a bare millisecond count to double as. Unlike sendPing (fire-and-forget, answered by nothing on an ordinary peer connection), this is answered only by a peer that replies to ping with pong -- today, relay-hub's own frame handling (wire-mesh#181) -- so calling this against a connection to a plain peer that never sends pong hangs until timeoutMs (if given) or forever. Exists to isolate the sender-to-hub leg of a relayed path.trace round trip: time this over the same connection a relayed manage-request travelled, then subtract it from path.trace's own end-to-end RTT to recover the hub-to-target leg. */
@@ -85,6 +87,8 @@ export interface MeshSession {
85
87
  setToken: (token: CapabilityToken) => void;
86
88
  /** Announces one or more already-minted revocation-entries to the peer. Sent directly over the connection, never relay-wrapped -- revocation-announce is a gossiped broadcast, not a request addressed to a specific peer, so it has no targetDevice/token parameters the way sendManageRequest does. */
87
89
  sendRevocationAnnounce: (entries: readonly RevocationEntry[]) => Promise<void>;
90
+ /** Sends one coordinator-frame directly over this session's own connection, never relay-wrapped: the claim is a gossiped broadcast like revocation-announce, not a request addressed to a specific peer. The frame itself comes from a CoordinatorElection (claim, or announceCurrent for a refresh), so the session never invents a term on its own. */
91
+ sendCoordinatorClaim: (frame: Readonly<CoordinatorFrame>) => Promise<void>;
88
92
  /** Re-sends this side's own self-advert with a fresh snapshot-seconds and, when given, extensions merged onto peer-advert's own open `* tstr => any` tail -- the mechanism a caller uses to keep gossiped presence status (or any other advertised fact) live over a connection's lifetime, since the initial self-advert wireUpConnection sends at connect time is otherwise never repeated. Callers own their own re-advertisement cadence (there is no timer inside MeshSession itself, matching its own DOM-free, fully unit-testable design); a caller not calling this again after connecting is exactly today's existing gossip-once-on-connect behaviour. */
89
93
  sendGossipUpdate: (extensions?: Record<string, unknown>) => Promise<void>;
90
94
  /** This session's own current topology snapshot -- the identical `topology/peers` value buildSelfAdvert merges into every gossiped self-advert (wire-mesh#180), read back directly rather than only from a possibly-stale gossiped copy elsewhere in the mesh. The live, cache-bust half of the cached-vs-live split `topology.get` itself establishes: a caller answering an incoming topology.get manage-request (see topology.ts's createTopologyGetHandler) calls this to build the response. Synchronous and side-effect-free -- unlike every other method here, it sends nothing and works even before this session has ever connected (an unconnected session simply has no direct peer and no relay pairings yet, both honestly empty). */
@@ -1,12 +1,12 @@
1
- import { a as OWN_VERSION, i as CORE_VERSION_GOSSIP_KEY, n as TOPOLOGY_PEERS_GOSSIP_KEY, o as isVersionGetCommand, r as validateGossipExtensions, s as versionGetOutcome } from "../gossip-extensions-B6WJjkAr.mjs";
1
+ import { messageFromFrame, tryDecodeFrame, wrapRelayData } from "../adapters/frame-codec.mjs";
2
2
  import { t as bytesEqual } from "../token-scope-Z4bmci4M.mjs";
3
+ import { a as OWN_VERSION, i as CORE_VERSION_GOSSIP_KEY, n as TOPOLOGY_PEERS_GOSSIP_KEY, o as isVersionGetCommand, r as validateGossipExtensions, s as versionGetOutcome } from "../gossip-extensions-B6WJjkAr.mjs";
3
4
  import { signPeerAdvert, verifyPeerAdvert } from "./peer-advert.mjs";
5
+ import { createAsyncQueue } from "./async-queue.mjs";
4
6
  import { deviceIdToHex } from "./device-id.mjs";
5
7
  import { negotiate } from "./handshake.mjs";
6
- import { createAsyncQueue } from "./async-queue.mjs";
7
8
  import { createPingRoundTrips } from "./ping-round-trips.mjs";
8
9
  import { createTopologySnapshotTracker } from "./topology-snapshot.mjs";
9
- import { messageFromFrame, tryDecodeFrame, wrapRelayData } from "../adapters/frame-codec.mjs";
10
10
  //#region src/domain/relay-pairing.ts
11
11
  function createRelayPairings() {
12
12
  const established = /* @__PURE__ */ new Map();
@@ -324,6 +324,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
324
324
  });
325
325
  const incomingQueue = createAsyncQueue();
326
326
  const revocationQueue = createAsyncQueue();
327
+ const coordinatorQueue = createAsyncQueue();
327
328
  const pingRoundTrips = createPingRoundTrips();
328
329
  function snapshot() {
329
330
  return {
@@ -465,6 +466,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
465
466
  else if (frame.type === "manage-request") applyManageRequest(frame);
466
467
  else if (frame.type === "pong") pingRoundTrips.resolveOldest(clock.now());
467
468
  else if (frame.type === "revocation-announce") for (const entry of frame.entries) revocationQueue.push(entry);
469
+ else if (frame.type === "coordinator") coordinatorQueue.push(frame);
468
470
  }
469
471
  /** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). Pairing with a new target never tears down an existing pairing with a different one: this connection can hold several simultaneously (wire-mesh#30's own multiplexed adjacency map), so a later request back to an already-paired target must not re-send relay-connect for it. relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
470
472
  async function ensureRelayPairing(targetDevice) {
@@ -631,10 +633,14 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
631
633
  events: eventQueue.stream,
632
634
  incomingManageRequests: incomingQueue.stream,
633
635
  revocationAnnouncements: revocationQueue.stream,
636
+ coordinatorFrames: coordinatorQueue.stream,
634
637
  async connect(address, localDomains) {
635
638
  if (connection !== null) throw new Error("a session connects once; create a new one to reconnect");
636
639
  attempt = 0;
637
- await doConnect(address, localDomains);
640
+ await doConnect(address, localDomains).catch((error) => {
641
+ handleDisconnect(error instanceof Error ? error.message : String(error), address, localDomains);
642
+ throw error;
643
+ });
638
644
  },
639
645
  async sendPing() {
640
646
  const link = requireConnectedLink();
@@ -717,6 +723,16 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
717
723
  await transmit(frame, false);
718
724
  emit();
719
725
  },
726
+ async sendCoordinatorClaim(frame) {
727
+ requireConnectedLink();
728
+ const claim = { ...frame };
729
+ frameLog.push({
730
+ direction: "sent",
731
+ frame: claim
732
+ });
733
+ await transmit(claim, false);
734
+ emit();
735
+ },
720
736
  async sendGossipUpdate(extensions) {
721
737
  requireConnectedLink();
722
738
  const frame = await buildSelfAdvert(extensions);
@@ -0,0 +1,30 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/domain/relay-advert.ts
3
+ /** The domain-qualified gossip extension key a node's relay offer rides under, the same `wire-mesh` domain the core's own gossiped version fact occupies. */
4
+ const RELAY_OFFER_GOSSIP_KEY = "wire-mesh/relay-offer";
5
+ /**
6
+ * Builds the relay-offer extension bag for `sendGossipUpdate`, carrying exactly the address list `relay-offer-frame` itself would name. Throws on an empty list or a non-string entry: an empty offer is not an offer (the caller should simply not advertise), and a malformed address on the wire would be every reader's problem rather than one caller's bug caught at the call site.
7
+ */
8
+ function buildRelayOfferExtension(addresses) {
9
+ if (addresses.length === 0) throw new Error("a relay offer names at least one address");
10
+ for (const address of addresses) if (typeof address !== "string") throw new Error(`a relay offer address is a string, got ${JSON.stringify(address)}`);
11
+ return { [RELAY_OFFER_GOSSIP_KEY]: [...addresses] };
12
+ }
13
+ /**
14
+ * Reads a peer's relay offer from its advert: the addresses it offers relay service at, or undefined when the peer advertises no offer (or the extension is malformed, which reads as no offer rather than an error). A receiver still decides for itself whether to use any offered address, exactly as it would an in-band relay-offer-frame.
15
+ */
16
+ function readRelayOffer(advert) {
17
+ const value = advert[RELAY_OFFER_GOSSIP_KEY];
18
+ if (!Array.isArray(value)) return void 0;
19
+ const addresses = [];
20
+ for (const entry of value) {
21
+ if (typeof entry !== "string") return void 0;
22
+ addresses.push(entry);
23
+ }
24
+ if (addresses.length === 0) return void 0;
25
+ return addresses;
26
+ }
27
+ //#endregion
28
+ exports.RELAY_OFFER_GOSSIP_KEY = RELAY_OFFER_GOSSIP_KEY;
29
+ exports.buildRelayOfferExtension = buildRelayOfferExtension;
30
+ exports.readRelayOffer = readRelayOffer;
@@ -0,0 +1,13 @@
1
+ import { et as PeerAdvert } from "../protocol-DCDae3z4.cjs";
2
+ //#region src/domain/relay-advert.d.ts
3
+ /** The domain-qualified gossip extension key a node's relay offer rides under, the same `wire-mesh` domain the core's own gossiped version fact occupies. */
4
+ export declare const RELAY_OFFER_GOSSIP_KEY = "wire-mesh/relay-offer";
5
+ /**
6
+ * Builds the relay-offer extension bag for `sendGossipUpdate`, carrying exactly the address list `relay-offer-frame` itself would name. Throws on an empty list or a non-string entry: an empty offer is not an offer (the caller should simply not advertise), and a malformed address on the wire would be every reader's problem rather than one caller's bug caught at the call site.
7
+ */
8
+ export declare function buildRelayOfferExtension(addresses: readonly string[]): Record<string, unknown>;
9
+ /**
10
+ * Reads a peer's relay offer from its advert: the addresses it offers relay service at, or undefined when the peer advertises no offer (or the extension is malformed, which reads as no offer rather than an error). A receiver still decides for itself whether to use any offered address, exactly as it would an in-band relay-offer-frame.
11
+ */
12
+ export declare function readRelayOffer(advert: Readonly<PeerAdvert>): readonly string[] | undefined;
13
+ //#endregion
@@ -0,0 +1,13 @@
1
+ import { et as PeerAdvert } from "../protocol-DCDae3z4.mjs";
2
+ //#region src/domain/relay-advert.d.ts
3
+ /** The domain-qualified gossip extension key a node's relay offer rides under, the same `wire-mesh` domain the core's own gossiped version fact occupies. */
4
+ export declare const RELAY_OFFER_GOSSIP_KEY = "wire-mesh/relay-offer";
5
+ /**
6
+ * Builds the relay-offer extension bag for `sendGossipUpdate`, carrying exactly the address list `relay-offer-frame` itself would name. Throws on an empty list or a non-string entry: an empty offer is not an offer (the caller should simply not advertise), and a malformed address on the wire would be every reader's problem rather than one caller's bug caught at the call site.
7
+ */
8
+ export declare function buildRelayOfferExtension(addresses: readonly string[]): Record<string, unknown>;
9
+ /**
10
+ * Reads a peer's relay offer from its advert: the addresses it offers relay service at, or undefined when the peer advertises no offer (or the extension is malformed, which reads as no offer rather than an error). A receiver still decides for itself whether to use any offered address, exactly as it would an in-band relay-offer-frame.
11
+ */
12
+ export declare function readRelayOffer(advert: Readonly<PeerAdvert>): readonly string[] | undefined;
13
+ //#endregion
@@ -0,0 +1,27 @@
1
+ //#region src/domain/relay-advert.ts
2
+ /** The domain-qualified gossip extension key a node's relay offer rides under, the same `wire-mesh` domain the core's own gossiped version fact occupies. */
3
+ const RELAY_OFFER_GOSSIP_KEY = "wire-mesh/relay-offer";
4
+ /**
5
+ * Builds the relay-offer extension bag for `sendGossipUpdate`, carrying exactly the address list `relay-offer-frame` itself would name. Throws on an empty list or a non-string entry: an empty offer is not an offer (the caller should simply not advertise), and a malformed address on the wire would be every reader's problem rather than one caller's bug caught at the call site.
6
+ */
7
+ function buildRelayOfferExtension(addresses) {
8
+ if (addresses.length === 0) throw new Error("a relay offer names at least one address");
9
+ for (const address of addresses) if (typeof address !== "string") throw new Error(`a relay offer address is a string, got ${JSON.stringify(address)}`);
10
+ return { [RELAY_OFFER_GOSSIP_KEY]: [...addresses] };
11
+ }
12
+ /**
13
+ * Reads a peer's relay offer from its advert: the addresses it offers relay service at, or undefined when the peer advertises no offer (or the extension is malformed, which reads as no offer rather than an error). A receiver still decides for itself whether to use any offered address, exactly as it would an in-band relay-offer-frame.
14
+ */
15
+ function readRelayOffer(advert) {
16
+ const value = advert[RELAY_OFFER_GOSSIP_KEY];
17
+ if (!Array.isArray(value)) return void 0;
18
+ const addresses = [];
19
+ for (const entry of value) {
20
+ if (typeof entry !== "string") return void 0;
21
+ addresses.push(entry);
22
+ }
23
+ if (addresses.length === 0) return void 0;
24
+ return addresses;
25
+ }
26
+ //#endregion
27
+ export { RELAY_OFFER_GOSSIP_KEY, buildRelayOfferExtension, readRelayOffer };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_tokens = require("../tokens-DSG3SDr8.cjs");
2
3
  const require_domain_device_id = require("./device-id.cjs");
3
- const require_tokens = require("../tokens-Dh6mG6p_.cjs");
4
4
  //#region src/domain/revocation-view.ts
5
5
  function revocationKey(tokenId) {
6
6
  return require_domain_device_id.bytesToHex(tokenId);
@@ -1,5 +1,5 @@
1
+ import { o as verifyRevocationEntry } from "../tokens-CMdWiRBb.mjs";
1
2
  import { bytesToHex } from "./device-id.mjs";
2
- import { o as verifyRevocationEntry } from "../tokens-B0dpTLhS.mjs";
3
3
  //#region src/domain/revocation-view.ts
4
4
  function revocationKey(tokenId) {
5
5
  return bytesToHex(tokenId);
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_tokens = require("../tokens-DSG3SDr8.cjs");
2
3
  const require_domain_device_id = require("./device-id.cjs");
3
- const require_tokens = require("../tokens-Dh6mG6p_.cjs");
4
4
  const require_domain_room_path = require("./room-path.cjs");
5
5
  //#region src/domain/room-token-verification.ts
6
6
  /**
@@ -1,5 +1,5 @@
1
+ import { a as verifyCapabilityToken } from "../tokens-CMdWiRBb.mjs";
1
2
  import { deviceIdToHex } from "./device-id.mjs";
2
- import { a as verifyCapabilityToken } from "../tokens-B0dpTLhS.mjs";
3
3
  import { parseRoomPath } from "./room-path.mjs";
4
4
  //#region src/domain/room-token-verification.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_tokens = require("../tokens-Dh6mG6p_.cjs");
2
+ const require_tokens = require("../tokens-DSG3SDr8.cjs");
3
3
  //#region src/domain/threshold-subject.ts
4
4
  /**
5
5
  * `threshold-subject` -- what a signing session actually asks a group to sign, and the single most important design decision in the whole protocol: it carries the FULL decoded content, never a bare hash. A participant that commits to an opaque digest is a blind signer, and the entire security value of T-of-N is that each participant independently reviews and authorises the content -- returning a round-1 commitment IS that act of authorisation. This module builds exactly the bytes a participant signs its round-1 commitment over: the RFC 9052 §4.4 `Sig_structure`, reusing `tokens.ts`'s own `sig1ToBeSigned` rather than a second hand-rolled construction, and NEVER accepting a pre-assembled to-be-signed blob from a coordinator. Mirrors `wire_mesh_threshold::subject` on the Rust side exactly.
@@ -1,4 +1,4 @@
1
- import { i as sig1ToBeSigned } from "../tokens-B0dpTLhS.mjs";
1
+ import { i as sig1ToBeSigned } from "../tokens-CMdWiRBb.mjs";
2
2
  //#region src/domain/threshold-subject.ts
3
3
  /**
4
4
  * `threshold-subject` -- what a signing session actually asks a group to sign, and the single most important design decision in the whole protocol: it carries the FULL decoded content, never a bare hash. A participant that commits to an opaque digest is a blind signer, and the entire security value of T-of-N is that each participant independently reviews and authorises the content -- returning a round-1 commitment IS that act of authorisation. This module builds exactly the bytes a participant signs its round-1 commitment over: the RFC 9052 §4.4 `Sig_structure`, reusing `tokens.ts`'s own `sig1ToBeSigned` rather than a second hand-rolled construction, and NEVER accepting a pre-assembled to-be-signed blob from a coordinator. Mirrors `wire_mesh_threshold::subject` on the Rust side exactly.
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_tokens = require("../tokens-Dh6mG6p_.cjs");
2
+ const require_tokens = require("../tokens-DSG3SDr8.cjs");
3
3
  exports.canGrant = require_tokens.canGrant;
4
4
  exports.mintCapabilityToken = require_tokens.mintCapabilityToken;
5
5
  exports.mintRevocationEntry = require_tokens.mintRevocationEntry;
@@ -1,2 +1,2 @@
1
- import { a as verifyCapabilityToken, i as sig1ToBeSigned, n as mintCapabilityToken, o as verifyRevocationEntry, r as mintRevocationEntry, t as canGrant } from "../tokens-B0dpTLhS.mjs";
1
+ import { a as verifyCapabilityToken, i as sig1ToBeSigned, n as mintCapabilityToken, o as verifyRevocationEntry, r as mintRevocationEntry, t as canGrant } from "../tokens-CMdWiRBb.mjs";
2
2
  export { canGrant, mintCapabilityToken, mintRevocationEntry, sig1ToBeSigned, verifyCapabilityToken, verifyRevocationEntry };
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_tokens = require("../tokens-Dh6mG6p_.cjs");
2
+ const require_tokens = require("../tokens-DSG3SDr8.cjs");
3
3
  //#region src/domain/webrtc-signaling.ts
4
4
  /** The one capability verb gating every core/webrtc message shape: an authority over this node's own signaling as a whole, not three separate resources, mirroring how core/exec's exec:pty gates all of its own inner verbs. webrtc.sfu-track-map is gated by this same verb (see spec/webrtc.cddl's own comment): an SFU able to shape a negotiation can already misreport whose media is on which mid, so a separate capability over the track-map itself would guard nothing a client doesn't already have to trust the SFU for. */
5
5
  const WEBRTC_SIGNAL_VERB = "webrtc:signal";
@@ -1,4 +1,4 @@
1
- import { a as verifyCapabilityToken } from "../tokens-B0dpTLhS.mjs";
1
+ import { a as verifyCapabilityToken } from "../tokens-CMdWiRBb.mjs";
2
2
  //#region src/domain/webrtc-signaling.ts
3
3
  /** The one capability verb gating every core/webrtc message shape: an authority over this node's own signaling as a whole, not three separate resources, mirroring how core/exec's exec:pty gates all of its own inner verbs. webrtc.sfu-track-map is gated by this same verb (see spec/webrtc.cddl's own comment): an SFU able to shape a negotiation can already misreport whose media is on which mid, so a separate capability over the track-map itself would guard nothing a client doesn't already have to trust the SFU for. */
4
4
  const WEBRTC_SIGNAL_VERB = "webrtc:signal";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wire-mesh-core",
3
- "version": "3.4.0",
3
+ "version": "3.5.1",
4
4
  "dependencies": {
5
5
  "cbor2": "2.3.0",
6
6
  "cddl.js": "1.0.1",
@@ -86,6 +86,10 @@
86
86
  "import": "./dist/domain/capability-request.mjs",
87
87
  "require": "./dist/domain/capability-request.cjs"
88
88
  },
89
+ "./domain/coordinator-election": {
90
+ "import": "./dist/domain/coordinator-election.mjs",
91
+ "require": "./dist/domain/coordinator-election.cjs"
92
+ },
89
93
  "./domain/data-sync": {
90
94
  "import": "./dist/domain/data-sync.mjs",
91
95
  "require": "./dist/domain/data-sync.cjs"
@@ -150,6 +154,10 @@
150
154
  "import": "./dist/domain/pinned-address.mjs",
151
155
  "require": "./dist/domain/pinned-address.cjs"
152
156
  },
157
+ "./domain/relay-advert": {
158
+ "import": "./dist/domain/relay-advert.mjs",
159
+ "require": "./dist/domain/relay-advert.cjs"
160
+ },
153
161
  "./domain/relay-hub": {
154
162
  "import": "./dist/domain/relay-hub.mjs",
155
163
  "require": "./dist/domain/relay-hub.cjs"
@@ -1,7 +1,7 @@
1
1
  import { capabilityTokenSchema, revocationClaimsSchema, tokenClaimsSchema } from "./generated/protocol.mjs";
2
2
  import { n as scopeNarrows, t as bytesEqual } from "./token-scope-Z4bmci4M.mjs";
3
- import { z } from "zod";
4
3
  import { cdeDecodeOptions, cdeEncodeOptions, decode, encode } from "cbor2";
4
+ import { z } from "zod";
5
5
  import { PredicateNodeSchema, evaluatePredicate } from "trilean";
6
6
  //#region src/domain/token-predicates.ts
7
7
  /** The wire shape of `token-claims.conditions` once CBOR-decoded: trilean's own PredicateNodeSchema is the single source of truth for what a condition entry may contain, re-validated here rather than trusted from a CDDL-generated shadow schema (see tokens.cddl's own comment on why `conditions` is an opaque bstr, not a native CDDL type) -- a token from an untrusted peer must pass trilean's real schema before any of its conditions are evaluated. Explicitly annotated: trilean's PredicateNodeSchema is a deeply recursive z.lazy() type whose inferred shape is too large for tsdown's declaration-file generator to serialise (TS7056) without this. */
@@ -1,7 +1,7 @@
1
1
  const require_generated_protocol = require("./generated/protocol.cjs");
2
2
  const require_token_scope = require("./token-scope-CxHXTT3u.cjs");
3
- let zod = require("zod");
4
3
  let cbor2 = require("cbor2");
4
+ let zod = require("zod");
5
5
  let trilean = require("trilean");
6
6
  //#region src/domain/token-predicates.ts
7
7
  /** The wire shape of `token-claims.conditions` once CBOR-decoded: trilean's own PredicateNodeSchema is the single source of truth for what a condition entry may contain, re-validated here rather than trusted from a CDDL-generated shadow schema (see tokens.cddl's own comment on why `conditions` is an opaque bstr, not a native CDDL type) -- a token from an untrusted peer must pass trilean's real schema before any of its conditions are evaluated. Explicitly annotated: trilean's PredicateNodeSchema is a deeply recursive z.lazy() type whose inferred shape is too large for tsdown's declaration-file generator to serialise (TS7056) without this. */