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