Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-mcp/src/races.ts

Snapshot at 34e3175

Containing directory

5,506 bytes · typescriptView raw
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