feat(permissions): expose network permission state (#103)

This commit is contained in:
2026-07-25 23:05:55 +02:00
parent e8731834f4
commit 3047dc9195
31 changed files with 2080 additions and 94 deletions

View File

@ -50,8 +50,10 @@ import type {
ProjectMcpToolPermissions,
ProjectPermissions,
ProjectWorkState,
ProjectSystemPermissions,
ProfileAvailability,
ResumableAgent,
ResolvedAgentSystemPermissions,
ServerExposurePreview,
ServerExposureSettings,
Skill,
@ -59,6 +61,7 @@ import type {
SkillScope,
Sprint,
Template,
SystemPermissionSet,
TerminalSession,
Ticket,
TicketCarnet,
@ -2186,6 +2189,8 @@ export class MockDeviceGateway implements DeviceGateway {
/** In-memory permissions gateway. */
export class MockPermissionGateway implements PermissionGateway {
private docs = new Map<string, ProjectPermissions>();
private systemDocs = new Map<string, ProjectSystemPermissions>();
private systemRuntime = new Map<string, ResolvedAgentSystemPermissions>();
private doc(projectId: string): ProjectPermissions {
if (!this.docs.has(projectId)) {
@ -2234,6 +2239,72 @@ export class MockPermissionGateway implements PermissionGateway {
};
}
private systemDoc(projectId: string): ProjectSystemPermissions {
if (!this.systemDocs.has(projectId)) {
this.systemDocs.set(projectId, { version: 1, agents: [] });
}
return this.systemDocs.get(projectId)!;
}
async getProjectSystemPermissions(
projectId: string,
): Promise<ProjectSystemPermissions> {
return structuredClone(this.systemDoc(projectId));
}
async updateProjectSystemPermissions(
projectId: string,
permissions: SystemPermissionSet | null,
): Promise<ProjectSystemPermissions> {
const doc = this.systemDoc(projectId);
if (permissions && Object.keys(permissions).length > 0) {
doc.projectDefault = structuredClone(permissions);
} else {
delete doc.projectDefault;
}
return structuredClone(doc);
}
async updateAgentSystemPermissions(
projectId: string,
agentId: string,
permissions: SystemPermissionSet | null,
): Promise<ProjectSystemPermissions> {
const doc = this.systemDoc(projectId);
const agents = (doc.agents ?? []).filter((entry) => entry.agentId !== agentId);
if (permissions && Object.keys(permissions).length > 0) {
agents.push({ agentId, permissions: structuredClone(permissions) });
}
doc.agents = agents;
return structuredClone(doc);
}
async resolveAgentSystemPermissions(
projectId: string,
agentId: string,
): Promise<ResolvedAgentSystemPermissions> {
const runtime = this.systemRuntime.get(agentId);
if (runtime) return structuredClone(runtime);
const doc = this.systemDoc(projectId);
const wanted =
doc.agents?.find((entry) => entry.agentId === agentId)?.permissions.network ??
doc.projectDefault?.network ??
null;
return {
wanted,
effective: wanted ?? "ask",
runtimeLock: { state: "none" },
control: { mode: "editable" },
};
}
setResolvedAgentSystemPermissions(
agentId: string,
resolved: ResolvedAgentSystemPermissions,
): void {
this.systemRuntime.set(agentId, structuredClone(resolved));
}
// ── MCP tool permissions (ticket #82) — mirrors the backend catalogue in
// `crates/infrastructure/src/orchestrator/mcp/tools.rs`, a separate durable
// document from the file/command permissions above. ─────────────────────