@cello-protocol/daemon 0.0.3 → 0.0.5

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 (153) hide show
  1. package/dist/agent-loader.d.ts +41 -0
  2. package/dist/agent-loader.d.ts.map +1 -0
  3. package/dist/agent-loader.js +94 -0
  4. package/dist/agent-loader.js.map +1 -0
  5. package/dist/bin/cello-daemon.d.ts +13 -0
  6. package/dist/bin/cello-daemon.d.ts.map +1 -0
  7. package/dist/bin/cello-daemon.js +170 -0
  8. package/dist/bin/cello-daemon.js.map +1 -0
  9. package/dist/cello-node-transport-dialer.d.ts +59 -0
  10. package/dist/cello-node-transport-dialer.d.ts.map +1 -0
  11. package/dist/cello-node-transport-dialer.js +108 -0
  12. package/dist/cello-node-transport-dialer.js.map +1 -0
  13. package/dist/challenge-verifier.d.ts +12 -0
  14. package/dist/challenge-verifier.d.ts.map +1 -0
  15. package/dist/challenge-verifier.js +11 -0
  16. package/dist/challenge-verifier.js.map +1 -0
  17. package/dist/connect-or-start.d.ts +25 -0
  18. package/dist/connect-or-start.d.ts.map +1 -0
  19. package/dist/connect-or-start.js +117 -0
  20. package/dist/connect-or-start.js.map +1 -0
  21. package/dist/content-park-client.d.ts +49 -0
  22. package/dist/content-park-client.d.ts.map +1 -0
  23. package/dist/content-park-client.js +196 -0
  24. package/dist/content-park-client.js.map +1 -0
  25. package/dist/daemon.d.ts +65 -0
  26. package/dist/daemon.d.ts.map +1 -0
  27. package/dist/daemon.js +3202 -0
  28. package/dist/daemon.js.map +1 -0
  29. package/dist/directory-bootstrap.d.ts +55 -0
  30. package/dist/directory-bootstrap.d.ts.map +1 -0
  31. package/dist/directory-bootstrap.js +102 -0
  32. package/dist/directory-bootstrap.js.map +1 -0
  33. package/dist/file-manifest-provider.d.ts +18 -0
  34. package/dist/file-manifest-provider.d.ts.map +1 -0
  35. package/dist/file-manifest-provider.js +72 -0
  36. package/dist/file-manifest-provider.js.map +1 -0
  37. package/dist/index.d.ts +18 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +18 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/ipc-client.d.ts +31 -0
  42. package/dist/ipc-client.d.ts.map +1 -0
  43. package/dist/ipc-client.js +112 -0
  44. package/dist/ipc-client.js.map +1 -0
  45. package/dist/ipc-server.d.ts +49 -0
  46. package/dist/ipc-server.d.ts.map +1 -0
  47. package/dist/ipc-server.js +268 -0
  48. package/dist/ipc-server.js.map +1 -0
  49. package/dist/lock-file.d.ts +27 -0
  50. package/dist/lock-file.d.ts.map +1 -0
  51. package/dist/lock-file.js +84 -0
  52. package/dist/lock-file.js.map +1 -0
  53. package/dist/manifest-loader.d.ts +33 -0
  54. package/dist/manifest-loader.d.ts.map +1 -0
  55. package/dist/manifest-loader.js +70 -0
  56. package/dist/manifest-loader.js.map +1 -0
  57. package/dist/manifest-poll-scheduler.d.ts +31 -0
  58. package/dist/manifest-poll-scheduler.d.ts.map +1 -0
  59. package/dist/manifest-poll-scheduler.js +59 -0
  60. package/dist/manifest-poll-scheduler.js.map +1 -0
  61. package/dist/manifest-version-store-file.d.ts +18 -0
  62. package/dist/manifest-version-store-file.d.ts.map +1 -0
  63. package/dist/manifest-version-store-file.js +40 -0
  64. package/dist/manifest-version-store-file.js.map +1 -0
  65. package/dist/manifest-version-store.d.ts +14 -0
  66. package/dist/manifest-version-store.d.ts.map +1 -0
  67. package/dist/manifest-version-store.js +13 -0
  68. package/dist/manifest-version-store.js.map +1 -0
  69. package/dist/network-directory-node.d.ts +94 -0
  70. package/dist/network-directory-node.d.ts.map +1 -0
  71. package/dist/network-directory-node.js +626 -0
  72. package/dist/network-directory-node.js.map +1 -0
  73. package/dist/nonce-dedup.d.ts +68 -0
  74. package/dist/nonce-dedup.d.ts.map +1 -0
  75. package/dist/nonce-dedup.js +204 -0
  76. package/dist/nonce-dedup.js.map +1 -0
  77. package/dist/notification-dispatcher.d.ts +65 -0
  78. package/dist/notification-dispatcher.d.ts.map +1 -0
  79. package/dist/notification-dispatcher.js +138 -0
  80. package/dist/notification-dispatcher.js.map +1 -0
  81. package/dist/registration-context.d.ts +69 -0
  82. package/dist/registration-context.d.ts.map +1 -0
  83. package/dist/registration-context.js +118 -0
  84. package/dist/registration-context.js.map +1 -0
  85. package/dist/registration-manager.d.ts +72 -0
  86. package/dist/registration-manager.d.ts.map +1 -0
  87. package/dist/registration-manager.js +267 -0
  88. package/dist/registration-manager.js.map +1 -0
  89. package/dist/registration-persistence.d.ts +131 -0
  90. package/dist/registration-persistence.d.ts.map +1 -0
  91. package/dist/registration-persistence.js +233 -0
  92. package/dist/registration-persistence.js.map +1 -0
  93. package/dist/retry-queue.d.ts +144 -0
  94. package/dist/retry-queue.d.ts.map +1 -0
  95. package/dist/retry-queue.js +444 -0
  96. package/dist/retry-queue.js.map +1 -0
  97. package/dist/seal-frontier-verify.d.ts +58 -0
  98. package/dist/seal-frontier-verify.d.ts.map +1 -0
  99. package/dist/seal-frontier-verify.js +87 -0
  100. package/dist/seal-frontier-verify.js.map +1 -0
  101. package/dist/seal-legibility-tbs.d.ts +25 -0
  102. package/dist/seal-legibility-tbs.d.ts.map +1 -0
  103. package/dist/seal-legibility-tbs.js +78 -0
  104. package/dist/seal-legibility-tbs.js.map +1 -0
  105. package/dist/seal-upgrade.d.ts +90 -0
  106. package/dist/seal-upgrade.d.ts.map +1 -0
  107. package/dist/seal-upgrade.js +178 -0
  108. package/dist/seal-upgrade.js.map +1 -0
  109. package/dist/session-assignment-parser.d.ts +22 -0
  110. package/dist/session-assignment-parser.d.ts.map +1 -0
  111. package/dist/session-assignment-parser.js +139 -0
  112. package/dist/session-assignment-parser.js.map +1 -0
  113. package/dist/session-ceremony.d.ts +156 -0
  114. package/dist/session-ceremony.d.ts.map +1 -0
  115. package/dist/session-ceremony.js +447 -0
  116. package/dist/session-ceremony.js.map +1 -0
  117. package/dist/session-connection-gater.d.ts +91 -0
  118. package/dist/session-connection-gater.d.ts.map +1 -0
  119. package/dist/session-connection-gater.js +146 -0
  120. package/dist/session-connection-gater.js.map +1 -0
  121. package/dist/session-node-manager.d.ts +585 -0
  122. package/dist/session-node-manager.d.ts.map +1 -0
  123. package/dist/session-node-manager.js +2609 -0
  124. package/dist/session-node-manager.js.map +1 -0
  125. package/dist/session-relay-client.d.ts +101 -0
  126. package/dist/session-relay-client.d.ts.map +1 -0
  127. package/dist/session-relay-client.js +520 -0
  128. package/dist/session-relay-client.js.map +1 -0
  129. package/dist/session-tree.d.ts +80 -0
  130. package/dist/session-tree.d.ts.map +1 -0
  131. package/dist/session-tree.js +123 -0
  132. package/dist/session-tree.js.map +1 -0
  133. package/dist/signaling-connect.d.ts +83 -0
  134. package/dist/signaling-connect.d.ts.map +1 -0
  135. package/dist/signaling-connect.js +266 -0
  136. package/dist/signaling-connect.js.map +1 -0
  137. package/dist/transcript-cipher.d.ts +31 -0
  138. package/dist/transcript-cipher.d.ts.map +1 -0
  139. package/dist/transcript-cipher.js +74 -0
  140. package/dist/transcript-cipher.js.map +1 -0
  141. package/dist/transport-composition.d.ts +31 -0
  142. package/dist/transport-composition.d.ts.map +1 -0
  143. package/dist/transport-composition.js +55 -0
  144. package/dist/transport-composition.js.map +1 -0
  145. package/dist/transport-selector.d.ts +189 -0
  146. package/dist/transport-selector.d.ts.map +1 -0
  147. package/dist/transport-selector.js +195 -0
  148. package/dist/transport-selector.js.map +1 -0
  149. package/dist/types.d.ts +265 -0
  150. package/dist/types.d.ts.map +1 -0
  151. package/dist/types.js +33 -0
  152. package/dist/types.js.map +1 -0
  153. package/package.json +4 -4
@@ -0,0 +1,55 @@
1
+ /**
2
+ * CELLO Daemon — transport-composition.ts (CELLO-M7-TRANSPORT-001)
3
+ *
4
+ * Composition-root selection of the transport adapters by CELLO_ENV. Per CLAUDE.md
5
+ * the daemon composition root instantiates all adapters and fails fast at startup
6
+ * (not at first session) when a production environment is missing required config.
7
+ *
8
+ * 'local' | 'test' → in-process stubs (no network).
9
+ * 'dev' | 'staging' | 'production' → real adapters; require their backing
10
+ * dependencies (a TransportDialer for the
11
+ * selector). Missing config → throw at
12
+ * startup naming what is missing (AC-010).
13
+ */
14
+ import { LocalTransportSelectorStub, TransportSelector, } from "./transport-selector.js";
15
+ /** Resolve CELLO_ENV. Unknown/undefined defaults to 'local' (in-process stubs). */
16
+ export function resolveCelloEnv(raw) {
17
+ switch (raw) {
18
+ case "local":
19
+ case "test":
20
+ case "dev":
21
+ case "staging":
22
+ case "production":
23
+ return raw;
24
+ default:
25
+ return "local";
26
+ }
27
+ }
28
+ /** True for environments that require real (network-backed) transport adapters. */
29
+ export function isProductionVariant(env) {
30
+ return env === "dev" || env === "staging" || env === "production";
31
+ }
32
+ /**
33
+ * Build the transport selector for the given environment. Stub for local/test;
34
+ * real TransportSelector for production variants (requires a TransportDialer).
35
+ */
36
+ export function createTransportSelector(opts) {
37
+ const { env, logger, transportDialer, directDialTimeoutMs } = opts;
38
+ if (!isProductionVariant(env)) {
39
+ return new LocalTransportSelectorStub();
40
+ }
41
+ if (!transportDialer) {
42
+ throw new Error(`CELLO_ENV='${env}' requires a transport dialer (directory/relay-backed) for the ` +
43
+ `transport selector, but none was provided to the daemon composition root. The ` +
44
+ `transport selector cannot dial a counterparty without it — fix the daemon ` +
45
+ `configuration (config.transportDialer) before startup.`);
46
+ }
47
+ return new TransportSelector({ dialer: transportDialer, logger, directDialTimeoutMs });
48
+ }
49
+ // NOTE (CELLO-M7-TRANSPORT-001): there is no createAutoNatService here. The
50
+ // daemon's runtime IAutoNatService is the NodeAutoNatService wrapping the standing
51
+ // receiver node — that node is created inside startDaemon (after this composition
52
+ // runs), so the AutoNAT service is resolved from SessionNodeManager.
53
+ // getStandingReceiverAutoNat() rather than constructed in the composition root.
54
+ // config.autoNatService remains an explicit test override.
55
+ //# sourceMappingURL=transport-composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-composition.js","sourceRoot":"","sources":["../src/transport-composition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,GAGlB,MAAM,yBAAyB,CAAC;AAKjC,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO,CAAC;QACb,KAAK,MAAM,CAAC;QACZ,KAAK,KAAK,CAAC;QACX,KAAK,SAAS,CAAC;QACf,KAAK,YAAY;YACf,OAAO,GAAG,CAAC;QACb;YACE,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,mBAAmB,CAAC,GAAa;IAC/C,OAAO,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,YAAY,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAKvC;IACC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,mBAAmB,EAAE,GAAG,IAAI,CAAC;IACnE,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,0BAA0B,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,cAAc,GAAG,iEAAiE;YAChF,gFAAgF;YAChF,4EAA4E;YAC5E,wDAAwD,CAC3D,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,iBAAiB,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;AACzF,CAAC;AAED,4EAA4E;AAC5E,mFAAmF;AACnF,kFAAkF;AAClF,qEAAqE;AACrE,gFAAgF;AAChF,2DAA2D"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * CELLO Daemon — transport-selector.ts (CELLO-M7-TRANSPORT-001)
3
+ *
4
+ * Direct-P2P-by-default transport selection with relay fallback and a best-effort
5
+ * dcutr hole-punch upgrade.
6
+ *
7
+ * ─── Specification (Phase S) ──────────────────────────────────────────────────
8
+ * Given a FROST-signed SessionAssignment (produced by WIRE-001), choose how to
9
+ * dial the counterparty's session node:
10
+ * 1. Read transport_mode from the assignment. This is the ONLY authority for
11
+ * the dial strategy (SI-001 / AC-015) — NEVER infer mode from address format.
12
+ * 2. transport_mode === 'direct' → dial counterparty_session_addrs directly.
13
+ * 3. direct dial fails OR transport_mode === 'relay' → dial the relay circuit
14
+ * address (from the daemon's relay registry, independent of transport_mode —
15
+ * AC-006).
16
+ * 4. After a relay connection is established → attempt a dcutr hole-punch upgrade
17
+ * (non-blocking, best-effort). Failure is non-fatal (SI-003).
18
+ * 5. If BOTH direct and relay fail → terminal 'relay_fallback_also_failed' (AC-008).
19
+ *
20
+ * Error code registry (fixed strings — also in CONTEXT.md):
21
+ * 'autonat_unavailable' — internal; no probers reachable.
22
+ * 'direct_dial_failed_falling_back_to_relay' — WARN; intermediate, non-fatal.
23
+ * 'relay_fallback_also_failed' — terminal session failure.
24
+ * 'dcutr_upgrade_failed' — DEBUG; non-fatal.
25
+ *
26
+ * ─── Pseudocode (Phase P) ─────────────────────────────────────────────────────
27
+ * dial(assignment, { correlationId }):
28
+ * mode = assignment.transport_mode // authoritative
29
+ * sessionId = hex(assignment.session_id)
30
+ * peerId = assignment.counterparty_session_peer_id
31
+ * directAddrs = assignment.counterparty_session_addrs ?? []
32
+ * if mode === 'direct':
33
+ * try { dialer.dialDirect(peerId, directAddrs); log mode.selected('direct'); return {ok, 'direct'} }
34
+ * catch e { log direct_dial.failed(WARN, e.message) } // fall through
35
+ * try { dialer.dialRelay(dialer.relayCircuitAddr(assignment), peerId) }
36
+ * catch { return {ok:false, 'relay_fallback_also_failed', guidance} } // NO mode.selected
37
+ * log mode.selected('relay')
38
+ * dcutrSettled = (async () => {
39
+ * try { dialer.attemptDcutr(peerId); log dcutr.upgraded(INFO); return true }
40
+ * catch e { log dcutr.failed(DEBUG, e.message); return false } })()
41
+ * return {ok:true, 'relay', dcutrSettled} // dial does NOT await dcutr
42
+ */
43
+ import type { SessionAssignment } from "@cello-protocol/protocol-types";
44
+ import type { Logger } from "./types.js";
45
+ import type { Dialability } from "@cello-protocol/transport";
46
+ export declare const TRANSPORT_ERROR: {
47
+ /** Internal: AutoNAT could not run because no directory-node probers are reachable. */
48
+ readonly AUTONAT_UNAVAILABLE: "autonat_unavailable";
49
+ /** WARN-level intermediate, non-fatal: direct dial failed, relay fallback initiated. */
50
+ readonly DIRECT_DIAL_FAILED_FALLING_BACK: "direct_dial_failed_falling_back_to_relay";
51
+ /** Terminal: both direct dial and relay fallback failed. */
52
+ readonly RELAY_FALLBACK_ALSO_FAILED: "relay_fallback_also_failed";
53
+ /** DEBUG-level non-fatal: dcutr hole-punch upgrade attempt failed. */
54
+ readonly DCUTR_UPGRADE_FAILED: "dcutr_upgrade_failed";
55
+ };
56
+ /** Default direct-dial timeout (ms). Not a user-facing option (implementation_notes). */
57
+ export declare const DEFAULT_DIRECT_DIAL_TIMEOUT_MS = 5000;
58
+ /**
59
+ * The outcome of a transport selection. Discriminated by `ok` so the failure
60
+ * branch is exactly the `{ ok:false, reason, guidance }` shape the MCP layer
61
+ * surfaces to the caller (AC-008).
62
+ *
63
+ * For the relay branch, `dcutrSettled` resolves when the background dcutr attempt
64
+ * settles — `true` if the connection was hole-punch upgraded to direct, `false`
65
+ * otherwise. dial() does NOT await it: the session is usable immediately upon the
66
+ * relay connection (AC-007). Production callers may ignore `dcutrSettled`.
67
+ */
68
+ export type TransportResult = {
69
+ ok: true;
70
+ mode: "direct";
71
+ } | {
72
+ ok: true;
73
+ mode: "relay";
74
+ dcutrSettled: Promise<boolean>;
75
+ } | {
76
+ ok: false;
77
+ reason: typeof TRANSPORT_ERROR.RELAY_FALLBACK_ALSO_FAILED;
78
+ guidance: string;
79
+ };
80
+ export interface TransportDialOptions {
81
+ /** Correlation ID threaded through every event for this session flow. */
82
+ correlationId: string;
83
+ }
84
+ /**
85
+ * Adapter interface for choosing and performing the transport dial.
86
+ * Real implementation: TransportSelector (drives a TransportDialer + Logger).
87
+ * Stub: LocalTransportSelectorStub (returns configurable canned results — used by
88
+ * the composition root when CELLO_ENV is 'local'|'test').
89
+ */
90
+ export interface ITransportSelector {
91
+ dial(assignment: SessionAssignment, opts: TransportDialOptions): Promise<TransportResult>;
92
+ }
93
+ /**
94
+ * The advertised address this daemon offers to the directory for a new session,
95
+ * derived from the standing receiver's AutoNAT dialability (AC-004 / AC-019):
96
+ * a direct multiaddr when dialable, the relay circuit address otherwise.
97
+ */
98
+ export interface SessionNegotiationContext {
99
+ agentName: string;
100
+ correlationId: string;
101
+ advertisedAddress: AdvertisedAddress;
102
+ params: Record<string, unknown>;
103
+ }
104
+ /**
105
+ * Outcome of directory session negotiation. On success it carries the
106
+ * FROST-signed SessionAssignment (transport_mode + counterparty_session_addrs)
107
+ * that the TransportSelector then consumes.
108
+ */
109
+ export type SessionNegotiationResult = {
110
+ ok: true;
111
+ assignment: SessionAssignment;
112
+ } | {
113
+ ok: false;
114
+ reason: string;
115
+ guidance: string;
116
+ };
117
+ /**
118
+ * Adapter that performs directory session negotiation and returns the
119
+ * FROST-signed SessionAssignment. The real implementation is owned by
120
+ * WIRE-001/SIGNAL-001 (directory signaling); it is injected into the daemon
121
+ * composition root. When absent, cello_initiate_session reports
122
+ * directory_signaling_not_configured (it does NOT crash — the transport adapters
123
+ * are still wired). This is the seam that lets cello_initiate_session drive the
124
+ * transport selector once an assignment exists (AC-010(c)).
125
+ */
126
+ export interface SessionNegotiator {
127
+ negotiate(ctx: SessionNegotiationContext): Promise<SessionNegotiationResult>;
128
+ }
129
+ /**
130
+ * The low-level dial operations the selector composes. The real implementation
131
+ * wraps a CelloNode (direct dial + relay circuit dial) plus the daemon's relay
132
+ * registry. Each operation throws on failure; the selector catches and routes.
133
+ */
134
+ export interface TransportDialer {
135
+ /** Dial the counterparty directly. Throws on timeout/refused/unreachable. */
136
+ dialDirect(peerId: string | undefined, addrs: string[], timeoutMs: number): Promise<void>;
137
+ /** Dial the counterparty via the relay circuit address. Throws on failure. */
138
+ dialRelay(relayCircuitAddr: string, peerId: string | undefined): Promise<void>;
139
+ /**
140
+ * The relay circuit address for this assignment. Sourced from the daemon's relay
141
+ * registry (populated during directory connection) — NOT parsed from the
142
+ * assignment's address fields, and available regardless of transport_mode (AC-006).
143
+ */
144
+ relayCircuitAddr(assignment: SessionAssignment): string;
145
+ /** Attempt a dcutr hole-punch upgrade. Throws on failure (non-fatal — SI-003). */
146
+ attemptDcutr(peerId: string | undefined): Promise<void>;
147
+ }
148
+ export declare class TransportSelector implements ITransportSelector {
149
+ #private;
150
+ constructor(opts: {
151
+ dialer: TransportDialer;
152
+ logger: Logger;
153
+ directDialTimeoutMs?: number;
154
+ });
155
+ dial(assignment: SessionAssignment, opts: TransportDialOptions): Promise<TransportResult>;
156
+ }
157
+ /**
158
+ * In-process transport selector stub. Returns a configurable canned result with
159
+ * no network access. Selected by the composition root when CELLO_ENV is
160
+ * 'local' | 'test', so the daemon's session establishment path is wired and does
161
+ * not crash with "adapter not wired" (AC-010). Defaults to a successful relay
162
+ * selection (the conservative, always-available transport).
163
+ */
164
+ export declare class LocalTransportSelectorStub implements ITransportSelector {
165
+ #private;
166
+ constructor(result?: TransportResult);
167
+ dial(_assignment: SessionAssignment, _opts: TransportDialOptions): Promise<TransportResult>;
168
+ /** Test/composition driver: set the canned result. */
169
+ setResult(result: TransportResult): void;
170
+ }
171
+ export type AdvertisedAddress = {
172
+ kind: "direct";
173
+ addr: string;
174
+ } | {
175
+ kind: "relay";
176
+ addr: string;
177
+ };
178
+ /**
179
+ * Decide what address a node advertises in its SessionAssignment negotiation,
180
+ * given its AutoNAT dialability and its relay circuit address.
181
+ *
182
+ * dialable with a publicAddr → advertise the direct address (transport_mode will
183
+ * be 'direct'). Otherwise (behind NAT, or AutoNAT unavailable because the
184
+ * directory is reconnecting) → advertise the relay circuit address (conservative
185
+ * fallback; transport_mode will be 'relay'). This decision is made once at
186
+ * session establishment and is immutable for that session (AC-019).
187
+ */
188
+ export declare function selectAdvertisedAddress(dialability: Dialability, relayCircuitAddr: string): AdvertisedAddress;
189
+ //# sourceMappingURL=transport-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-selector.d.ts","sourceRoot":"","sources":["../src/transport-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAI7D,eAAO,MAAM,eAAe;IAC1B,uFAAuF;;IAEvF,wFAAwF;;IAExF,4DAA4D;;IAE5D,sEAAsE;;CAE9D,CAAC;AAMX,yFAAyF;AACzF,eAAO,MAAM,8BAA8B,OAAO,CAAC;AAInD;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GAC3D;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,OAAO,eAAe,CAAC,0BAA0B,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEN,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;CACvB;AAID;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC3F;AAID;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,iBAAiB,CAAA;CAAE,GAC3C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,GAAG,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAC9E;AAID;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,8EAA8E;IAC9E,SAAS,CAAC,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E;;;;OAIG;IACH,gBAAgB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACxD,kFAAkF;IAClF,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAcD,qBAAa,iBAAkB,YAAW,kBAAkB;;gBAK9C,IAAI,EAAE;QAChB,MAAM,EAAE,eAAe,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B;IAMK,IAAI,CACR,UAAU,EAAE,iBAAiB,EAC7B,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,eAAe,CAAC;CAwF5B;AAID;;;;;;GAMG;AACH,qBAAa,0BAA2B,YAAW,kBAAkB;;gBAGvD,MAAM,CAAC,EAAE,eAAe;IAK9B,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAIjG,sDAAsD;IACtD,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;CAGzC;AAID,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpC;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,MAAM,GACvB,iBAAiB,CAKnB"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * CELLO Daemon — transport-selector.ts (CELLO-M7-TRANSPORT-001)
3
+ *
4
+ * Direct-P2P-by-default transport selection with relay fallback and a best-effort
5
+ * dcutr hole-punch upgrade.
6
+ *
7
+ * ─── Specification (Phase S) ──────────────────────────────────────────────────
8
+ * Given a FROST-signed SessionAssignment (produced by WIRE-001), choose how to
9
+ * dial the counterparty's session node:
10
+ * 1. Read transport_mode from the assignment. This is the ONLY authority for
11
+ * the dial strategy (SI-001 / AC-015) — NEVER infer mode from address format.
12
+ * 2. transport_mode === 'direct' → dial counterparty_session_addrs directly.
13
+ * 3. direct dial fails OR transport_mode === 'relay' → dial the relay circuit
14
+ * address (from the daemon's relay registry, independent of transport_mode —
15
+ * AC-006).
16
+ * 4. After a relay connection is established → attempt a dcutr hole-punch upgrade
17
+ * (non-blocking, best-effort). Failure is non-fatal (SI-003).
18
+ * 5. If BOTH direct and relay fail → terminal 'relay_fallback_also_failed' (AC-008).
19
+ *
20
+ * Error code registry (fixed strings — also in CONTEXT.md):
21
+ * 'autonat_unavailable' — internal; no probers reachable.
22
+ * 'direct_dial_failed_falling_back_to_relay' — WARN; intermediate, non-fatal.
23
+ * 'relay_fallback_also_failed' — terminal session failure.
24
+ * 'dcutr_upgrade_failed' — DEBUG; non-fatal.
25
+ *
26
+ * ─── Pseudocode (Phase P) ─────────────────────────────────────────────────────
27
+ * dial(assignment, { correlationId }):
28
+ * mode = assignment.transport_mode // authoritative
29
+ * sessionId = hex(assignment.session_id)
30
+ * peerId = assignment.counterparty_session_peer_id
31
+ * directAddrs = assignment.counterparty_session_addrs ?? []
32
+ * if mode === 'direct':
33
+ * try { dialer.dialDirect(peerId, directAddrs); log mode.selected('direct'); return {ok, 'direct'} }
34
+ * catch e { log direct_dial.failed(WARN, e.message) } // fall through
35
+ * try { dialer.dialRelay(dialer.relayCircuitAddr(assignment), peerId) }
36
+ * catch { return {ok:false, 'relay_fallback_also_failed', guidance} } // NO mode.selected
37
+ * log mode.selected('relay')
38
+ * dcutrSettled = (async () => {
39
+ * try { dialer.attemptDcutr(peerId); log dcutr.upgraded(INFO); return true }
40
+ * catch e { log dcutr.failed(DEBUG, e.message); return false } })()
41
+ * return {ok:true, 'relay', dcutrSettled} // dial does NOT await dcutr
42
+ */
43
+ // ─── Error codes (fixed) ──────────────────────────────────────────────────────
44
+ export const TRANSPORT_ERROR = {
45
+ /** Internal: AutoNAT could not run because no directory-node probers are reachable. */
46
+ AUTONAT_UNAVAILABLE: "autonat_unavailable",
47
+ /** WARN-level intermediate, non-fatal: direct dial failed, relay fallback initiated. */
48
+ DIRECT_DIAL_FAILED_FALLING_BACK: "direct_dial_failed_falling_back_to_relay",
49
+ /** Terminal: both direct dial and relay fallback failed. */
50
+ RELAY_FALLBACK_ALSO_FAILED: "relay_fallback_also_failed",
51
+ /** DEBUG-level non-fatal: dcutr hole-punch upgrade attempt failed. */
52
+ DCUTR_UPGRADE_FAILED: "dcutr_upgrade_failed",
53
+ };
54
+ const RELAY_FALLBACK_GUIDANCE = "Both direct P2P connection and relay fallback failed. The counterparty may be " +
55
+ "offline or unreachable from any path. Try again later, or check network connectivity.";
56
+ /** Default direct-dial timeout (ms). Not a user-facing option (implementation_notes). */
57
+ export const DEFAULT_DIRECT_DIAL_TIMEOUT_MS = 5000;
58
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
59
+ function sessionIdHex(assignment) {
60
+ return Buffer.from(assignment.session_id).toString("hex");
61
+ }
62
+ function errMessage(err) {
63
+ return err instanceof Error ? err.message : String(err);
64
+ }
65
+ // ─── TransportSelector (real) ─────────────────────────────────────────────────
66
+ export class TransportSelector {
67
+ #dialer;
68
+ #logger;
69
+ #directDialTimeoutMs;
70
+ constructor(opts) {
71
+ this.#dialer = opts.dialer;
72
+ this.#logger = opts.logger;
73
+ this.#directDialTimeoutMs = opts.directDialTimeoutMs ?? DEFAULT_DIRECT_DIAL_TIMEOUT_MS;
74
+ }
75
+ async dial(assignment, opts) {
76
+ const sessionId = sessionIdHex(assignment);
77
+ const { correlationId } = opts;
78
+ const peerId = assignment.counterparty_session_peer_id;
79
+ const directAddrs = assignment.counterparty_session_addrs ?? [];
80
+ // transport_mode is the ONLY authority (SI-001 / AC-015). Never inferred.
81
+ const mode = assignment.transport_mode;
82
+ // Step 1: direct dial when transport_mode === 'direct'.
83
+ if (mode === "direct") {
84
+ try {
85
+ await this.#dialer.dialDirect(peerId, directAddrs, this.#directDialTimeoutMs);
86
+ this.#logger.info("session.transport.mode.selected", {
87
+ sessionId,
88
+ mode: "direct",
89
+ correlationId,
90
+ });
91
+ return { ok: true, mode: "direct" };
92
+ }
93
+ catch (err) {
94
+ // Recoverable: log WARN and fall through to relay fallback (AC-006).
95
+ // failureReason is the actual error message — never ${error}.
96
+ this.#logger.warn("session.transport.direct_dial.failed", {
97
+ sessionId,
98
+ counterpartyPeerId: peerId,
99
+ failureReason: errMessage(err),
100
+ reason: TRANSPORT_ERROR.DIRECT_DIAL_FAILED_FALLING_BACK,
101
+ correlationId,
102
+ });
103
+ }
104
+ }
105
+ // Step 2: relay fallback (transport_mode === 'relay', or direct dial failed).
106
+ try {
107
+ const relayAddr = this.#dialer.relayCircuitAddr(assignment);
108
+ await this.#dialer.dialRelay(relayAddr, peerId);
109
+ }
110
+ catch (err) {
111
+ // Terminal: both direct and relay failed. session.transport.mode.selected is
112
+ // NOT logged (no mode was selected — AC-008). We DO log the underlying relay
113
+ // failure reason at WARN so the on-call engineer can diagnose why the
114
+ // fallback failed (never swallow the exception silently).
115
+ this.#logger.warn("session.transport.relay_fallback.failed", {
116
+ sessionId,
117
+ counterpartyPeerId: peerId,
118
+ failureReason: errMessage(err),
119
+ reason: TRANSPORT_ERROR.RELAY_FALLBACK_ALSO_FAILED,
120
+ correlationId,
121
+ });
122
+ return {
123
+ ok: false,
124
+ reason: TRANSPORT_ERROR.RELAY_FALLBACK_ALSO_FAILED,
125
+ guidance: RELAY_FALLBACK_GUIDANCE,
126
+ };
127
+ }
128
+ this.#logger.info("session.transport.mode.selected", {
129
+ sessionId,
130
+ mode: "relay",
131
+ correlationId,
132
+ });
133
+ // Step 3: best-effort dcutr upgrade — non-blocking. The session is usable
134
+ // immediately upon relay connection (AC-007); dcutr runs in the background.
135
+ const dcutrSettled = this.#attemptDcutr(peerId, sessionId, correlationId);
136
+ return { ok: true, mode: "relay", dcutrSettled };
137
+ }
138
+ async #attemptDcutr(peerId, sessionId, correlationId) {
139
+ try {
140
+ await this.#dialer.attemptDcutr(peerId);
141
+ this.#logger.info("session.transport.dcutr.upgraded", { sessionId, correlationId });
142
+ return true;
143
+ }
144
+ catch (err) {
145
+ // Non-fatal (SI-003). DEBUG because dcutr failure is expected in many
146
+ // network topologies. failureReason is error.message — never ${error}.
147
+ this.#logger.debug("session.transport.dcutr.failed", {
148
+ sessionId,
149
+ failureReason: errMessage(err),
150
+ reason: TRANSPORT_ERROR.DCUTR_UPGRADE_FAILED,
151
+ correlationId,
152
+ });
153
+ return false;
154
+ }
155
+ }
156
+ }
157
+ // ─── LocalTransportSelectorStub ───────────────────────────────────────────────
158
+ /**
159
+ * In-process transport selector stub. Returns a configurable canned result with
160
+ * no network access. Selected by the composition root when CELLO_ENV is
161
+ * 'local' | 'test', so the daemon's session establishment path is wired and does
162
+ * not crash with "adapter not wired" (AC-010). Defaults to a successful relay
163
+ * selection (the conservative, always-available transport).
164
+ */
165
+ export class LocalTransportSelectorStub {
166
+ #result;
167
+ constructor(result) {
168
+ this.#result =
169
+ result ?? { ok: true, mode: "relay", dcutrSettled: Promise.resolve(false) };
170
+ }
171
+ async dial(_assignment, _opts) {
172
+ return this.#result;
173
+ }
174
+ /** Test/composition driver: set the canned result. */
175
+ setResult(result) {
176
+ this.#result = result;
177
+ }
178
+ }
179
+ /**
180
+ * Decide what address a node advertises in its SessionAssignment negotiation,
181
+ * given its AutoNAT dialability and its relay circuit address.
182
+ *
183
+ * dialable with a publicAddr → advertise the direct address (transport_mode will
184
+ * be 'direct'). Otherwise (behind NAT, or AutoNAT unavailable because the
185
+ * directory is reconnecting) → advertise the relay circuit address (conservative
186
+ * fallback; transport_mode will be 'relay'). This decision is made once at
187
+ * session establishment and is immutable for that session (AC-019).
188
+ */
189
+ export function selectAdvertisedAddress(dialability, relayCircuitAddr) {
190
+ if (dialability.dialable && dialability.publicAddr) {
191
+ return { kind: "direct", addr: dialability.publicAddr };
192
+ }
193
+ return { kind: "relay", addr: relayCircuitAddr };
194
+ }
195
+ //# sourceMappingURL=transport-selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-selector.js","sourceRoot":"","sources":["../src/transport-selector.ts"],"names":[],"mappings":"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"}