Skip to content
245 linesCodeBlameRaw
1import type { RepoPath } from "./repos";
2import type { Result } from "./result";
3import type { User, Viewer } from "./identity";
4
5/**
6 * Rulesets: what may happen to a repository's branches and tags, and what a
7 * pull request needs before it merges. A ruleset belongs to a repository or
8 * to a workspace (and through it to the repositories it selects), targets
9 * branches or tags by name, is `active`, `evaluate` (a dry run that records
10 * what it would have refused) or `disabled`, lists who may bypass it, and
11 * holds rules. Rulesets stack: every rule of each holds.
12 *
13 * Mirrors `crates/contracts/src/rules.rs`. Rulesets travel in the shape the
14 * API shows them, `snake_case`, so an export imports anywhere unchanged.
15 */
16
17export const DEFAULT_BRANCH = "~DEFAULT_BRANCH";
18export const ALL = "~ALL";
19export const MAX_RULESETS = 75;
20
21export type Enforcement = "active" | "evaluate" | "disabled";
22export type Target = "branch" | "tag";
23export type Level = "repository" | "workspace";
24export type AppliesTo = "everyone" | "agents" | "people";
25export type BypassActorKind = "role" | "team" | "user" | "token" | "g1t";
26export type BypassMode = "always" | "pull_requests";
27export type MergeMethod = "merge" | "squash" | "rebase";
28export type Integration = "actions" | "deployments" | "security" | "g1t" | "api";
29export type PatternOperator = "starts_with" | "ends_with" | "contains" | "regex";
30
31export type Weekday = "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun";
32
33export type RefCondition = { include: string[]; exclude: string[] };
34export type RepositoryCondition = {
35 include: string[];
36 exclude: string[];
37 visibility: "any" | "public" | "private";
38 topics: string[];
39};
40export type Conditions = { ref_name: RefCondition; repository?: RepositoryCondition };
41export type BypassActor = { kind: BypassActorKind; value: string; mode: BypassMode };
42
43export type PullRequestParameters = {
44 required_approvals: number;
45 count_agent_approvals: boolean;
46 dismiss_stale_reviews_on_push: boolean;
47 require_code_owner_review: boolean;
48 require_last_push_approval: boolean;
49 allowed_merge_methods: MergeMethod[];
50 /** Only the ruleset made from branch protection that did not refuse pushes. */
51 allow_direct_pushes?: boolean;
52};
53export type RulesetCheck = { context: string; integration?: Integration };
54export type StatusChecksParameters = {
55 checks: RulesetCheck[];
56 strict: boolean;
57 paths: string[];
58 allow_bypass_on_merge: boolean;
59};
60export type MergeQueueParameters = {
61 merge_method: MergeMethod;
62 max_entries_to_build: number;
63 min_entries_to_merge: number;
64 min_entries_wait_minutes: number;
65 check_response_timeout_minutes: number;
66};
67export type PatternParameters = { name: string; operator: PatternOperator; pattern: string; negate: boolean };
68export type WeeklyWindow = { days: Weekday[]; start: string; end: string };
69export type Period = { start: string; end?: string | null; reason: string };
70export type MergeWindowParameters = {
71 time_zone: string;
72 windows: WeeklyWindow[];
73 freezes: Period[];
74 exceptions: Period[];
75};
76
77/** Every rule type, with its parameters. */
78export type Rule =
79 | { type: "creation"; parameters: Record<string, never> }
80 | { type: "update"; parameters: Record<string, never> }
81 | { type: "deletion"; parameters: Record<string, never> }
82 | { type: "non_fast_forward"; parameters: Record<string, never> }
83 | { type: "required_linear_history"; parameters: Record<string, never> }
84 | { type: "required_signatures"; parameters: Record<string, never> }
85 | { type: "pull_request"; parameters: PullRequestParameters }
86 | { type: "required_status_checks"; parameters: StatusChecksParameters }
87 | { type: "merge_queue"; parameters: MergeQueueParameters }
88 | { type: "required_deployments"; parameters: { environments: string[] } }
89 | { type: "commit_message_pattern"; parameters: PatternParameters }
90 | { type: "commit_author_email_pattern"; parameters: PatternParameters }
91 | { type: "committer_email_pattern"; parameters: PatternParameters }
92 | { type: "branch_name_pattern"; parameters: PatternParameters }
93 | { type: "tag_name_pattern"; parameters: PatternParameters }
94 | { type: "file_path_restriction"; parameters: { restricted_file_paths: string[] } }
95 | { type: "file_extension_restriction"; parameters: { restricted_file_extensions: string[] } }
96 | { type: "max_file_size"; parameters: { max_file_size_mb: number } }
97 | { type: "max_file_path_length"; parameters: { max_file_path_length: number } }
98 | { type: "max_files_changed"; parameters: { max_files: number } }
99 | { type: "secret_scanning"; parameters: Record<string, never> }
100 | { type: "path_review"; parameters: { paths: string[]; required_approvals: number; team?: string | null } }
101 | { type: "merge_window"; parameters: MergeWindowParameters }
102 | { type: "agent_auto_merge"; parameters: { allowed: boolean } };
103
104/**
105 * Rule types no longer offered, which were about g1t's own agents. A stored
106 * ruleset that has one still reads: the rule is left out. Mirrors
107 * `RETIRED_RULES` in `crates/contracts/src/rules.rs`.
108 */
109export const RETIRED_RULES: readonly string[] = ["confidence_threshold", "cost_cap"];
110
111export type RuleType = Rule["type"];
112export type RuleEntry = Rule & { applies_to: AppliesTo };
113
114/** What a ruleset says: what is created, changed, exported and imported. */
115export type RulesetSpec = {
116 name: string;
117 enforcement: Enforcement;
118 target: Target;
119 conditions: Conditions;
120 bypass_actors: BypassActor[];
121 rules: RuleEntry[];
122};
123
124export type Ruleset = RulesetSpec & {
125 id: string;
126 level: Level;
127 workspace: string;
128 repo_id?: string;
129 repository?: string;
130 /** `branch_protection` for the one made from branch protection settings. */
131 source?: string;
132 created_by: string;
133 created_at: string;
134 updated_by: string;
135 updated_at: string;
136};
137
138export type RulesetSummary = {
139 id: string;
140 name: string;
141 level: Level;
142 enforcement: Enforcement;
143 bypass_actors: BypassActor[];
144};
145
146export type EffectiveRule = RuleEntry & {
147 ruleset_id: string;
148 ruleset_name: string;
149 level: Level;
150 enforcement: Enforcement;
151};
152
153export type EffectiveRules = {
154 name: string;
155 target: Target;
156 default_branch: boolean;
157 rules: EffectiveRule[];
158 rulesets: RulesetSummary[];
159};
160
161export type RuleVerdict = "pass" | "fail" | "bypass";
162export type Action = "push" | "merge" | "create_ref" | "delete_ref" | "rename_ref" | "commit";
163
164export type Violation = {
165 rule: string;
166 ruleset_id: string;
167 ruleset_name: string;
168 enforcement: Enforcement;
169 message: string;
170 remedy: string;
171};
172
173export type Evaluation = {
174 id: string;
175 repo_id: string;
176 workspace: string;
177 ruleset_id: string;
178 ruleset_name: string;
179 enforcement: Enforcement;
180 action: Action;
181 git_ref: string;
182 actor: string;
183 actor_kind: string;
184 verdict: RuleVerdict;
185 violations: Violation[];
186 number: number | null;
187 sha: string | null;
188 repository: string;
189 created_at: string;
190};
191
192export type Insights = {
193 days: number;
194 total: number;
195 passed: number;
196 blocked: number;
197 would_block: number;
198 bypassed: number;
199 by_ruleset: {
200 ruleset_id: string;
201 ruleset_name: string;
202 enforcement: Enforcement;
203 total: number;
204 blocked: number;
205 would_block: number;
206 bypassed: number;
207 }[];
208 by_rule: { rule: string; count: number }[];
209};
210
211export type EvaluationPage = { evaluations: Evaluation[]; next: string | null; insights: Insights };
212
213/** What a pull request's merge box shows of the rules of its base. */
214export type MergeRules = {
215 unmet: Violation[];
216 bypassable: Violation[];
217 evaluate: Violation[];
218 rulesets: RulesetSummary[];
219 merge_queue: boolean;
220 /** What the active rules ask, as they stack. */
221 required_approvals: number;
222 strict: boolean;
223 allow_bypass_on_merge: boolean;
224};
225
226/** Whose rulesets: a repository's or a workspace's. */
227export type RulesetOwner = { repo: RepoPath } | { workspace: string };
228
229export type EvaluationFilter = {
230 ruleset_id?: string;
231 verdict?: RuleVerdict;
232 problems_only?: boolean;
233 before?: string;
234 limit?: number;
235};
236
237export interface RulesApi {
238 listRulesets(owner: RulesetOwner, viewer: Viewer, includeParents?: boolean): Promise<Result<Ruleset[]>>;
239 getRuleset(owner: RulesetOwner, id: string, viewer: Viewer): Promise<Result<Ruleset>>;
240 /** Creates one (no `id`) or replaces one. */
241 saveRuleset(actor: User, owner: RulesetOwner, ruleset: RulesetSpec, id?: string): Promise<Result<Ruleset>>;
242 deleteRuleset(actor: User, owner: RulesetOwner, id: string): Promise<Result<boolean>>;
243 effectiveRules(repo: RepoPath, name: string, viewer: Viewer, target?: Target): Promise<Result<EffectiveRules>>;
244 ruleEvaluations(owner: RulesetOwner, viewer: Viewer, filter?: EvaluationFilter): Promise<Result<EvaluationPage>>;
245}