Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-mcp/src/races.ts
Snapshot at 34e3175
1import { z } from "zod";
2import { PublicError } from "./core";
3import { budgetStateSchema, usageSourcesSchema } from "./usage";
4
5// FH-052/053 wire seam only. Core owns enrollment, milestones and placings.
6const name = z.string().min(1);
7const agentName = z
8 .string()
9 .max(32)
10 .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
11const forkName = z
12 .string()
13 .max(128)
14 .regex(/^[a-z0-9][a-z0-9-]*$/);
15const packetId = z
16 .string()
17 .max(128)
18 .regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/);
19const timestamp = z.string().min(1).nullable();
20const entrant = z
21 .object({
22 agent: agentName,
23 repoName: forkName,
24 claimedAt: z.string().min(1),
25 firstPushAt: timestamp,
26 reviewRequestedAt: timestamp,
27 receiptPassedAt: timestamp,
28 approvedAt: timestamp,
29 promotedAt: timestamp,
30 place: z.number().int().min(1).max(3).nullable(),
31 closedAt: timestamp,
32 closedBy: z.enum(["release", "owner", "race-finished"]).nullable(),
33 promoteRejected: z.object({ at: name, expected: name, found: name }).nullable().optional(),
34 usage: usageSourcesSchema.optional(),
35 budget: budgetStateSchema.optional(),
36 })
37 .passthrough();
38const race = z
39 .object({
40 packet: packetId,
41 tokenBudget: z.number().int().positive().nullable().optional(),
42 status: z.enum(["open", "finished"]),
43 finishedAt: timestamp,
44 winner: agentName.nullable(),
45 // This is ledger[].id, never a numeric sequence.
46 ledgerEntryId: name.nullable(),
47 // Integrated FH-053 exposes the finish's agent/place summary as well.
48 places: z
49 .array(z.object({ agent: agentName, place: z.number().int().min(1).max(3) }))
50 .max(3)
51 .optional(),
52 entrants: z.array(entrant).max(3),
53 judge: agentName.nullable().optional(),
54 verdict: z
55 .object({
56 ranking: z
57 .array(agentName)
58 .max(3)
59 .refine(
60 (ranking) => new Set(ranking).size === ranking.length,
61 "Duplicate entrants are not allowed.",
62 ),
63 rationale: z.string().min(1).max(2000),
64 at: z.string().min(1),
65 })
66 .nullable()
67 .optional(),
68 })
69 .passthrough();
70const packets = z.array(
71 z.object({ id: packetId, mode: z.enum(["standard", "race"]).default("standard") }).passthrough(),
72);
73const races = z.array(race);
74export type RaceState = z.infer<typeof race>;
75
76/** Legacy state has no races; supplied race data must satisfy the fixed contract. */
77export function raceSnapshot(snapshot: Record<string, unknown>) {
78 return {
79 packets: snapshot.packets === undefined ? undefined : packets.parse(snapshot.packets),
80 races: snapshot.races === undefined ? [] : races.parse(snapshot.races),
81 };
82}
83
84export function callerRaces(states: RaceState[], agent: string): RaceState[] {
85 // Include closed entries: /me contains live work, standings retain race history.
86 return states.filter((state) => state.entrants.some((entry) => entry.agent === agent));
87}
88
89/** Judge assignments come only from public core state, never from client arguments. */
90export function judgeRaces(states: RaceState[], agent: string): RaceState[] {
91 return states.filter((state) => state.judge === agent);
92}
93
94/** Select a live race binding without ever falling back to a standard claim. */
95export function raceBinding(
96 state: ReturnType<typeof raceSnapshot>,
97 packet: string,
98 agent: string,
99): { packet: string; agent: string; repoName: string } | undefined {
100 const metadata = state.packets?.filter((entry) => entry.id === packet) ?? [];
101 const standings = state.races.filter((entry) => entry.packet === packet);
102 if (metadata.length > 1 || standings.length > 1)
103 throw new PublicError(409, "Exactly one race packet binding is required.");
104 if (metadata[0]?.mode !== "race" && standings.length === 0) return undefined;
105 if (metadata[0]?.mode === "standard")
106 throw new PublicError(409, "Race state disagrees with packet mode; refresh status.");
107 const active = standings[0]?.entrants.filter((entry) => entry.agent === agent) ?? [];
108 if (
109 standings[0]?.status !== "open" ||
110 active.length !== 1 ||
111 active[0]!.closedAt !== null ||
112 active[0]!.closedBy !== null
113 )
114 throw new PublicError(409, "Exactly one live caller-owned race entrant binding is required.");
115 return { packet, agent, repoName: active[0]!.repoName };
116}
117
118export function raceLine(state: RaceState): string {
119 const standings = state.entrants.map((entry) => {
120 const milestones = [
121 ["Claimed", entry.claimedAt],
122 ["First push", entry.firstPushAt],
123 ["Review requested", entry.reviewRequestedAt],
124 ["Checks passed", entry.receiptPassedAt],
125 ["Approved", entry.approvedAt],
126 ["Promoted", entry.promotedAt],
127 ] as const;
128 const reached = milestones.filter(([, at]) => at !== null).at(-1)!;
129 return (
130 `${entry.agent} on ${entry.repoName}: ${reached[0]} at ${reached[1]}` +
131 (entry.place === null ? "" : `; place ${entry.place}`) +
132 (entry.closedAt === null ? "" : `; closed by ${entry.closedBy} at ${entry.closedAt}`) +
133 (entry.promoteRejected
134 ? `; promote rejected at ${entry.promoteRejected.at} (expected ${entry.promoteRejected.expected}, found ${entry.promoteRejected.found})`
135 : "")
136 );
137 });
138 return (
139 `${state.packet} race ${state.status}` +
140 (state.winner === null ? "" : `; winner ${state.winner}`) +
141 (state.ledgerEntryId === null ? "" : `; ledger ${state.ledgerEntryId}`) +
142 ` — ${standings.join(" | ")}`
143 ).replace(/[\r\n\u2028\u2029]/g, " ");
144}
145