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