Skip to content
231 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1//! A repository's settings for how g1t's agents handle its pull requests,
2//! and the branch protection settings that are now its "Default branch
3//! protection" ruleset (rulesets.rs).
4//!
5//! What a merge needs is decided by the rules of the branch it merges into
6//! (`Work::merge_gate`); `approvals_gap` asks them for what people must
7//! still do, as g1t sees it when it merges by itself.
Keep g1t's own runs off the model proxy until it holds g1t's key8
9use g1t_contracts::time::rfc3339;
10use g1t_contracts::work::*;
11use g1t_contracts::{FailureCode, Outcome};
12use g1t_kit::now_ms;
13use serde::Deserialize;
14use worker::Result;
15
16use crate::Work;
17
18const MAX_REQUIRED_APPROVALS: u32 = 6;
19const MAX_REVISIONS: u32 = 5;
Merge branch 'worktree-agent-ad8a36dfcd4176015' into spend-guardrails20/// The most times in a day people's mentions may send g1t back to one pull
21/// request. They outrank `MAX_REVISIONS` and a stall, but not this.
22pub(crate) const MAX_MENTION_REVISIONS_PER_DAY: u32 = 10;
Keep g1t's own runs off the model proxy until it holds g1t's key23
24#[derive(Deserialize)]
25struct SettingsRow {
26 auto_merge: u8,
27 require_up_to_date: u8,
28 required_approvals: u32,
29 count_agent_approvals: u8,
30 allow_ignoring_checks: u8,
31 agent_review: u8,
32 max_revisions: u32,
33 #[serde(default)]
34 merge_queue: u8,
Fast pages, required checks on the branch, self-hosted runners, honest incidents35 /// JSON array of names.
36 #[serde(default)]
37 required_checks: Option<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar38 #[serde(default)]
39 require_code_owner_review: u8,
Keep g1t's own runs off the model proxy until it holds g1t's key40 updated_by: String,
41 updated_at: String,
42}
43
44impl From<SettingsRow> for RepoSettings {
45 fn from(row: SettingsRow) -> Self {
46 RepoSettings {
47 auto_merge: row.auto_merge != 0,
Fast pages, required checks on the branch, self-hosted runners, honest incidents48 required_checks: row
49 .required_checks
50 .as_deref()
51 .and_then(|names| serde_json::from_str(names).ok())
52 .unwrap_or_default(),
Keep g1t's own runs off the model proxy until it holds g1t's key53 require_up_to_date: row.require_up_to_date != 0,
54 required_approvals: row.required_approvals,
55 count_agent_approvals: row.count_agent_approvals != 0,
56 allow_ignoring_checks: row.allow_ignoring_checks != 0,
57 agent_review: row.agent_review != 0,
58 max_revisions: row.max_revisions,
59 merge_queue: row.merge_queue != 0,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step60 // Kept in its own table (confidence.rs), read beside this row.
61 hold_low_confidence: true,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar62 require_code_owner_review: row.require_code_owner_review != 0,
Keep g1t's own runs off the model proxy until it holds g1t's key63 updated_by: Some(row.updated_by),
64 updated_at: Some(row.updated_at),
65 }
66 }
67}
68
69impl Work {
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge70 /// A repository's stored settings, by its id: how g1t's agents handle
71 /// its pull requests. Defaults if none were set. Branch protection is
72 /// its rules': see `settings_on`.
Keep g1t's own runs off the model proxy until it holds g1t's key73 pub(crate) async fn settings(&self, repo_id: &str) -> Result<RepoSettings> {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily74 if let Some(found) = self.prefetched_repo(repo_id) {
75 let row = found.first::<SettingsRow>(crate::prefetch::Slot::Settings)?;
76 let hold = found
77 .first::<crate::rows::NumberRow>(crate::prefetch::Slot::Hold)?
78 .is_none_or(|row| row.n != 0);
79 return Ok(RepoSettings {
80 hold_low_confidence: hold,
81 ..row.map_or_else(RepoSettings::default, RepoSettings::from)
82 });
83 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step84 let row = async {
85 self.db
86 .prepare("SELECT * FROM repo_settings WHERE repo_id = ?")
87 .bind(&[repo_id.into()])?
88 .first::<SettingsRow>(None)
89 .await
90 };
91 let (row, hold) = futures_util::future::try_join(row, self.holds_low_confidence(repo_id)).await?;
92 Ok(RepoSettings {
93 hold_low_confidence: hold,
94 ..row.map_or_else(RepoSettings::default, RepoSettings::from)
95 })
Keep g1t's own runs off the model proxy until it holds g1t's key96 }
97
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge98 /// The default branch's settings: the stored ones, with branch
99 /// protection as its rules stack.
Keep g1t's own runs off the model proxy until it holds g1t's key100 pub(crate) async fn get_settings(&self, a: ViewArgs) -> Result<Outcome<RepoSettings>> {
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge101 let repo = match self.repo(&a.repo, &a.viewer).await? {
102 Outcome::Ok(repo) => repo,
103 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
104 };
105 Ok(Outcome::Ok(self.timing.db(3, self.default_branch_settings(&repo)).await?))
Keep g1t's own runs off the model proxy until it holds g1t's key106 }
107
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge108 /// Replaces a repository's settings: how g1t's agents work, kept here,
109 /// and the branch protection ones, written to its "Default branch
110 /// protection" ruleset (made when it has none and they protect
111 /// anything). Rules that only rulesets have stay as they are.
Keep g1t's own runs off the model proxy until it holds g1t's key112 pub(crate) async fn update_settings(
113 &self,
114 a: UpdateSettingsArgs,
115 ) -> Result<Outcome<RepoSettings>> {
116 let repo = match self.repo(&a.repo, &Some(a.actor.clone())).await? {
117 Outcome::Ok(repo) => repo,
118 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
119 };
Merge branch 'mirroring' into artifacts-mode120 if let Outcome::Fail(failure) = crate::retired::not_archived(&repo) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look121 return Ok(Outcome::Fail(failure));
122 }
123 if !a.actor.verified {
124 return Ok(Outcome::fail(FailureCode::Forbidden, crate::UNVERIFIED));
125 }
126 if let Outcome::Fail(failure) =
127 crate::allowed(Some(&a.actor), &repo, g1t_contracts::access::Capability::ManageSettings)
128 {
129 return Ok(Outcome::Fail(failure));
Keep g1t's own runs off the model proxy until it holds g1t's key130 }
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge131 let before = self.default_branch_settings(&repo).await?;
Keep g1t's own runs off the model proxy until it holds g1t's key132 let settings = RepoSettings {
133 required_approvals: a.settings.required_approvals.min(MAX_REQUIRED_APPROVALS),
134 max_revisions: a.settings.max_revisions.min(MAX_REVISIONS),
Fast pages, required checks on the branch, self-hosted runners, honest incidents135 required_checks: tidy_required(&a.settings.required_checks),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge136 updated_by: Some(a.actor.username.clone()),
Keep g1t's own runs off the model proxy until it holds g1t's key137 updated_at: Some(rfc3339(now_ms())),
138 ..a.settings
139 };
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge140 // Branch protection changes need the role that changes it.
141 if protection_changed(&before, &settings)
142 && let Outcome::Fail(failure) =
143 crate::allowed(Some(&a.actor), &repo, g1t_contracts::access::Capability::ManageProtection)
144 {
145 return Ok(Outcome::Fail(failure));
146 }
Keep g1t's own runs off the model proxy until it holds g1t's key147 self.db
148 .prepare(
149 "INSERT INTO repo_settings
150 (repo_id, auto_merge, require_up_to_date, required_approvals,
151 count_agent_approvals, allow_ignoring_checks, agent_review, max_revisions,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar152 merge_queue, required_checks, require_code_owner_review, updated_by, updated_at)
153 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Keep g1t's own runs off the model proxy until it holds g1t's key154 ON CONFLICT (repo_id) DO UPDATE SET
155 auto_merge = excluded.auto_merge,
156 require_up_to_date = excluded.require_up_to_date,
157 required_approvals = excluded.required_approvals,
158 count_agent_approvals = excluded.count_agent_approvals,
159 allow_ignoring_checks = excluded.allow_ignoring_checks,
160 agent_review = excluded.agent_review,
161 max_revisions = excluded.max_revisions,
162 merge_queue = excluded.merge_queue,
Fast pages, required checks on the branch, self-hosted runners, honest incidents163 required_checks = excluded.required_checks,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar164 require_code_owner_review = excluded.require_code_owner_review,
Keep g1t's own runs off the model proxy until it holds g1t's key165 updated_by = excluded.updated_by,
166 updated_at = excluded.updated_at",
167 )
168 .bind(&[
169 repo.id.as_str().into(),
170 u32::from(settings.auto_merge).into(),
171 u32::from(settings.require_up_to_date).into(),
172 settings.required_approvals.into(),
173 u32::from(settings.count_agent_approvals).into(),
174 u32::from(settings.allow_ignoring_checks).into(),
175 u32::from(settings.agent_review).into(),
176 settings.max_revisions.into(),
177 u32::from(settings.merge_queue).into(),
Fast pages, required checks on the branch, self-hosted runners, honest incidents178 serde_json::to_string(&settings.required_checks)?.into(),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar179 u32::from(settings.require_code_owner_review).into(),
Keep g1t's own runs off the model proxy until it holds g1t's key180 settings.updated_by.as_deref().unwrap_or_default().into(),
181 settings.updated_at.as_deref().unwrap_or_default().into(),
182 ])?
183 .run()
184 .await?;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step185 self.set_hold_low_confidence(
186 &repo.id,
187 settings.hold_low_confidence,
188 settings.updated_by.as_deref().unwrap_or_default(),
189 settings.updated_at.as_deref().unwrap_or_default(),
190 )
191 .await?;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge192 if protection_changed(&before, &settings) {
193 self.write_branch_protection(&repo, &settings, &a.actor).await?;
194 }
195 Ok(Outcome::Ok(self.default_branch_settings(&repo).await?))
Keep g1t's own runs off the model proxy until it holds g1t's key196 }
197}
198
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge199/// Whether the branch protection part of the settings differs.
200fn protection_changed(before: &RepoSettings, after: &RepoSettings) -> bool {
201 let names = |settings: &RepoSettings| -> Vec<String> {
202 let mut names: Vec<String> = settings.required_checks.iter().map(|name| name.to_lowercase()).collect();
203 names.sort();
204 names
205 };
206 names(before) != names(after)
207 || before.require_up_to_date != after.require_up_to_date
208 || before.required_approvals != after.required_approvals
209 || before.count_agent_approvals != after.count_agent_approvals
210 || before.allow_ignoring_checks != after.allow_ignoring_checks
211 || before.merge_queue != after.merge_queue
212 || before.require_code_owner_review != after.require_code_owner_review
213}
214
Keep g1t's own runs off the model proxy until it holds g1t's key215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge220 fn only_protection_changes_count_as_protection_changes() {
221 let before = RepoSettings::default();
222 let agents = RepoSettings { auto_merge: true, agent_review: false, max_revisions: 4, ..RepoSettings::default() };
223 assert!(!protection_changed(&before, &agents));
224 let checks = RepoSettings { required_checks: vec!["CI".into()], ..RepoSettings::default() };
225 assert!(protection_changed(&before, &checks));
226 let same = RepoSettings { required_checks: vec!["ci".into()], ..RepoSettings::default() };
227 assert!(!protection_changed(&checks, &same), "names compare without case");
228 let approvals = RepoSettings { required_approvals: 1, ..RepoSettings::default() };
229 assert!(protection_changed(&before, &approvals));
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights230 }
Keep g1t's own runs off the model proxy until it holds g1t's key231}

This file's history is long; its oldest lines are credited to the oldest commit read.