import { randomUUID } from "node:crypto"; import { isInside } from "./paths.ts"; import type { Capability } from "./config.ts"; export type GrantScope = "once" | "session"; export type GrantKind = "file" | "bash"; export interface Grant { id: string; capability: Capability; pathPrefix: string; scope: GrantScope; kind: GrantKind; command?: string; source: "judge" | "user"; createdAt: number; } export class GrantStore { private grants: Grant[] = []; add(input: Omit): Grant { const grant: Grant = { ...input, id: randomUUID(), createdAt: Date.now() }; this.grants.push(grant); return grant; } all(): Grant[] { return [...this.grants]; } clear(): void { this.grants = []; } matching( capability: Capability, path: string, kind: GrantKind, command?: string, ): Grant[] { return this.grants.filter((grant) => { if (grant.capability !== capability || grant.kind !== kind) return false; if (!isInside(grant.pathPrefix, path)) return false; if (grant.command !== undefined && grant.command !== command) return false; return true; }); } consume(grants: readonly Grant[]): void { const ids = new Set(grants.filter((grant) => grant.scope === "once").map((grant) => grant.id)); if (ids.size > 0) this.grants = this.grants.filter((grant) => !ids.has(grant.id)); } }