* Run one validator cycle. Intended to be called from the main evolve loop. * Returns a summary object (useful for logging/tests). * * @param {{ skipStake?: boolean }} [opts]
(opts)
| 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 | // |
no test coverage detected