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.
- package/CHANGELOG.md +637 -0
- package/CONTRIBUTING.md +42 -0
- package/Caddyfile +77 -0
- package/README.md +119 -29
- package/adapters/adapter-mlv-bible/README.md +124 -0
- package/adapters/adapter-mlv-bible/index.js +400 -0
- package/adapters/chat-mod-adapter.js +532 -0
- package/adapters/content-adapter.js +273 -0
- package/content/api.js +50 -41
- package/content/index.js +2 -2
- package/content/store.js +355 -173
- package/dashboard/index.html +19 -3
- package/database/replication.js +117 -37
- package/docs/CRYPTO-AGILITY.md +204 -0
- package/docs/MTLS-RESEARCH.md +367 -0
- package/docs/NAMCHE-SPEC.md +681 -0
- package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
- package/docs/PRECISION-DISCLOSURE.md +96 -0
- package/docs/README.md +76 -0
- package/docs/ROADMAP-2.4.0.md +447 -0
- package/docs/ROADMAP-2.5.0.md +244 -0
- package/docs/SECURITY-AUDIT-REPORT.md +306 -0
- package/docs/SST-INTEGRATION.md +712 -0
- package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
- package/docs/TERNARY-AUDIT-REPORT.md +247 -0
- package/docs/TME-FAQ.md +221 -0
- package/docs/WHITEPAPER.md +623 -0
- package/docs/adapters.html +1001 -0
- package/docs/advanced-systems.html +1045 -0
- package/docs/annex.html +1046 -0
- package/docs/api.html +970 -0
- package/docs/business/response-templates.md +160 -0
- package/docs/c2c.html +1225 -0
- package/docs/cli.html +1332 -0
- package/docs/configuration.html +1248 -0
- package/docs/darshan.html +1085 -0
- package/docs/dharma.html +966 -0
- package/docs/docs-bundle.html +1075 -0
- package/docs/docs.css +3120 -0
- package/docs/docs.js +556 -0
- package/docs/doko.html +969 -0
- package/docs/geo-proof.html +858 -0
- package/docs/getting-started.html +840 -0
- package/docs/gumba-tutorial.html +1144 -0
- package/docs/gumba.html +1098 -0
- package/docs/index.html +914 -0
- package/docs/jhilke.html +1312 -0
- package/docs/karma.html +1100 -0
- package/docs/katha.html +1037 -0
- package/docs/lama.html +978 -0
- package/docs/mandala.html +1067 -0
- package/docs/mani.html +964 -0
- package/docs/mantra.html +967 -0
- package/docs/mesh.html +1409 -0
- package/docs/nakpak.html +869 -0
- package/docs/namche.html +928 -0
- package/docs/nav-order.json +53 -0
- package/docs/prahari.html +1043 -0
- package/docs/prism-bash.min.js +1 -0
- package/docs/prism-javascript.min.js +1 -0
- package/docs/prism-json.min.js +1 -0
- package/docs/prism-tomorrow.min.css +1 -0
- package/docs/prism.min.js +1 -0
- package/docs/privacy.html +699 -0
- package/docs/quick-reference.html +1181 -0
- package/docs/sakshi.html +1402 -0
- package/docs/sandboxing.md +386 -0
- package/docs/seva.html +911 -0
- package/docs/sherpa.html +871 -0
- package/docs/studio.html +860 -0
- package/docs/stupa.html +995 -0
- package/docs/tailwind.min.css +2 -0
- package/docs/tattva.html +1332 -0
- package/docs/terms.html +686 -0
- package/docs/time-server-deployment.md +166 -0
- package/docs/time-sources.html +1392 -0
- package/docs/tivra.html +1127 -0
- package/docs/trademark-policy.html +686 -0
- package/docs/tribhuj.html +1183 -0
- package/docs/trust-security.html +1029 -0
- package/docs/tutorials/backup-recovery.html +654 -0
- package/docs/tutorials/dashboard.html +604 -0
- package/docs/tutorials/domain-setup.html +605 -0
- package/docs/tutorials/host-website.html +456 -0
- package/docs/tutorials/mesh-network.html +505 -0
- package/docs/tutorials/mobile-access.html +445 -0
- package/docs/tutorials/privacy.html +467 -0
- package/docs/tutorials/raspberry-pi.html +600 -0
- package/docs/tutorials/security-basics.html +539 -0
- package/docs/tutorials/share-files.html +431 -0
- package/docs/tutorials/troubleshooting.html +637 -0
- package/docs/tutorials/trust-karma.html +419 -0
- package/docs/tutorials/yak-protocol.html +456 -0
- package/docs/tutorials.html +1034 -0
- package/docs/vani.html +1270 -0
- package/docs/webserver.html +809 -0
- package/docs/yak-protocol.html +940 -0
- package/docs/yak-timeserver-design.md +475 -0
- package/docs/yakapp.html +1015 -0
- package/docs/ypc27.html +1069 -0
- package/docs/yurt.html +1344 -0
- package/embedded-docs/bundle.js +334 -74
- package/gossip/protocol.js +247 -27
- package/identity/key-resolver.js +262 -0
- package/identity/machine-seed.js +632 -0
- package/identity/node-key.js +669 -368
- package/identity/tribhuj-ratchet.js +506 -0
- package/knowledge-base.js +37 -8
- package/launcher/yakmesh.bat +62 -0
- package/launcher/yakmesh.sh +70 -0
- package/mesh/annex.js +462 -108
- package/mesh/beacon-broadcast.js +113 -1
- package/mesh/darshan.js +1718 -0
- package/mesh/gumba.js +1567 -0
- package/mesh/jhilke.js +651 -0
- package/mesh/katha.js +1012 -0
- package/mesh/nakpak-routing.js +8 -5
- package/mesh/network.js +724 -34
- package/mesh/pulse-sync.js +4 -1
- package/mesh/rate-limiter.js +127 -15
- package/mesh/seva.js +526 -0
- package/mesh/sherpa-discovery.js +89 -8
- package/mesh/sybil-defense.js +19 -5
- package/mesh/temporal-encoder.js +4 -3
- package/mesh/vani.js +1364 -0
- package/mesh/yurt.js +1340 -0
- package/models/entropy-sentinel.onnx +0 -0
- package/models/karma-trust.onnx +0 -0
- package/models/manifest.json +43 -0
- package/models/sakshi-anomaly.onnx +0 -0
- package/oracle/code-proof-protocol.js +7 -6
- package/oracle/codebase-lock.js +257 -28
- package/oracle/index.js +74 -15
- package/oracle/ma902-snmp.js +678 -0
- package/oracle/module-sealer.js +5 -3
- package/oracle/network-identity.js +16 -0
- package/oracle/packet-checksum.js +201 -0
- package/oracle/sst.js +579 -0
- package/oracle/ternary-144t.js +714 -0
- package/oracle/ternary-ml.js +481 -0
- package/oracle/time-api.js +239 -0
- package/oracle/time-source.js +137 -47
- package/oracle/validation-oracle-hardened.js +1111 -1071
- package/oracle/validation-oracle.js +4 -2
- package/oracle/ypc27.js +211 -0
- package/package.json +20 -3
- package/protocol/yak-handler.js +35 -9
- package/protocol/yak-protocol.js +28 -13
- package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
- package/reference/cpp/yakmesh_ypc27.cpp +179 -0
- package/sbom.json +87 -0
- package/scripts/security-audit.mjs +264 -0
- package/scripts/update-docs-nav.js +194 -0
- package/scripts/update-docs-sidebar.cjs +164 -0
- package/security/crypto-config.js +4 -3
- package/security/dharma-moderation.js +517 -0
- package/security/doko-identity.js +193 -143
- package/security/domain-consensus.js +86 -85
- package/security/fs-hardening.js +620 -0
- package/security/hardware-attestation.js +5 -3
- package/security/hybrid-trust.js +227 -87
- package/security/karma-rate-limiter.js +692 -0
- package/security/khata-protocol.js +22 -21
- package/security/khata-trust-integration.js +277 -150
- package/security/memory-safety.js +635 -0
- package/security/mesh-auth.js +11 -10
- package/security/mesh-revocation.js +373 -5
- package/security/namche-gateway.js +298 -69
- package/security/sakshi.js +460 -3
- package/security/sangha.js +770 -0
- package/security/secure-config.js +473 -0
- package/security/silicon-parity.js +13 -10
- package/security/steadywatch.js +1142 -0
- package/security/strike-system.js +32 -3
- package/security/temporal-signing.js +488 -0
- package/security/trit-commitment.js +464 -0
- package/server/crypto/annex.js +247 -0
- package/server/darshan-api.js +343 -0
- package/server/index.js +3259 -362
- package/server/komm-api.js +668 -0
- package/utils/accel.js +2273 -0
- package/utils/ternary-id.js +79 -0
- package/utils/verify-worker.js +57 -0
- package/webserver/index.js +95 -5
- package/assets/yakmesh-logo.png +0 -0
- package/assets/yakmesh-logo.svg +0 -80
- package/assets/yakmesh-logo2.png +0 -0
- package/assets/yakmesh-logo2sm.png +0 -0
- package/assets/ymsm.png +0 -0
- package/website/assets/silhouettes/adapters.svg +0 -107
- package/website/assets/silhouettes/api-endpoints.svg +0 -115
- package/website/assets/silhouettes/atomic-clock.svg +0 -83
- package/website/assets/silhouettes/base-camp.svg +0 -81
- package/website/assets/silhouettes/bridge.svg +0 -69
- package/website/assets/silhouettes/docs-bundle.svg +0 -113
- package/website/assets/silhouettes/doko-basket.svg +0 -70
- package/website/assets/silhouettes/fortress.svg +0 -93
- package/website/assets/silhouettes/gateway.svg +0 -54
- package/website/assets/silhouettes/gears.svg +0 -93
- package/website/assets/silhouettes/globe-satellite.svg +0 -67
- package/website/assets/silhouettes/karma-wheel.svg +0 -137
- package/website/assets/silhouettes/lama-council.svg +0 -141
- package/website/assets/silhouettes/mandala-network.svg +0 -169
- package/website/assets/silhouettes/mani-stones.svg +0 -149
- package/website/assets/silhouettes/mantra-wheel.svg +0 -116
- package/website/assets/silhouettes/mesh-nodes.svg +0 -113
- package/website/assets/silhouettes/nakpak.svg +0 -56
- package/website/assets/silhouettes/peak-lightning.svg +0 -73
- package/website/assets/silhouettes/sherpa.svg +0 -69
- package/website/assets/silhouettes/stupa-tower.svg +0 -119
- package/website/assets/silhouettes/tattva-eye.svg +0 -78
- package/website/assets/silhouettes/terminal.svg +0 -74
- package/website/assets/silhouettes/webserver.svg +0 -145
- package/website/assets/silhouettes/yak.svg +0 -78
- package/website/assets/yakmesh-logo.png +0 -0
- package/website/assets/yakmesh-logo.webp +0 -0
- package/website/assets/yakmesh-logo128x140.webp +0 -0
- package/website/assets/yakmesh-logo2.png +0 -0
- package/website/assets/yakmesh-logo2.svg +0 -51
- package/website/assets/yakmesh-logo40x44.webp +0 -0
- package/website/assets/yakmesh.gif +0 -0
- package/website/assets/yakmesh.ico +0 -0
- package/website/assets/yakmesh.jpg +0 -0
- package/website/assets/yakmesh.pdf +0 -0
- package/website/assets/yakmesh.png +0 -0
- package/website/assets/yakmesh.svg +0 -70
- package/website/assets/yakmesh128.webp +0 -0
- package/website/assets/yakmesh32.png +0 -0
- package/website/assets/yakmesh32.svg +0 -65
- package/website/assets/yakmesh32o.ico +0 -2
- package/website/assets/yakmesh32o.svg +0 -65
- 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 };
|
package/oracle/time-source.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|