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
163 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.

Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged1/**
2 * Run credentials and the audit log. Mirrors `crates/contracts/src/credentials.rs`
3 * and `crates/contracts/src/audit.rs`.
4 *
5 * Every sandbox run gets its own tokens, bound to the run, its repository
6 * and what its kind of work needs, and acting as an agent on behalf of the
7 * person who started the work. Everything done with them is recorded in
8 * the workspace's audit log, with the rule that allowed or refused it.
9 */
10
11import type { ServiceBinding } from "./clients";
12import type { RepoPath } from "./repos";
13import type { User } from "./identity";
14
15/** What a run does, as far as its credentials are concerned. */
16export type RunCredentialKind =
17 | "implement"
18 | "revise"
19 | "review"
20 | "answer"
21 | "update"
22 | "plan"
23 | "checks"
24 | "queue"
25 | "mergecheck"
26 | "deploy"
27 | "bump";
28
29/**
30 * `runner`: g1t's runner in the sandbox, which clones, pushes and records
31 * the session, acting downstream as the person. `tools`: the agent's own
32 * MCP tools, acting as the agent.
33 */
34export type CredentialUse = "runner" | "tools";
35
36/** A repository a run may push to; `branch` null means any branch of it. */
37export type GitGrant = { repo: RepoPath; branch?: string | null };
38
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.39/** What binds a run's token to one run. */
Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged40export type RunBinding = {
41 kind: RunCredentialKind;
42 use: CredentialUse;
43 runId?: string | null;
44 number?: number | null;
45 agent: string;
46 read: RepoPath[];
47 push: GitGrant[];
48 /** g1t's own run: the workspace's credential, acting on behalf of g1t. */
49 system?: boolean;
50};
51
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.52/** Set on a run's token (or a historical `@g1t` agent's) resolved from its token: the composite identity. */
Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged53export type Acting = {
54 credentialId: string;
55 agent: string;
56 onBehalfOf: { id: string; username: string };
57 scope: { repo: RepoPath; operations: string[]; run?: RunBinding };
58};
59
60export type CreateRunCredentialInput = {
61 onBehalfOf: User;
62 repo: RepoPath;
63 kind: RunCredentialKind;
64 use: CredentialUse;
65 /** The pull request the run works on. */
66 number?: number | null;
67 /** Repositories it may clone besides those it may push to. */
68 read?: RepoPath[];
69 push?: GitGrant[];
70 /** The run's timeout. */
71 ttlSeconds: number;
72};
73
74export type ActorKind = "person" | "agent" | "workspace" | "runner" | "system";
75export type AuditOutcome = "allowed" | "denied";
76/** Where it came in: the API, MCP, git, or g1t.sh's own pages. */
77export type AuditSurface = "rest" | "mcp" | "git" | "web" | "registry";
78
79export type AuditEntry = {
80 id: string;
81 /** RFC 3339. */
82 time: string;
83 actorKind: ActorKind;
84 /** A person's username, the agent's name, or a workspace's slug. */
85 actor: string;
86 actorId: string;
87 agent: string | null;
88 /** The person an agent acted for. */
89 onBehalfOf: string | null;
90 runId: string | null;
91 runKind: string | null;
92 credentialId: string | null;
93 /** An operation such as `create_issue`, or `git.push` and `git.fetch`. */
94 action: string;
95 surface: AuditSurface;
96 workspace: string;
97 /** `owner/name`. */
98 repo: string | null;
99 number: number | null;
100 gitRef: string | null;
101 path: string | null;
102 outcome: AuditOutcome;
103 /** The rule that allowed or refused it, such as `run:implement/tools` or `scope:repository`. */
104 rule: string;
105 /** `ok`, or the failure's code. */
106 result: string | null;
107 message: string | null;
108 requestId: string;
109};
110
111/**
112 * Which of a workspace's entries the viewer may see: an owner everything;
113 * a member what was done to its projects, and what they did or had done
114 * on their behalf.
115 */
116export type AuditVisibility = { kind: "all" } | { kind: "projects"; username: string };
117
118export type AuditQuery = {
119 workspace: string;
120 visibility: AuditVisibility;
121 actor?: string | null;
122 agent?: string | null;
123 action?: string | null;
124 repo?: string | null;
125 number?: number | null;
126 outcome?: AuditOutcome | null;
127 actorKind?: ActorKind | null;
128 runIds?: string[];
129 /** RFC 3339, inclusive. */
130 since?: string | null;
131 /** RFC 3339, exclusive. */
132 until?: string | null;
133 before?: string | null;
134 /** At most 500. */
135 limit?: number | null;
136};
137
138export type AuditPage = { entries: AuditEntry[]; next: string | null };
139
140export interface AuditApi {
141 /** Newest first. The caller has checked who may see the workspace's log. */
142 list(query: AuditQuery): Promise<AuditPage>;
143}
144
145/** The audit log, which the events service keeps. */
146export function auditClient(events: ServiceBinding): AuditApi {
147 return {
148 list: async (query) => {
149 const response = await events.fetch("https://service/rpc/audit_list", {
150 method: "POST",
151 headers: { "content-type": "application/json" },
152 body: JSON.stringify(query),
153 });
154 if (!response.ok) throw new Error(`audit_list failed with status ${response.status}`);
155 return (await response.json()) as AuditPage;
156 },
157 };
158}
159
160/** How an actor is shown: "g1t on behalf of syntaqx". */
161export function describeActor(entry: Pick<AuditEntry, "actor" | "agent" | "onBehalfOf">): string {
162 return entry.onBehalfOf ? `${entry.agent ?? entry.actor} on behalf of ${entry.onBehalfOf}` : entry.actor;
163}

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