From a088381749628cfb90d7fadff0ea66d39865e423 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Wed, 2 Sep 2026 12:07:25 -0500 Subject: [PATCH] Store the person uuid the master issues, so renames keep our identity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The welcome now carries a personId; it lands in the instance_id setting and rides on every hello and HTTP report from then on. The master keys the person by that uuid, so changing instance_name moves our history to the new name instead of starting a stranger. Read-only in Settings on purpose — editing it by hand would detach this machine from its person. Co-Authored-By: Claude Fable 5 --- app/api/settings/route.ts | 3 +++ app/settings/page.tsx | 11 ++++++++++- lib/db.ts | 4 ++++ lib/master-link.ts | 10 +++++++++- lib/master-protocol.ts | 7 +++++-- lib/reporter.ts | 9 ++++++++- 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/app/api/settings/route.ts b/app/api/settings/route.ts index 688c81e..376029d 100644 --- a/app/api/settings/route.ts +++ b/app/api/settings/route.ts @@ -9,6 +9,9 @@ export async function GET() { for (const key of VALID_KEYS) { result[key] = getSetting(key); } + // Read-only on purpose (not in VALID_KEYS): the master issues this uuid, + // and editing it by hand would detach the instance from its person. + result.instance_id = getSetting('instance_id'); return NextResponse.json(result); } diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 49e3f50..91c7e6e 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -17,6 +17,7 @@ interface AppSettings { max_concurrent_accounts: string | null; master_dashboard_url: string | null; instance_name: string | null; + instance_id: string | null; trading_hours: string | null; } @@ -34,6 +35,7 @@ export default function SettingsPage() { } | null>(null); const [syncing, setSyncing] = useState(false); const [instanceName, setInstanceName] = useState(''); + const [instanceId, setInstanceId] = useState(''); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); @@ -75,6 +77,9 @@ export default function SettingsPage() { if (s.instance_name != null) { setInstanceName(s.instance_name); } + if (s.instance_id != null) { + setInstanceId(s.instance_id); + } }); // Load cached contracts; if cache is empty, auto-resolve @@ -211,7 +216,11 @@ export default function SettingsPage() {

Instance Name

- Identifier for this autotrader on the master dashboard + Identifier for this autotrader on the master dashboard. + Renaming is safe — the master tracks this machine by its + person ID{instanceId ? ( + <> ({instanceId.slice(0, 8)}) + ) : ' once it has connected'}, so history follows the new name.

{ + // The stored uuid, once the master has issued one — it, not the name, + // is who this instance is, so renaming instance_name keeps the same + // person (and their accounting history) on the master. + const instanceId = getSetting('instance_id'); socket.send(JSON.stringify({ type: 'hello', instance, + id: instanceId || undefined, capabilities: collectCapabilities(), } satisfies InstanceMessage)); }); @@ -235,6 +240,9 @@ function connect() { if (msg.type === 'welcome') { s.ready = true; s.attempt = 0; + if (msg.personId && msg.personId !== getSetting('instance_id')) { + setSetting('instance_id', msg.personId); + } console.log(`[master-link] connected to ${url} as "${instance}"`); return; } diff --git a/lib/master-protocol.ts b/lib/master-protocol.ts index 733e985..b51d117 100644 --- a/lib/master-protocol.ts +++ b/lib/master-protocol.ts @@ -106,10 +106,13 @@ export type Command = | { kind: 'report-now' }; export type MasterMessage = - | { type: 'welcome' } + /** personId is the person's stable uuid; the instance stores it and + * presents it on later hellos, which is what makes a rename follow the + * person. */ + | { type: 'welcome'; personId?: string } | { type: 'command'; id: string; command: Command }; export type InstanceMessage = - | { type: 'hello'; instance: string; capabilities: InstanceCapabilities } + | { type: 'hello'; instance: string; id?: string; capabilities: InstanceCapabilities } | { type: 'report'; firms: FirmStats[]; scheduler: SchedulerState; capabilities?: InstanceCapabilities } | { type: 'ack'; id: string; ok: boolean; result?: unknown; error?: string }; diff --git a/lib/reporter.ts b/lib/reporter.ts index 9d330f5..952ead4 100644 --- a/lib/reporter.ts +++ b/lib/reporter.ts @@ -132,7 +132,14 @@ export async function sendReport(): Promise { await fetch(new URL('/api/report', url), { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ instance: instanceName, firms, scheduler }), + body: JSON.stringify({ + instance: instanceName, + // Same identity the socket hello carries, so a rename resolves + // to the same person whichever transport the report takes. + id: getSetting('instance_id') || undefined, + firms, + scheduler, + }), }); } catch { // Master might be down — silently continue