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

packages/forkhorse-stable/src/contracts.ts

Snapshot at 34e3175

Containing directory

4,674 bytes · typescriptView raw
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