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
1/**
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
39/** What binds a run's token to one run. */
40export 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
52/** Set on a run's token (or a historical `@g1t` agent's) resolved from its token: the composite identity. */
53export 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}