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 merge | 1 | //! 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. | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 8 | |
| 9 | use g1t_contracts::time::rfc3339; | |
| 10 | use g1t_contracts::work::*; | |
| 11 | use g1t_contracts::{FailureCode, Outcome}; | |
| 12 | use g1t_kit::now_ms; | |
| 13 | use serde::Deserialize; | |
| 14 | use worker::Result; | |
| 15 | ||
| 16 | use crate::Work; | |
| 17 | ||
| 18 | const MAX_REQUIRED_APPROVALS: u32 = 6; | |
| 19 | ||
| 20 | #[derive(Deserialize)] | |
| 21 | struct SettingsRow { | |
| 22 | auto_merge: u8, | |
| 23 | require_up_to_date: u8, | |
| 24 | required_approvals: u32, | |
| 25 | count_agent_approvals: u8, | |
| 26 | allow_ignoring_checks: u8, | |
| 27 | #[serde(default)] | |
| 28 | merge_queue: u8, | |
| 29 | /// JSON array of names. | |
| 30 | #[serde(default)] | |
| 31 | required_checks: Option<String>, | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 32 | #[serde(default)] |
| 33 | require_code_owner_review: u8, | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 34 | updated_by: String, |
| 35 | updated_at: String, | |
| 36 | } | |
| 37 | ||
| 38 | impl From<SettingsRow> for RepoSettings { | |
| 39 | fn from(row: SettingsRow) -> Self { | |
| 40 | RepoSettings { | |
| 41 | auto_merge: row.auto_merge != 0, | |
| 42 | required_checks: row | |
| 43 | .required_checks | |
| 44 | .as_deref() | |
| 45 | .and_then(|names| serde_json::from_str(names).ok()) | |
| 46 | .unwrap_or_default(), | |
| 47 | require_up_to_date: row.require_up_to_date != 0, | |
| 48 | required_approvals: row.required_approvals, | |
| 49 | count_agent_approvals: row.count_agent_approvals != 0, | |
| 50 | allow_ignoring_checks: row.allow_ignoring_checks != 0, | |
| 51 | merge_queue: row.merge_queue != 0, | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 52 | require_code_owner_review: row.require_code_owner_review != 0, |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 53 | updated_by: Some(row.updated_by), |
| 54 | updated_at: Some(row.updated_at), | |
| 55 | } | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | impl Work { | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 60 | /// A repository's stored settings, by its id: how g1t's agents handle |
| 61 | /// its pull requests. Defaults if none were set. Branch protection is | |
| 62 | /// its rules': see `settings_on`. | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 63 | pub(crate) async fn settings(&self, repo_id: &str) -> Result<RepoSettings> { |
| 64 | if let Some(found) = self.prefetched_repo(repo_id) { | |
| 65 | let row = found.first::<SettingsRow>(crate::prefetch::Slot::Settings)?; | |
| The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were. | 66 | return Ok(row.map_or_else(RepoSettings::default, RepoSettings::from)); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 67 | } |
| The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were. | 68 | let row = self |
| 69 | .db | |
| 70 | .prepare("SELECT * FROM repo_settings WHERE repo_id = ?") | |
| 71 | .bind(&[repo_id.into()])? | |
| 72 | .first::<SettingsRow>(None) | |
| 73 | .await?; | |
| 74 | Ok(row.map_or_else(RepoSettings::default, RepoSettings::from)) | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 75 | } |
| 76 | ||
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 77 | /// The default branch's settings: the stored ones, with branch |
| 78 | /// protection as its rules stack. | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 79 | pub(crate) async fn get_settings(&self, a: ViewArgs) -> Result<Outcome<RepoSettings>> { |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 80 | let repo = match self.repo(&a.repo, &a.viewer).await? { |
| 81 | Outcome::Ok(repo) => repo, | |
| 82 | Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)), | |
| 83 | }; | |
| 84 | Ok(Outcome::Ok(self.timing.db(3, self.default_branch_settings(&repo)).await?)) | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 85 | } |
| 86 | ||
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 87 | /// Replaces a repository's settings: how g1t's agents work, kept here, |
| 88 | /// and the branch protection ones, written to its "Default branch | |
| 89 | /// protection" ruleset (made when it has none and they protect | |
| 90 | /// anything). Rules that only rulesets have stay as they are. | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 91 | pub(crate) async fn update_settings( |
| 92 | &self, | |
| 93 | a: UpdateSettingsArgs, | |
| 94 | ) -> Result<Outcome<RepoSettings>> { | |
| 95 | let repo = match self.repo(&a.repo, &Some(a.actor.clone())).await? { | |
| 96 | Outcome::Ok(repo) => repo, | |
| 97 | Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)), | |
| 98 | }; | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 99 | if let Outcome::Fail(failure) = crate::retired::writable(&repo) { |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 100 | return Ok(Outcome::Fail(failure)); |
| 101 | } | |
| 102 | if !a.actor.verified { | |
| 103 | return Ok(Outcome::fail(FailureCode::Forbidden, crate::UNVERIFIED)); | |
| 104 | } | |
| 105 | if let Outcome::Fail(failure) = | |
| 106 | crate::allowed(Some(&a.actor), &repo, g1t_contracts::access::Capability::ManageSettings) | |
| 107 | { | |
| 108 | return Ok(Outcome::Fail(failure)); | |
| 109 | } | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 110 | let before = self.default_branch_settings(&repo).await?; |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 111 | let settings = RepoSettings { |
| 112 | required_approvals: a.settings.required_approvals.min(MAX_REQUIRED_APPROVALS), | |
| 113 | required_checks: tidy_required(&a.settings.required_checks), | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 114 | updated_by: Some(a.actor.username.clone()), |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 115 | updated_at: Some(rfc3339(now_ms())), |
| 116 | ..a.settings | |
| 117 | }; | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 118 | // Branch protection changes need the role that changes it. |
| 119 | if protection_changed(&before, &settings) | |
| 120 | && let Outcome::Fail(failure) = | |
| 121 | crate::allowed(Some(&a.actor), &repo, g1t_contracts::access::Capability::ManageProtection) | |
| 122 | { | |
| 123 | return Ok(Outcome::Fail(failure)); | |
| 124 | } | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 125 | self.db |
| 126 | .prepare( | |
| 127 | "INSERT INTO repo_settings | |
| 128 | (repo_id, auto_merge, require_up_to_date, required_approvals, | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 129 | count_agent_approvals, allow_ignoring_checks, |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 130 | merge_queue, required_checks, require_code_owner_review, updated_by, updated_at) |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 131 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 132 | ON CONFLICT (repo_id) DO UPDATE SET |
| 133 | auto_merge = excluded.auto_merge, | |
| 134 | require_up_to_date = excluded.require_up_to_date, | |
| 135 | required_approvals = excluded.required_approvals, | |
| 136 | count_agent_approvals = excluded.count_agent_approvals, | |
| 137 | allow_ignoring_checks = excluded.allow_ignoring_checks, | |
| 138 | merge_queue = excluded.merge_queue, | |
| 139 | required_checks = excluded.required_checks, | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 140 | require_code_owner_review = excluded.require_code_owner_review, |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 141 | updated_by = excluded.updated_by, |
| 142 | updated_at = excluded.updated_at", | |
| 143 | ) | |
| 144 | .bind(&[ | |
| 145 | repo.id.as_str().into(), | |
| 146 | u32::from(settings.auto_merge).into(), | |
| 147 | u32::from(settings.require_up_to_date).into(), | |
| 148 | settings.required_approvals.into(), | |
| 149 | u32::from(settings.count_agent_approvals).into(), | |
| 150 | u32::from(settings.allow_ignoring_checks).into(), | |
| 151 | u32::from(settings.merge_queue).into(), | |
| 152 | serde_json::to_string(&settings.required_checks)?.into(), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 153 | u32::from(settings.require_code_owner_review).into(), |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 154 | settings.updated_by.as_deref().unwrap_or_default().into(), |
| 155 | settings.updated_at.as_deref().unwrap_or_default().into(), | |
| 156 | ])? | |
| 157 | .run() | |
| 158 | .await?; | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 159 | if protection_changed(&before, &settings) { |
| 160 | self.write_branch_protection(&repo, &settings, &a.actor).await?; | |
| 161 | } | |
| 162 | Ok(Outcome::Ok(self.default_branch_settings(&repo).await?)) | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 163 | } |
| 164 | } | |
| 165 | ||
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 166 | /// Whether the branch protection part of the settings differs. |
| 167 | fn protection_changed(before: &RepoSettings, after: &RepoSettings) -> bool { | |
| 168 | let names = |settings: &RepoSettings| -> Vec<String> { | |
| 169 | let mut names: Vec<String> = settings.required_checks.iter().map(|name| name.to_lowercase()).collect(); | |
| 170 | names.sort(); | |
| 171 | names | |
| 172 | }; | |
| 173 | names(before) != names(after) | |
| 174 | || before.require_up_to_date != after.require_up_to_date | |
| 175 | || before.required_approvals != after.required_approvals | |
| 176 | || before.count_agent_approvals != after.count_agent_approvals | |
| 177 | || before.allow_ignoring_checks != after.allow_ignoring_checks | |
| 178 | || before.merge_queue != after.merge_queue | |
| 179 | || before.require_code_owner_review != after.require_code_owner_review | |
| 180 | } | |
| 181 | ||
| Merge branch 'worktree-agent-a6a121745e81f639f' | 182 | #[cfg(test)] |
| 183 | mod tests { | |
| 184 | use super::*; | |
| 185 | ||
| 186 | #[test] | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 187 | fn only_protection_changes_count_as_protection_changes() { |
| 188 | let before = RepoSettings::default(); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 189 | let auto = RepoSettings { auto_merge: true, ..RepoSettings::default() }; |
| 190 | assert!(!protection_changed(&before, &auto)); | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 191 | let checks = RepoSettings { required_checks: vec!["CI".into()], ..RepoSettings::default() }; |
| 192 | assert!(protection_changed(&before, &checks)); | |
| 193 | let same = RepoSettings { required_checks: vec!["ci".into()], ..RepoSettings::default() }; | |
| 194 | assert!(!protection_changed(&checks, &same), "names compare without case"); | |
| 195 | let approvals = RepoSettings { required_approvals: 1, ..RepoSettings::default() }; | |
| 196 | assert!(protection_changed(&before, &approvals)); | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 197 | } |
| 198 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.