g1t/packages/contracts/src/guardrails.ts

112 lines4,382 bytesCodeBlame
1/**
2 * Guardrails: what a workspace lets its agents do in a sandbox, kept by the
3 * work service. Mirrors `g1t_contracts::guardrails`.
4 */
5import type { ServiceBinding } from "./clients";
6import type { User, Viewer } from "./identity";
7import type { RepoPath } from "./repos";
8import type { Result } from "./result";
9
10/** What one level, the workspace or a project, sets. Unset is inherited. */
11export type GuardrailSettings = {
12 restrictNetwork?: boolean | null;
13 /** The registries that are on, by id. Replaces the inherited list. */
14 registries?: string[] | null;
15 /** More hosts to allow; they add to the other level's. */
16 domains?: string[];
17 /** Hosts only workflow jobs may reach, never agents; they add to the other level's. */
18 workflowDomains?: WorkflowDomain[];
19 /** Built-in command rules turned on or off, by id. */
20 rules?: Record<string, boolean>;
21 /** Permission rules to refuse; they add to the other level's. */
22 deny?: string[];
23 /** The most a run may cost, in US dollars. Zero means no cap. */
24 budgetUsd?: number | null;
25 /** How long each kind of run may take, in minutes. */
26 minutes?: Record<string, number>;
27 updatedBy?: string | null;
28 updatedAt?: string | null;
29};
30
31/** What a run actually gets. */
32export type Guardrails = {
33 restrictNetwork: boolean;
34 registries: string[];
35 domains: string[];
36 /** Every host a sandbox may reach. `*.example.com` covers subdomains. */
37 hosts: string[];
38 /** Hosts only some workflow jobs may reach (never in `hosts`). */
39 workflowDomains?: WorkflowDomain[];
40 rules: Record<string, boolean>;
41 deny: string[];
42 /** Null: no cap. */
43 budgetUsd: number | null;
44 minutes: Record<string, number>;
45};
46
47/**
48 * A host only workflow jobs may reach: jobs of a trusted run (not a pull
49 * request from a fork), of the workflows named, in the environments
50 * named. Agents, checks, the merge queue and g1t.page builds never do.
51 */
52export type WorkflowDomain = {
53 /** `api.example.com`, or `*.example.com` for its subdomains. */
54 domain: string;
55 /** Workflow files by name, such as `deploy.yml`. Empty: any workflow. */
56 workflows: string[];
57 /** Environments a job must name with `environment:`. Empty: any job. */
58 environments: string[];
59};
60
61export type RegistryInfo = { id: string; name: string; hosts: string[] };
62export type RuleInfo = { id: string; title: string; about: string };
63
64export type GuardrailsView = {
65 workspace: GuardrailSettings;
66 project: GuardrailSettings | null;
67 defaults: Guardrails;
68 /** g1t's defaults with the workspace's: what a project inherits. */
69 inherited: Guardrails;
70 effective: Guardrails;
71 g1tHosts: string[];
72 registries: RegistryInfo[];
73 rules: RuleInfo[];
74};
75
76export interface GuardrailsApi {
77 /** Members only. With `repo`, that project's level too. */
78 getGuardrails(viewer: Viewer, workspace: string, repo?: RepoPath | null): Promise<Result<GuardrailsView>>;
79 /** The workspace's level (owners), or with `repo`, that project's (members). */
80 updateGuardrails(
81 actor: User,
82 workspace: string,
83 repo: RepoPath | null,
84 settings: GuardrailSettings,
85 ): Promise<Result<GuardrailsView>>;
86 /**
87 * For the runner: what a run in `repo` gets, which is always its
88 * project's. `repoId` names the project however it has moved since; a
89 * pull request's working copy (`pulls/<pull id>`) stands for the
90 * repository the pull request is to.
91 */
92 runGuardrails(repo: RepoPath, repoId?: string | null): Promise<Result<Guardrails>>;
93}
94
95/** The guardrails methods of the work service. */
96export function guardrailsClient(service: ServiceBinding): GuardrailsApi {
97 const call = async <T>(method: string, args: object): Promise<T> => {
98 const response = await service.fetch(`https://service/rpc/${method}`, {
99 method: "POST",
100 headers: { "content-type": "application/json" },
101 body: JSON.stringify(args),
102 });
103 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
104 return (await response.json()) as T;
105 };
106 return {
107 getGuardrails: (viewer, workspace, repo) => call("get_guardrails", { viewer, workspace, repo: repo ?? null }),
108 updateGuardrails: (actor, workspace, repo, settings) =>
109 call("update_guardrails", { actor, workspace, repo, settings }),
110 runGuardrails: (repo, repoId) => call("run_guardrails", { repo, repo_id: repoId ?? null }),
111 };
112}