@aster-rpc/aster 0.1.2

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 (233) hide show
  1. package/dist/capabilities.d.ts +26 -0
  2. package/dist/capabilities.d.ts.map +1 -0
  3. package/dist/capabilities.js +29 -0
  4. package/dist/capabilities.js.map +1 -0
  5. package/dist/client.d.ts +65 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +108 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/codec.d.ts +156 -0
  10. package/dist/codec.d.ts.map +1 -0
  11. package/dist/codec.js +477 -0
  12. package/dist/codec.js.map +1 -0
  13. package/dist/config.d.ts +102 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +454 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/contract/identity.d.ts +115 -0
  18. package/dist/contract/identity.d.ts.map +1 -0
  19. package/dist/contract/identity.js +188 -0
  20. package/dist/contract/identity.js.map +1 -0
  21. package/dist/contract/manifest.d.ts +77 -0
  22. package/dist/contract/manifest.d.ts.map +1 -0
  23. package/dist/contract/manifest.js +127 -0
  24. package/dist/contract/manifest.js.map +1 -0
  25. package/dist/contract/publication.d.ts +71 -0
  26. package/dist/contract/publication.d.ts.map +1 -0
  27. package/dist/contract/publication.js +85 -0
  28. package/dist/contract/publication.js.map +1 -0
  29. package/dist/decorators.d.ts +139 -0
  30. package/dist/decorators.d.ts.map +1 -0
  31. package/dist/decorators.js +175 -0
  32. package/dist/decorators.js.map +1 -0
  33. package/dist/dynamic.d.ts +61 -0
  34. package/dist/dynamic.d.ts.map +1 -0
  35. package/dist/dynamic.js +147 -0
  36. package/dist/dynamic.js.map +1 -0
  37. package/dist/framing.d.ts +74 -0
  38. package/dist/framing.d.ts.map +1 -0
  39. package/dist/framing.js +162 -0
  40. package/dist/framing.js.map +1 -0
  41. package/dist/health.d.ts +127 -0
  42. package/dist/health.d.ts.map +1 -0
  43. package/dist/health.js +236 -0
  44. package/dist/health.js.map +1 -0
  45. package/dist/index.d.ts +67 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +101 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/interceptors/audit.d.ts +25 -0
  50. package/dist/interceptors/audit.d.ts.map +1 -0
  51. package/dist/interceptors/audit.js +46 -0
  52. package/dist/interceptors/audit.js.map +1 -0
  53. package/dist/interceptors/auth.d.ts +13 -0
  54. package/dist/interceptors/auth.d.ts.map +1 -0
  55. package/dist/interceptors/auth.js +34 -0
  56. package/dist/interceptors/auth.js.map +1 -0
  57. package/dist/interceptors/base.d.ts +74 -0
  58. package/dist/interceptors/base.d.ts.map +1 -0
  59. package/dist/interceptors/base.js +103 -0
  60. package/dist/interceptors/base.js.map +1 -0
  61. package/dist/interceptors/capability.d.ts +16 -0
  62. package/dist/interceptors/capability.d.ts.map +1 -0
  63. package/dist/interceptors/capability.js +63 -0
  64. package/dist/interceptors/capability.js.map +1 -0
  65. package/dist/interceptors/circuit-breaker.d.ts +40 -0
  66. package/dist/interceptors/circuit-breaker.d.ts.map +1 -0
  67. package/dist/interceptors/circuit-breaker.js +91 -0
  68. package/dist/interceptors/circuit-breaker.js.map +1 -0
  69. package/dist/interceptors/compression.d.ts +11 -0
  70. package/dist/interceptors/compression.d.ts.map +1 -0
  71. package/dist/interceptors/compression.js +12 -0
  72. package/dist/interceptors/compression.js.map +1 -0
  73. package/dist/interceptors/deadline.d.ts +12 -0
  74. package/dist/interceptors/deadline.d.ts.map +1 -0
  75. package/dist/interceptors/deadline.js +28 -0
  76. package/dist/interceptors/deadline.js.map +1 -0
  77. package/dist/interceptors/metrics.d.ts +43 -0
  78. package/dist/interceptors/metrics.d.ts.map +1 -0
  79. package/dist/interceptors/metrics.js +132 -0
  80. package/dist/interceptors/metrics.js.map +1 -0
  81. package/dist/interceptors/rate-limit.d.ts +24 -0
  82. package/dist/interceptors/rate-limit.d.ts.map +1 -0
  83. package/dist/interceptors/rate-limit.js +84 -0
  84. package/dist/interceptors/rate-limit.js.map +1 -0
  85. package/dist/interceptors/retry.d.ts +25 -0
  86. package/dist/interceptors/retry.d.ts.map +1 -0
  87. package/dist/interceptors/retry.js +55 -0
  88. package/dist/interceptors/retry.js.map +1 -0
  89. package/dist/limits.d.ts +77 -0
  90. package/dist/limits.d.ts.map +1 -0
  91. package/dist/limits.js +137 -0
  92. package/dist/limits.js.map +1 -0
  93. package/dist/logging.d.ts +40 -0
  94. package/dist/logging.d.ts.map +1 -0
  95. package/dist/logging.js +92 -0
  96. package/dist/logging.js.map +1 -0
  97. package/dist/metadata.d.ts +14 -0
  98. package/dist/metadata.d.ts.map +1 -0
  99. package/dist/metadata.js +68 -0
  100. package/dist/metadata.js.map +1 -0
  101. package/dist/metrics.d.ts +40 -0
  102. package/dist/metrics.d.ts.map +1 -0
  103. package/dist/metrics.js +92 -0
  104. package/dist/metrics.js.map +1 -0
  105. package/dist/peer-store.d.ts +53 -0
  106. package/dist/peer-store.d.ts.map +1 -0
  107. package/dist/peer-store.js +105 -0
  108. package/dist/peer-store.js.map +1 -0
  109. package/dist/protocol.d.ts +44 -0
  110. package/dist/protocol.d.ts.map +1 -0
  111. package/dist/protocol.js +59 -0
  112. package/dist/protocol.js.map +1 -0
  113. package/dist/registration.d.ts +81 -0
  114. package/dist/registration.d.ts.map +1 -0
  115. package/dist/registration.js +161 -0
  116. package/dist/registration.js.map +1 -0
  117. package/dist/registry/acl.d.ts +57 -0
  118. package/dist/registry/acl.d.ts.map +1 -0
  119. package/dist/registry/acl.js +104 -0
  120. package/dist/registry/acl.js.map +1 -0
  121. package/dist/registry/client.d.ts +70 -0
  122. package/dist/registry/client.d.ts.map +1 -0
  123. package/dist/registry/client.js +115 -0
  124. package/dist/registry/client.js.map +1 -0
  125. package/dist/registry/gossip.d.ts +43 -0
  126. package/dist/registry/gossip.d.ts.map +1 -0
  127. package/dist/registry/gossip.js +102 -0
  128. package/dist/registry/gossip.js.map +1 -0
  129. package/dist/registry/keys.d.ts +25 -0
  130. package/dist/registry/keys.d.ts.map +1 -0
  131. package/dist/registry/keys.js +47 -0
  132. package/dist/registry/keys.js.map +1 -0
  133. package/dist/registry/models.d.ts +80 -0
  134. package/dist/registry/models.d.ts.map +1 -0
  135. package/dist/registry/models.js +35 -0
  136. package/dist/registry/models.js.map +1 -0
  137. package/dist/registry/publisher.d.ts +65 -0
  138. package/dist/registry/publisher.d.ts.map +1 -0
  139. package/dist/registry/publisher.js +164 -0
  140. package/dist/registry/publisher.js.map +1 -0
  141. package/dist/runtime.d.ts +267 -0
  142. package/dist/runtime.d.ts.map +1 -0
  143. package/dist/runtime.js +1366 -0
  144. package/dist/runtime.js.map +1 -0
  145. package/dist/server.d.ts +100 -0
  146. package/dist/server.d.ts.map +1 -0
  147. package/dist/server.js +511 -0
  148. package/dist/server.js.map +1 -0
  149. package/dist/service.d.ts +72 -0
  150. package/dist/service.d.ts.map +1 -0
  151. package/dist/service.js +98 -0
  152. package/dist/service.js.map +1 -0
  153. package/dist/session.d.ts +64 -0
  154. package/dist/session.d.ts.map +1 -0
  155. package/dist/session.js +350 -0
  156. package/dist/session.js.map +1 -0
  157. package/dist/status.d.ts +113 -0
  158. package/dist/status.d.ts.map +1 -0
  159. package/dist/status.js +206 -0
  160. package/dist/status.js.map +1 -0
  161. package/dist/transport/base.d.ts +46 -0
  162. package/dist/transport/base.d.ts.map +1 -0
  163. package/dist/transport/base.js +10 -0
  164. package/dist/transport/base.js.map +1 -0
  165. package/dist/transport/iroh.d.ts +45 -0
  166. package/dist/transport/iroh.d.ts.map +1 -0
  167. package/dist/transport/iroh.js +225 -0
  168. package/dist/transport/iroh.js.map +1 -0
  169. package/dist/transport/local.d.ts +48 -0
  170. package/dist/transport/local.d.ts.map +1 -0
  171. package/dist/transport/local.js +139 -0
  172. package/dist/transport/local.js.map +1 -0
  173. package/dist/trust/admission.d.ts +60 -0
  174. package/dist/trust/admission.d.ts.map +1 -0
  175. package/dist/trust/admission.js +149 -0
  176. package/dist/trust/admission.js.map +1 -0
  177. package/dist/trust/bootstrap.d.ts +109 -0
  178. package/dist/trust/bootstrap.d.ts.map +1 -0
  179. package/dist/trust/bootstrap.js +311 -0
  180. package/dist/trust/bootstrap.js.map +1 -0
  181. package/dist/trust/clock.d.ts +93 -0
  182. package/dist/trust/clock.d.ts.map +1 -0
  183. package/dist/trust/clock.js +154 -0
  184. package/dist/trust/clock.js.map +1 -0
  185. package/dist/trust/consumer.d.ts +139 -0
  186. package/dist/trust/consumer.d.ts.map +1 -0
  187. package/dist/trust/consumer.js +323 -0
  188. package/dist/trust/consumer.js.map +1 -0
  189. package/dist/trust/credentials.d.ts +98 -0
  190. package/dist/trust/credentials.d.ts.map +1 -0
  191. package/dist/trust/credentials.js +250 -0
  192. package/dist/trust/credentials.js.map +1 -0
  193. package/dist/trust/delegated.d.ts +118 -0
  194. package/dist/trust/delegated.d.ts.map +1 -0
  195. package/dist/trust/delegated.js +292 -0
  196. package/dist/trust/delegated.js.map +1 -0
  197. package/dist/trust/gossip.d.ts +146 -0
  198. package/dist/trust/gossip.d.ts.map +1 -0
  199. package/dist/trust/gossip.js +334 -0
  200. package/dist/trust/gossip.js.map +1 -0
  201. package/dist/trust/hooks.d.ts +84 -0
  202. package/dist/trust/hooks.d.ts.map +1 -0
  203. package/dist/trust/hooks.js +125 -0
  204. package/dist/trust/hooks.js.map +1 -0
  205. package/dist/trust/iid.d.ts +65 -0
  206. package/dist/trust/iid.d.ts.map +1 -0
  207. package/dist/trust/iid.js +104 -0
  208. package/dist/trust/iid.js.map +1 -0
  209. package/dist/trust/mesh.d.ts +43 -0
  210. package/dist/trust/mesh.d.ts.map +1 -0
  211. package/dist/trust/mesh.js +105 -0
  212. package/dist/trust/mesh.js.map +1 -0
  213. package/dist/trust/nonce.d.ts +39 -0
  214. package/dist/trust/nonce.d.ts.map +1 -0
  215. package/dist/trust/nonce.js +46 -0
  216. package/dist/trust/nonce.js.map +1 -0
  217. package/dist/trust/producer.d.ts +80 -0
  218. package/dist/trust/producer.d.ts.map +1 -0
  219. package/dist/trust/producer.js +151 -0
  220. package/dist/trust/producer.js.map +1 -0
  221. package/dist/trust/rcan.d.ts +29 -0
  222. package/dist/trust/rcan.d.ts.map +1 -0
  223. package/dist/trust/rcan.js +57 -0
  224. package/dist/trust/rcan.js.map +1 -0
  225. package/dist/types.d.ts +57 -0
  226. package/dist/types.d.ts.map +1 -0
  227. package/dist/types.js +50 -0
  228. package/dist/types.js.map +1 -0
  229. package/dist/xlang.d.ts +26 -0
  230. package/dist/xlang.d.ts.map +1 -0
  231. package/dist/xlang.js +55 -0
  232. package/dist/xlang.js.map +1 -0
  233. package/package.json +59 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Producer mesh bootstrap -- founding node + join.
3
+ *
4
+ * Spec reference: Aster-trust-spec.md S2.1, S2.5. Plan: ASTER_PLAN.md S14.5.
5
+ *
6
+ * Two startup modes:
7
+ *
8
+ * startFoundingNode()
9
+ * The first producer in a new mesh. Generates a random 32-byte salt, derives
10
+ * the gossip topic, initializes MeshState, and returns it for the caller.
11
+ *
12
+ * joinMesh()
13
+ * A subsequent producer. Builds an AdmissionRequest from its credential.
14
+ * The caller dials the bootstrap peer and sends the request, then calls
15
+ * applyAdmissionResponse() with the result.
16
+ *
17
+ * handleAdmissionRpc()
18
+ * Server-side handler: parses a request, runs offline admission checks,
19
+ * and returns an AdmissionResponse.
20
+ */
21
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ import { homedir } from 'node:os';
24
+ import { randomBytes } from 'node:crypto';
25
+ import { MeshState, saveMeshState } from './mesh.js';
26
+ import { deriveGossipTopic } from './gossip.js';
27
+ import { hexToBytes, bytesToHex } from './credentials.js';
28
+ import { checkOffline } from './admission.js';
29
+ // ── Internal helpers ────────────────────────────────────────────────────────
30
+ const DEFAULT_STATE_DIR = join(homedir(), '.aster');
31
+ function stateDir(config) {
32
+ return config?.stateDir ?? process.env.ASTER_MESH_STATE_DIR ?? DEFAULT_STATE_DIR;
33
+ }
34
+ function statePath(name, config) {
35
+ return join(stateDir(config), name);
36
+ }
37
+ function ensureStateDir(config) {
38
+ const dir = stateDir(config);
39
+ if (!existsSync(dir)) {
40
+ mkdirSync(dir, { recursive: true });
41
+ }
42
+ }
43
+ /**
44
+ * Load an EnrollmentCredential from a JSON file.
45
+ * Path resolved from argument -> ASTER_ENROLLMENT env var.
46
+ */
47
+ function loadEnrollmentCredential(path) {
48
+ const envPath = path ?? process.env.ASTER_ENROLLMENT;
49
+ if (!envPath) {
50
+ throw new Error('Set ASTER_ENROLLMENT to the path of your enrollment credential JSON file');
51
+ }
52
+ const raw = readFileSync(envPath, 'utf-8');
53
+ const d = JSON.parse(raw);
54
+ return {
55
+ endpointId: d.endpoint_id,
56
+ rootPubkey: d.root_pubkey, // already hex
57
+ expiresAt: Number(d.expires_at),
58
+ attributes: d.attributes ?? {},
59
+ signature: d.signature ?? '',
60
+ };
61
+ }
62
+ /**
63
+ * Load or generate 32-byte mesh salt.
64
+ * Persists to stateDir/mesh_salt for crash recovery.
65
+ */
66
+ function loadOrGenerateSalt(config) {
67
+ const saltPath = statePath('mesh_salt', config);
68
+ ensureStateDir(config);
69
+ if (existsSync(saltPath)) {
70
+ const buf = readFileSync(saltPath);
71
+ if (buf.length !== 32) {
72
+ throw new Error(`mesh_salt at ${saltPath} is ${buf.length} bytes; expected 32`);
73
+ }
74
+ return new Uint8Array(buf);
75
+ }
76
+ const salt = randomBytes(32);
77
+ writeFileSync(saltPath, salt);
78
+ return new Uint8Array(salt);
79
+ }
80
+ /**
81
+ * Persist a MeshState to stateDir/mesh_state.json (atomic rename).
82
+ */
83
+ function persistMeshState(state, config) {
84
+ const path = statePath('mesh_state.json', config);
85
+ saveMeshState(state, path);
86
+ }
87
+ // ── Founding node ───────────────────────────────────────────────────────────
88
+ /**
89
+ * Start the founding node of a new producer mesh.
90
+ *
91
+ * Steps (S2.1):
92
+ * 1. Load credential from JSON file.
93
+ * 2. Verify credential offline.
94
+ * 3. Generate or load 32-byte salt.
95
+ * 4. Derive gossip topic.
96
+ * 5. Create MeshState with self as only accepted producer.
97
+ * 6. Persist state.
98
+ *
99
+ * @returns The initialized MeshState.
100
+ */
101
+ export async function startFoundingNode(enrollmentPath, config) {
102
+ // 1. Load credential
103
+ const cred = loadEnrollmentCredential(enrollmentPath);
104
+ // 2. Verify offline
105
+ const result = await checkOffline(cred, cred.endpointId);
106
+ if (!result.admitted) {
107
+ throw new Error(`Founding node credential invalid: ${result.reason}`);
108
+ }
109
+ // 3. Salt
110
+ const salt = loadOrGenerateSalt(config);
111
+ // 4. Topic derivation
112
+ await deriveGossipTopic(hexToBytes(cred.rootPubkey), salt); // validates topic derivation
113
+ // 5. MeshState -- self is the only accepted producer
114
+ const state = new MeshState();
115
+ state.addPeer(cred.endpointId);
116
+ // 6. Persist
117
+ persistMeshState(state, config);
118
+ return state;
119
+ }
120
+ // ── Join mesh ───────────────────────────────────────────────────────────────
121
+ /**
122
+ * Build an AdmissionRequest from a credential for joining an existing mesh.
123
+ *
124
+ * The caller should send this request to the bootstrap peer over
125
+ * the aster.producer_admission ALPN, then call applyAdmissionResponse()
126
+ * with the result.
127
+ */
128
+ export function joinMesh(credential, iidToken) {
129
+ const credJson = JSON.stringify({
130
+ endpoint_id: credential.endpointId,
131
+ root_pubkey: credential.rootPubkey,
132
+ expires_at: credential.expiresAt,
133
+ attributes: credential.attributes,
134
+ signature: credential.signature,
135
+ });
136
+ return {
137
+ credentialJson: credJson,
138
+ iidToken,
139
+ };
140
+ }
141
+ // ── Apply admission response ────────────────────────────────────────────────
142
+ /**
143
+ * Finalize MeshState after receiving a successful AdmissionResponse.
144
+ *
145
+ * @param response The AdmissionResponse from the bootstrap peer.
146
+ * @param ownEndpointId This node's endpoint ID.
147
+ * @param rootPubkey The root public key (raw bytes) for topic derivation.
148
+ * @returns Initialized MeshState ready for gossip subscription.
149
+ * @throws If response.accepted is false.
150
+ */
151
+ export async function applyAdmissionResponse(response, ownEndpointId, rootPubkey) {
152
+ if (!response.accepted) {
153
+ throw new Error(`Admission refused: ${response.reason ?? '(no reason provided)'}`);
154
+ }
155
+ const salt = hexToBytes(response.salt);
156
+ await deriveGossipTopic(rootPubkey, salt); // validates topic derivation
157
+ const state = new MeshState();
158
+ // Add all accepted producers + self
159
+ for (const peerId of response.acceptedProducers) {
160
+ state.addPeer(peerId);
161
+ }
162
+ state.addPeer(ownEndpointId);
163
+ return state;
164
+ }
165
+ // ── Server-side admission RPC ───────────────────────────────────────────────
166
+ /**
167
+ * Server-side handler for aster.producer_admission ALPN.
168
+ *
169
+ * Parses an AdmissionRequest, runs offline admission checks, and returns
170
+ * an AdmissionResponse. On success, the peer is added to ownState.
171
+ *
172
+ * @param requestJson JSON-serialized credential (the credentialJson field
173
+ * from AdmissionRequest, or a raw credential JSON).
174
+ * @param ownState The founding/accepting node's MeshState.
175
+ * @param ownRootPubkey Hex-encoded root public key this mesh trusts.
176
+ * @param config Optional BootstrapConfig.
177
+ * @returns AdmissionResponse (accepted or rejected with reason).
178
+ */
179
+ export async function handleAdmissionRpc(requestJson, ownState, ownRootPubkey, config) {
180
+ let cred;
181
+ try {
182
+ const d = JSON.parse(requestJson);
183
+ cred = {
184
+ endpointId: d.endpoint_id,
185
+ rootPubkey: d.root_pubkey,
186
+ expiresAt: Number(d.expires_at),
187
+ attributes: d.attributes ?? {},
188
+ signature: d.signature ?? '',
189
+ };
190
+ }
191
+ catch {
192
+ return {
193
+ accepted: false,
194
+ salt: '',
195
+ acceptedProducers: [],
196
+ reason: 'malformed request',
197
+ };
198
+ }
199
+ // Verify the credential's root_pubkey matches the mesh's trusted key
200
+ if (cred.rootPubkey !== ownRootPubkey) {
201
+ return {
202
+ accepted: false,
203
+ salt: '',
204
+ acceptedProducers: [],
205
+ reason: 'untrusted root key',
206
+ };
207
+ }
208
+ // Run offline admission checks (signature, expiry, endpoint_id match)
209
+ const result = await checkOffline(cred, cred.endpointId);
210
+ if (!result.admitted) {
211
+ return {
212
+ accepted: false,
213
+ salt: '',
214
+ acceptedProducers: [],
215
+ reason: result.reason ?? 'admission check failed',
216
+ };
217
+ }
218
+ // Accept: add peer to mesh state
219
+ ownState.addPeer(cred.endpointId);
220
+ persistMeshState(ownState, config);
221
+ // Load salt from state dir for response
222
+ let saltHex = '';
223
+ try {
224
+ const saltPath = statePath('mesh_salt', config);
225
+ if (existsSync(saltPath)) {
226
+ saltHex = bytesToHex(new Uint8Array(readFileSync(saltPath)));
227
+ }
228
+ }
229
+ catch {
230
+ // Salt unavailable -- ephemeral mesh
231
+ }
232
+ return {
233
+ accepted: true,
234
+ salt: saltHex,
235
+ acceptedProducers: ownState.allPeers(),
236
+ reason: '', // never leak reason on wire
237
+ };
238
+ }
239
+ // ── Per-connection handler ──────────────────────────────────────────────────
240
+ /**
241
+ * Handle one producer admission connection: read request, write response.
242
+ *
243
+ * @param conn An IrohConnection-like object with acceptBi() and remoteId().
244
+ * @param ownRootPubkey Hex-encoded root public key.
245
+ * @param ownState This node's MeshState; mutated on accept.
246
+ * @param config Optional BootstrapConfig.
247
+ * @returns The AdmissionResponse that was sent.
248
+ */
249
+ export async function handleProducerAdmissionConnection(conn, ownRootPubkey, ownState, config) {
250
+ const peerId = conn.remoteId();
251
+ try {
252
+ const [send, recv] = await conn.acceptBi();
253
+ const raw = await recv.readToEnd(64 * 1024);
254
+ if (raw.length === 0) {
255
+ const resp = {
256
+ accepted: false,
257
+ salt: '',
258
+ acceptedProducers: [],
259
+ reason: 'empty request',
260
+ };
261
+ return resp;
262
+ }
263
+ // Parse the AdmissionRequest wrapper and extract credential_json
264
+ let credJson;
265
+ try {
266
+ const wrapper = JSON.parse(new TextDecoder().decode(raw));
267
+ credJson = wrapper.credential_json ?? '';
268
+ }
269
+ catch {
270
+ // Back-compat: accept raw credential JSON as well
271
+ credJson = new TextDecoder().decode(raw);
272
+ }
273
+ const response = await handleAdmissionRpc(credJson, ownState, ownRootPubkey, config);
274
+ // Write response (strip reason -- oracle protection)
275
+ const wirePayload = {
276
+ accepted: response.accepted,
277
+ salt: response.salt,
278
+ accepted_producers: response.acceptedProducers,
279
+ reason: '', // oracle protection -- never leak on wire
280
+ };
281
+ await send.writeAll(new TextEncoder().encode(JSON.stringify(wirePayload)));
282
+ await send.finish();
283
+ return response;
284
+ }
285
+ catch (exc) {
286
+ return {
287
+ accepted: false,
288
+ salt: '',
289
+ acceptedProducers: [],
290
+ reason: `connection error from ${peerId}: ${exc}`,
291
+ };
292
+ }
293
+ }
294
+ // ── Ephemeral state ─────────────────────────────────────────────────────────
295
+ /**
296
+ * Build an in-memory MeshState for a standalone producer.
297
+ *
298
+ * Useful for demos, tests, and single-node setups. Generates a fresh random
299
+ * salt and an empty accepted-producer set with no persistence.
300
+ */
301
+ export async function makeEphemeralMeshState(rootPubkey) {
302
+ const salt = randomBytes(32);
303
+ const state = new MeshState();
304
+ // Derive topic if rootPubkey provided (for gossip subscription)
305
+ if (rootPubkey != null) {
306
+ const topicId = await deriveGossipTopic(rootPubkey, new Uint8Array(salt));
307
+ state.topicId = bytesToHex(topicId);
308
+ }
309
+ return state;
310
+ }
311
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/trust/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAsB9C,+EAA+E;AAE/E,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAEpD,SAAS,QAAQ,CAAC,MAAwB;IACxC,OAAO,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,iBAAiB,CAAC;AACnF,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,MAAwB;IACvD,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,cAAc,CAAC,MAAwB;IAC9C,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,wBAAwB,CAAC,IAAa;IAC7C,MAAM,OAAO,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACrD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO;QACL,UAAU,EAAE,CAAC,CAAC,WAAW;QACzB,UAAU,EAAE,CAAC,CAAC,WAAW,EAAI,cAAc;QAC3C,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;QAC/B,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;QAC9B,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE;KAC7B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,MAAwB;IAClD,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAChD,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,gBAAgB,QAAQ,OAAO,GAAG,CAAC,MAAM,qBAAqB,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9B,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,KAAgB,EAAE,MAAwB;IAClE,MAAM,IAAI,GAAG,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;IAClD,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,+EAA+E;AAE/E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,cAAsB,EACtB,MAAwB;IAExB,qBAAqB;IACrB,MAAM,IAAI,GAAG,wBAAwB,CAAC,cAAc,CAAC,CAAC;IAEtD,oBAAoB;IACpB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACzD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,qCAAqC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,UAAU;IACV,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAExC,sBAAsB;IACtB,MAAM,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,6BAA6B;IAEzF,qDAAqD;IACrD,MAAM,KAAK,GAAG,IAAI,SAAS,EAAE,CAAC;IAC9B,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAE/B,aAAa;IACb,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAEhC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CACtB,UAAgC,EAChC,QAAiB;IAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,WAAW,EAAE,UAAU,CAAC,UAAU;QAClC,WAAW,EAAE,UAAU,CAAC,UAAU;QAClC,UAAU,EAAE,UAAU,CAAC,SAAS;QAChC,UAAU,EAAE,UAAU,CAAC,UAAU;QACjC,SAAS,EAAE,UAAU,CAAC,SAAS;KAChC,CAAC,CAAC;IACH,OAAO;QACL,cAAc,EAAE,QAAQ;QACxB,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAA2B,EAC3B,aAAqB,EACrB,UAAsB;IAEtB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,sBAAsB,QAAQ,CAAC,MAAM,IAAI,sBAAsB,EAAE,CAClE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,6BAA6B;IAExE,MAAM,KAAK,GAAG,IAAI,SAAS,EAAE,CAAC;IAC9B,oCAAoC;IACpC,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QAChD,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAE7B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+EAA+E;AAE/E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,QAAmB,EACnB,aAAqB,EACrB,MAAwB;IAExB,IAAI,IAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAClC,IAAI,GAAG;YACL,UAAU,EAAE,CAAC,CAAC,WAAW;YACzB,UAAU,EAAE,CAAC,CAAC,WAAW;YACzB,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;YAC/B,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;YAC9B,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE;SAC7B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,EAAE;YACR,iBAAiB,EAAE,EAAE;YACrB,MAAM,EAAE,mBAAmB;SAC5B,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,IAAI,IAAI,CAAC,UAAU,KAAK,aAAa,EAAE,CAAC;QACtC,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,EAAE;YACR,iBAAiB,EAAE,EAAE;YACrB,MAAM,EAAE,oBAAoB;SAC7B,CAAC;IACJ,CAAC;IAED,sEAAsE;IACtE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACzD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,EAAE;YACR,iBAAiB,EAAE,EAAE;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,wBAAwB;SAClD,CAAC;IACJ,CAAC;IAED,iCAAiC;IACjC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEnC,wCAAwC;IACxC,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzB,OAAO,GAAG,UAAU,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;IACvC,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,OAAO;QACb,iBAAiB,EAAE,QAAQ,CAAC,QAAQ,EAAE;QACtC,MAAM,EAAE,EAAE,EAAG,4BAA4B;KAC1C,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACrD,IAGC,EACD,aAAqB,EACrB,QAAmB,EACnB,MAAwB;IAExB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QAC5C,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,GAAsB;gBAC9B,QAAQ,EAAE,KAAK;gBACf,IAAI,EAAE,EAAE;gBACR,iBAAiB,EAAE,EAAE;gBACrB,MAAM,EAAE,eAAe;aACxB,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,iEAAiE;QACjE,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1D,QAAQ,GAAG,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,kDAAkD;YAClD,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;QAErF,qDAAqD;QACrD,MAAM,WAAW,GAAG;YAClB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,kBAAkB,EAAE,QAAQ,CAAC,iBAAiB;YAC9C,MAAM,EAAE,EAAE,EAAG,0CAA0C;SACxD,CAAC;QACF,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QAEpB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,EAAE;YACR,iBAAiB,EAAE,EAAE;YACrB,MAAM,EAAE,yBAAyB,MAAM,KAAK,GAAG,EAAE;SAClD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,UAAuB;IAClE,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,IAAI,SAAS,EAAE,CAAC;IAC9B,gEAAgE;IAChE,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,UAAU,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1E,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Clock drift detection for mesh peers.
3
+ *
4
+ * Spec reference: Aster-trust-spec.md
5
+ *
6
+ * Detects excessive clock skew between mesh peers by comparing
7
+ * timestamps in gossip heartbeats. Peers with drift beyond
8
+ * the configured tolerance are isolated.
9
+ */
10
+ /** Clock drift configuration. */
11
+ export interface ClockDriftConfig {
12
+ /** Maximum allowed drift in milliseconds (default: 30 seconds). */
13
+ toleranceMs: number;
14
+ /** Grace period after mesh join before drift checks kick in (default: 60 seconds). */
15
+ gracePeriodMs: number;
16
+ /** Minimum number of peers required for mesh median computation (default: 3). */
17
+ minPeers: number;
18
+ }
19
+ /** Default clock drift config. */
20
+ export declare const DEFAULT_CLOCK_DRIFT_CONFIG: ClockDriftConfig;
21
+ /**
22
+ * Check if a peer's timestamp indicates excessive clock drift.
23
+ *
24
+ * @param peerTimestampMs - Epoch ms from the peer's heartbeat
25
+ * @param localTimestampMs - Local epoch ms (default: Date.now())
26
+ * @returns The drift in milliseconds (positive = peer is ahead, negative = behind)
27
+ */
28
+ export declare function computeDrift(peerTimestampMs: number, localTimestampMs?: number): number;
29
+ /**
30
+ * Check whether a peer should be isolated due to clock drift.
31
+ *
32
+ * @param driftMs - The computed drift (from computeDrift)
33
+ * @param meshJoinedAtMs - When we joined the mesh
34
+ * @param config - Drift configuration
35
+ * @returns true if the peer should be isolated
36
+ */
37
+ export declare function shouldIsolate(driftMs: number, meshJoinedAtMs: number, config?: ClockDriftConfig): boolean;
38
+ /**
39
+ * Clock drift tracker for multiple peers.
40
+ *
41
+ * Records per-peer clock offsets and tracks which peers have been
42
+ * isolated due to excessive drift.
43
+ */
44
+ export declare class ClockDriftTracker {
45
+ private offsets;
46
+ private isolated;
47
+ private meshJoinedAtMs;
48
+ private config;
49
+ constructor(meshJoinedAtMs?: number, config?: Partial<ClockDriftConfig>);
50
+ /**
51
+ * Update a peer's clock offset from a heartbeat.
52
+ *
53
+ * @returns true if the peer was newly isolated
54
+ */
55
+ update(peerEndpointId: string, peerTimestampMs: number): boolean;
56
+ /**
57
+ * Track a peer's clock offset from a received timestamp.
58
+ * Alias for update() — tracks the offset and returns true if peer was newly isolated.
59
+ */
60
+ trackOffset(peerEndpointId: string, peerTimestampMs: number): boolean;
61
+ /** Check if a peer is currently isolated. */
62
+ isIsolated(peerEndpointId: string): boolean;
63
+ /** Get drift for a peer in ms. */
64
+ getDrift(peerEndpointId: string): number | undefined;
65
+ /** All isolated peers. */
66
+ isolatedPeers(): string[];
67
+ /** Return a copy of the current peer offset map. */
68
+ peerOffsets(): Map<string, number>;
69
+ /**
70
+ * Compute the mesh median offset from all tracked peers.
71
+ *
72
+ * Returns undefined if fewer than minPeers peers are tracked
73
+ * (not enough data for meaningful drift detection).
74
+ *
75
+ * Uses median_high (higher-middle for even counts) for determinism.
76
+ */
77
+ meshMedianOffset(): number | undefined;
78
+ /**
79
+ * Check if this node's own clock appears to be the outlier.
80
+ *
81
+ * @param selfOffsetEstimate - now_ms - msg.epoch_ms computed when this node
82
+ * sends a message (i.e. ~0 if the clock is correct).
83
+ * @returns true if self is the outlier. Returns false during grace period or
84
+ * when there are too few peers.
85
+ */
86
+ selfInDrift(selfOffsetEstimate: number): boolean;
87
+ /**
88
+ * Remove a peer from the offset tracking table and isolation set.
89
+ * Called on Depart or lease expiry.
90
+ */
91
+ removePeer(endpointId: string): void;
92
+ }
93
+ //# sourceMappingURL=clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../src/trust/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,kCAAkC;AAClC,eAAO,MAAM,0BAA0B,EAAE,gBAIxC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,eAAe,EAAE,MAAM,EACvB,gBAAgB,SAAa,GAC5B,MAAM,CAER;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,gBAA6C,GACpD,OAAO,CAMT;AAiBD;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAmB;gBAErB,cAAc,SAAa,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAK3E;;;;OAIG;IACH,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO;IAiBhE;;;OAGG;IACH,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO;IAIrE,6CAA6C;IAC7C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAI3C,kCAAkC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD,0BAA0B;IAC1B,aAAa,IAAI,MAAM,EAAE;IAIzB,oDAAoD;IACpD,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAIlC;;;;;;;OAOG;IACH,gBAAgB,IAAI,MAAM,GAAG,SAAS;IAQtC;;;;;;;OAOG;IACH,WAAW,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO;IAUhD;;;OAGG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAIrC"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Clock drift detection for mesh peers.
3
+ *
4
+ * Spec reference: Aster-trust-spec.md
5
+ *
6
+ * Detects excessive clock skew between mesh peers by comparing
7
+ * timestamps in gossip heartbeats. Peers with drift beyond
8
+ * the configured tolerance are isolated.
9
+ */
10
+ /** Default clock drift config. */
11
+ export const DEFAULT_CLOCK_DRIFT_CONFIG = {
12
+ toleranceMs: 30_000,
13
+ gracePeriodMs: 60_000,
14
+ minPeers: 3,
15
+ };
16
+ /**
17
+ * Check if a peer's timestamp indicates excessive clock drift.
18
+ *
19
+ * @param peerTimestampMs - Epoch ms from the peer's heartbeat
20
+ * @param localTimestampMs - Local epoch ms (default: Date.now())
21
+ * @returns The drift in milliseconds (positive = peer is ahead, negative = behind)
22
+ */
23
+ export function computeDrift(peerTimestampMs, localTimestampMs = Date.now()) {
24
+ return peerTimestampMs - localTimestampMs;
25
+ }
26
+ /**
27
+ * Check whether a peer should be isolated due to clock drift.
28
+ *
29
+ * @param driftMs - The computed drift (from computeDrift)
30
+ * @param meshJoinedAtMs - When we joined the mesh
31
+ * @param config - Drift configuration
32
+ * @returns true if the peer should be isolated
33
+ */
34
+ export function shouldIsolate(driftMs, meshJoinedAtMs, config = DEFAULT_CLOCK_DRIFT_CONFIG) {
35
+ // Don't isolate during grace period
36
+ const elapsed = Date.now() - meshJoinedAtMs;
37
+ if (elapsed < config.gracePeriodMs)
38
+ return false;
39
+ return Math.abs(driftMs) > config.toleranceMs;
40
+ }
41
+ /**
42
+ * Compute the high median of an array of numbers.
43
+ *
44
+ * For odd-length arrays, returns the middle element.
45
+ * For even-length arrays, returns the higher of the two middle elements
46
+ * (matches Python's statistics.median_high for determinism).
47
+ *
48
+ * @param values - Array of numbers (must not be empty).
49
+ */
50
+ function medianHigh(values) {
51
+ const sorted = [...values].sort((a, b) => a - b);
52
+ const mid = Math.floor(sorted.length / 2);
53
+ return sorted[mid];
54
+ }
55
+ /**
56
+ * Clock drift tracker for multiple peers.
57
+ *
58
+ * Records per-peer clock offsets and tracks which peers have been
59
+ * isolated due to excessive drift.
60
+ */
61
+ export class ClockDriftTracker {
62
+ offsets = new Map(); // peer -> latest drift ms
63
+ isolated = new Set();
64
+ meshJoinedAtMs;
65
+ config;
66
+ constructor(meshJoinedAtMs = Date.now(), config) {
67
+ this.meshJoinedAtMs = meshJoinedAtMs;
68
+ this.config = { ...DEFAULT_CLOCK_DRIFT_CONFIG, ...config };
69
+ }
70
+ /**
71
+ * Update a peer's clock offset from a heartbeat.
72
+ *
73
+ * @returns true if the peer was newly isolated
74
+ */
75
+ update(peerEndpointId, peerTimestampMs) {
76
+ const drift = computeDrift(peerTimestampMs);
77
+ this.offsets.set(peerEndpointId, drift);
78
+ if (shouldIsolate(drift, this.meshJoinedAtMs, this.config)) {
79
+ if (!this.isolated.has(peerEndpointId)) {
80
+ this.isolated.add(peerEndpointId);
81
+ return true;
82
+ }
83
+ }
84
+ else {
85
+ // Peer recovered — remove from isolation
86
+ this.isolated.delete(peerEndpointId);
87
+ }
88
+ return false;
89
+ }
90
+ /**
91
+ * Track a peer's clock offset from a received timestamp.
92
+ * Alias for update() — tracks the offset and returns true if peer was newly isolated.
93
+ */
94
+ trackOffset(peerEndpointId, peerTimestampMs) {
95
+ return this.update(peerEndpointId, peerTimestampMs);
96
+ }
97
+ /** Check if a peer is currently isolated. */
98
+ isIsolated(peerEndpointId) {
99
+ return this.isolated.has(peerEndpointId);
100
+ }
101
+ /** Get drift for a peer in ms. */
102
+ getDrift(peerEndpointId) {
103
+ return this.offsets.get(peerEndpointId);
104
+ }
105
+ /** All isolated peers. */
106
+ isolatedPeers() {
107
+ return [...this.isolated];
108
+ }
109
+ /** Return a copy of the current peer offset map. */
110
+ peerOffsets() {
111
+ return new Map(this.offsets);
112
+ }
113
+ /**
114
+ * Compute the mesh median offset from all tracked peers.
115
+ *
116
+ * Returns undefined if fewer than minPeers peers are tracked
117
+ * (not enough data for meaningful drift detection).
118
+ *
119
+ * Uses median_high (higher-middle for even counts) for determinism.
120
+ */
121
+ meshMedianOffset() {
122
+ const values = [...this.offsets.values()];
123
+ if (values.length < this.config.minPeers) {
124
+ return undefined;
125
+ }
126
+ return medianHigh(values);
127
+ }
128
+ /**
129
+ * Check if this node's own clock appears to be the outlier.
130
+ *
131
+ * @param selfOffsetEstimate - now_ms - msg.epoch_ms computed when this node
132
+ * sends a message (i.e. ~0 if the clock is correct).
133
+ * @returns true if self is the outlier. Returns false during grace period or
134
+ * when there are too few peers.
135
+ */
136
+ selfInDrift(selfOffsetEstimate) {
137
+ const elapsed = Date.now() - this.meshJoinedAtMs;
138
+ if (elapsed < this.config.gracePeriodMs)
139
+ return false;
140
+ const median = this.meshMedianOffset();
141
+ if (median === undefined)
142
+ return false;
143
+ return Math.abs(selfOffsetEstimate - median) > this.config.toleranceMs;
144
+ }
145
+ /**
146
+ * Remove a peer from the offset tracking table and isolation set.
147
+ * Called on Depart or lease expiry.
148
+ */
149
+ removePeer(endpointId) {
150
+ this.offsets.delete(endpointId);
151
+ this.isolated.delete(endpointId);
152
+ }
153
+ }
154
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../../src/trust/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,kCAAkC;AAClC,MAAM,CAAC,MAAM,0BAA0B,GAAqB;IAC1D,WAAW,EAAE,MAAM;IACnB,aAAa,EAAE,MAAM;IACrB,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,eAAuB,EACvB,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE;IAE7B,OAAO,eAAe,GAAG,gBAAgB,CAAC;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,cAAsB,EACtB,SAA2B,0BAA0B;IAErD,oCAAoC;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;IAC5C,IAAI,OAAO,GAAG,MAAM,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC;IAEjD,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;AAChD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,UAAU,CAAC,MAAgB;IAClC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IACpB,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,0BAA0B;IAC/D,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAC7B,cAAc,CAAS;IACvB,MAAM,CAAmB;IAEjC,YAAY,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,MAAkC;QACzE,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,0BAA0B,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7D,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,cAAsB,EAAE,eAAuB;QACpD,MAAM,KAAK,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QAExC,IAAI,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3D,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;gBACvC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBAClC,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;aAAM,CAAC;YACN,yCAAyC;YACzC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QACvC,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,cAAsB,EAAE,eAAuB;QACzD,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;IACtD,CAAC;IAED,6CAA6C;IAC7C,UAAU,CAAC,cAAsB;QAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC;IAED,kCAAkC;IAClC,QAAQ,CAAC,cAAsB;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1C,CAAC;IAED,0BAA0B;IAC1B,aAAa;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,oDAAoD;IACpD,WAAW;QACT,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;OAOG;IACH,gBAAgB;QACd,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1C,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACzC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED;;;;;;;OAOG;IACH,WAAW,CAAC,kBAA0B;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,cAAc,CAAC;QACjD,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa;YAAE,OAAO,KAAK,CAAC;QAEtD,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAEvC,OAAO,IAAI,CAAC,GAAG,CAAC,kBAAkB,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;IACzE,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,UAAkB;QAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;CACF"}