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