feat: heartbeat reconciliation — panel re-dispatches resources a node lost

Agents (Go + Node) now report the resource IDs they actually hold in
each heartbeat. When an expected resource is missing locally (e.g. the
node's /etc/nexusgate state was wiped and only the agent restarted, so
no re-enrollment happens), the panel re-queues applies for just the
affected nodes via reconcileFromHeartbeat: gated on no queued/running
jobs for the server (in-flight jobs are never failed by this path) and
throttled to one trigger per server per 120s, audited as
system:heartbeat. reconcileServer gains optional onlyNodeIds /
failInFlight parameters; the enrollment call is unchanged. Heartbeats
without a resources array (old agents) behave exactly as before.
This commit is contained in:
aaaamoooo
2026-10-20 07:46:33 +00:00
parent f2b7d8e181
commit abb1a4344b
5 changed files with 342 additions and 46 deletions
+5 -1
View File
@@ -339,7 +339,11 @@ function serviceHealth(service) {
}
async function heartbeat() {
await request('/api/agent/heartbeat', { method: 'POST', body: JSON.stringify({ version: VERSION, system: systemInfo(), engine: engineHealth() }) });
const engine = engineHealth();
// Report the resources actually held locally; the panel re-dispatches any
// expected resource missing from this list (e.g. after a state wipe).
const resources = readResources().map((resource) => resource && resource.id).filter((value) => typeof value === 'string' && value);
await request('/api/agent/heartbeat', { method: 'POST', body: JSON.stringify({ version: VERSION, system: systemInfo(), engine, resources }) });
try { fs.writeFileSync('/etc/nexusgate/last-heartbeat.json', `${JSON.stringify({ at:new Date().toISOString() })}\n`, { mode:0o600 }); }
catch (error) { log('Heartbeat reached controller but readiness file failed', error.message); }
}