Skip to content

Commit 7544765

Browse files
authored
Merge pull request #10 from open-gitagent/feat/policy-guardrails
Feat/policy guardrails
2 parents 4b25aa0 + 0427ad0 commit 7544765

21 files changed

Lines changed: 1440 additions & 139 deletions

File tree

agentos/src/App.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { LogsTab } from "./components/LogsTab.tsx";
44
import { WorkspaceTab } from "./components/WorkspaceTab.tsx";
55
import { SchedulesTab } from "./components/SchedulesTab.tsx";
66
import { HomePage } from "./components/HomePage.tsx";
7+
import { PolicyTab } from "./components/PolicyTab.tsx";
8+
import { PoliciesPage } from "./components/PoliciesPage.tsx";
79

8-
type Tab = "chat" | "schedules" | "logs";
9-
type View = "home" | "dashboard";
10+
type Tab = "chat" | "schedules" | "policy" | "logs";
11+
type View = "home" | "dashboard" | "policies";
1012

1113
function timeAgo(iso: string | null): string {
1214
if (!iso) return "never";
@@ -91,7 +93,7 @@ export default function App() {
9193
</div>
9294
</div>
9395
</div>
94-
<div className="px-2 pt-2">
96+
<div className="px-2 pt-2 space-y-1">
9597
<button
9698
onClick={() => setView("home")}
9799
className={`w-full text-left rounded-lg px-3 py-2 text-sm transition flex items-center gap-2 ${
@@ -100,6 +102,14 @@ export default function App() {
100102
>
101103
<span>🏠</span> Home
102104
</button>
105+
<button
106+
onClick={() => setView("policies")}
107+
className={`w-full text-left rounded-lg px-3 py-2 text-sm transition flex items-center gap-2 ${
108+
view === "policies" ? "bg-ink-600 ring-1 ring-accent/40" : "hover:bg-ink-700"
109+
}`}
110+
>
111+
<span>🛡️</span> Policies
112+
</button>
103113
</div>
104114
{/* Agents folder (file-system style) */}
105115
<div className="px-2 pt-2">
@@ -145,7 +155,9 @@ export default function App() {
145155

146156
{/* Main */}
147157
<main className="flex-1 flex flex-col min-w-0">
148-
{view === "home" ? (
158+
{view === "policies" ? (
159+
<PoliciesPage />
160+
) : view === "home" ? (
149161
<HomePage onLaunch={launchFromHome} onOpenDashboard={() => agents[0] && openAgent(agents[0].name)} />
150162
) : agent ? (
151163
<>
@@ -161,7 +173,7 @@ export default function App() {
161173
</div>
162174
</div>
163175
<nav className="ml-auto flex gap-1 bg-ink-800 rounded-lg p-1">
164-
{(["chat", "schedules", "logs"] as Tab[]).map((t) => (
176+
{(["chat", "schedules", "policy", "logs"] as Tab[]).map((t) => (
165177
<button
166178
key={t}
167179
onClick={() => setTab(t)}
@@ -185,6 +197,14 @@ export default function App() {
185197
/>
186198
)}
187199
{tab === "schedules" && <SchedulesTab key={agent.name} agent={agent.name} agentLabel={agent.label} />}
200+
{tab === "policy" && (
201+
<PolicyTab
202+
key={agent.name}
203+
agent={agent.name}
204+
agentLabel={agent.label}
205+
onManagePolicies={() => setView("policies")}
206+
/>
207+
)}
188208
{tab === "logs" && <LogsTab key={agent.name} agent={agent.name} />}
189209
</section>
190210
</>

agentos/src/api.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,59 @@ export interface SessionDetail {
4848
entries: TranscriptEntry[];
4949
}
5050

51+
// Policies — SRS-managed. The browser only sees the bits that matter for
52+
// the runtime (name, description, cedar/opa subsections). Everything else
53+
// is forwarded by the server-side proxy; we don't reshape it.
54+
export interface CedarPolicyEntry {
55+
id: string;
56+
name?: string;
57+
description?: string;
58+
policy_text: string;
59+
enabled?: boolean;
60+
}
61+
export interface CedarGuardrailConfig {
62+
enabled: boolean;
63+
policies: CedarPolicyEntry[];
64+
fail_open?: boolean;
65+
}
66+
export interface OPAManagedBinding {
67+
policy_id: string;
68+
hooks?: string[];
69+
}
70+
export interface OPAGuardrailConfig {
71+
enabled: boolean;
72+
source: "managed" | "external";
73+
managed_policies: OPAManagedBinding[];
74+
server_url?: string | null;
75+
policy_path?: string | null;
76+
mode?: "audit" | "enforce" | "fail_open" | "fail_closed";
77+
timeout_seconds?: number;
78+
}
79+
export interface PolicyDoc {
80+
_id: string;
81+
name: string;
82+
description: string;
83+
cedar_guardrail?: CedarGuardrailConfig | null;
84+
opa_guardrail?: OPAGuardrailConfig | null;
85+
created_at?: string;
86+
updated_at?: string;
87+
[k: string]: unknown;
88+
}
89+
export interface AgentPolicyBinding {
90+
_id: string; // agent name
91+
policyId: string;
92+
updatedAt: string;
93+
}
94+
95+
export interface OPAPolicyDoc {
96+
_id: string;
97+
name: string;
98+
description?: string;
99+
rego_content: string;
100+
created_at?: string;
101+
updated_at?: string;
102+
}
103+
51104
export interface Schedule {
52105
_id: string;
53106
agentName: string;
@@ -122,4 +175,26 @@ export const api = {
122175
reqJSON<{ schedule: Schedule }>("PATCH", `/schedules/${encodeURIComponent(id)}`, fields).then((d) => d.schedule),
123176
deleteSchedule: (id: string) => reqJSON<{ ok: boolean }>("DELETE", `/schedules/${encodeURIComponent(id)}`),
124177
runScheduleNow: (id: string) => postJSON<{ ok: boolean }>(`/schedules/${encodeURIComponent(id)}/run-now`, {}),
178+
// Policies — SRS-proxied. Server injects x-api-key.
179+
policies: () => getJSON<{ policies: PolicyDoc[] }>("/policies").then((d) => d.policies),
180+
policy: (id: string) => getJSON<PolicyDoc>(`/policies/${encodeURIComponent(id)}`),
181+
createPolicy: (body: Partial<PolicyDoc>) => postJSON<PolicyDoc>("/policies", body),
182+
updatePolicy: (id: string, body: Partial<PolicyDoc>) =>
183+
reqJSON<{ success?: boolean } | PolicyDoc>("PUT", `/policies/${encodeURIComponent(id)}`, body),
184+
deletePolicy: (id: string) =>
185+
reqJSON<{ success?: boolean }>("DELETE", `/policies/${encodeURIComponent(id)}`),
186+
// Per-agent policy binding (Mongo, ours).
187+
getAgentPolicy: (agent: string) =>
188+
getJSON<{ binding: AgentPolicyBinding | null }>(`/agents/${encodeURIComponent(agent)}/policy`).then((d) => d.binding),
189+
setAgentPolicy: (agent: string, policyId: string | null) =>
190+
reqJSON<{ binding: AgentPolicyBinding | null }>("PUT", `/agents/${encodeURIComponent(agent)}/policy`, { policy_id: policyId }).then((d) => d.binding),
191+
// OPA rego policies (managed by SRS, referenced from RAI policies' opa_guardrail).
192+
opaPolicies: () => getJSON<{ policies: OPAPolicyDoc[] } | OPAPolicyDoc[]>("/opa-policies").then((d) => (Array.isArray(d) ? d : d.policies)),
193+
opaPolicy: (id: string) => getJSON<OPAPolicyDoc>(`/opa-policies/${encodeURIComponent(id)}`),
194+
createOpaPolicy: (body: { name: string; description?: string; rego_content: string }) =>
195+
postJSON<OPAPolicyDoc>("/opa-policies", body),
196+
updateOpaPolicy: (id: string, body: Partial<OPAPolicyDoc>) =>
197+
reqJSON<OPAPolicyDoc | { success?: boolean }>("PUT", `/opa-policies/${encodeURIComponent(id)}`, body),
198+
deleteOpaPolicy: (id: string) =>
199+
reqJSON<{ success?: boolean }>("DELETE", `/opa-policies/${encodeURIComponent(id)}`),
125200
};

0 commit comments

Comments
 (0)