flagon-io/g1t

public

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/apps/web/app/lib/audit.server.ts

51 lines1,862 bytesCodeBlame

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 API1import { env } from "cloudflare:workers";
2
3import { type AuditEntry, type AuditQuery, type Viewer, auditClient } from "@g1t/contracts";
4
5import { visibilityFor } from "./audit";
6import { roleIn } from "./session.server";
7
8/** The audit log, which the events service keeps. */
9export const audit = auditClient(env.EVENTS);
10
11/** The most rows one export writes. */
12export const EXPORT_LIMIT = 10_000;
13
14/**
15 * Entries of a workspace's log the viewer may see, or null when they may
16 * see none of it.
17 */
18export async function auditPage(viewer: Viewer, query: Omit<AuditQuery, "visibility">) {
19 const visibility = viewer ? visibilityFor(roleIn(viewer, query.workspace), viewer.username) : null;
20 if (!visibility) return null;
21 return audit.list({ ...query, workspace: query.workspace.toLowerCase(), visibility });
22}
23
24/** Every entry matching `query`, page by page, up to `EXPORT_LIMIT`. */
25export async function auditAll(viewer: Viewer, query: Omit<AuditQuery, "visibility">): Promise<AuditEntry[] | null> {
26 const entries: AuditEntry[] = [];
27 let before = query.before ?? null;
28 while (entries.length < EXPORT_LIMIT) {
29 const page = await auditPage(viewer, { ...query, before, limit: 500 });
30 if (!page) return null;
31 entries.push(...page.entries);
32 if (!page.next) break;
33 before = page.next;
34 }
35 return entries.slice(0, EXPORT_LIMIT);
36}
37
38/**
39 * What the given runs did, oldest first, for members of the workspace.
40 * Not narrowed to one repository: an attempt on another is what most
41 * needs to be seen.
42 */
43export async function runAudit(viewer: Viewer, owner: string, runIds: string[]): Promise<AuditEntry[]> {
44 if (runIds.length === 0) return [];
45 const page = await auditPage(viewer, {
46 workspace: owner,
47 runIds: runIds.slice(0, 50),
48 limit: 200,
49 }).catch(() => null);
50 return page ? [...page.entries].reverse() : [];
51}