Report each tick's outcome and the updater's state to the master
The master saw a scheduler's last run time and nothing of what it did. Each tick now records, per lane, its outcome, a line on what happened (what fired, or the spread, contract or hold cap that stopped it) and why the lane's accounts sat it out, counted by reason with a few names each. It rides the scheduler status as lastTicks. Each report also carries the auto-updater's state from the files it keeps and from git: its last check, a failed update waiting to retry, how far the checkout is behind origin, and tracked files edited by hand, which stop the fast-forward pull. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
db0ad99a76
commit
ab3132182a
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* The auto-updater's state on this machine (scripts/update-check.mjs), for
|
||||
* the master: its last check, an update that failed and waits to retry, how
|
||||
* far the checkout is behind origin, and tracked files edited by hand — which
|
||||
* stop the fast-forward pull. Read from the files the updater keeps and from
|
||||
* git, and cached: the reporter asks every 30 seconds.
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { UpdaterStatus } from './master-protocol';
|
||||
|
||||
const CACHE_MS = 2 * 60_000;
|
||||
const BRANCH = process.env.AUTOFIRMER_BRANCH ?? 'master';
|
||||
|
||||
// Per process: instrumentation.ts and the route handlers each load this module.
|
||||
const g = globalThis as typeof globalThis & { __updaterStatus?: { at: number; status: UpdaterStatus } };
|
||||
|
||||
function readScript(file: string): string | null {
|
||||
try {
|
||||
return readFileSync(path.join(process.cwd(), 'scripts', file), 'utf8');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function git(args: string[]): string | null {
|
||||
try {
|
||||
return execFileSync('git', args, {
|
||||
cwd: process.cwd(),
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
timeout: 5_000,
|
||||
}).trimEnd();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readFailure(): UpdaterStatus['failed'] {
|
||||
try {
|
||||
const f = JSON.parse(readScript('.update-failed') ?? 'null') as Record<string, unknown> | null;
|
||||
if (!f || typeof f.target !== 'string') return null;
|
||||
const at = Number(f.at);
|
||||
return {
|
||||
target: f.target.slice(0, 12),
|
||||
step: String(f.step ?? '').slice(0, 40),
|
||||
at: Number.isFinite(at) ? new Date(at).toISOString() : '',
|
||||
attempts: Number(f.count) || 1,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function updaterStatus(now = Date.now()): UpdaterStatus {
|
||||
const cached = g.__updaterStatus;
|
||||
if (cached && now - cached.at < CACHE_MS) return cached.status;
|
||||
|
||||
// "<ISO time> <status>", rewritten on every check.
|
||||
const last = /^(\S+)\s+(.*)$/.exec(readScript('.last-check')?.trim() ?? '');
|
||||
const behind = git(['rev-list', '--count', `HEAD..origin/${BRANCH}`]);
|
||||
const changed = git(['status', '--porcelain', '--untracked-files=no']);
|
||||
const status: UpdaterStatus = {
|
||||
lastCheckAt: last ? last[1] : null,
|
||||
lastCheck: last ? last[2].slice(0, 200) : null,
|
||||
failed: readFailure(),
|
||||
builtCommit: readScript('.built-commit')?.trim().slice(0, 12) || null,
|
||||
behind: behind !== null && /^\d+$/.test(behind) ? Number(behind) : null,
|
||||
// "XY path" per line; the status column can start with a space.
|
||||
localChanges: changed
|
||||
? changed.split('\n').map((line) => line.slice(3).trim()).filter(Boolean).slice(0, 10)
|
||||
: [],
|
||||
};
|
||||
g.__updaterStatus = { at: now, status };
|
||||
return status;
|
||||
}
|
||||
Reference in New Issue
Block a user