Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/src/contracts.ts
Snapshot at 34e3175
1/** FH-060 cumulative snapshots; absent fields preserve legacy state compatibility. */
2export interface Usage {
3 inputTokens: number;
4 outputTokens: number;
5 model?: string;
6 source: "reported" | "metered";
7 at: string;
8}
9export interface UsageSources {
10 reported: Usage | null;
11 metered: Usage | null;
12}
13export type TokenBudgetState = "unchecked" | "within" | "near" | "over";
14export interface BlockerResolution {
15 at: string;
16 by: "release" | "promote" | "owner";
17 reason?: string;
18}
19/** FH-048 public history. Missing fields on legacy records mean open/unknown. */
20export interface WireBlocker {
21 id: string;
22 packet: string;
23 claimingAgent: string;
24 pushingAgent: string;
25 repoName: string;
26 sha: string;
27 paths: string[];
28 resolved?: BlockerResolution | null;
29 at?: string | null;
30 pushingPacket?: string | null;
31}
32export interface BlockerView {
33 id: string;
34 agent: string;
35 packet: string;
36 paths: string[];
37 reason: string;
38 resolved: boolean;
39 resolution?: BlockerResolution | null;
40 claimingAgent?: string;
41 pushingAgent?: string;
42 sha?: string;
43 repoName?: string;
44 at?: string | null;
45 pushingPacket?: string | null;
46}
47
48/** View projection of the public FH-005 wire state; never includes credentials. */
49export interface ProjectState {
50 project: {
51 id: string;
52 repoName: string;
53 defaultBranch: string;
54 remote?: string;
55 events?: "pending" | "active";
56 settings?: { requireReceipt: boolean };
57 };
58 canonicalHead?: string;
59 recentActivity?: Activity[];
60 agents: { name: string; createdAt?: string | null }[];
61 forks: { agent: string; repoName: string; head: string; events?: "pending" | "active" }[];
62 packets: Packet[];
63 claims: {
64 packet: string;
65 agent: string;
66 repoName: string;
67 paths: string[];
68 active: boolean;
69 usage?: UsageSources;
70 createdAt?: string;
71 }[];
72 blockers: BlockerView[];
73 races?: Race[];
74 reviews: Review[];
75 ledger: {
76 id?: string;
77 before?: string;
78 review: string;
79 sha: string;
80 promotedAt: string;
81 receiptDigest?: string | null;
82 race?: string;
83 }[];
84 reconcilerAlerts: {
85 sha: string;
86 message: string;
87 id?: string;
88 repoName?: string;
89 ref?: string;
90 before?: string;
91 after?: string;
92 }[];
93 promotionPending?: LedgerEntry | null;
94}
95
96export interface Packet {
97 id: string;
98 title: string;
99 goal: string;
100 paths: string[];
101 acceptance: string[];
102 mode?: "standard" | "race";
103 tokenBudget?: number | null;
104 /** Absent legacy records are treated as null. */
105 archived?: null | { at: string; by: "owner" };
106}
107
108export interface Review {
109 id: string;
110 repoName: string;
111 agent: string;
112 packet: string;
113 sha: string;
114 receiptDigest: string | null;
115 requestedAt?: string;
116 approvedAt?: string | null;
117 base?: string;
118 status: "pending" | "approved" | "stale" | "promoted";
119 /** Raw core flag, independent of the derived display status. */
120 promoted?: boolean;
121 stale?: boolean;
122 returned?: null | { reason: string; at: string };
123 approval?: { sha: string; receiptDigest: string | null } | null;
124 receipt?: ReceiptSummary | null;
125 usageAtReview?: UsageSources;
126}
127
128/** FH-032 fixed public wire summary: logs belong only to the owner receipt endpoint. */
129export type ReceiptStatus = "running" | "passed" | "failed" | "infrastructure-error" | "timed-out";
130export interface ReceiptSummary {
131 status: ReceiptStatus;
132 trigger: "review-created" | "before-promote";
133 digest: string | null;
134 startedAt: string;
135 finishedAt: string | null;
136 durationMs: number | null;
137 checks: {
138 name: string;
139 tier: "full" | "quick";
140 runtime?: "container" | "dynamic-worker";
141 message?: string;
142 outcome: ReceiptStatus;
143 durationMs: number | null;
144 }[];
145}
146/** Stored runner receipt returned by the owner-only endpoint. No bearer token plumbing. */
147export interface OwnerReceipt {
148 digest: string;
149 receipt: {
150 commit: string;
151 checks: {
152 check: { name: string };
153 logs: { stdout: string; stderr: string; truncated: boolean };
154 }[];
155 };
156}
157
158/** FH-003 receive sequence is the cursor, not event time. */
159export interface Activity {
160 sequence: number;
161 namespace: string;
162 repoName: string;
163 agent: string | null;
164 ref: string;
165 before: string;
166 after: string;
167 changedPaths: string[];
168 changedPathCount?: number;
169 changedPathsTruncated?: boolean;
170 eventTimestamp: string;
171 recordedAt: string;
172}
173
174export interface ActivityPage {
175 activity: Activity[];
176 nextAfter: number;
177}
178export interface ProjectOption {
179 id: string;
180 label: string;
181}
182export interface Brand {
183 name: string;
184 tagline: string;
185}
186
187/** An HTTP implementation today; a host may supply its Cap'n Web ui capability later. */
188export interface DashboardTransport {
189 /** Local connection state, separate from the Core wire snapshot. */
190 live?: {
191 subscribe(listener: (paused: boolean) => void): () => void;
192 reconnect(): Promise<void>;
193 };
194 getReview?(project: string, review: string): Promise<ReviewDetail>;
195 getDiff?(project: string, review: string, path: string): Promise<ReviewDiff>;
196 returnReview?(
197 project: string,
198 review: string,
199 reason: string,
200 ): Promise<{ review: StateResponse["reviews"][number] }>;
201 archivePacket?(project: string, packet: string): Promise<{ packet: Packet }>;
202 unarchivePacket?(project: string, packet: string): Promise<{ packet: Packet }>;
203 resolveBlocker?(project: string, blocker: string, reason: string): Promise<{ blocker: WireBlocker }>;
204 getState(project: string): Promise<ProjectState>;
205 getActivity(project: string, after?: number): Promise<ActivityPage>;
206 approve(project: string, review: string): Promise<unknown>;
207 promote(project: string, review: string): Promise<{ sha: string }>;
208 /** Optional because the read-only agent host has no owner capabilities. */
209 getReceipt?(project: string, review: string): Promise<OwnerReceipt>;
210 setSettings?(
211 project: string,
212 settings: { requireReceipt: boolean },
213 ): Promise<{ settings: { requireReceipt: boolean } }>;
214 /** Optional invalidations only. The authoritative snapshot still comes from getState. */
215 subscribe?(project: string, invalidate: () => void): () => void;
216}
217
218/** FH-005 JSON snapshot. Only live claims appear; blockers retain resolved history. */
219export interface StateResponse {
220 project: {
221 id: string;
222 namespace: string;
223 repoName: string;
224 remote: string;
225 defaultBranch: string;
226 events?: "pending" | "active";
227 settings?: { requireReceipt: boolean };
228 };
229 agents: { name: string; createdAt?: string | null }[];
230 forks: {
231 agent: string;
232 repoName: string;
233 head: string;
234 remote: string;
235 createdAt: string;
236 events?: "pending" | "active";
237 }[];
238 packets: ProjectState["packets"];
239 claims: { packet: string; agent: string; repoName: string; paths: string[]; createdAt: string; usage?: UsageSources }[];
240 blockers: WireBlocker[];
241 races?: Race[];
242 reviews: {
243 approvedAt?: string | null;
244 usageAtReview?: UsageSources;
245 id: string;
246 packet: string;
247 agent: string;
248 repoName: string;
249 sha: string;
250 base: string;
251 receiptDigest: string | null;
252 receipt?: ReceiptSummary | null;
253 returned?: null | { reason: string; at: string };
254 approval: { sha: string; receiptDigest: string | null } | null;
255 stale: boolean;
256 promoted: boolean;
257 createdAt: string;
258 }[];
259 ledger: LedgerEntry[];
260 activity: Activity[];
261 reconcilerAlerts: {
262 id: string;
263 repoName: string;
264 ref: string;
265 before: string;
266 after: string;
267 reason: string;
268 }[];
269 promotionPending: LedgerEntry | null;
270}
271export interface LedgerEntry {
272 id: string;
273 review: string;
274 repoName: string;
275 ref: string;
276 before: string;
277 after: string;
278 receiptDigest: string | null;
279 recordedAt: string;
280 race?: string;
281}
282
283/** FH-039 fixed bounded detail contract. Public reads never contain logs or authentication credentials. */
284export interface ReviewDetail {
285 review: StateResponse["reviews"][number];
286 packet: ProjectState["packets"][number];
287 changes: {
288 base: string;
289 head: string;
290 fileCount: number;
291 truncated: boolean;
292 files: { path: string; status: "added" | "modified" | "deleted" }[];
293 };
294 commits: {
295 sha: string;
296 subject: string;
297 authorName: string;
298 packetTrailer: string | null;
299 agentTrailer: string | null;
300 }[];
301 commitsTruncated: boolean;
302 promotion: null | {
303 before: string;
304 after: string;
305 recordedAt: string;
306 receiptDigest: string | null;
307 };
308 blockers: StateResponse["blockers"];
309}
310export interface ReviewDiff {
311 path: string;
312 status: "added" | "modified" | "deleted";
313 binary: boolean;
314 patch: string;
315 truncated: boolean;
316}
317
318/** Optional FH-052/053 read projection; absent evidence is never synthesized. */
319export interface RaceEntrant {
320 usage?: UsageSources;
321 budget?: TokenBudgetState;
322 agent: string;
323 repoName: string;
324 claimedAt: string;
325 firstPushAt: string | null;
326 reviewRequestedAt: string | null;
327 receiptPassedAt: string | null;
328 approvedAt: string | null;
329 promotedAt: string | null;
330 place: number | null;
331 closedAt: string | null;
332 closedBy?: "release" | "owner" | "race-finished" | null;
333 promoteRejected?: { at: string; expected: string; found: string } | null;
334}
335/** FH-063 fixed public read seam; absent fields preserve races created before judges. */
336export interface RaceAdvice { ranking: string[]; rationale: string; at: string; }
337export interface Race {
338 judge?: string | null;
339 verdict?: RaceAdvice | null;
340 packet: string;
341 status: "open" | "finished";
342 tokenBudget?: number | null;
343 entrants: RaceEntrant[];
344 finishedAt?: string | null;
345 winner?: string | null;
346 /** Exact string reference to ledger[].id; never an array position. */
347 ledgerEntryId?: string | null;
348 places?: { agent: string; place: number }[];
349}
350