Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/test/fixtures.ts
Snapshot at 34e3175
1import type {
2 Activity,
3 ProjectState,
4 StateResponse,
5 ReceiptSummary,
6 ReceiptStatus,
7 OwnerReceipt,
8} from "../src/contracts.ts";
9
10export const HEAD = "a".repeat(40);
11export const NEXT_HEAD = "b".repeat(40);
12export const DIGEST = "d".repeat(64);
13
14/** Fictional fixture data. Neither this nor fixture routes ship in production assets. */
15function fixtureData(id = "demo"): ProjectState {
16 if (id === "quiet")
17 return {
18 project: { id, repoName: id, defaultBranch: "main", settings: { requireReceipt: true } },
19 canonicalHead: HEAD,
20 agents: [],
21 forks: [],
22 packets: [],
23 claims: [],
24 blockers: [],
25 reviews: [],
26 ledger: [],
27 reconcilerAlerts: [],
28 };
29 return {
30 project: { id, repoName: id, defaultBranch: "main", settings: { requireReceipt: true } },
31 canonicalHead: HEAD,
32 agents: [{ name: "builder" }, { name: "reviewer" }, { name: "docs" }],
33 forks: [
34 { agent: "builder", repoName: "demo--builder-1", head: NEXT_HEAD },
35 { agent: "reviewer", repoName: "demo--reviewer-1", head: "c".repeat(40) },
36 { agent: "docs", repoName: "demo--docs-1", head: "e".repeat(40) },
37 ],
38 packets: [
39 {
40 id: "ui-01",
41 title: "Project navigation & owner review",
42 goal: "Keep scoped work and its evidence together, from claim to promotion.",
43 paths: ["src/dashboard/**"],
44 acceptance: ["Phone and desktop layouts render", "Approve uses the review endpoint"],
45 },
46 {
47 id: "api-02",
48 title: "Claim conflict detection",
49 goal: "Raise a blocker when an agent pushes into another live claim.",
50 paths: ["src/claims/**"],
51 acceptance: ["Conflicting path creates a blocker"],
52 },
53 {
54 id: "docs-03",
55 title: "Owner setup guide",
56 goal: "Describe deployment configuration and the evidence required for release.",
57 paths: ["docs/**"],
58 acceptance: ["Examples use fictional fixture values"],
59 },
60 ],
61 claims: [
62 {
63 packet: "ui-01",
64 agent: "builder",
65 repoName: "demo--builder-1",
66 paths: ["src/dashboard/**"],
67 active: true,
68 },
69 {
70 packet: "api-02",
71 agent: "reviewer",
72 repoName: "demo--reviewer-1",
73 paths: ["src/claims/**"],
74 active: true,
75 },
76 {
77 packet: "docs-03",
78 agent: "docs",
79 repoName: "demo--docs-1",
80 paths: ["docs/**"],
81 active: true,
82 },
83 ],
84 blockers: [
85 {
86 id: "block-1",
87 agent: "reviewer",
88 packet: "api-02",
89 paths: ["src/claims/index.ts"],
90 reason: "Push overlaps a path held by another agent's live claim.",
91 resolved: false,
92 },
93 ],
94 reviews: [
95 {
96 id: "review-1",
97 repoName: "demo--builder-1",
98 agent: "builder",
99 packet: "ui-01",
100 sha: NEXT_HEAD,
101 receiptDigest: DIGEST,
102 status: "pending",
103 receipt: fixtureReceipt("passed"),
104 },
105 {
106 id: "review-2",
107 repoName: "demo--docs-1",
108 agent: "docs",
109 packet: "docs-03",
110 sha: "f".repeat(40),
111 receiptDigest: "1".repeat(64),
112 status: "stale",
113 receipt: fixtureReceipt("passed"),
114 },
115 {
116 id: "review-3",
117 repoName: "demo--reviewer-1",
118 agent: "reviewer",
119 packet: "api-02",
120 sha: "c".repeat(40),
121 receiptDigest: null,
122 status: "pending",
123 },
124 ],
125 ledger: [],
126 reconcilerAlerts: [],
127 };
128}
129
130export function fixtureActivity(): Activity[] {
131 return [
132 {
133 sequence: 1,
134 namespace: "fixture",
135 repoName: "demo--builder-1",
136 agent: "builder",
137 ref: "refs/heads/main",
138 before: HEAD,
139 after: NEXT_HEAD,
140 changedPaths: ["src/dashboard/index.ts"],
141 eventTimestamp: "2026-10-12T00:10:00.000Z",
142 recordedAt: "2026-10-12T00:10:01.000Z",
143 },
144 {
145 sequence: 2,
146 namespace: "fixture",
147 repoName: "demo--reviewer-1",
148 agent: "reviewer",
149 ref: "refs/heads/main",
150 before: HEAD,
151 after: "c".repeat(40),
152 changedPaths: ["src/claims/index.ts"],
153 eventTimestamp: "2026-10-12T00:12:00.000Z",
154 recordedAt: "2026-10-12T00:12:01.000Z",
155 },
156 {
157 sequence: 3,
158 namespace: "fixture",
159 repoName: "demo--docs-1",
160 agent: "docs",
161 ref: "refs/heads/main",
162 before: HEAD,
163 after: "e".repeat(40),
164 changedPaths: ["docs/setup.md"],
165 eventTimestamp: "2026-10-12T00:15:00.000Z",
166 recordedAt: "2026-10-12T00:15:01.000Z",
167 },
168 ];
169}
170
171/** Fixture API emits the FH-005 wire contract, not the view's normalized projection. */
172export function fixtureState(id = "demo"): StateResponse {
173 const data = fixtureData(id);
174 const createdAt = "2026-10-12T00:00:00.000Z";
175 const result: StateResponse = {
176 project: {
177 ...data.project,
178 namespace: "fixture",
179 events: "active",
180 remote: `https://git.idiot.tax/fixture/${id}.git`,
181 },
182 agents: data.agents,
183 forks: data.forks.map((fork, index) => ({
184 events: index === 0 ? "active" : "pending",
185 ...fork,
186 remote: `https://git.idiot.tax/fixture/${fork.repoName}.git`,
187 createdAt,
188 })),
189 packets: data.packets,
190 claims: data.claims.map(({ active: _active, ...claim }) => ({ ...claim, createdAt })),
191 blockers: data.blockers.map((blocker) => ({
192 id: blocker.id,
193 packet: blocker.packet,
194 claimingAgent: "builder",
195 pushingAgent: blocker.agent,
196 repoName: "demo--reviewer-1",
197 sha: "c".repeat(40),
198 paths: blocker.paths,
199 })),
200 reviews: data.reviews.map(({ status, ...review }) => ({
201 ...review,
202 base: HEAD,
203 approval:
204 status === "approved" ? { sha: review.sha, receiptDigest: review.receiptDigest } : null,
205 stale: status === "stale",
206 promoted: status === "promoted",
207 createdAt,
208 })),
209 ledger: [],
210 activity: id === "quiet" ? [] : fixtureActivity(),
211 reconcilerAlerts: [],
212 promotionPending: null,
213 };
214 if (["large", "truncated"].includes(id)) {
215 const agent = "oauth-aee4061c-representative-long-agent-name";
216 result.agents[0].name = agent;
217 result.forks[0].agent = agent;
218 result.claims[0].agent = agent;
219 result.reviews[0].agent = agent;
220 result.activity[0].agent = agent;
221 }
222 if (id === "superseded") {
223 const stale = structuredClone(result.reviews[0]);
224 stale.id = "superseded-stale";
225 stale.stale = true;
226 stale.sha = "9".repeat(40);
227 stale.createdAt = "2026-10-11T00:00:00Z";
228 result.reviews[0].promoted = true;
229 result.reviews = [result.reviews[0], stale];
230 }
231 if (["large", "first", "partial", "truncated", "deleted"].includes(id)) {
232 const paths = Array.from(
233 { length: 450 },
234 (_, i) => `src/imported/assets/group-${i}/very-long-filename-for-path-overflow-check-${i}.ts`,
235 );
236 for (const packet of result.packets) packet.paths = paths;
237 for (const claim of result.claims) claim.paths = paths;
238 for (const blocker of result.blockers) blocker.paths = paths;
239 for (const activity of result.activity) {
240 activity.changedPaths =
241 id === "partial" ? paths.slice(0, 30) : id === "truncated" ? paths.slice(0, 50) : paths;
242 if (id === "first") activity.before = "0".repeat(40);
243 if (id === "deleted") {
244 activity.after = "0".repeat(40);
245 activity.ref = "refs/heads/topic";
246 }
247 if (id === "truncated") {
248 activity.changedPathCount = 205;
249 activity.changedPathsTruncated = true;
250 }
251 if (id === "partial") {
252 activity.changedPathCount = 450;
253 activity.changedPathsTruncated = true;
254 }
255 }
256 }
257 if (
258 [
259 "receipts",
260 "advisory",
261 "recheck",
262 "rerun-failed",
263 "gating-recheck",
264 "history-required",
265 "history-advisory",
266 ].includes(id)
267 ) {
268 const statuses: (ReceiptStatus | null)[] = [
269 "running",
270 "running",
271 "passed",
272 "failed",
273 "timed-out",
274 "infrastructure-error",
275 null,
276 ];
277 result.project.settings = { requireReceipt: id !== "advisory" && id !== "receipts" };
278 result.blockers = [];
279 result.claims = [];
280 result.packets = [];
281 result.forks = [];
282 result.reviews = statuses.map((status, index) => {
283 const sha = String(index + 1).repeat(40);
284 const packet = `ui-${String(index + 1).padStart(2, "0")}`;
285 const agent = `builder-${index + 1}`;
286 const repoName = `${id}--${agent}-1`;
287 result.packets.push({
288 id: packet,
289 title: `Receipt ${packet}`,
290 goal: "Inspect fixture checks",
291 paths: ["src/dashboard/**"],
292 acceptance: ["Receipt state shown"],
293 });
294 result.forks.push({
295 agent,
296 repoName,
297 head: sha,
298 remote: `https://git.idiot.tax/fixture/${repoName}.git`,
299 createdAt,
300 });
301 const receipt = status
302 ? fixtureReceipt(status, index === 1 ? "before-promote" : "review-created")
303 : null;
304 return {
305 id: `receipt-${index + 1}`,
306 packet,
307 agent,
308 repoName,
309 sha,
310 base: HEAD,
311 receiptDigest: receipt?.digest ?? null,
312 receipt,
313 approval:
314 index === 2 || (index === 1 && id !== "advisory")
315 ? { sha, receiptDigest: index === 1 ? DIGEST : (receipt?.digest ?? null) }
316 : null,
317 stale: false,
318 promoted: false,
319 createdAt,
320 };
321 });
322 // A truthful required gate fixture also carries the two queue running trigger variants.
323 if (id === "receipts") result.project.settings.requireReceipt = true;
324 if (id === "advisory") result.reviews[1].receipt!.trigger = "review-created";
325 if (id === "gating-recheck") {
326 for (const index of [2, 4, 5]) {
327 const review = result.reviews[index];
328 review.receipt = fixtureReceipt(
329 index === 2 ? "failed" : index === 4 ? "timed-out" : "infrastructure-error",
330 "before-promote",
331 );
332 review.receiptDigest = review.receipt.digest;
333 review.approval = { sha: review.sha, receiptDigest: DIGEST };
334 }
335 }
336 }
337 if (id === "history-required" || id === "history-advisory") {
338 result.project.settings = { requireReceipt: id === "history-required" };
339 for (const review of result.reviews) {
340 if (review.receipt?.status === "running") review.receipt.startedAt = "2026-10-12T00:00:00Z";
341 }
342 const source = result.reviews[2];
343 // Shuffled history spans repeated packet IDs and both promotion modes.
344 const promoted = Array.from({ length: 7 }, (_, index) => {
345 const receipt = fixtureReceipt(
346 index === 6 && id === "history-advisory" ? "failed" : "passed",
347 id === "history-required" ? "before-promote" : "review-created",
348 );
349 receipt.digest = String(index + 1).repeat(64);
350 receipt.finishedAt = `2026-10-12T01:0${index}:41Z`;
351 const review = {
352 ...structuredClone(source),
353 id: `promoted-${index}`,
354 packet: `history-${index}`,
355 sha: String(index + 1).repeat(40),
356 receipt,
357 receiptDigest: receipt.digest,
358 approval: null,
359 promoted: true,
360 };
361 result.ledger.push({
362 id: `promotion-${index}`,
363 review: review.id,
364 repoName: id,
365 ref: "refs/heads/main",
366 before: HEAD,
367 after: review.sha,
368 receiptDigest: receipt.digest,
369 recordedAt: receipt.finishedAt,
370 });
371 return review;
372 });
373 result.reviews.push(...promoted.toReversed());
374 }
375 return result;
376}
377
378export function fixtureReceipt(
379 status: ReceiptStatus,
380 trigger: ReceiptSummary["trigger"] = "review-created",
381): ReceiptSummary {
382 return {
383 status,
384 trigger,
385 digest: status === "running" ? null : DIGEST,
386 startedAt:
387 status === "running" ? new Date(Date.now() - 12000).toISOString() : "2026-10-12T00:00:00Z",
388 finishedAt: status === "running" ? null : "2026-10-12T00:00:41Z",
389 durationMs: status === "running" ? null : 41000,
390 checks: [
391 fixtureCheck(
392 "brand",
393 status,
394 status === "running" ? null : status === "timed-out" ? 110000 : 13000,
395 ),
396 fixtureCheck(
397 "types",
398 status === "running" ? "running" : "passed",
399 status === "running" ? null : 14000,
400 ),
401 fixtureCheck(
402 "tests",
403 status === "running" ? "running" : "passed",
404 status === "running" ? null : 14000,
405 ),
406 ],
407 };
408}
409export function fixtureOwnerReceipt(review: StateResponse["reviews"][number]): OwnerReceipt {
410 return {
411 digest: review.receipt!.digest!,
412 receipt: {
413 commit: review.sha,
414 checks: review.receipt!.checks.map((check) => ({
415 check: { name: check.name },
416 logs: {
417 stdout: Array.from(
418 { length: 220 },
419 (_, index) =>
420 `${check.name} fixture output ${index + 1}${index === 219 ? " <script>fixture</script>" : ""}`,
421 ).join("\n"),
422 stderr: "Fixture stderr",
423 truncated: false,
424 },
425 })),
426 },
427 };
428}
429
430function fixtureCheck(name: string, outcome: ReceiptStatus, durationMs: number | null) {
431 return { name, tier: "full" as const, outcome, durationMs };
432}
433