@evomap/evolver 1.90.0 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -564
- package/bin/evolver-llm-proxy.js +3 -0
- package/bin/evolver-mcp.js +2 -0
- package/bin/evolver-proxy.js +3 -0
- package/bin/evolver.js +4 -0
- package/index.js +1 -3670
- package/package.json +32 -62
- package/CONTRIBUTING.md +0 -19
- package/LICENSE +0 -641
- package/README.ja-JP.md +0 -521
- package/README.ko-KR.md +0 -520
- package/README.zh-CN.md +0 -531
- package/SKILL.md +0 -365
- package/assets/cover.png +0 -0
- package/assets/gep/genes.seed.json +0 -496
- package/conformance/savings-core/constants.json +0 -30
- package/conformance/savings-core/golden-vectors.json +0 -333
- package/scripts/a2a_export.js +0 -63
- package/scripts/a2a_ingest.js +0 -79
- package/scripts/a2a_promote.js +0 -118
- package/scripts/analyze_by_skill.js +0 -121
- package/scripts/build_binaries.js +0 -479
- package/scripts/check-changelog.js +0 -166
- package/scripts/extract_log.js +0 -85
- package/scripts/generate_history.js +0 -75
- package/scripts/gep_append_event.js +0 -96
- package/scripts/gep_personality_report.js +0 -234
- package/scripts/human_report.js +0 -147
- package/scripts/recall-verify-report.js +0 -234
- package/scripts/recover_loop.js +0 -61
- package/scripts/refresh_stars_badge.js +0 -168
- package/scripts/seed-merchants.js +0 -91
- package/scripts/skill2recipes.js +0 -118
- package/scripts/suggest_version.js +0 -89
- package/scripts/validate-modules.js +0 -38
- package/scripts/validate-suite.js +0 -78
- package/src/adapters/claudeCode.js +0 -194
- package/src/adapters/codex.js +0 -216
- package/src/adapters/cursor.js +0 -91
- package/src/adapters/hookAdapter.js +0 -469
- package/src/adapters/kiro.js +0 -195
- package/src/adapters/opencode.js +0 -326
- package/src/adapters/scripts/_lockPaths.js +0 -74
- package/src/adapters/scripts/_memoryFiltering.js +0 -35
- package/src/adapters/scripts/_runtimePaths.js +0 -440
- package/src/adapters/scripts/evolver-session-end.js +0 -321
- package/src/adapters/scripts/evolver-session-start.js +0 -587
- package/src/adapters/scripts/evolver-signal-detect.js +0 -98
- package/src/adapters/scripts/evolver-task-recall.js +0 -173
- package/src/atp/atpExecute.js +0 -283
- package/src/atp/atpTaskPickup.js +0 -233
- package/src/atp/autoBuyer.js +0 -382
- package/src/atp/autoDeliver.js +0 -215
- package/src/atp/cli.js +0 -354
- package/src/atp/cliAutobuyPrompt.js +0 -154
- package/src/atp/consumerAgent.js +0 -157
- package/src/atp/defaultHandler.js +0 -69
- package/src/atp/heartbeatSignalsHandler.js +0 -254
- package/src/atp/hubClient.js +0 -317
- package/src/atp/index.js +0 -38
- package/src/atp/merchantAgent.js +0 -118
- package/src/atp/protocol.js +0 -41
- package/src/atp/questionComposer.js +0 -133
- package/src/atp/serviceHelper.js +0 -92
- package/src/canary.js +0 -13
- package/src/config.js +0 -351
- package/src/evolve/guards.js +0 -1
- package/src/evolve/pipeline/collect.js +0 -1
- package/src/evolve/pipeline/dispatch.js +0 -1
- package/src/evolve/pipeline/enrich.js +0 -1
- package/src/evolve/pipeline/hub.js +0 -1
- package/src/evolve/pipeline/select.js +0 -1
- package/src/evolve/pipeline/signals.js +0 -1
- package/src/evolve/utils.js +0 -1
- package/src/evolve.js +0 -1
- package/src/experiment/agentRunner.js +0 -229
- package/src/experiment/cli.js +0 -159
- package/src/experiment/comparison.js +0 -233
- package/src/experiment/metrics.js +0 -75
- package/src/forceUpdate.js +0 -991
- package/src/gep/a2a.js +0 -173
- package/src/gep/a2aProtocol.js +0 -1
- package/src/gep/analyzer.js +0 -35
- package/src/gep/antiAbuseTelemetry.js +0 -1
- package/src/gep/assetCallLog.js +0 -197
- package/src/gep/assetStore.js +0 -723
- package/src/gep/assets.js +0 -36
- package/src/gep/autoDistillConv.js +0 -1
- package/src/gep/autoDistillLlm.js +0 -1
- package/src/gep/bridge.js +0 -138
- package/src/gep/candidateEval.js +0 -1
- package/src/gep/candidates.js +0 -1
- package/src/gep/claimNudge.js +0 -121
- package/src/gep/cliContracts.js +0 -1190
- package/src/gep/contentHash.js +0 -1
- package/src/gep/conversationDistiller.js +0 -1
- package/src/gep/conversationSniffer.js +0 -1
- package/src/gep/crypto.js +0 -1
- package/src/gep/curriculum.js +0 -1
- package/src/gep/deviceId.js +0 -1
- package/src/gep/directoryClient.js +0 -115
- package/src/gep/envFingerprint.js +0 -1
- package/src/gep/epigenetics.js +0 -1
- package/src/gep/execBridge.js +0 -1
- package/src/gep/executionTrace.js +0 -291
- package/src/gep/explore.js +0 -1
- package/src/gep/featureFlags.js +0 -121
- package/src/gep/gitOps.js +0 -265
- package/src/gep/hash.js +0 -1
- package/src/gep/hostErrorClassifier.js +0 -34
- package/src/gep/hubFetch.js +0 -1
- package/src/gep/hubReview.js +0 -1
- package/src/gep/hubSearch.js +0 -1
- package/src/gep/hubVerify.js +0 -1
- package/src/gep/idleScheduler.js +0 -400
- package/src/gep/issueReporter.js +0 -416
- package/src/gep/learningSignals.js +0 -1
- package/src/gep/llmReview.js +0 -92
- package/src/gep/localStateAwareness.js +0 -243
- package/src/gep/mailboxTransport.js +0 -119
- package/src/gep/memoryGraph.js +0 -1
- package/src/gep/memoryGraphAdapter.js +0 -1
- package/src/gep/mutation.js +0 -1
- package/src/gep/narrativeMemory.js +0 -1
- package/src/gep/oauthLogin.js +0 -181
- package/src/gep/openPRRegistry.js +0 -1
- package/src/gep/paths.js +0 -522
- package/src/gep/personality.js +0 -1
- package/src/gep/policyCheck.js +0 -1
- package/src/gep/portable.js +0 -103
- package/src/gep/privacyClient.js +0 -235
- package/src/gep/prompt.js +0 -1
- package/src/gep/questionGenerator.js +0 -518
- package/src/gep/recallInject.js +0 -1
- package/src/gep/recallVerifier.js +0 -1
- package/src/gep/reflection.js +0 -1
- package/src/gep/sanitize.js +0 -264
- package/src/gep/savingsCore.js +0 -1
- package/src/gep/schemas/capsule.js +0 -170
- package/src/gep/schemas/gene.js +0 -154
- package/src/gep/schemas/index.js +0 -8
- package/src/gep/schemas/protocol.js +0 -51
- package/src/gep/schemas/task.js +0 -74
- package/src/gep/selector.js +0 -1
- package/src/gep/selfPR.js +0 -469
- package/src/gep/signals.js +0 -776
- package/src/gep/skill2gep.js +0 -1056
- package/src/gep/skill2gepAudit.js +0 -303
- package/src/gep/skill2recipes.js +0 -511
- package/src/gep/skillDistiller.js +0 -1
- package/src/gep/skillPublisher.js +0 -358
- package/src/gep/solidify.js +0 -1
- package/src/gep/strategy.js +0 -1
- package/src/gep/taskReceiver.js +0 -575
- package/src/gep/tokenSavings.js +0 -1
- package/src/gep/trajectoryExport.js +0 -1
- package/src/gep/validationReport.js +0 -55
- package/src/gep/validator/index.js +0 -411
- package/src/gep/validator/reporter.js +0 -210
- package/src/gep/validator/sandboxExecutor.js +0 -480
- package/src/gep/validator/stakeBootstrap.js +0 -357
- package/src/gep/workspaceKeychain.js +0 -1
- package/src/ops/cleanup.js +0 -80
- package/src/ops/commentary.js +0 -60
- package/src/ops/health_check.js +0 -104
- package/src/ops/index.js +0 -11
- package/src/ops/innovation.js +0 -67
- package/src/ops/lifecycle.js +0 -798
- package/src/ops/self_repair.js +0 -76
- package/src/ops/skills_monitor.js +0 -147
- package/src/ops/trigger.js +0 -33
- package/src/proxy/clientSettings.js +0 -405
- package/src/proxy/envelope.js +0 -59
- package/src/proxy/extensions/dmHandler.js +0 -45
- package/src/proxy/extensions/sessionHandler.js +0 -141
- package/src/proxy/extensions/skillUpdater.js +0 -64
- package/src/proxy/extensions/traceControl.js +0 -1
- package/src/proxy/index.js +0 -1395
- package/src/proxy/inject.js +0 -1
- package/src/proxy/lifecycle/manager.js +0 -1568
- package/src/proxy/mailbox/state.js +0 -207
- package/src/proxy/mailbox/store.js +0 -602
- package/src/proxy/router/cache_passthrough.js +0 -26
- package/src/proxy/router/features.js +0 -84
- package/src/proxy/router/gemini_route.js +0 -154
- package/src/proxy/router/messages_route.js +0 -535
- package/src/proxy/router/model_router.js +0 -113
- package/src/proxy/router/models_route.js +0 -52
- package/src/proxy/router/ollama_route.js +0 -103
- package/src/proxy/router/responses_route.js +0 -170
- package/src/proxy/router/vertex_route.js +0 -110
- package/src/proxy/server/http.js +0 -363
- package/src/proxy/server/routes.js +0 -558
- package/src/proxy/server/settings.js +0 -115
- package/src/proxy/sync/engine.js +0 -179
- package/src/proxy/sync/inbound.js +0 -211
- package/src/proxy/sync/outbound.js +0 -361
- package/src/proxy/task/monitor.js +0 -131
- package/src/proxy/trace/extractor.js +0 -1
- package/src/proxy/trace/usage.js +0 -1
- package/src/solo/breaker.js +0 -25
- package/src/solo/gitGuard.js +0 -65
- package/src/webui/client/clientJs/assets.js +0 -111
- package/src/webui/client/clientJs/bootstrap.js +0 -92
- package/src/webui/client/clientJs/common.js +0 -77
- package/src/webui/client/clientJs/i18n.js +0 -366
- package/src/webui/client/clientJs/index.js +0 -35
- package/src/webui/client/clientJs/interactions.js +0 -351
- package/src/webui/client/clientJs/overview.js +0 -152
- package/src/webui/client/clientJs/personality.js +0 -285
- package/src/webui/client/clientJs/pipelines.js +0 -330
- package/src/webui/client/indexHtml.js +0 -221
- package/src/webui/client/static.js +0 -23
- package/src/webui/client/stylesCss.js +0 -639
- package/src/webui/client/vendor/README.md +0 -15
- package/src/webui/client/vendor/echarts.min.js +0 -45
- package/src/webui/index.js +0 -14
- package/src/webui/observer/assets.js +0 -146
- package/src/webui/observer/index.js +0 -37
- package/src/webui/observer/interactions.js +0 -127
- package/src/webui/observer/jsonl.js +0 -75
- package/src/webui/observer/paths.js +0 -46
- package/src/webui/observer/personality.js +0 -43
- package/src/webui/observer/pipelineEvents.js +0 -58
- package/src/webui/observer/redact.js +0 -63
- package/src/webui/observer/runs.js +0 -356
- package/src/webui/observer/safety.js +0 -57
- package/src/webui/observer/skills.js +0 -70
- package/src/webui/observer/status.js +0 -71
- package/src/webui/server/http.js +0 -138
- package/src/webui/server/routes.js +0 -41
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Standardized ValidationReport type for GEP.
|
|
2
|
-
// Machine-readable, self-contained, and interoperable.
|
|
3
|
-
// Can be consumed by external Hubs or Judges for automated assessment.
|
|
4
|
-
|
|
5
|
-
const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
|
|
6
|
-
const { captureEnvFingerprint, envFingerprintKey } = require('./envFingerprint');
|
|
7
|
-
|
|
8
|
-
// Build a standardized ValidationReport from raw validation results.
|
|
9
|
-
function buildValidationReport({ geneId, commands, results, envFp, startedAt, finishedAt }) {
|
|
10
|
-
const env = envFp || captureEnvFingerprint();
|
|
11
|
-
const resultsList = Array.isArray(results) ? results : [];
|
|
12
|
-
const cmdsList = Array.isArray(commands) ? commands : resultsList.map(function (r) { return r && r.cmd ? String(r.cmd) : ''; });
|
|
13
|
-
const overallOk = resultsList.length > 0 && resultsList.every(function (r) { return r && r.ok; });
|
|
14
|
-
const durationMs =
|
|
15
|
-
Number.isFinite(startedAt) && Number.isFinite(finishedAt) ? finishedAt - startedAt : null;
|
|
16
|
-
|
|
17
|
-
const report = {
|
|
18
|
-
type: 'ValidationReport',
|
|
19
|
-
schema_version: SCHEMA_VERSION,
|
|
20
|
-
id: 'vr_' + Date.now(),
|
|
21
|
-
gene_id: geneId || null,
|
|
22
|
-
env_fingerprint: env,
|
|
23
|
-
env_fingerprint_key: envFingerprintKey(env),
|
|
24
|
-
commands: cmdsList.map(function (cmd, i) {
|
|
25
|
-
const r = resultsList[i] || {};
|
|
26
|
-
return {
|
|
27
|
-
command: String(cmd || ''),
|
|
28
|
-
ok: !!r.ok,
|
|
29
|
-
stdout: String(r.out || r.stdout || '').slice(0, 4000), // Updated to support both 'out' and 'stdout'
|
|
30
|
-
stderr: String(r.err || r.stderr || '').slice(0, 4000), // Updated to support both 'err' and 'stderr'
|
|
31
|
-
};
|
|
32
|
-
}),
|
|
33
|
-
overall_ok: overallOk,
|
|
34
|
-
duration_ms: durationMs,
|
|
35
|
-
created_at: new Date().toISOString(),
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
report.asset_id = computeAssetId(report);
|
|
39
|
-
return report;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Validate that an object is a well-formed ValidationReport.
|
|
43
|
-
function isValidValidationReport(obj) {
|
|
44
|
-
if (!obj || typeof obj !== 'object') return false;
|
|
45
|
-
if (obj.type !== 'ValidationReport') return false;
|
|
46
|
-
if (!obj.id || typeof obj.id !== 'string') return false;
|
|
47
|
-
if (!Array.isArray(obj.commands)) return false;
|
|
48
|
-
if (typeof obj.overall_ok !== 'boolean') return false;
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
module.exports = {
|
|
53
|
-
buildValidationReport,
|
|
54
|
-
isValidValidationReport,
|
|
55
|
-
};
|
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
// src/gep/validator/index.js
|
|
2
|
-
//
|
|
3
|
-
// Validator mode entry-point. Feature-gated by EVOLVER_VALIDATOR_ENABLED.
|
|
4
|
-
// Intended usage: called once per evolve cycle, it will fetch assigned
|
|
5
|
-
// validation tasks from the Hub, execute the provided commands in a
|
|
6
|
-
// sandbox, and submit a ValidationReport back to the Hub.
|
|
7
|
-
//
|
|
8
|
-
// Failure modes are all non-fatal -- a validator that cannot reach the Hub
|
|
9
|
-
// or cannot sandbox-execute will simply skip and try again next cycle.
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
const { getNodeId, buildHubHeaders, buildNodeScopedHubHeaders } = require('../a2aProtocol');
|
|
13
|
-
const { hubFetch } = require('../hubFetch');
|
|
14
|
-
const { runInSandbox, runPreflight } = require('./sandboxExecutor');
|
|
15
|
-
const { buildReportPayload, submitReport } = require('./reporter');
|
|
16
|
-
const { ensureValidatorStake } = require('./stakeBootstrap');
|
|
17
|
-
const { readFeatureFlag } = require('../featureFlags');
|
|
18
|
-
const { resolveHubUrl: resolveDefaultHubUrl } = require('../../config');
|
|
19
|
-
|
|
20
|
-
const FETCH_TIMEOUT_MS = Number(process.env.EVOLVER_VALIDATOR_FETCH_TIMEOUT_MS) || 8_000;
|
|
21
|
-
const MAX_TASKS_PER_CYCLE = Math.max(1, Number(process.env.EVOLVER_VALIDATOR_MAX_TASKS_PER_CYCLE) || 2);
|
|
22
|
-
|
|
23
|
-
function buildValidatorHubHeaders() {
|
|
24
|
-
const buildHeaders = buildNodeScopedHubHeaders || buildHubHeaders;
|
|
25
|
-
return buildHeaders();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Three-tier resolution:
|
|
29
|
-
// 1. Local env (highest priority - user escape hatch). Both ON and OFF are honored.
|
|
30
|
-
// 2. Persisted feature flag from disk (set by hub mailbox).
|
|
31
|
-
// 3. Code default: ON (validator role is opt-out as of v1.69.0).
|
|
32
|
-
function isValidatorEnabled() {
|
|
33
|
-
const raw = String(process.env.EVOLVER_VALIDATOR_ENABLED || '').toLowerCase().trim();
|
|
34
|
-
if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') return false;
|
|
35
|
-
if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on') return true;
|
|
36
|
-
try {
|
|
37
|
-
const flag = readFeatureFlag('validator_enabled');
|
|
38
|
-
if (typeof flag === 'boolean') return flag;
|
|
39
|
-
} catch (_) {}
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function resolveHubUrl() {
|
|
44
|
-
// Always go through config.resolveHubUrl() — it reads the same env vars
|
|
45
|
-
// getHubUrl() reads plus EVOLVER_DEFAULT_HUB_URL, falls back to the
|
|
46
|
-
// PUBLIC_DEFAULT_HUB_URL constant, and enforces the https schema check
|
|
47
|
-
// (with EVOMAP_HUB_ALLOW_INSECURE=1 as the explicit escape hatch).
|
|
48
|
-
// Preferring the raw getHubUrl() here would let an http:// URL slip past
|
|
49
|
-
// this frame; hubFetch would still catch it, but the in-validator code
|
|
50
|
-
// path should not look like it accepts an unvalidated URL.
|
|
51
|
-
return resolveDefaultHubUrl();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Fetch validation tasks assigned to this node.
|
|
56
|
-
*/
|
|
57
|
-
async function fetchValidationTasks() {
|
|
58
|
-
const nodeId = getNodeId();
|
|
59
|
-
if (!nodeId) return [];
|
|
60
|
-
const hubUrl = resolveHubUrl();
|
|
61
|
-
const url = hubUrl.replace(/\/+$/, '') + '/a2a/fetch';
|
|
62
|
-
|
|
63
|
-
const controller = new AbortController();
|
|
64
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
65
|
-
|
|
66
|
-
const msg = {
|
|
67
|
-
protocol: 'gep-a2a',
|
|
68
|
-
protocol_version: '1.0.0',
|
|
69
|
-
message_type: 'fetch',
|
|
70
|
-
message_id: 'msg_' + Date.now().toString(36),
|
|
71
|
-
sender_id: nodeId,
|
|
72
|
-
timestamp: new Date().toISOString(),
|
|
73
|
-
payload: {
|
|
74
|
-
// tasks_only short-circuits the Hub's asset search + GDI credit charge.
|
|
75
|
-
// Pre-v1.77.0 this sent `validation_only: true`, which Hub ignored,
|
|
76
|
-
// causing ~96 credits per 15s poll to be deducted from the owner. Keep
|
|
77
|
-
// validation_only as a belt-and-suspenders hint for newer Hubs that
|
|
78
|
-
// understand it; tasks_only is the universal no-charge switch.
|
|
79
|
-
tasks_only: true,
|
|
80
|
-
validation_only: true,
|
|
81
|
-
include_tasks: true,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const res = await hubFetch(url, {
|
|
87
|
-
method: 'POST',
|
|
88
|
-
headers: buildValidatorHubHeaders(),
|
|
89
|
-
body: JSON.stringify(msg),
|
|
90
|
-
signal: controller.signal,
|
|
91
|
-
});
|
|
92
|
-
clearTimeout(timer);
|
|
93
|
-
if (!res.ok) return [];
|
|
94
|
-
const data = await res.json();
|
|
95
|
-
const p = data.payload || data;
|
|
96
|
-
const list = Array.isArray(p.validation_tasks) ? p.validation_tasks : [];
|
|
97
|
-
return list;
|
|
98
|
-
} catch (_) {
|
|
99
|
-
clearTimeout(timer);
|
|
100
|
-
return [];
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Validate a single task.
|
|
106
|
-
* @param {object} task - Hub-provided validation task
|
|
107
|
-
* @returns {Promise<{ status: string, report?: object, response?: object, reason?: string }>}
|
|
108
|
-
*/
|
|
109
|
-
async function validateOneTask(task) {
|
|
110
|
-
if (!task || !task.task_id || !task.nonce) {
|
|
111
|
-
return { status: 'skipped', reason: 'invalid_task_shape' };
|
|
112
|
-
}
|
|
113
|
-
const commands = Array.isArray(task.validation_commands) ? task.validation_commands : [];
|
|
114
|
-
if (commands.length === 0) {
|
|
115
|
-
// Nothing to run -- report overall_ok=false so the Hub records a fail and moves on.
|
|
116
|
-
const payload = buildReportPayload(task, { results: [], overallOk: false, durationMs: 0 });
|
|
117
|
-
const r = await submitReport(payload);
|
|
118
|
-
return { status: 'reported_empty', report: payload, response: r };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let execution;
|
|
122
|
-
try {
|
|
123
|
-
execution = await runInSandbox(commands, {});
|
|
124
|
-
} catch (err) {
|
|
125
|
-
execution = {
|
|
126
|
-
results: [{
|
|
127
|
-
cmd: commands[0],
|
|
128
|
-
ok: false,
|
|
129
|
-
stdout: '',
|
|
130
|
-
stderr: 'sandbox_error: ' + (err && err.message ? err.message : String(err)),
|
|
131
|
-
exitCode: -1,
|
|
132
|
-
durationMs: 0,
|
|
133
|
-
timedOut: false,
|
|
134
|
-
}],
|
|
135
|
-
overallOk: false,
|
|
136
|
-
durationMs: 0,
|
|
137
|
-
stoppedEarly: true,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const payload = buildReportPayload(task, execution);
|
|
142
|
-
const response = await submitReport(payload);
|
|
143
|
-
return {
|
|
144
|
-
status: response && response.ok ? 'reported' : 'report_failed',
|
|
145
|
-
report: payload,
|
|
146
|
-
response,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Lazy single-shot preflight cache. Runs once per process, reused by both the
|
|
151
|
-
// daemon and the inline runValidatorCycle() called from the main evolve loop.
|
|
152
|
-
let _preflightPromise = null;
|
|
153
|
-
let _preflightResult = null;
|
|
154
|
-
|
|
155
|
-
async function _ensurePreflight() {
|
|
156
|
-
if (_preflightResult) return _preflightResult;
|
|
157
|
-
if (_preflightPromise) return _preflightPromise;
|
|
158
|
-
_preflightPromise = (async () => {
|
|
159
|
-
try {
|
|
160
|
-
_preflightResult = await runPreflight();
|
|
161
|
-
} catch (err) {
|
|
162
|
-
_preflightResult = {
|
|
163
|
-
ok: false,
|
|
164
|
-
reason: 'preflight_threw',
|
|
165
|
-
durationMs: 0,
|
|
166
|
-
stderrTail: err && err.message ? String(err.message) : String(err),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
_daemonStats.preflight = {
|
|
170
|
-
ok: !!_preflightResult.ok,
|
|
171
|
-
reason: _preflightResult.reason || null,
|
|
172
|
-
duration_ms: _preflightResult.durationMs || 0,
|
|
173
|
-
at: Date.now(),
|
|
174
|
-
};
|
|
175
|
-
if (!_preflightResult.ok) {
|
|
176
|
-
_preflightDisabled = true;
|
|
177
|
-
try {
|
|
178
|
-
console.warn(
|
|
179
|
-
'[Validator] Preflight FAILED (' + (_preflightResult.reason || 'unknown') + '): ' +
|
|
180
|
-
'cannot spawn `node <script>` inside the sandbox on this host. ' +
|
|
181
|
-
'Validator role is being SKIPPED to avoid flooding the Hub with env_fail reports. ' +
|
|
182
|
-
'Likely causes: node binary not on PATH for headless invocations, missing exec perm, ' +
|
|
183
|
-
'or unwritable TMPDIR. Diagnostic stderr: ' + (_preflightResult.stderrTail || '<empty>')
|
|
184
|
-
);
|
|
185
|
-
} catch (_) { /* console unavailable -- non-fatal */ }
|
|
186
|
-
}
|
|
187
|
-
return _preflightResult;
|
|
188
|
-
})();
|
|
189
|
-
return _preflightPromise;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Run one validator cycle. Intended to be called from the main evolve loop.
|
|
194
|
-
* Returns a summary object (useful for logging/tests).
|
|
195
|
-
*
|
|
196
|
-
* @param {{ skipStake?: boolean }} [opts]
|
|
197
|
-
*/
|
|
198
|
-
async function runValidatorCycle(opts) {
|
|
199
|
-
const options = opts || {};
|
|
200
|
-
if (!isValidatorEnabled()) {
|
|
201
|
-
return { skipped: 'disabled' };
|
|
202
|
-
}
|
|
203
|
-
// Lazy preflight gate: refuse to talk to the Hub if the local toolchain
|
|
204
|
-
// cannot even run a trivial `node <script>` in the sandbox. Without this,
|
|
205
|
-
// every validation task posted to this node returns duration_ms=1 /
|
|
206
|
-
// commands_passed=0 and the Hub auto-quarantines the node for chronic
|
|
207
|
-
// env_fail.
|
|
208
|
-
const pf = await _ensurePreflight();
|
|
209
|
-
if (!pf.ok) {
|
|
210
|
-
return { skipped: 'preflight_failed', reason: pf.reason || 'unknown' };
|
|
211
|
-
}
|
|
212
|
-
if (!options.skipStake) {
|
|
213
|
-
try {
|
|
214
|
-
await ensureValidatorStake({});
|
|
215
|
-
} catch (err) {
|
|
216
|
-
// non-fatal -- stake may already exist or will retry later
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const tasks = await fetchValidationTasks();
|
|
221
|
-
if (!tasks || tasks.length === 0) {
|
|
222
|
-
return { tasks: 0, processed: 0 };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const slice = tasks.slice(0, MAX_TASKS_PER_CYCLE);
|
|
226
|
-
const outcomes = [];
|
|
227
|
-
for (const t of slice) {
|
|
228
|
-
try {
|
|
229
|
-
const outcome = await validateOneTask(t);
|
|
230
|
-
outcomes.push({ task_id: t.task_id, ...outcome });
|
|
231
|
-
} catch (err) {
|
|
232
|
-
outcomes.push({
|
|
233
|
-
task_id: t.task_id,
|
|
234
|
-
status: 'error',
|
|
235
|
-
reason: err && err.message ? err.message : String(err),
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return { tasks: tasks.length, processed: outcomes.length, outcomes };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// --- Background daemon ---
|
|
243
|
-
//
|
|
244
|
-
// In long-running modes (--loop / --mad-dog) the validator role used to share
|
|
245
|
-
// the main evolve loop and was suppressed by idle gating (skipHubCalls) when
|
|
246
|
-
// the host was saturated. The daemon runs independently on its own timer so
|
|
247
|
-
// that validator participation does not depend on the agent's foreground load.
|
|
248
|
-
|
|
249
|
-
function _envIntDefault(key, fallback) {
|
|
250
|
-
const raw = process.env[key];
|
|
251
|
-
if (raw === undefined || raw === '') return fallback;
|
|
252
|
-
const n = Number(raw);
|
|
253
|
-
return Number.isFinite(n) ? n : fallback;
|
|
254
|
-
}
|
|
255
|
-
const DAEMON_INTERVAL_MS = Math.max(15000, _envIntDefault('EVOLVER_VALIDATOR_DAEMON_INTERVAL_MS', 60000));
|
|
256
|
-
const DAEMON_FIRST_DELAY_MS = Math.max(0, _envIntDefault('EVOLVER_VALIDATOR_DAEMON_FIRST_DELAY_MS', 30000));
|
|
257
|
-
|
|
258
|
-
let _daemonTimer = null;
|
|
259
|
-
let _daemonRunning = false;
|
|
260
|
-
let _daemonInflight = false;
|
|
261
|
-
let _daemonStats = { ticks: 0, processed: 0, lastError: null, lastRunAt: 0, preflight: null };
|
|
262
|
-
let _preflightDisabled = false;
|
|
263
|
-
// Generation counter: every poke / start bumps this. Tick captures
|
|
264
|
-
// its gen at entry; if it doesn't match on resume, the tick refuses
|
|
265
|
-
// to schedule its own next timer (a fresher path already owns it).
|
|
266
|
-
// Mirrors the `_heartbeatGen` pattern in proxy/lifecycle/manager.js.
|
|
267
|
-
let _daemonGen = 0;
|
|
268
|
-
|
|
269
|
-
async function _daemonTick() {
|
|
270
|
-
if (_daemonInflight) return;
|
|
271
|
-
_daemonInflight = true;
|
|
272
|
-
const myGen = _daemonGen;
|
|
273
|
-
try {
|
|
274
|
-
if (!isValidatorEnabled()) {
|
|
275
|
-
_daemonStats.ticks += 1;
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
if (_preflightDisabled) {
|
|
279
|
-
// Preflight failed during startup -- stay silent. We still tick so the
|
|
280
|
-
// operator can re-enable later by restarting the agent after fixing PATH.
|
|
281
|
-
_daemonStats.ticks += 1;
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
const out = await runValidatorCycle({});
|
|
285
|
-
_daemonStats.ticks += 1;
|
|
286
|
-
_daemonStats.lastRunAt = Date.now();
|
|
287
|
-
if (out && typeof out.processed === 'number') {
|
|
288
|
-
_daemonStats.processed += out.processed;
|
|
289
|
-
if (out.processed > 0) {
|
|
290
|
-
console.log('[ValidatorDaemon] processed ' + out.processed + '/' + (out.tasks || 0) + ' task(s).');
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
} catch (err) {
|
|
294
|
-
_daemonStats.lastError = err && err.message || String(err);
|
|
295
|
-
console.warn('[ValidatorDaemon] tick failed (non-fatal): ' + _daemonStats.lastError);
|
|
296
|
-
} finally {
|
|
297
|
-
_daemonInflight = false;
|
|
298
|
-
// Generation guard: a poke or start fired while we were awaiting
|
|
299
|
-
// runValidatorCycle(). The fresher path already armed its own
|
|
300
|
-
// timer (T2); scheduling here would overwrite the T2 reference
|
|
301
|
-
// without clearing it, leaving two self-rearming chains running
|
|
302
|
-
// at doubled cadence.
|
|
303
|
-
if (_daemonRunning && myGen === _daemonGen) {
|
|
304
|
-
_daemonTimer = setTimeout(_daemonTick, DAEMON_INTERVAL_MS);
|
|
305
|
-
if (_daemonTimer && typeof _daemonTimer.unref === 'function') _daemonTimer.unref();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Start an independent validator daemon. Safe to call once at process boot
|
|
312
|
-
* from --loop / --mad-dog modes. No-op if already running.
|
|
313
|
-
*/
|
|
314
|
-
function startValidatorDaemon() {
|
|
315
|
-
if (_daemonRunning) return false;
|
|
316
|
-
_daemonRunning = true;
|
|
317
|
-
_daemonGen += 1;
|
|
318
|
-
if (isValidatorEnabled()) {
|
|
319
|
-
// Surface an explicit notice every time validator mode starts so that users
|
|
320
|
-
// who do not read docs cannot later claim they were unaware the validator
|
|
321
|
-
// consumes network / stake / CPU. See GH issue #451.
|
|
322
|
-
try {
|
|
323
|
-
console.log(
|
|
324
|
-
'[Validator] Validator mode is ENABLED. Your node will participate in ' +
|
|
325
|
-
'Hub validation tasks: CPU, network bandwidth, and staked credits WILL ' +
|
|
326
|
-
'be used. To opt out, set EVOLVER_VALIDATOR_ENABLED=false (or unset it).'
|
|
327
|
-
);
|
|
328
|
-
} catch (_) { /* console unavailable -- non-fatal */ }
|
|
329
|
-
// Fire preflight immediately so the user-visible warning lands before the
|
|
330
|
-
// first DAEMON_FIRST_DELAY_MS tick. _ensurePreflight is idempotent and the
|
|
331
|
-
// first call from runValidatorCycle will reuse this promise.
|
|
332
|
-
_ensurePreflight();
|
|
333
|
-
}
|
|
334
|
-
_daemonTimer = setTimeout(_daemonTick, DAEMON_FIRST_DELAY_MS);
|
|
335
|
-
if (_daemonTimer && typeof _daemonTimer.unref === 'function') _daemonTimer.unref();
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function stopValidatorDaemon() {
|
|
340
|
-
_daemonRunning = false;
|
|
341
|
-
if (_daemonTimer) {
|
|
342
|
-
clearTimeout(_daemonTimer);
|
|
343
|
-
_daemonTimer = null;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Force the daemon to fire its next tick immediately. The internal
|
|
349
|
-
* setTimeout runs on libuv's monotonic clock, which freezes during macOS
|
|
350
|
-
* sleep -- without a poke, the post-wake tick may be up to
|
|
351
|
-
* DAEMON_INTERVAL_MS (default 60s) away on the resumed clock, leaving
|
|
352
|
-
* the node "alive on heartbeat but functionally inert" for that window.
|
|
353
|
-
*
|
|
354
|
-
* Safe to call when the daemon is stopped (no-op) or when a tick is
|
|
355
|
-
* already in flight (the in-flight guard at _daemonTick / the running
|
|
356
|
-
* gate at the top of the function both protect against double-firing).
|
|
357
|
-
*
|
|
358
|
-
* Wired from the index.js SIGCONT handler so wake events fan out to the
|
|
359
|
-
* validator the same way they already do to heartbeat and SSE.
|
|
360
|
-
*/
|
|
361
|
-
function pokeValidatorDaemon() {
|
|
362
|
-
if (!_daemonRunning) return false;
|
|
363
|
-
if (_daemonTimer) {
|
|
364
|
-
clearTimeout(_daemonTimer);
|
|
365
|
-
_daemonTimer = null;
|
|
366
|
-
}
|
|
367
|
-
// Bump generation: any in-flight `_daemonTick` from before the
|
|
368
|
-
// poke will see its captured gen mismatch on resume and skip its
|
|
369
|
-
// tail-`setTimeout`, so we don't end up with two concurrent timers
|
|
370
|
-
// self-rearming at doubled cadence.
|
|
371
|
-
_daemonGen += 1;
|
|
372
|
-
_daemonTimer = setTimeout(_daemonTick, 0);
|
|
373
|
-
if (_daemonTimer && typeof _daemonTimer.unref === 'function') _daemonTimer.unref();
|
|
374
|
-
return true;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function getValidatorDaemonStats() {
|
|
378
|
-
return Object.assign({ running: _daemonRunning, intervalMs: DAEMON_INTERVAL_MS }, _daemonStats);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function _resetPreflightForTests() {
|
|
382
|
-
_preflightPromise = null;
|
|
383
|
-
_preflightResult = null;
|
|
384
|
-
_preflightDisabled = false;
|
|
385
|
-
_daemonStats.preflight = null;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function _setPreflightForTests(result) {
|
|
389
|
-
_preflightResult = result || { ok: true, durationMs: 0 };
|
|
390
|
-
_preflightPromise = Promise.resolve(_preflightResult);
|
|
391
|
-
_preflightDisabled = !_preflightResult.ok;
|
|
392
|
-
_daemonStats.preflight = {
|
|
393
|
-
ok: !!_preflightResult.ok,
|
|
394
|
-
reason: _preflightResult.reason || null,
|
|
395
|
-
duration_ms: _preflightResult.durationMs || 0,
|
|
396
|
-
at: Date.now(),
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
module.exports = {
|
|
401
|
-
runValidatorCycle,
|
|
402
|
-
fetchValidationTasks,
|
|
403
|
-
validateOneTask,
|
|
404
|
-
isValidatorEnabled,
|
|
405
|
-
startValidatorDaemon,
|
|
406
|
-
stopValidatorDaemon,
|
|
407
|
-
pokeValidatorDaemon,
|
|
408
|
-
getValidatorDaemonStats,
|
|
409
|
-
_resetPreflightForTests,
|
|
410
|
-
_setPreflightForTests,
|
|
411
|
-
};
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
// src/gep/validator/reporter.js
|
|
2
|
-
//
|
|
3
|
-
// Builds and submits validation reports for validation tasks.
|
|
4
|
-
// Sends via POST /a2a/report with `task_id` + `nonce` so the Hub routes the
|
|
5
|
-
// body into processValidationReport instead of the normal reporting path.
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
const { buildHubHeaders, buildNodeScopedHubHeaders, getHubUrl, getNodeId } = require('../a2aProtocol');
|
|
10
|
-
const { hubFetch } = require('../hubFetch');
|
|
11
|
-
const { captureEnvFingerprint } = require('../envFingerprint');
|
|
12
|
-
const { resolveHubUrl: resolveDefaultHubUrl } = require('../../config');
|
|
13
|
-
|
|
14
|
-
const REPORT_TIMEOUT_MS = Number(process.env.EVOLVER_VALIDATOR_REPORT_TIMEOUT_MS) || 10_000;
|
|
15
|
-
|
|
16
|
-
function buildValidatorReportHeaders() {
|
|
17
|
-
const buildHeaders = buildNodeScopedHubHeaders || buildHubHeaders;
|
|
18
|
-
return buildHeaders();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Per-command stderr/stdout tail bundled into the report. Bounded so a noisy
|
|
22
|
-
// validator cannot blow up the Hub's a2a/report payload size.
|
|
23
|
-
const REPORT_CMD_TAIL_CHARS = 240;
|
|
24
|
-
const REPORT_MAX_COMMANDS = 8;
|
|
25
|
-
|
|
26
|
-
// Failure classes attached to each command result so the Hub can distinguish
|
|
27
|
-
// "validator host is broken" (env_fail) from "Gene's validation cmd cannot
|
|
28
|
-
// run in our hardened sandbox" (sandbox_block) from a real assertion failure
|
|
29
|
-
// (exit_nonzero).
|
|
30
|
-
const FAILURE_CLASS = {
|
|
31
|
-
OK: 'ok',
|
|
32
|
-
PARSE_FAILED: 'parse_failed',
|
|
33
|
-
EXEC_NOT_ALLOWED: 'executable_not_allowed',
|
|
34
|
-
SANDBOX_BLOCK_NODE_FLAG: 'sandbox_block_node_flag',
|
|
35
|
-
SPAWN_FAILED: 'spawn_failed',
|
|
36
|
-
TIMEOUT: 'timeout',
|
|
37
|
-
EXIT_NONZERO: 'exit_nonzero',
|
|
38
|
-
UNKNOWN: 'unknown',
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
function classifyCommandFailure(result) {
|
|
42
|
-
if (!result) return FAILURE_CLASS.UNKNOWN;
|
|
43
|
-
if (result.ok) return FAILURE_CLASS.OK;
|
|
44
|
-
const stderr = String(result.stderr || '');
|
|
45
|
-
if (result.timedOut) return FAILURE_CLASS.TIMEOUT;
|
|
46
|
-
// Check sandbox-block-on-node-flag BEFORE the generic parse_failed check
|
|
47
|
-
// because assertNodeCommandSafe wraps its rejection inside the
|
|
48
|
-
// `command_parse_failed:` stderr prefix; a plain prefix check would
|
|
49
|
-
// misclassify the Gene/Hub-incompatibility case as a parse error.
|
|
50
|
-
if (stderr.includes('node flag not allowed in sandbox')
|
|
51
|
-
|| stderr.includes('node requires a script file argument')) {
|
|
52
|
-
return FAILURE_CLASS.SANDBOX_BLOCK_NODE_FLAG;
|
|
53
|
-
}
|
|
54
|
-
if (stderr.startsWith('executable_not_allowed:')) return FAILURE_CLASS.EXEC_NOT_ALLOWED;
|
|
55
|
-
if (stderr.startsWith('command_parse_failed:')) return FAILURE_CLASS.PARSE_FAILED;
|
|
56
|
-
if (stderr.startsWith('spawn_failed:')) return FAILURE_CLASS.SPAWN_FAILED;
|
|
57
|
-
if (typeof result.exitCode === 'number' && result.exitCode !== 0) {
|
|
58
|
-
return FAILURE_CLASS.EXIT_NONZERO;
|
|
59
|
-
}
|
|
60
|
-
return FAILURE_CLASS.UNKNOWN;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function tailString(s, n) {
|
|
64
|
-
const str = typeof s === 'string' ? s : '';
|
|
65
|
-
if (str.length <= n) return str;
|
|
66
|
-
return str.slice(-n);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function summarizeResult(result) {
|
|
70
|
-
return {
|
|
71
|
-
cmd: typeof result.cmd === 'string' ? result.cmd.slice(0, 200) : '',
|
|
72
|
-
ok: !!result.ok,
|
|
73
|
-
exit_code: typeof result.exitCode === 'number' ? result.exitCode : null,
|
|
74
|
-
duration_ms: typeof result.durationMs === 'number' ? result.durationMs : 0,
|
|
75
|
-
timed_out: !!result.timedOut,
|
|
76
|
-
failure_class: classifyCommandFailure(result),
|
|
77
|
-
stderr_tail: tailString(result.stderr, REPORT_CMD_TAIL_CHARS),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Aggregate failure_class for the whole report -- the first non-OK class wins.
|
|
82
|
-
// Surfaces a single label in `failure_class` at the top of the report so that
|
|
83
|
-
// the Hub can route on it without iterating per-command.
|
|
84
|
-
function aggregateFailureClass(commands) {
|
|
85
|
-
if (!Array.isArray(commands) || commands.length === 0) return FAILURE_CLASS.UNKNOWN;
|
|
86
|
-
if (commands.every((c) => c.failure_class === FAILURE_CLASS.OK)) return FAILURE_CLASS.OK;
|
|
87
|
-
for (const c of commands) {
|
|
88
|
-
if (c.failure_class && c.failure_class !== FAILURE_CLASS.OK) return c.failure_class;
|
|
89
|
-
}
|
|
90
|
-
return FAILURE_CLASS.UNKNOWN;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function resolveHubUrl() {
|
|
94
|
-
try {
|
|
95
|
-
const u = getHubUrl && getHubUrl();
|
|
96
|
-
if (u && typeof u === 'string') return u;
|
|
97
|
-
} catch (_) {}
|
|
98
|
-
return resolveDefaultHubUrl();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function hashExecutionLog(results) {
|
|
102
|
-
const list = Array.isArray(results) ? results : [];
|
|
103
|
-
const hash = crypto.createHash('sha256');
|
|
104
|
-
for (const r of list) {
|
|
105
|
-
hash.update(String(r.cmd || ''));
|
|
106
|
-
hash.update('\0');
|
|
107
|
-
hash.update(String(r.ok ? 1 : 0));
|
|
108
|
-
hash.update('\0');
|
|
109
|
-
hash.update(String(r.exitCode || 0));
|
|
110
|
-
hash.update('\0');
|
|
111
|
-
hash.update((r.stdout || '').slice(0, 4000));
|
|
112
|
-
hash.update('\0');
|
|
113
|
-
hash.update((r.stderr || '').slice(0, 4000));
|
|
114
|
-
hash.update('\0');
|
|
115
|
-
}
|
|
116
|
-
return hash.digest('hex');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Build the validation report payload the Hub expects.
|
|
121
|
-
*
|
|
122
|
-
* @param {{ task_id: string, nonce: string }} task
|
|
123
|
-
* @param {{ results: Array, overallOk: boolean, durationMs: number }} execution
|
|
124
|
-
* @param {{ reproductionScore?: number }} [opts]
|
|
125
|
-
*/
|
|
126
|
-
function buildReportPayload(task, execution, opts) {
|
|
127
|
-
const options = opts || {};
|
|
128
|
-
const results = Array.isArray(execution && execution.results) ? execution.results : [];
|
|
129
|
-
const commandsTotal = results.length;
|
|
130
|
-
const commandsPassed = results.filter((r) => r && r.ok).length;
|
|
131
|
-
const env = captureEnvFingerprint();
|
|
132
|
-
|
|
133
|
-
const reproductionScore = Number.isFinite(options.reproductionScore)
|
|
134
|
-
? options.reproductionScore
|
|
135
|
-
: (commandsTotal > 0 ? commandsPassed / commandsTotal : 0);
|
|
136
|
-
|
|
137
|
-
// Per-command summaries. Bounded to REPORT_MAX_COMMANDS so a runaway batch
|
|
138
|
-
// cannot blow up the Hub payload size.
|
|
139
|
-
const commands = results.slice(0, REPORT_MAX_COMMANDS).map(summarizeResult);
|
|
140
|
-
const failureClass = !execution.overallOk
|
|
141
|
-
? aggregateFailureClass(commands)
|
|
142
|
-
: FAILURE_CLASS.OK;
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
task_id: task.task_id,
|
|
146
|
-
nonce: task.nonce,
|
|
147
|
-
overall_ok: !!execution.overallOk,
|
|
148
|
-
commands_passed: commandsPassed,
|
|
149
|
-
commands_total: commandsTotal,
|
|
150
|
-
duration_ms: execution.durationMs || 0,
|
|
151
|
-
execution_log_hash: hashExecutionLog(results),
|
|
152
|
-
env_fingerprint: env,
|
|
153
|
-
reproduction_score: Math.max(0, Math.min(1, reproductionScore)),
|
|
154
|
-
// New diagnostic surface (Hub-side classifier should prefer these when
|
|
155
|
-
// present; older Hubs that ignore unknown fields keep working unchanged).
|
|
156
|
-
failure_class: failureClass,
|
|
157
|
-
commands,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function submitReport(payload) {
|
|
162
|
-
const nodeId = getNodeId();
|
|
163
|
-
if (!nodeId) return { ok: false, error: 'no_node_id' };
|
|
164
|
-
const hubUrl = resolveHubUrl();
|
|
165
|
-
const url = hubUrl.replace(/\/+$/, '') + '/a2a/report';
|
|
166
|
-
|
|
167
|
-
const controller = new AbortController();
|
|
168
|
-
const timer = setTimeout(() => controller.abort(), REPORT_TIMEOUT_MS);
|
|
169
|
-
|
|
170
|
-
const msg = {
|
|
171
|
-
protocol: 'gep-a2a',
|
|
172
|
-
protocol_version: '1.0.0',
|
|
173
|
-
message_type: 'report',
|
|
174
|
-
message_id: 'msg_' + Date.now().toString(36) + '_' + crypto.randomBytes(3).toString('hex'),
|
|
175
|
-
sender_id: nodeId,
|
|
176
|
-
timestamp: new Date().toISOString(),
|
|
177
|
-
payload,
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const res = await hubFetch(url, {
|
|
182
|
-
method: 'POST',
|
|
183
|
-
headers: buildValidatorReportHeaders(),
|
|
184
|
-
body: JSON.stringify(msg),
|
|
185
|
-
signal: controller.signal,
|
|
186
|
-
});
|
|
187
|
-
clearTimeout(timer);
|
|
188
|
-
const text = await res.text();
|
|
189
|
-
if (!res.ok) {
|
|
190
|
-
return { ok: false, status: res.status, error: text.slice(0, 400) };
|
|
191
|
-
}
|
|
192
|
-
try {
|
|
193
|
-
return { ok: true, data: JSON.parse(text) };
|
|
194
|
-
} catch {
|
|
195
|
-
return { ok: true, data: text };
|
|
196
|
-
}
|
|
197
|
-
} catch (err) {
|
|
198
|
-
clearTimeout(timer);
|
|
199
|
-
return { ok: false, error: err && err.message ? err.message : String(err) };
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
module.exports = {
|
|
204
|
-
buildReportPayload,
|
|
205
|
-
submitReport,
|
|
206
|
-
hashExecutionLog,
|
|
207
|
-
classifyCommandFailure,
|
|
208
|
-
aggregateFailureClass,
|
|
209
|
-
FAILURE_CLASS,
|
|
210
|
-
};
|