yakmesh 2.8.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/CHANGELOG.md +637 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/Caddyfile +77 -0
  4. package/README.md +119 -29
  5. package/adapters/adapter-mlv-bible/README.md +124 -0
  6. package/adapters/adapter-mlv-bible/index.js +400 -0
  7. package/adapters/chat-mod-adapter.js +532 -0
  8. package/adapters/content-adapter.js +273 -0
  9. package/content/api.js +50 -41
  10. package/content/index.js +2 -2
  11. package/content/store.js +355 -173
  12. package/dashboard/index.html +19 -3
  13. package/database/replication.js +117 -37
  14. package/docs/CRYPTO-AGILITY.md +204 -0
  15. package/docs/MTLS-RESEARCH.md +367 -0
  16. package/docs/NAMCHE-SPEC.md +681 -0
  17. package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
  18. package/docs/PRECISION-DISCLOSURE.md +96 -0
  19. package/docs/README.md +76 -0
  20. package/docs/ROADMAP-2.4.0.md +447 -0
  21. package/docs/ROADMAP-2.5.0.md +244 -0
  22. package/docs/SECURITY-AUDIT-REPORT.md +306 -0
  23. package/docs/SST-INTEGRATION.md +712 -0
  24. package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
  25. package/docs/TERNARY-AUDIT-REPORT.md +247 -0
  26. package/docs/TME-FAQ.md +221 -0
  27. package/docs/WHITEPAPER.md +623 -0
  28. package/docs/adapters.html +1001 -0
  29. package/docs/advanced-systems.html +1045 -0
  30. package/docs/annex.html +1046 -0
  31. package/docs/api.html +970 -0
  32. package/docs/business/response-templates.md +160 -0
  33. package/docs/c2c.html +1225 -0
  34. package/docs/cli.html +1332 -0
  35. package/docs/configuration.html +1248 -0
  36. package/docs/darshan.html +1085 -0
  37. package/docs/dharma.html +966 -0
  38. package/docs/docs-bundle.html +1075 -0
  39. package/docs/docs.css +3120 -0
  40. package/docs/docs.js +556 -0
  41. package/docs/doko.html +969 -0
  42. package/docs/geo-proof.html +858 -0
  43. package/docs/getting-started.html +840 -0
  44. package/docs/gumba-tutorial.html +1144 -0
  45. package/docs/gumba.html +1098 -0
  46. package/docs/index.html +914 -0
  47. package/docs/jhilke.html +1312 -0
  48. package/docs/karma.html +1100 -0
  49. package/docs/katha.html +1037 -0
  50. package/docs/lama.html +978 -0
  51. package/docs/mandala.html +1067 -0
  52. package/docs/mani.html +964 -0
  53. package/docs/mantra.html +967 -0
  54. package/docs/mesh.html +1409 -0
  55. package/docs/nakpak.html +869 -0
  56. package/docs/namche.html +928 -0
  57. package/docs/nav-order.json +53 -0
  58. package/docs/prahari.html +1043 -0
  59. package/docs/prism-bash.min.js +1 -0
  60. package/docs/prism-javascript.min.js +1 -0
  61. package/docs/prism-json.min.js +1 -0
  62. package/docs/prism-tomorrow.min.css +1 -0
  63. package/docs/prism.min.js +1 -0
  64. package/docs/privacy.html +699 -0
  65. package/docs/quick-reference.html +1181 -0
  66. package/docs/sakshi.html +1402 -0
  67. package/docs/sandboxing.md +386 -0
  68. package/docs/seva.html +911 -0
  69. package/docs/sherpa.html +871 -0
  70. package/docs/studio.html +860 -0
  71. package/docs/stupa.html +995 -0
  72. package/docs/tailwind.min.css +2 -0
  73. package/docs/tattva.html +1332 -0
  74. package/docs/terms.html +686 -0
  75. package/docs/time-server-deployment.md +166 -0
  76. package/docs/time-sources.html +1392 -0
  77. package/docs/tivra.html +1127 -0
  78. package/docs/trademark-policy.html +686 -0
  79. package/docs/tribhuj.html +1183 -0
  80. package/docs/trust-security.html +1029 -0
  81. package/docs/tutorials/backup-recovery.html +654 -0
  82. package/docs/tutorials/dashboard.html +604 -0
  83. package/docs/tutorials/domain-setup.html +605 -0
  84. package/docs/tutorials/host-website.html +456 -0
  85. package/docs/tutorials/mesh-network.html +505 -0
  86. package/docs/tutorials/mobile-access.html +445 -0
  87. package/docs/tutorials/privacy.html +467 -0
  88. package/docs/tutorials/raspberry-pi.html +600 -0
  89. package/docs/tutorials/security-basics.html +539 -0
  90. package/docs/tutorials/share-files.html +431 -0
  91. package/docs/tutorials/troubleshooting.html +637 -0
  92. package/docs/tutorials/trust-karma.html +419 -0
  93. package/docs/tutorials/yak-protocol.html +456 -0
  94. package/docs/tutorials.html +1034 -0
  95. package/docs/vani.html +1270 -0
  96. package/docs/webserver.html +809 -0
  97. package/docs/yak-protocol.html +940 -0
  98. package/docs/yak-timeserver-design.md +475 -0
  99. package/docs/yakapp.html +1015 -0
  100. package/docs/ypc27.html +1069 -0
  101. package/docs/yurt.html +1344 -0
  102. package/embedded-docs/bundle.js +334 -74
  103. package/gossip/protocol.js +247 -27
  104. package/identity/key-resolver.js +262 -0
  105. package/identity/machine-seed.js +632 -0
  106. package/identity/node-key.js +669 -368
  107. package/identity/tribhuj-ratchet.js +506 -0
  108. package/knowledge-base.js +37 -8
  109. package/launcher/yakmesh.bat +62 -0
  110. package/launcher/yakmesh.sh +70 -0
  111. package/mesh/annex.js +462 -108
  112. package/mesh/beacon-broadcast.js +113 -1
  113. package/mesh/darshan.js +1718 -0
  114. package/mesh/gumba.js +1567 -0
  115. package/mesh/jhilke.js +651 -0
  116. package/mesh/katha.js +1012 -0
  117. package/mesh/nakpak-routing.js +8 -5
  118. package/mesh/network.js +724 -34
  119. package/mesh/pulse-sync.js +4 -1
  120. package/mesh/rate-limiter.js +127 -15
  121. package/mesh/seva.js +526 -0
  122. package/mesh/sherpa-discovery.js +89 -8
  123. package/mesh/sybil-defense.js +19 -5
  124. package/mesh/temporal-encoder.js +4 -3
  125. package/mesh/vani.js +1364 -0
  126. package/mesh/yurt.js +1340 -0
  127. package/models/entropy-sentinel.onnx +0 -0
  128. package/models/karma-trust.onnx +0 -0
  129. package/models/manifest.json +43 -0
  130. package/models/sakshi-anomaly.onnx +0 -0
  131. package/oracle/code-proof-protocol.js +7 -6
  132. package/oracle/codebase-lock.js +257 -28
  133. package/oracle/index.js +74 -15
  134. package/oracle/ma902-snmp.js +678 -0
  135. package/oracle/module-sealer.js +5 -3
  136. package/oracle/network-identity.js +16 -0
  137. package/oracle/packet-checksum.js +201 -0
  138. package/oracle/sst.js +579 -0
  139. package/oracle/ternary-144t.js +714 -0
  140. package/oracle/ternary-ml.js +481 -0
  141. package/oracle/time-api.js +239 -0
  142. package/oracle/time-source.js +137 -47
  143. package/oracle/validation-oracle-hardened.js +1111 -1071
  144. package/oracle/validation-oracle.js +4 -2
  145. package/oracle/ypc27.js +211 -0
  146. package/package.json +20 -3
  147. package/protocol/yak-handler.js +35 -9
  148. package/protocol/yak-protocol.js +28 -13
  149. package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
  150. package/reference/cpp/yakmesh_ypc27.cpp +179 -0
  151. package/sbom.json +87 -0
  152. package/scripts/security-audit.mjs +264 -0
  153. package/scripts/update-docs-nav.js +194 -0
  154. package/scripts/update-docs-sidebar.cjs +164 -0
  155. package/security/crypto-config.js +4 -3
  156. package/security/dharma-moderation.js +517 -0
  157. package/security/doko-identity.js +193 -143
  158. package/security/domain-consensus.js +86 -85
  159. package/security/fs-hardening.js +620 -0
  160. package/security/hardware-attestation.js +5 -3
  161. package/security/hybrid-trust.js +227 -87
  162. package/security/karma-rate-limiter.js +692 -0
  163. package/security/khata-protocol.js +22 -21
  164. package/security/khata-trust-integration.js +277 -150
  165. package/security/memory-safety.js +635 -0
  166. package/security/mesh-auth.js +11 -10
  167. package/security/mesh-revocation.js +373 -5
  168. package/security/namche-gateway.js +298 -69
  169. package/security/sakshi.js +460 -3
  170. package/security/sangha.js +770 -0
  171. package/security/secure-config.js +473 -0
  172. package/security/silicon-parity.js +13 -10
  173. package/security/steadywatch.js +1142 -0
  174. package/security/strike-system.js +32 -3
  175. package/security/temporal-signing.js +488 -0
  176. package/security/trit-commitment.js +464 -0
  177. package/server/crypto/annex.js +247 -0
  178. package/server/darshan-api.js +343 -0
  179. package/server/index.js +3259 -362
  180. package/server/komm-api.js +668 -0
  181. package/utils/accel.js +2273 -0
  182. package/utils/ternary-id.js +79 -0
  183. package/utils/verify-worker.js +57 -0
  184. package/webserver/index.js +95 -5
  185. package/assets/yakmesh-logo.png +0 -0
  186. package/assets/yakmesh-logo.svg +0 -80
  187. package/assets/yakmesh-logo2.png +0 -0
  188. package/assets/yakmesh-logo2sm.png +0 -0
  189. package/assets/ymsm.png +0 -0
  190. package/website/assets/silhouettes/adapters.svg +0 -107
  191. package/website/assets/silhouettes/api-endpoints.svg +0 -115
  192. package/website/assets/silhouettes/atomic-clock.svg +0 -83
  193. package/website/assets/silhouettes/base-camp.svg +0 -81
  194. package/website/assets/silhouettes/bridge.svg +0 -69
  195. package/website/assets/silhouettes/docs-bundle.svg +0 -113
  196. package/website/assets/silhouettes/doko-basket.svg +0 -70
  197. package/website/assets/silhouettes/fortress.svg +0 -93
  198. package/website/assets/silhouettes/gateway.svg +0 -54
  199. package/website/assets/silhouettes/gears.svg +0 -93
  200. package/website/assets/silhouettes/globe-satellite.svg +0 -67
  201. package/website/assets/silhouettes/karma-wheel.svg +0 -137
  202. package/website/assets/silhouettes/lama-council.svg +0 -141
  203. package/website/assets/silhouettes/mandala-network.svg +0 -169
  204. package/website/assets/silhouettes/mani-stones.svg +0 -149
  205. package/website/assets/silhouettes/mantra-wheel.svg +0 -116
  206. package/website/assets/silhouettes/mesh-nodes.svg +0 -113
  207. package/website/assets/silhouettes/nakpak.svg +0 -56
  208. package/website/assets/silhouettes/peak-lightning.svg +0 -73
  209. package/website/assets/silhouettes/sherpa.svg +0 -69
  210. package/website/assets/silhouettes/stupa-tower.svg +0 -119
  211. package/website/assets/silhouettes/tattva-eye.svg +0 -78
  212. package/website/assets/silhouettes/terminal.svg +0 -74
  213. package/website/assets/silhouettes/webserver.svg +0 -145
  214. package/website/assets/silhouettes/yak.svg +0 -78
  215. package/website/assets/yakmesh-logo.png +0 -0
  216. package/website/assets/yakmesh-logo.webp +0 -0
  217. package/website/assets/yakmesh-logo128x140.webp +0 -0
  218. package/website/assets/yakmesh-logo2.png +0 -0
  219. package/website/assets/yakmesh-logo2.svg +0 -51
  220. package/website/assets/yakmesh-logo40x44.webp +0 -0
  221. package/website/assets/yakmesh.gif +0 -0
  222. package/website/assets/yakmesh.ico +0 -0
  223. package/website/assets/yakmesh.jpg +0 -0
  224. package/website/assets/yakmesh.pdf +0 -0
  225. package/website/assets/yakmesh.png +0 -0
  226. package/website/assets/yakmesh.svg +0 -70
  227. package/website/assets/yakmesh128.webp +0 -0
  228. package/website/assets/yakmesh32.png +0 -0
  229. package/website/assets/yakmesh32.svg +0 -65
  230. package/website/assets/yakmesh32o.ico +0 -2
  231. package/website/assets/yakmesh32o.svg +0 -65
  232. package/website/assets/yakmesh32o.svgz +0 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * ╔═══════════════════════════════════════════════════════════════════════════════╗
3
+ * ║ ⏱️ YAKMESH TIME API — ATOMIC CLOCK SERVICE ⏱️ ║
4
+ * ╠═══════════════════════════════════════════════════════════════════════════════╣
5
+ * ║ ║
6
+ * ║ HTTP endpoint that serves GPS-derived atomic time from the MA-902/S-C1 ║
7
+ * ║ GPS Gigabit Time Server via the existing SNMP monitor module. ║
8
+ * ║ ║
9
+ * ║ Runs on port 3099 (configurable via YAKMESH_TIME_API_PORT). ║
10
+ * ║ Reverse-proxied by Caddy at time.yakmesh.dev. ║
11
+ * ║ ║
12
+ * ║ Endpoints: ║
13
+ * ║ GET /api/time — Full time + satellite telemetry (JSON) ║
14
+ * ║ GET /api/time/simple — Minimal { t, s, q } response ║
15
+ * ║ GET /api/health — Satellite health + alarm status ║
16
+ * ║ HEAD /api/time — Headers only, zero body ║
17
+ * ║ ║
18
+ * ╚═══════════════════════════════════════════════════════════════════════════════╝
19
+ *
20
+ * @module oracle/time-api
21
+ */
22
+
23
+ import http from 'node:http';
24
+ import { getMA902Monitor } from './ma902-snmp.js';
25
+ import { createLogger } from '../utils/logger.js';
26
+
27
+ const log = createLogger('time-api');
28
+
29
+ const PORT = parseInt(process.env.YAKMESH_TIME_API_PORT || '3099', 10);
30
+ const HOST = process.env.YAKMESH_TIME_API_HOST || '0.0.0.0';
31
+
32
+ // ============================================================
33
+ // MA-902 MONITOR — cached telemetry from SNMP polling
34
+ // ============================================================
35
+
36
+ let lastTelemetry = null;
37
+ let monitor = null;
38
+
39
+ /**
40
+ * Initialize the MA-902 monitor and begin SNMP polling.
41
+ * Telemetry updates are cached in `lastTelemetry` for low-latency HTTP responses.
42
+ */
43
+ function initMonitor() {
44
+ monitor = getMA902Monitor({
45
+ host: process.env.MA902_HOST || '192.168.1.30',
46
+ community: process.env.MA902_COMMUNITY || 'public',
47
+ pollInterval: parseInt(process.env.MA902_POLL_INTERVAL || '5000', 10),
48
+ verbose: process.env.MA902_VERBOSE === 'true',
49
+ });
50
+
51
+ monitor.on('telemetry', (data) => {
52
+ lastTelemetry = data;
53
+ });
54
+
55
+ monitor.on('error', (err) => {
56
+ log.warn('MA-902 monitor error', { error: err.message });
57
+ });
58
+
59
+ return monitor;
60
+ }
61
+
62
+ // ============================================================
63
+ // QUALITY ASSESSMENT
64
+ // ============================================================
65
+
66
+ /**
67
+ * Derive a human-readable quality string from telemetry.
68
+ * Matches the thresholds in ma902-snmp.js: excellent(≥8), good(≥5), marginal(≥3), degraded(≥1).
69
+ */
70
+ function getQuality() {
71
+ if (!lastTelemetry) return 'unknown';
72
+ if (!lastTelemetry.locked) return 'degraded';
73
+ const sats = lastTelemetry.satellites?.used || 0;
74
+ if (sats >= 8) return 'excellent';
75
+ if (sats >= 5) return 'good';
76
+ if (sats >= 3) return 'marginal';
77
+ return 'degraded';
78
+ }
79
+
80
+ // ============================================================
81
+ // HTTP SERVER
82
+ // ============================================================
83
+
84
+ const server = http.createServer((req, res) => {
85
+ const url = new URL(req.url, `http://${HOST}:${PORT}`);
86
+ const pathname = url.pathname;
87
+
88
+ // CORS — time is a public good
89
+ res.setHeader('Access-Control-Allow-Origin', '*');
90
+ res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
91
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
92
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
93
+
94
+ // CORS preflight
95
+ if (req.method === 'OPTIONS') {
96
+ res.setHeader('Access-Control-Max-Age', '86400');
97
+ res.writeHead(204);
98
+ return res.end();
99
+ }
100
+
101
+ // Timing headers on every response
102
+ const now = Date.now();
103
+ const unixS = now / 1000;
104
+ const locked = lastTelemetry?.locked ?? false;
105
+
106
+ res.setHeader('X-Yakmesh-Time', unixS.toFixed(3));
107
+ res.setHeader('X-Yakmesh-Stratum', locked ? '1' : '2');
108
+ res.setHeader('X-Yakmesh-Source', locked ? 'GPS' : 'system');
109
+
110
+ // ---- HEAD /api/time — zero body ----
111
+ if (pathname === '/api/time' && req.method === 'HEAD') {
112
+ res.writeHead(200);
113
+ return res.end();
114
+ }
115
+
116
+ // ---- GET /api/time/simple — minimal response ----
117
+ if (pathname === '/api/time/simple' && req.method === 'GET') {
118
+ const body = JSON.stringify({
119
+ t: now,
120
+ s: locked ? 1 : 2,
121
+ q: getQuality(),
122
+ });
123
+ res.writeHead(200, { 'Content-Type': 'application/json' });
124
+ return res.end(body);
125
+ }
126
+
127
+ // ---- GET /api/time — full atomic time with telemetry ----
128
+ if (pathname === '/api/time' && req.method === 'GET') {
129
+ const sats = lastTelemetry?.satellites || {};
130
+ const body = JSON.stringify({
131
+ iso: new Date(now).toISOString(),
132
+ unix: unixS,
133
+ unix_ms: now,
134
+ stratum: locked ? 1 : 2,
135
+ source: 'MA-902/S-C1 GPS',
136
+ accuracy_ms: locked ? 1 : 50,
137
+ leap_indicator: 0,
138
+ satellites: {
139
+ visible: sats.visible ?? 0,
140
+ used: sats.used ?? 0,
141
+ tracking: sats.tracking ?? 0,
142
+ constellations: sats.constellations ?? [],
143
+ },
144
+ lock: locked,
145
+ quality: getQuality(),
146
+ offset_ns: lastTelemetry?.offset ?? 0,
147
+ reference_id: locked ? 'GPS' : 'SYS',
148
+ });
149
+ res.writeHead(200, { 'Content-Type': 'application/json' });
150
+ return res.end(body);
151
+ }
152
+
153
+ // ---- GET /api/health — satellite health & SNMP telemetry ----
154
+ if (pathname === '/api/health' && req.method === 'GET') {
155
+ const body = JSON.stringify({
156
+ status: locked ? 'healthy' : 'degraded',
157
+ lock: locked,
158
+ satellites_visible: lastTelemetry?.satellites?.visible ?? 0,
159
+ satellites_used: lastTelemetry?.satellites?.used ?? 0,
160
+ constellations: lastTelemetry?.satellites?.constellations ?? [],
161
+ alarm: lastTelemetry?.alarm ?? false,
162
+ quality: lastTelemetry?.satellites?.quality ?? 'unknown',
163
+ last_poll: lastTelemetry?.timestamp
164
+ ? new Date(lastTelemetry.timestamp).toISOString()
165
+ : null,
166
+ drift_ns: lastTelemetry?.offset ?? 0,
167
+ trust_level: lastTelemetry?.maniTrust?.level ?? 'unknown',
168
+ });
169
+ res.writeHead(200, { 'Content-Type': 'application/json' });
170
+ return res.end(body);
171
+ }
172
+
173
+ // ---- GET / — redirect to info page on yakmesh.dev ----
174
+ if (pathname === '/' && req.method === 'GET') {
175
+ res.writeHead(302, { 'Location': 'https://yakmesh.dev/time/' });
176
+ return res.end();
177
+ }
178
+
179
+ // ---- 404 ----
180
+ res.writeHead(404, { 'Content-Type': 'application/json' });
181
+ res.end(JSON.stringify({ error: 'Not found', endpoints: ['/api/time', '/api/time/simple', '/api/health'] }));
182
+ });
183
+
184
+ // ============================================================
185
+ // STARTUP
186
+ // ============================================================
187
+
188
+ /**
189
+ * Start the Time API server and MA-902 monitor.
190
+ * @returns {{ server: http.Server, monitor: MA902Monitor }}
191
+ */
192
+ export async function startTimeApi() {
193
+ const mon = initMonitor();
194
+ const started = await mon.start();
195
+
196
+ if (!started) {
197
+ log.warn('MA-902 SNMP not available — Time API will return system time (Stratum 2)');
198
+ }
199
+
200
+ return new Promise((resolve, reject) => {
201
+ server.listen(PORT, HOST, () => {
202
+ log.info('Yakmesh Time API listening', { url: `http://${HOST}:${PORT}`, monitor: started ? 'MA-902 SNMP active' : 'system fallback' });
203
+ resolve({ server, monitor: mon });
204
+ });
205
+ server.on('error', (err) => {
206
+ log.error('Time API failed to start', { error: err.message });
207
+ reject(err);
208
+ });
209
+ });
210
+ }
211
+
212
+ /**
213
+ * Stop the Time API server and monitor.
214
+ */
215
+ export async function stopTimeApi() {
216
+ if (monitor) {
217
+ monitor.stop();
218
+ monitor = null;
219
+ lastTelemetry = null;
220
+ }
221
+ return new Promise((resolve) => {
222
+ server.close(() => {
223
+ log.info('Yakmesh Time API stopped');
224
+ resolve();
225
+ });
226
+ });
227
+ }
228
+
229
+ // ---- Direct execution support ----
230
+ // node oracle/time-api.js
231
+ const isMain = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, '/')}`;
232
+ if (isMain) {
233
+ startTimeApi().catch((err) => {
234
+ console.error('Fatal:', err.message);
235
+ process.exit(1);
236
+ });
237
+ }
238
+
239
+ export default { startTimeApi, stopTimeApi };
@@ -43,6 +43,7 @@ import { existsSync, readFileSync } from 'fs';
43
43
  import { platform } from 'os';
44
44
  import { EventEmitter } from 'events';
45
45
  import { createLogger } from '../utils/logger.js';
46
+ import { MA902Monitor, getMA902Monitor } from './ma902-snmp.js';
46
47
 
47
48
  const log = createLogger('mani:time-source');
48
49
 
@@ -174,6 +175,9 @@ export class ManiTimeDetector extends EventEmitter {
174
175
  refreshInterval: options.refreshInterval || 60000,
175
176
  // Verbose logging
176
177
  verbose: options.verbose || false,
178
+ // MA-902 SNMP monitoring configuration
179
+ ma902: options.ma902 || null,
180
+ // Example: { host: '192.168.1.30', pollInterval: 10000 }
177
181
  };
178
182
 
179
183
  this.platform = platform();
@@ -182,12 +186,54 @@ export class ManiTimeDetector extends EventEmitter {
182
186
  this.trustLevel = ManiTrustLevel.UNSYNC;
183
187
  this.lastCheck = null;
184
188
  this.refreshTimer = null;
189
+
190
+ // MA-902 SNMP monitor instance
191
+ this.ma902Monitor = null;
185
192
  }
186
193
 
187
194
  /**
188
195
  * Start continuous monitoring of time sources
189
196
  */
190
- start() {
197
+ async start() {
198
+ // Start MA-902 SNMP monitor if configured
199
+ if (this.options.ma902) {
200
+ try {
201
+ this.ma902Monitor = new MA902Monitor({
202
+ verbose: this.options.verbose,
203
+ ...this.options.ma902,
204
+ });
205
+
206
+ // Forward MA-902 events
207
+ this.ma902Monitor.on('telemetry', (data) => {
208
+ this.emit('ma902:telemetry', data);
209
+ });
210
+ this.ma902Monitor.on('lockLost', (data) => {
211
+ log.warn('MA-902 satellite lock lost — GPS trust degraded');
212
+ this.emit('ma902:lockLost', data);
213
+ // Re-detect to update trust level
214
+ this.detect();
215
+ });
216
+ this.ma902Monitor.on('lockAcquired', (data) => {
217
+ log.info('MA-902 satellite lock acquired — GPS trust restored');
218
+ this.emit('ma902:lockAcquired', data);
219
+ this.detect();
220
+ });
221
+ this.ma902Monitor.on('alarm', (data) => {
222
+ this.emit('ma902:alarm', data);
223
+ this.detect();
224
+ });
225
+ this.ma902Monitor.on('trustChanged', (data) => {
226
+ this.emit('ma902:trustChanged', data);
227
+ this.detect();
228
+ });
229
+
230
+ await this.ma902Monitor.start();
231
+ } catch (err) {
232
+ log.warn('MA-902 SNMP monitor failed to start', { error: err.message });
233
+ this.ma902Monitor = null;
234
+ }
235
+ }
236
+
191
237
  this.detect();
192
238
 
193
239
  if (this.options.refreshInterval > 0) {
@@ -207,6 +253,10 @@ export class ManiTimeDetector extends EventEmitter {
207
253
  clearInterval(this.refreshTimer);
208
254
  this.refreshTimer = null;
209
255
  }
256
+ if (this.ma902Monitor) {
257
+ this.ma902Monitor.stop();
258
+ this.ma902Monitor = null;
259
+ }
210
260
  }
211
261
 
212
262
  /**
@@ -231,8 +281,31 @@ export class ManiTimeDetector extends EventEmitter {
231
281
  this.detectedSources.set('atomic', atomicResult);
232
282
  }
233
283
 
234
- // Check for GPS/PPS
284
+ // Check for GPS/PPS (enriched with MA-902 SNMP telemetry)
235
285
  const gpsResult = this.detectGPS();
286
+
287
+ // Enrich GPS result with MA-902 SNMP data if available
288
+ if (this.ma902Monitor && this.ma902Monitor.isAvailable()) {
289
+ const telemetry = this.ma902Monitor.getTelemetry();
290
+ if (telemetry) {
291
+ gpsResult.detected = true;
292
+ gpsResult.device = gpsResult.device || 'MA-902/S-C1';
293
+ gpsResult.synchronized = telemetry.synchronized;
294
+ gpsResult.satellites = telemetry.satellites.used;
295
+ gpsResult.ma902 = {
296
+ host: telemetry.host,
297
+ locked: telemetry.locked,
298
+ satellites: telemetry.satellites,
299
+ gpsTime: telemetry.gpsTimeISO,
300
+ clockDelta: telemetry.clockDeltaSeconds,
301
+ alarm: telemetry.alarm,
302
+ quality: telemetry.qualityIndicator,
303
+ trust: telemetry.maniTrust,
304
+ constellations: telemetry.satellites.constellations,
305
+ };
306
+ }
307
+ }
308
+
236
309
  if (gpsResult.detected) {
237
310
  results.sources.gps = gpsResult;
238
311
  this.detectedSources.set('gps', gpsResult);
@@ -245,8 +318,14 @@ export class ManiTimeDetector extends EventEmitter {
245
318
  this.detectedSources.set('ptp', ptpResult);
246
319
  }
247
320
 
248
- // Check NTP status
321
+ // Check NTP status — if MA-902 is the NTP source, note that
249
322
  const ntpResult = this.detectNTP();
323
+ if (ntpResult.server && this.ma902Monitor?.isAvailable()) {
324
+ const ma902Host = this.ma902Monitor.options.host;
325
+ if (ntpResult.server === ma902Host || ntpResult.server.includes(ma902Host)) {
326
+ ntpResult.ma902Backed = true;
327
+ }
328
+ }
250
329
  results.sources.ntp = ntpResult;
251
330
  this.detectedSources.set('ntp', ntpResult);
252
331
 
@@ -265,6 +344,11 @@ export class ManiTimeDetector extends EventEmitter {
265
344
  results.trustLevel = TimeTrustLevel.NTP;
266
345
  }
267
346
 
347
+ // MA-902 data enrichment for results
348
+ if (this.ma902Monitor) {
349
+ results.ma902 = this.ma902Monitor.getStatus();
350
+ }
351
+
268
352
  results.phaseTolerance = PhaseTolerance[results.trustLevel];
269
353
 
270
354
  } catch (error) {
@@ -501,45 +585,7 @@ export class ManiTimeDetector extends EventEmitter {
501
585
  }
502
586
  // 4. Check for Meinberg PTP hardware (PTP270PEX, etc.)
503
587
  try {
504
- const meinbergCheck = execSilent('lspci 2>/dev/null | grep -i meinberg', { encoding: 'utf8', timeout: 5000 });
505
- if (meinbergCheck.trim()) {
506
- result.detected = true;
507
- result.device = 'Meinberg PTP';
508
- result.type = meinbergCheck.includes('270') ? 'PTP270PEX' : 'Meinberg PTP Card';
509
- try {
510
- const mbgStatus = execSilent('mbgstatus 2>/dev/null | head -20', { encoding: 'utf8', timeout: 5000 });
511
- if (mbgStatus.includes('SYNC') || mbgStatus.includes('synchronized')) {
512
- result.synchronized = true;
513
- }
514
- const offsetMatch = mbgStatus.match(/offset[:\s]+(-?\d+)/i);
515
- if (offsetMatch) {
516
- result.offset = parseInt(offsetMatch[1]);
517
- }
518
- } catch (e) { /* mbgstatus not available */ }
519
- }
520
- } catch (e) { /* Meinberg not found */ }
521
- // 4. Check for Meinberg PTP hardware (PTP270PEX, etc.)
522
- try {
523
- const meinbergCheck = execSilent('lspci 2>/dev/null | grep -i meinberg', { encoding: 'utf8', timeout: 5000 });
524
- if (meinbergCheck.trim()) {
525
- result.detected = true;
526
- result.device = 'Meinberg PTP';
527
- result.type = meinbergCheck.includes('270') ? 'PTP270PEX' : 'Meinberg PTP Card';
528
- try {
529
- const mbgStatus = execSilent('mbgstatus 2>/dev/null | head -20', { encoding: 'utf8', timeout: 5000 });
530
- if (mbgStatus.includes('SYNC') || mbgStatus.includes('synchronized')) {
531
- result.synchronized = true;
532
- }
533
- const offsetMatch = mbgStatus.match(/offset[:\s]+(-?\d+)/i);
534
- if (offsetMatch) {
535
- result.offset = parseInt(offsetMatch[1]);
536
- }
537
- } catch (e) { /* mbgstatus not available */ }
538
- }
539
- } catch (e) { /* Meinberg not found */ }
540
- // 4. Check for Meinberg PTP hardware (PTP270PEX, etc.)
541
- try {
542
- // Check for Meinberg driver/software
588
+ // Check for Meinberg driver/software via lspci
543
589
  const meinbergCheck = execSilent('lspci 2>/dev/null | grep -i meinberg', { encoding: 'utf8', timeout: 5000 });
544
590
  if (meinbergCheck.trim()) {
545
591
  result.detected = true;
@@ -565,13 +611,27 @@ export class ManiTimeDetector extends EventEmitter {
565
611
  // Meinberg not found via lspci
566
612
  }
567
613
 
568
- // 5. Windows: Check for Meinberg driver
614
+ // 5. Windows: Check for Meinberg driver + MbgAdjTm service
569
615
  if (this.platform === 'win32') {
570
616
  try {
571
617
  const driverCheck = execSilent('driverquery /v 2>nul | findstr /i meinberg', { encoding: 'utf8', timeout: 5000 });
572
618
  if (driverCheck.trim()) {
573
619
  result.detected = true;
574
- result.type = 'Meinberg (Windows)';
620
+ result.type = 'Meinberg PTP270PEX (Windows)';
621
+
622
+ // Check if MbgAdjTm service is running (disciplines system clock from PTP card)
623
+ const svcCheck = execSilent('sc query MbgAdjTm 2>nul', { encoding: 'utf8', timeout: 3000 });
624
+ if (svcCheck && /RUNNING/i.test(svcCheck)) {
625
+ result.serviceRunning = true;
626
+
627
+ // Cross-reference with MA-902 SNMP: if MA-902 is GPS-locked and serving PTP,
628
+ // and MbgAdjTm is running, the PTP card is receiving disciplined time
629
+ if (this.ma902Monitor?.isAvailable() && this.ma902Monitor.isLocked()) {
630
+ result.synchronized = true;
631
+ result.source = 'ma902-ptp';
632
+ result.device = 'PTP270PEX ← MA-902/S-C1';
633
+ }
634
+ }
575
635
  }
576
636
  } catch (e) {
577
637
  // Meinberg driver not found
@@ -712,11 +772,21 @@ export class ManiTimeDetector extends EventEmitter {
712
772
  }
713
773
 
714
774
  if (results.sources.gps?.detected) {
715
- log.info('GPS detected', {
775
+ const gpsLog = {
716
776
  device: results.sources.gps.device || 'detected',
717
777
  hasPPS: results.sources.gps.hasPPS,
718
778
  synchronized: results.sources.gps.synchronized,
719
- });
779
+ };
780
+ // Enrich log with MA-902 SNMP data if available
781
+ if (results.sources.gps.ma902) {
782
+ const ma = results.sources.gps.ma902;
783
+ gpsLog.ma902 = true;
784
+ gpsLog.satellites = `${ma.satellites.used}/${ma.satellites.tracking}/${ma.satellites.visible}`;
785
+ gpsLog.constellations = ma.constellations.join('+');
786
+ gpsLog.locked = ma.locked;
787
+ gpsLog.trust = ma.trust.level;
788
+ }
789
+ log.info('GPS detected', gpsLog);
720
790
  }
721
791
 
722
792
  if (results.sources.ptp?.detected) {
@@ -776,7 +846,7 @@ export class ManiTimeDetector extends EventEmitter {
776
846
  * Get status object for API responses
777
847
  */
778
848
  getStatus() {
779
- return {
849
+ const status = {
780
850
  trustLevel: this.trustLevel,
781
851
  phaseTolerance: this.getPhaseTolerance(),
782
852
  stratum: this.getStratum(),
@@ -789,6 +859,21 @@ export class ManiTimeDetector extends EventEmitter {
789
859
  tightPhaseWindow: this.trustLevel !== TimeTrustLevel.UNSYNC,
790
860
  },
791
861
  };
862
+
863
+ // Include MA-902 SNMP status if monitor is active
864
+ if (this.ma902Monitor) {
865
+ status.ma902 = this.ma902Monitor.getStatus();
866
+ }
867
+
868
+ return status;
869
+ }
870
+
871
+ /**
872
+ * Get the MA-902 monitor instance (if configured)
873
+ * @returns {MA902Monitor|null}
874
+ */
875
+ getMA902Monitor() {
876
+ return this.ma902Monitor;
792
877
  }
793
878
  }
794
879
 
@@ -883,6 +968,9 @@ export {
883
968
  // Backward compatibility exports (original naming)
884
969
  export { ManiTimeDetector as TimeSourceDetector };
885
970
 
971
+ // Re-export MA-902 monitor for direct access
972
+ export { MA902Monitor, getMA902Monitor } from './ma902-snmp.js';
973
+
886
974
  export default {
887
975
  ManiTimeDetector,
888
976
  ManiTrustLevel,
@@ -891,6 +979,8 @@ export default {
891
979
  createPhaseConfig,
892
980
  getManiTimeDetector,
893
981
  detectTimeSources,
982
+ MA902Monitor,
983
+ getMA902Monitor,
894
984
  // Backward compatibility
895
985
  TimeSourceDetector: ManiTimeDetector,
896
986
  TimeTrustLevel: ManiTrustLevel,