g1t/packages/contracts/src/guardrails.ts

94 lines3,604 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 /** Built-in command rules turned on or off, by id. */
18 rules?: Record<string, boolean>;
19 /** Permission rules to refuse; they add to the other level's. */
20 deny?: string[];
21 /** The most a run may cost, in US dollars. Zero means no cap. */
22 budgetUsd?: number | null;
23 /** How long each kind of run may take, in minutes. */
24 minutes?: Record<string, number>;
25 updatedBy?: string | null;
26 updatedAt?: string | null;
27};
28
29/** What a run actually gets. */
30export type Guardrails = {
31 restrictNetwork: boolean;
32 registries: string[];
33 domains: string[];
34 /** Every host a sandbox may reach. `*.example.com` covers subdomains. */
35 hosts: string[];
36 rules: Record<string, boolean>;
37 deny: string[];
38 /** Null: no cap. */
39 budgetUsd: number | null;
40 minutes: Record<string, number>;
41};
42
43export type RegistryInfo = { id: string; name: string; hosts: string[] };
44export type RuleInfo = { id: string; title: string; about: string };
45
46export type GuardrailsView = {
47 workspace: GuardrailSettings;
48 project: GuardrailSettings | null;
49 defaults: Guardrails;
50 /** g1t's defaults with the workspace's: what a project inherits. */
51 inherited: Guardrails;
52 effective: Guardrails;
53 g1tHosts: string[];
54 registries: RegistryInfo[];
55 rules: RuleInfo[];
56};
57
58export interface GuardrailsApi {
59 /** Members only. With `repo`, that project's level too. */
60 getGuardrails(viewer: Viewer, workspace: string, repo?: RepoPath | null): Promise<Result<GuardrailsView>>;
61 /** The workspace's level (owners), or with `repo`, that project's (members). */
62 updateGuardrails(
63 actor: User,
64 workspace: string,
65 repo: RepoPath | null,
66 settings: GuardrailSettings,
67 ): Promise<Result<GuardrailsView>>;
68 /**
69 * For the runner: what a run in `repo` gets, which is always its
70 * project's. `repoId` names the project however it has moved since; a
71 * pull request's working copy (`pulls/<pull id>`) stands for the
72 * repository the pull request is to.
73 */
74 runGuardrails(repo: RepoPath, repoId?: string | null): Promise<Result<Guardrails>>;
75}
76
77/** The guardrails methods of the work service. */
78export function guardrailsClient(service: ServiceBinding): GuardrailsApi {
79 const call = async <T>(method: string, args: object): Promise<T> => {
80 const response = await service.fetch(`https://service/rpc/${method}`, {
81 method: "POST",
82 headers: { "content-type": "application/json" },
83 body: JSON.stringify(args),
84 });
85 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
86 return (await response.json()) as T;
87 };
88 return {
89 getGuardrails: (viewer, workspace, repo) => call("get_guardrails", { viewer, workspace, repo: repo ?? null }),
90 updateGuardrails: (actor, workspace, repo, settings) =>
91 call("update_guardrails", { actor, workspace, repo, settings }),
92 runGuardrails: (repo, repoId) => call("run_guardrails", { repo, repo_id: repoId ?? null }),
93 };
94}