Report the running build's version to the master

The version is the checkout's HEAD commit (short hash + commit time),
read once at startup — updates arrive as `git pull`, so there is no
release number to bump. It rides on the socket hello, socket reports,
and the HTTP report fallback; the field is optional in the protocol so
older masters ignore it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-12 18:54:19 -05:00
co-authored by Claude Fable 5
parent c493796602
commit 38a9c6c1b7
5 changed files with 65 additions and 2 deletions
+3
View File
@@ -20,6 +20,7 @@ import { runTrade, startScheduler, stopScheduler, getSchedulerStatus } from './a
import { FIRMS, automationKey } from './automations';
import { POINT_VALUES } from './trading-logic';
import { queueRun } from './queue-run';
import { getBuildVersion } from './version';
import type {
Command,
InstanceCapabilities,
@@ -161,6 +162,7 @@ export function pushReport(firms: unknown[], scheduler: unknown): boolean {
firms: firms as never,
scheduler: scheduler as never,
capabilities: collectCapabilities(),
version: getBuildVersion() ?? undefined,
});
}
@@ -226,6 +228,7 @@ function connect() {
instance,
id: instanceId || undefined,
capabilities: collectCapabilities(),
version: getBuildVersion() ?? undefined,
} satisfies InstanceMessage));
});
+10 -2
View File
@@ -87,6 +87,14 @@ export interface SchedulerState {
sessionOpenDelayMin?: number;
}
/** The build this instance is running — its checkout's HEAD commit. The
* commit time is what orders two different versions. Optional in every
* message so an older instance or master simply ignores it. */
export interface BuildVersion {
commit: string;
time: string;
}
export interface AutomationSummary {
key: string;
label: string;
@@ -200,6 +208,6 @@ export type MasterMessage =
| { type: 'command'; id: string; command: Command };
export type InstanceMessage =
| { type: 'hello'; instance: string; id?: string; capabilities: InstanceCapabilities }
| { type: 'report'; firms: FirmStats[]; scheduler: SchedulerState; capabilities?: InstanceCapabilities }
| { type: 'hello'; instance: string; id?: string; capabilities: InstanceCapabilities; version?: BuildVersion }
| { type: 'report'; firms: FirmStats[]; scheduler: SchedulerState; capabilities?: InstanceCapabilities; version?: BuildVersion }
| { type: 'ack'; id: string; ok: boolean; result?: unknown; error?: string };
+2
View File
@@ -13,6 +13,7 @@
import { getSetting } from './db';
import { pushReport } from './master-link';
import { getBuildVersion } from './version';
import { getClients } from './clients';
import { getTradableFirms } from './firm-catalog';
import { getAccountConfig } from './account-type-resolver';
@@ -292,6 +293,7 @@ export async function sendReport(): Promise<void> {
id: getSetting('instance_id') || undefined,
firms,
scheduler,
version: getBuildVersion() ?? undefined,
}),
});
} catch {
+43
View File
@@ -0,0 +1,43 @@
/**
* The version of the running build.
*
* Updates arrive as `git pull` (scripts/update-check.mjs), so the checkout's
* HEAD commit *is* the version — there is no release number to bump and
* nothing for anyone to forget. The commit time rides along because hashes
* don't order: it is what lets the master say which of two different
* versions is the stale one.
*
* Read once and cached: the update task restarts the process after every
* pull, so HEAD cannot move under a running build — and when it briefly does
* (pull landed, restart pending), the cached value still describes the code
* that is actually executing.
*/
import { execFileSync } from 'node:child_process';
export interface BuildVersion {
/** Short commit hash of HEAD, e.g. "c493796". */
commit: string;
/** ISO commit time of HEAD — orders versions, which hashes cannot. */
time: string;
}
let cached: BuildVersion | null | undefined;
/** null when this isn't a git checkout or git isn't installed — both mean
* "no version to report", and the master shows the machine as an old build. */
export function getBuildVersion(): BuildVersion | null {
if (cached !== undefined) return cached;
try {
const out = execFileSync('git', ['log', '-1', '--format=%h %cI'], {
cwd: process.cwd(),
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
const [commit, time] = out.split(' ');
cached = commit && time ? { commit, time } : null;
} catch {
cached = null;
}
return cached;
}