Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-stable/src/contracts.ts
Snapshot at 34e3175
1import type {
2 Blocker, Claim, LedgerEntry, Packet, RaceEntrant, ReceiptSummary, Review,
3 StateSnapshot, UsageSources, WorkItem,
4} from "./records.ts";
5
6/** Public subscription metadata returned by core's publicEvents(). */
7export interface EventStatus {
8 events: "pending" | "active";
9 subscriptionId?: string;
10}
11
12export interface ProjectRequest { project: string }
13export type ContextRequest = ProjectRequest;
14/** GET /state plus GET /me's work, matching the existing MCP context projection. */
15export type ContextResponse = Omit<StateSnapshot, "project" | "forks"> & {
16 project: StateSnapshot["project"] & EventStatus;
17 forks: (StateSnapshot["forks"][number] & EventStatus)[];
18 myWork: WorkItem[];
19};
20
21/** POST /join. HTTP adapters should use 60 seconds for the discarded Git grant. */
22export interface JoinRequest extends ProjectRequest { agent: string; ttlSeconds?: number }
23/** Explicit public projection: the core token is never part of this contract. */
24export interface JoinResponse extends EventStatus {
25 project: string;
26 agent: string;
27 repoName: string;
28 remote: string;
29 scope: "write";
30 expiresAt: string;
31 ttlSeconds: number;
32}
33
34/** POST /packets/:packet/claim; race entry uses this same core route. */
35export interface ClaimRequest extends ProjectRequest { packet: string; repoName?: string }
36export interface ClaimResponse {
37 claim: (Claim | RaceEntrant) & { usage: UsageSources };
38}
39/** DELETE /packets/:packet/claim; authenticated agent only, no owner override. */
40export interface ReleaseRequest extends ProjectRequest { packet: string }
41export interface ReleaseResponse { released: true }
42
43/** POST /reviews; sha must be the exact full commit at the caller's fork head. */
44export interface RequestReviewRequest extends ProjectRequest {
45 packet: string;
46 repoName: string;
47 sha: string;
48}
49export interface RequestReviewResponse { review: Review }
50
51/** GET /reviews/:review; core authorizes the review's agent or the owner. */
52export interface ReadReviewRequest extends ProjectRequest { review: string }
53export interface ReadReviewResponse {
54 review: Review;
55 packet: Pick<Packet, "id" | "title" | "goal" | "paths" | "acceptance">;
56 changes: {
57 base: string;
58 head: string;
59 fileCount: number;
60 truncated: boolean;
61 files: { path: string; status: "added" | "modified" | "deleted" }[];
62 };
63 commits: {
64 sha: string;
65 subject: string;
66 authorName: string;
67 packetTrailer: string | null;
68 agentTrailer: string | null;
69 }[];
70 commitsTruncated: boolean;
71 promotion: (Pick<LedgerEntry, "before" | "after" | "recordedAt" | "receiptDigest"> &
72 Pick<LedgerEntry, "requireReceipt">) | null;
73 blockers: Blocker[];
74}
75
76/** Read only the review's public summary, never the owner-only /receipt log route. */
77export type ReadReceiptsRequest = ReadReviewRequest;
78export interface ReadReceiptsResponse { receipt: ReceiptSummary | null }
79
80export interface ContributionContract {
81 context: { request: ContextRequest; response: ContextResponse };
82 join: { request: JoinRequest; response: JoinResponse };
83 claim: { request: ClaimRequest; response: ClaimResponse };
84 release: { request: ReleaseRequest; response: ReleaseResponse };
85 requestReview: { request: RequestReviewRequest; response: RequestReviewResponse };
86 readReview: { request: ReadReviewRequest; response: ReadReviewResponse };
87 readReceipts: { request: ReadReceiptsRequest; response: ReadReceiptsResponse };
88}
89
90/** v1 names are reserved capabilities only; they carry no request/response shapes. */
91export const contributionVerbs = [
92 { name: "context", status: "v0" },
93 { name: "join", status: "v0" },
94 { name: "claim", status: "v0" },
95 { name: "release", status: "v0" },
96 { name: "requestReview", status: "v0" },
97 { name: "readReview", status: "v0" },
98 { name: "readReceipts", status: "v0" },
99 { name: "list_projects", status: "v1" },
100 { name: "start_work", status: "v1" },
101 { name: "resume_work", status: "v1" },
102 { name: "end_work", status: "v1" },
103 { name: "read_tree", status: "v1" },
104 { name: "read_file", status: "v1" },
105 { name: "read_diff", status: "v1" },
106 { name: "run_checks", status: "v1" },
107] as const;
108
109export type ContributionVerb = typeof contributionVerbs[number]["name"];
110export type V0Verb = keyof ContributionContract;
111export type V1Verb = Extract<typeof contributionVerbs[number], { status: "v1" }>["name"];
112export type ContributionRequest<V extends V0Verb> = ContributionContract[V]["request"];
113export type ContributionResponse<V extends V0Verb> = ContributionContract[V]["response"];
114export type ContributionApi = {
115 [V in V0Verb]: (request: ContributionRequest<V>) => Promise<ContributionResponse<V>>;
116};
117