Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
425 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.

A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait1//! Rows as they come out of D1, and their conversion to contract types.
2
3use g1t_contracts::User;
4use g1t_contracts::repos::RepoPath;
5use g1t_contracts::work::{
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar6 AgentRef, AgentVerdict, CheckStatus, Comment, CommentKind, Issue, IssueReason, MilestoneRef, Pull, PullStatus, Runtime, SessionEntry,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait7 SessionEntryKind, State, Verdict,
8};
9use serde::Deserialize;
10
11/// An author or actor as stored: g1t itself when the id is its own (a
12/// security update it opened, a merge its settings made), g1t's agent by
13/// its id (what it made or filed), a person otherwise.
14pub(crate) fn user(id: String, username: String) -> User {
15 let kind = if g1t_contracts::system::is_system_id(&id) {
16 g1t_contracts::PrincipalKind::System
17 } else if id == g1t_contracts::identity::AGENT_ID {
18 g1t_contracts::PrincipalKind::Agent
19 } else {
20 g1t_contracts::PrincipalKind::User
21 };
22 User {
23 id,
24 username,
25 kind,
26 ..User::default()
27 }
28}
29
30/// Who asked g1t for the work, as stored beside its author: both columns,
31/// or nobody.
32pub(crate) fn requester(id: Option<String>, username: Option<String>) -> Option<User> {
33 Some(user(id?, username?))
34}
35
36#[derive(Deserialize)]
37pub struct IssueRow {
38 pub id: String,
39 pub repo_id: String,
40 pub number: u32,
41 pub title: String,
42 pub body: String,
43 /// JSON array of label names.
44 pub labels: String,
45 pub state: State,
46 pub reason: Option<IssueReason>,
47 pub resolved_by: Option<u32>,
48 pub author_id: String,
49 pub author_name: String,
50 #[serde(default)]
51 pub requested_by_id: Option<String>,
52 #[serde(default)]
53 pub requested_by_name: Option<String>,
54 pub created_at: String,
55 pub updated_at: String,
56 pub closed_at: Option<String>,
57 pub pull_count: u32,
58 pub comment_count: u32,
59 /// JSON array of usernames.
60 pub assignees: String,
61 /// JSON array of issue numbers.
62 pub blocked_by: String,
63 /// JSON of who queued it for a g1t agent, if anyone has.
64 pub queued_by: Option<String>,
65 pub agent: Option<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar66 /// The number of its milestone, and that milestone's title.
67 #[serde(default)]
68 pub milestone: Option<u32>,
69 #[serde(default)]
70 pub milestone_title: Option<String>,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait71}
72
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar73/// A milestone as an issue or pull request names it, from its number and
74/// title as read beside it.
75pub(crate) fn milestone_ref(number: Option<u32>, title: Option<String>) -> Option<MilestoneRef> {
76 Some(MilestoneRef { number: number?, title: title? })
77}
78
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait79impl From<IssueRow> for Issue {
80 fn from(row: IssueRow) -> Self {
81 Issue {
82 id: row.id,
83 repo_id: row.repo_id,
84 number: row.number,
85 title: row.title,
86 body: row.body,
87 labels: serde_json::from_str(&row.labels).unwrap_or_default(),
88 state: row.state,
89 reason: row.reason,
90 resolved_by: row.resolved_by,
91 author: user(row.author_id, row.author_name),
92 requested_by: requester(row.requested_by_id, row.requested_by_name),
93 created_at: row.created_at,
94 updated_at: row.updated_at,
95 closed_at: row.closed_at,
96 pull_count: row.pull_count,
97 comment_count: row.comment_count,
98 assignees: serde_json::from_str(&row.assignees).unwrap_or_default(),
99 blocked_by: serde_json::from_str(&row.blocked_by).unwrap_or_default(),
100 queued: row.queued_by.is_some(),
101 agent: row.agent,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar102 milestone: milestone_ref(row.milestone, row.milestone_title),
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait103 }
104 }
105}
106
107/// The remembered assessment of a pull request, read beside its row.
108#[derive(Deserialize)]
109pub struct Snapshot {
110 pub stage: Option<g1t_contracts::work::Stage>,
111 pub stage_detail: Option<String>,
112 #[serde(default)]
113 pub revisions: u32,
114 /// Whether g1t is seeing it through at all.
115 #[serde(default)]
116 pub managed: u32,
117}
118
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.119/// A pull request's columns, with its milestone's title beside them: what
120/// every read of a [`PullRow`] selects.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar121pub const PULL_COLUMNS: &str = "pulls.*,
122 (SELECT title FROM milestones
123 WHERE milestones.repo_id = pulls.repo_id AND milestones.number = pulls.milestone) AS milestone_title";
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait124
125#[derive(Deserialize)]
126pub struct PullRow {
127 pub id: String,
128 pub repo_id: String,
129 pub number: u32,
130 pub issue_number: Option<u32>,
131 pub title: String,
132 pub body: Option<String>,
133 pub agent: String,
134 pub runtime: Runtime,
135 pub status: PullStatus,
136 pub fork_repo_id: Option<String>,
137 pub fork_namespace: Option<String>,
138 pub fork_name: Option<String>,
139 pub source_branch: Option<String>,
140 pub head_commit: Option<String>,
141 pub merge_base: Option<String>,
142 pub merged_by: Option<String>,
143 pub merged_at: Option<String>,
144 pub superseded_by: Option<u32>,
145 pub check_status: Option<CheckStatus>,
146 /// JSON array of changed files; `None` until first worked out.
147 pub files: Option<String>,
148 /// JSON arrays of usernames.
149 pub assignees: String,
150 pub reviewers: String,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar151 /// JSON array of `workspace/team`.
152 #[serde(default)]
153 pub team_reviewers: Option<String>,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait154 pub author_id: String,
155 pub author_name: String,
156 #[serde(default)]
157 pub requested_by_id: Option<String>,
158 #[serde(default)]
159 pub requested_by_name: Option<String>,
160 pub created_at: String,
161 pub updated_at: String,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar162 /// JSON array of label names.
163 #[serde(default)]
164 pub labels: Option<String>,
165 #[serde(default)]
166 pub milestone: Option<u32>,
167 #[serde(default)]
168 pub milestone_title: Option<String>,
169 /// The branch it merges into; NULL for the default branch.
170 #[serde(default)]
171 pub base_branch: Option<String>,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge172 #[serde(default)]
173 pub head_pushed_by: Option<String>,
174 #[serde(default)]
175 pub head_pushed_at: Option<String>,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait176}
177
178impl From<PullRow> for Pull {
179 fn from(row: PullRow) -> Self {
180 Pull {
181 id: row.id,
182 repo_id: row.repo_id,
183 number: row.number,
184 issue: row.issue_number,
185 title: row.title,
186 body: row.body,
187 agent: row.agent,
188 runtime: row.runtime,
189 status: row.status,
190 fork: row
191 .fork_namespace
192 .zip(row.fork_name)
193 .map(|(namespace, name)| RepoPath { namespace, name }),
194 fork_repo_id: row.fork_repo_id,
195 branch: row.source_branch,
196 head_commit: row.head_commit,
197 merge_base: row.merge_base,
198 merged_by: row.merged_by,
199 merged_at: row.merged_at,
200 superseded_by: row.superseded_by,
201 check_status: row.check_status,
202 files: row
203 .files
204 .as_deref()
205 .and_then(|files| serde_json::from_str(files).ok())
206 .unwrap_or_default(),
207 assignees: serde_json::from_str(&row.assignees).unwrap_or_default(),
208 reviewers: serde_json::from_str(&row.reviewers).unwrap_or_default(),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar209 team_reviewers: row
210 .team_reviewers
211 .as_deref()
212 .and_then(|teams| serde_json::from_str(teams).ok())
213 .unwrap_or_default(),
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait214 author: user(row.author_id, row.author_name),
215 requested_by: requester(row.requested_by_id, row.requested_by_name),
216 created_at: row.created_at,
217 updated_at: row.updated_at,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar218 labels: row
219 .labels
220 .as_deref()
221 .and_then(|labels| serde_json::from_str(labels).ok())
222 .unwrap_or_default(),
223 milestone: milestone_ref(row.milestone, row.milestone_title),
224 base: row.base_branch,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge225 head_pushed_by: row.head_pushed_by,
226 head_pushed_at: row.head_pushed_at,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait227 }
228 }
229}
230
231#[derive(Deserialize)]
232pub struct CommentRow {
233 pub id: String,
234 pub kind: CommentKind,
235 pub author_id: String,
236 pub author_name: String,
237 pub body: String,
238 pub path: Option<String>,
239 pub line: Option<u32>,
240 pub verdict: Option<Verdict>,
241 pub created_at: String,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts242 #[serde(default)]
243 pub edited_at: Option<String>,
244 /// The issue or pull request it is on, where the query reads it.
245 #[serde(default)]
246 pub number: u32,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar247 /// One of the workspace's agents that wrote it, as itself, and whom it
248 /// acted for (migrations/0033_agent_comments.sql).
249 #[serde(default)]
250 pub agent_id: Option<String>,
251 #[serde(default)]
252 pub agent_handle: Option<String>,
253 #[serde(default)]
254 pub agent_name: Option<String>,
255 #[serde(default)]
256 pub agent_avatar_seed: Option<String>,
257 #[serde(default)]
258 pub acting_for_id: Option<String>,
259 #[serde(default)]
260 pub acting_for_name: Option<String>,
261 /// An agent review's verdict, kept apart from `verdict`, which every
262 /// rule about approvals reads: so it is advisory.
263 #[serde(default)]
264 pub agent_verdict: Option<String>,
265}
266
267impl CommentRow {
268 /// Who answers for it: whoever an agent acted for, or its author.
269 pub fn answerable_id(&self) -> &str {
270 self.acting_for_id.as_deref().unwrap_or(&self.author_id)
271 }
272
273 /// Whether it carries a verdict, a person's or an agent's.
274 pub fn has_verdict(&self) -> bool {
275 self.verdict.is_some() || self.agent_verdict.is_some()
276 }
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait277}
278
279impl From<CommentRow> for Comment {
280 fn from(row: CommentRow) -> Self {
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar281 let agent = match (row.agent_id, row.agent_handle) {
282 (Some(id), Some(handle)) => Some(AgentRef {
283 display_name: row.agent_name.unwrap_or_else(|| handle.clone()),
284 avatar_seed: row.agent_avatar_seed.unwrap_or_else(|| handle.clone()),
285 id,
286 handle,
287 }),
288 _ => None,
289 };
290 let advisory_verdict = row.agent_verdict.as_deref().and_then(AgentVerdict::parse);
291 let mut author = user(row.author_id, row.author_name);
292 if agent.is_some() {
293 author.kind = g1t_contracts::PrincipalKind::Agent;
294 }
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait295 Comment {
296 id: row.id,
297 kind: row.kind,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar298 author,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait299 body: row.body,
300 path: row.path,
301 line: row.line,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar302 // A person's verdict, or for showing, an agent's advisory one.
303 verdict: row.verdict.or(advisory_verdict.and_then(AgentVerdict::verdict)),
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait304 created_at: row.created_at,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts305 edited_at: row.edited_at,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar306 acting_for: agent.as_ref().and(requester(row.acting_for_id, row.acting_for_name)),
307 advisory: agent.is_some() && advisory_verdict.is_some(),
308 agent,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait309 }
310 }
311}
312
313#[derive(Deserialize)]
314pub struct SessionRow {
315 pub seq: u32,
316 pub kind: SessionEntryKind,
317 pub text: String,
318 pub tool: Option<String>,
319 pub commit: Option<String>,
320 pub at: String,
321}
322
323impl From<SessionRow> for SessionEntry {
324 fn from(row: SessionRow) -> Self {
325 SessionEntry {
326 seq: row.seq,
327 kind: row.kind,
328 text: row.text,
329 tool: row.tool,
330 commit: row.commit,
331 at: row.at,
332 }
333 }
334}
335
336/// A pull request whose head a push moved.
337#[derive(Deserialize)]
338pub struct MovedRow {
339 pub id: String,
340 pub repo_id: String,
341 pub number: u32,
342 pub issue_number: Option<u32>,
343 pub status: PullStatus,
344 pub author_id: String,
345 pub author_name: String,
346 #[serde(default)]
347 pub requested_by_id: Option<String>,
348 #[serde(default)]
349 pub requested_by_name: Option<String>,
350}
351
352/// A single number selected as `n`.
353#[derive(Deserialize)]
354pub struct NumberRow {
355 pub n: u32,
356}
357
358/// A single string selected as `value`.
359#[derive(Deserialize)]
360pub struct ValueRow {
361 pub value: String,
362}
363
364/// Rows as the database holds them, for tests of the rules that read them.
365#[cfg(test)]
366pub(crate) mod stored {
367 use super::*;
368
369 /// A pull request g1t made in a fork, read back as stored: g1t as its
370 /// author, and `requested_by` the person who asked, if anyone did.
371 pub(crate) fn pull(author: (&str, &str), requested_by: Option<(&str, &str)>) -> Pull {
372 let row: PullRow = serde_json::from_value(serde_json::json!({
373 "id": "pr_1", "repo_id": "rep_1", "number": 14, "issue_number": 12, "title": "Fix it",
374 "body": null, "agent": "g1t", "runtime": "hosted", "status": "open",
375 "fork_repo_id": "rep_f", "fork_namespace": "pulls", "fork_name": "pr_1",
376 "source_branch": null, "head_commit": null, "merge_base": null, "merged_by": null,
377 "merged_at": null, "superseded_by": null, "check_status": null, "files": null,
378 "assignees": "[]", "reviewers": "[]",
379 "author_id": author.0, "author_name": author.1,
380 "requested_by_id": requested_by.map(|(id, _)| id),
381 "requested_by_name": requested_by.map(|(_, name)| name),
382 "created_at": "", "updated_at": ""
383 }))
384 .unwrap();
385 row.into()
386 }
387
388 /// g1t's agent, as an author is stored.
389 pub(crate) const G1T: (&str, &str) = (g1t_contracts::identity::AGENT_ID, "g1t");
390 /// The person who asked g1t for the work.
391 pub(crate) const ASKER: (&str, &str) = ("usr_1", "syntaqx");
392}
393
394#[cfg(test)]
395mod tests {
396 use super::stored::{ASKER, G1T, pull};
397 use super::*;
398 use g1t_contracts::PrincipalKind;
399
400 #[test]
401 fn g1t_s_pull_request_reads_back_as_g1t_s_requested_by_the_person() {
402 let made = pull(G1T, Some(ASKER));
403 assert_eq!((made.author.username.as_str(), made.author.kind), ("g1t", PrincipalKind::Agent));
404 let asked = made.requested_by.as_ref().expect("who asked is kept");
405 assert_eq!((asked.id.as_str(), asked.username.as_str(), asked.kind), ("usr_1", "syntaqx", PrincipalKind::User));
406 assert_eq!(made.owner().id, "usr_1");
407 }
408
409 #[test]
410 fn anyone_else_s_reads_back_as_theirs_alone() {
411 let own = pull(ASKER, None);
412 assert_eq!(own.author.kind, PrincipalKind::User);
413 assert!(own.requested_by.is_none());
414 assert_eq!(own.owner().id, "usr_1");
415 // Half a requester is nobody.
416 assert!(requester(Some("usr_1".into()), None).is_none());
417 }
418
419 #[test]
420 fn g1t_s_own_work_reads_back_as_g1t_itself() {
421 let own = pull(("g1t", "g1t"), None);
422 assert_eq!(own.author.kind, PrincipalKind::System);
423 assert!(own.requested_by.is_none());
424 }
425}

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