Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-stable/test/contracts.test.ts
Snapshot at 34e3175
1import assert from "node:assert/strict";
2import { execFileSync } from "node:child_process";
3import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4import { tmpdir } from "node:os";
5import { join } from "node:path";
6import { test } from "node:test";
7import { fileURLToPath } from "node:url";
8import { contributionVerbs } from "../src/index.ts";
9import type {
10 ClaimResponse, ContributionRequest, JoinResponse, ReadReceiptsResponse,
11 ReadReviewResponse, ReleaseResponse, RequestReviewResponse,
12} from "../src/index.ts";
13
14test("v0 verbs are complete and v1 entries declare names/status only", () => {
15 assert.deepEqual(contributionVerbs.filter(v => v.status === "v0").map(v => v.name),
16 ["context", "join", "claim", "release", "requestReview", "readReview", "readReceipts"]);
17 assert.deepEqual(contributionVerbs.filter(v => v.status === "v1").map(v => v.name),
18 ["list_projects", "start_work", "resume_work", "end_work", "read_tree", "read_file", "read_diff", "run_checks"]);
19 assert.equal(new Set(contributionVerbs.map(v => v.name)).size, contributionVerbs.length);
20 for (const verb of contributionVerbs) assert.deepEqual(Object.keys(verb), ["name", "status"]);
21});
22
23test("public v0 fixtures retain exact commit, race, return and receipt fields", () => {
24 const project = "demo", packet = "sample", repoName = "demo--builder-1", agent = "builder";
25 const sha = "a".repeat(40), at = "2026-01-01T00:00:00.000Z";
26 const request = { project, packet, repoName, sha } satisfies ContributionRequest<"requestReview">;
27 const joined = { project, agent, repoName, remote: "https://git.idiot.tax/demo.git",
28 scope: "write", events: "pending", expiresAt: at, ttlSeconds: 60 } satisfies JoinResponse;
29 const claim = { claim: { packet, agent, repoName, paths: ["src"], createdAt: at,
30 usage: { reported: null, metered: null }, firstPushAt: null, reviewRequestedAt: null,
31 receiptPassedAt: null, approvedAt: null, promotedAt: null, place: null,
32 closedAt: null, closedBy: null } } satisfies ClaimResponse;
33 const review = { id: "review-1", packet, agent, repoName, sha, base: "b".repeat(40),
34 receiptDigest: null, receipt: null, approval: null, stale: false, promoted: false,
35 returned: { reason: "Adjust the fixture", at }, createdAt: at,
36 usageAtReview: { reported: null, metered: null } } satisfies RequestReviewResponse["review"];
37 const detail = { review,
38 packet: { id: packet, title: "Fixture", goal: "Check shape", paths: ["src"], acceptance: [] },
39 changes: { base: review.base, head: sha, fileCount: 1, truncated: false,
40 files: [{ path: "src/main.ts", status: "modified" }] },
41 commits: [{ sha, subject: "Fixture", authorName: "Builder", packetTrailer: packet, agentTrailer: agent }],
42 commitsTruncated: false, promotion: null, blockers: [],
43 } satisfies ReadReviewResponse;
44 const receipts = { receipt: review.receipt } satisfies ReadReceiptsResponse;
45 const release = { released: true } satisfies ReleaseResponse;
46 assert.equal(detail.review.sha, request.sha);
47 assert.equal(detail.review.returned.reason, "Adjust the fixture");
48 assert.equal(claim.claim.closedAt, null);
49 assert.equal(receipts.receipt, null);
50 assert.equal(release.released, true);
51 assert.ok(!("token" in joined));
52});
53
54test("public records and requests remain type-compatible with accepted core declarations", () => {
55 const dir = mkdtempSync(join(tmpdir(), "stable-contract-"));
56 try {
57 // Use current source declarations, not a committed fixture. Extracting only
58 // the records avoids pulling Worker implementations into this pure TS test.
59 const workflow = readFileSync(new URL("../../forkhorse-core/src/workflow-types.ts", import.meta.url), "utf8");
60 const records = readFileSync(new URL("../../forkhorse-core/src/types.ts", import.meta.url), "utf8");
61 const operations = readFileSync(new URL("../../forkhorse-mcp/src/core.ts", import.meta.url), "utf8");
62 const recordStart = records.indexOf("export interface Project {");
63 const operationStart = operations.indexOf("export type CoreOperation =");
64 const operationEnd = operations.indexOf("/** Narrow adapter seam:", operationStart);
65 assert.ok(recordStart >= 0 && operationStart >= 0 && operationEnd > operationStart);
66 writeFileSync(join(dir, "workflow-types.d.ts"), workflow);
67 writeFileSync(join(dir, "types.d.ts"), records.slice(recordStart));
68 // WorkFile is referenced by the existing commit branch of CoreOperation.
69 const fileStart = operations.indexOf("export interface WorkFile {");
70 const fileEnd = operations.indexOf("/** Owner identity", fileStart);
71 assert.ok(fileStart >= 0 && fileEnd > fileStart);
72 writeFileSync(join(dir, "operations.d.ts"), operations.slice(fileStart, fileEnd) + operations.slice(operationStart, operationEnd));
73 const assertions = fileURLToPath(new URL("./core-shapes.ts", import.meta.url));
74 writeFileSync(join(dir, "tsconfig.json"), JSON.stringify({
75 compilerOptions: {
76 strict: true, noEmit: true, skipLibCheck: false, target: "ES2022",
77 module: "ESNext", moduleResolution: "bundler", types: [],
78 allowImportingTsExtensions: true,
79 paths: { "core-workflow": [join(dir, "workflow-types.d.ts")],
80 "core-records": [join(dir, "types.d.ts")], "core-operations": [join(dir, "operations.d.ts")] },
81 },
82 files: [assertions],
83 }));
84 const compiler = fileURLToPath(new URL("../../../node_modules/typescript/bin/tsc", import.meta.url));
85 execFileSync(process.execPath, [compiler, "-p", join(dir, "tsconfig.json")], { encoding: "utf8", stdio: "pipe" });
86 } catch (error) {
87 // The compiler sees only public types and fixtures; keep diagnostics on failure.
88 if (error && typeof error === "object" && "stdout" in error) assert.fail(String(error.stdout));
89 throw error;
90 } finally {
91 rmSync(dir, { recursive: true, force: true });
92 }
93});
94