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