feat(permissions): voie projection CLI (LP0→LP3) + checkpoint Codex/input
Jalon vert regroupant deux chantiers entrelacés dans le working tree, indissociables au niveau fichier mais tous deux verts (cargo test --workspace + tests frontend permissions au vert). Permissions — voie « projection CLI » (advisory), complète : - LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs). - LP1 store : FsPermissionStore (.ideai/permissions.json). - LP2 use cases : Get/Update project, Update agent override, Resolve. - LP3 projecteurs Claude/Codex (settings.local.json / config.toml), câblage launch-path + PermissionProjectorRegistry, nettoyage des fichiers Replace orphelins au swap de profil (LP3-4), composition root + commandes Tauri, UI PermissionsPanel (projet + override agent). - ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap). Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS airtight (Landlock fichiers) + résumé de permissions injecté. Inclut aussi le chantier Codex/input/sessions structurées en cours (McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -17,23 +17,25 @@ use application::{
|
||||
CreateLayout, CreateMemory, CreateProject, CreateSkill, CreateTemplate, DeleteAgent,
|
||||
DeleteEmbedderProfile, DeleteLayout, DeleteMemory, DeleteProfile, DeleteSkill, DeleteTemplate,
|
||||
DescribeEmbedderEngines, DetectAgentDrift, DetectProfiles, DismissEmbedderSuggestion,
|
||||
FirstRunState, GetMemory, GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog,
|
||||
GitStage, GitStatus, GitUnstage, HealthUseCase, InspectConversation, LaunchAgent, ListAgents,
|
||||
ListAgentsInput, ListEmbedderProfiles, ListLayouts, ListMemories, ListProfiles, ListProjects,
|
||||
ListResumableAgents, ListSkills, ListTemplates, LiveAgentRegistry, LoadLayout, McpRuntime,
|
||||
MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject, OpenTerminal,
|
||||
OrchestratorService, ReadAgentContext, ReadMemoryIndex, ReadProjectContext, RecallMemory,
|
||||
ReconcileLayouts, RecordTurn, RecordTurnProvider, ReferenceProfiles, RenameLayout,
|
||||
ResizeTerminal, ResolveMemoryLinks, SaveEmbedderProfile, SaveProfile, SetActiveLayout,
|
||||
SnapshotRunningAgents, StructuredSessions, SuggestedThisSession, SyncAgentWithTemplate,
|
||||
TerminalSessions, UnassignSkillFromAgent, UpdateAgentContext, UpdateMemory,
|
||||
UpdateProjectContext, UpdateSkill, UpdateTemplate, WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
FirstRunState, GetMemory, GetProjectPermissions, GitBranches, GitCheckout, GitCommit, GitGraph,
|
||||
GitInit, GitLog, GitStage, GitStatus, GitUnstage, HealthUseCase, InspectConversation,
|
||||
LaunchAgent, ListAgents, ListAgentsInput, ListEmbedderProfiles, ListLayouts, ListMemories,
|
||||
ListProfiles, ListProjects, ListResumableAgents, ListSkills, ListTemplates, LiveAgentRegistry,
|
||||
LoadLayout, McpRuntime, MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject,
|
||||
PermissionProjectorRegistry,
|
||||
OpenTerminal, OrchestratorService, ReadAgentContext, ReadMemoryIndex, ReadProjectContext,
|
||||
RecallMemory, ReconcileLayouts, RecordTurn, RecordTurnProvider, ReferenceProfiles,
|
||||
RenameLayout, ResizeTerminal, ResolveAgentPermissions, ResolveMemoryLinks, SaveEmbedderProfile,
|
||||
SaveProfile, SetActiveLayout, SnapshotRunningAgents, StructuredSessions, SuggestedThisSession,
|
||||
SyncAgentWithTemplate, TerminalSessions, UnassignSkillFromAgent, UpdateAgentContext,
|
||||
UpdateAgentPermissions, UpdateMemory, UpdateProjectContext, UpdateProjectPermissions,
|
||||
UpdateSkill, UpdateTemplate, WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
};
|
||||
use domain::ports::{
|
||||
AgentContextStore, AgentRuntime, AgentSessionFactory, Clock, Embedder, EmbedderEnvInspector,
|
||||
EmbedderProfileStore, EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator,
|
||||
MemoryRecall, MemoryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore,
|
||||
TemplateStore,
|
||||
MemoryRecall, MemoryStore, PermissionStore, ProcessSpawner, ProfileStore, ProjectStore,
|
||||
PtyPort, SkillStore, TemplateStore,
|
||||
};
|
||||
use domain::profile::{
|
||||
AgentProfile, ContextInjection, McpConfigStrategy, McpTransport, StructuredAdapter,
|
||||
@ -44,10 +46,11 @@ use serde_json::{json, Map, Value};
|
||||
use uuid::Uuid;
|
||||
|
||||
use infrastructure::{
|
||||
embedder_from_profile, AdaptiveMemoryRecall, ClaudeTranscriptInspector, CliAgentRuntime,
|
||||
embedder_from_profile, AdaptiveMemoryRecall, ClaudePermissionProjector,
|
||||
ClaudeTranscriptInspector, CliAgentRuntime, CodexPermissionProjector,
|
||||
EmbedderEnvProbe, FsConversationLog, FsEmbedderProfileStore, FsEmbedderPromptStore,
|
||||
FsHandoffStore, FsMemoryStore, FsOrchestratorWatcher, FsProfileStore, FsProjectStore,
|
||||
FsProviderSessionStore, FsSkillStore, FsTemplateStore, Git2Repository,
|
||||
FsHandoffStore, FsMemoryStore, FsOrchestratorWatcher, FsPermissionStore, FsProfileStore,
|
||||
FsProjectStore, FsProviderSessionStore, FsSkillStore, FsTemplateStore, Git2Repository,
|
||||
HeuristicHandoffSummarizer, IdeaiContextStore, InMemoryConversationRegistry, InMemoryMailbox,
|
||||
LocalFileSystem, LocalProcessSpawner, McpServer, MediatedInbox, NaiveMemoryRecall,
|
||||
OrchestratorWatchHandle, PortablePtyAdapter, StructuredSessionFactory, SystemClock,
|
||||
@ -230,6 +233,14 @@ pub struct AppState {
|
||||
pub inspect_conversation: Arc<InspectConversation>,
|
||||
/// Project registry — used by agent commands to resolve a `Project` from an id.
|
||||
pub project_store: Arc<dyn ProjectStore>,
|
||||
/// Read the project permission document.
|
||||
pub get_project_permissions: Arc<GetProjectPermissions>,
|
||||
/// Update project-level default permissions.
|
||||
pub update_project_permissions: Arc<UpdateProjectPermissions>,
|
||||
/// Update one agent permission override.
|
||||
pub update_agent_permissions: Arc<UpdateAgentPermissions>,
|
||||
/// Resolve effective permissions for one agent.
|
||||
pub resolve_agent_permissions: Arc<ResolveAgentPermissions>,
|
||||
// --- Windows (L10) ---
|
||||
/// Detach a tab into a new OS window (persists the workspace topology).
|
||||
pub move_tab: Arc<MoveTabToNewWindow>,
|
||||
@ -498,6 +509,10 @@ impl AppState {
|
||||
let contexts = Arc::new(IdeaiContextStore::new(Arc::clone(&fs_port)));
|
||||
let contexts_port = Arc::clone(&contexts) as Arc<dyn AgentContextStore>;
|
||||
|
||||
// --- Project permissions (LP1) ---
|
||||
let permission_store = Arc::new(FsPermissionStore::new(Arc::clone(&fs_port)));
|
||||
let permission_store_port = Arc::clone(&permission_store) as Arc<dyn PermissionStore>;
|
||||
|
||||
// --- Skill store (L12) ---
|
||||
// Global skills live in the machine-local app-data dir; project skills are
|
||||
// resolved per call from each project's `.ideai/` (so one store serves all
|
||||
@ -632,6 +647,20 @@ impl AppState {
|
||||
// PTH : tout profil (Claude/Codex inclus) ouvre une cellule terminal. Le code
|
||||
// `launch_structured` reste en place (mort-code retiré au lot de nettoyage B-6).
|
||||
let _session_factory = session_factory; // décâblé en B-2 (nettoyage B-6)
|
||||
|
||||
// --- Permission projectors (lot LP3-5) ---
|
||||
// UN seul registre, source unique de vérité, injecté à l'identique dans
|
||||
// `LaunchAgent` (projection au lancement) ET `ChangeAgentProfile` (nettoyage des
|
||||
// fichiers orphelins au swap). Les deux projecteurs concrets vivent dans
|
||||
// l'infrastructure, keyés par leur `ProjectorKey` via `with(...)`.
|
||||
let permission_projectors = Arc::new(
|
||||
PermissionProjectorRegistry::new()
|
||||
.with(Arc::new(ClaudePermissionProjector)
|
||||
as Arc<dyn domain::PermissionProjector>)
|
||||
.with(Arc::new(CodexPermissionProjector)
|
||||
as Arc<dyn domain::PermissionProjector>),
|
||||
);
|
||||
|
||||
let launch_agent = Arc::new(
|
||||
LaunchAgent::new(
|
||||
Arc::clone(&contexts_port),
|
||||
@ -646,6 +675,7 @@ impl AppState {
|
||||
Arc::clone(&memory_recall_port),
|
||||
Some(Arc::clone(&check_embedder_suggestion)),
|
||||
)
|
||||
.with_permission_store(Arc::clone(&permission_store_port))
|
||||
// Reprise conversationnelle (lot P7) : à chaque (re)lancement, si la cellule
|
||||
// porte une conversation et qu'un handoff existe (`<root>/.ideai/conversations/`),
|
||||
// son résumé est réinjecté dans le convention file. Best-effort, additif :
|
||||
@ -658,7 +688,11 @@ impl AppState {
|
||||
// `<root>/.ideai/conversations/providers.json`. Best-effort, additif : une
|
||||
// écriture en échec / pas d'id moteur ⇒ lancement normal, aucune écriture.
|
||||
.with_provider_session_provider(Arc::new(AppProviderSessionProvider)
|
||||
as Arc<dyn application::ProviderSessionProvider>),
|
||||
as Arc<dyn application::ProviderSessionProvider>)
|
||||
// Projection des permissions au (re)lancement (lot LP3-5) : avec le
|
||||
// permission store câblé ci-dessus, `resolve` a une source ⇒ le projecteur
|
||||
// du profil matérialise la config de permission de la CLI dans le run dir.
|
||||
.with_permission_projectors(Arc::clone(&permission_projectors)),
|
||||
);
|
||||
|
||||
// Hot-swap an agent's runtime profile (§15.1). Reuses the shared context/
|
||||
@ -676,7 +710,10 @@ impl AppState {
|
||||
Arc::clone(&launch_agent),
|
||||
Arc::clone(&events_port),
|
||||
)
|
||||
.with_structured(Arc::clone(&structured_sessions)),
|
||||
.with_structured(Arc::clone(&structured_sessions))
|
||||
// Même registre que `LaunchAgent` (lot LP3-5) : au swap cross-profile, on
|
||||
// nettoie les fichiers `Replace` orphelins de l'ancien profil avant relance.
|
||||
.with_permission_projectors(Arc::clone(&permission_projectors)),
|
||||
);
|
||||
|
||||
// Read-only inventory of resumable agent cells (§15.2). Reuses the shared
|
||||
@ -707,6 +744,18 @@ impl AppState {
|
||||
));
|
||||
|
||||
let project_store = Arc::clone(&store_port);
|
||||
let get_project_permissions = Arc::new(GetProjectPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
let update_project_permissions = Arc::new(UpdateProjectPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
let update_agent_permissions = Arc::new(UpdateAgentPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
let resolve_agent_permissions = Arc::new(ResolveAgentPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
|
||||
// --- Template store + use cases (L7) ---
|
||||
let template_store = Arc::new(FsTemplateStore::new(
|
||||
@ -856,8 +905,7 @@ impl AppState {
|
||||
}
|
||||
});
|
||||
}
|
||||
let input_mediator =
|
||||
Arc::clone(&mediated_inbox) as Arc<dyn domain::input::InputMediator>;
|
||||
let input_mediator = Arc::clone(&mediated_inbox) as Arc<dyn domain::input::InputMediator>;
|
||||
// Registre des conversations par paire (cadrage C3) : un fil par paire, session
|
||||
// vivante keyée par conversation (lève l'ambiguïté session/agent).
|
||||
let conversation_registry = Arc::new(InMemoryConversationRegistry::new())
|
||||
@ -957,6 +1005,10 @@ impl AppState {
|
||||
list_resumable_agents,
|
||||
inspect_conversation,
|
||||
project_store,
|
||||
get_project_permissions,
|
||||
update_project_permissions,
|
||||
update_agent_permissions,
|
||||
resolve_agent_permissions,
|
||||
create_template,
|
||||
update_template,
|
||||
list_templates,
|
||||
@ -1069,9 +1121,20 @@ impl AppState {
|
||||
// The project-id string the handshake guard compares against: the same
|
||||
// hyphen-free hex form the endpoint encodes, which M5d's `--project` reuses.
|
||||
let project_id = project.id.as_uuid().simple().to_string();
|
||||
// Readiness de démarrage : quand le pont MCP d'un agent se connecte (initialize),
|
||||
// libère son éventuel 1er tour différé (fix race cold-launch via signal MCP).
|
||||
// L'id arrive en hex (handshake `requester`) ⇒ on le parse en AgentId ici (la
|
||||
// composition root est la seule à connaître la frontière infra↔domaine).
|
||||
let service_for_ready = Arc::clone(&self.orchestrator_service);
|
||||
let ready_sink: Arc<dyn Fn(&str) + Send + Sync> = Arc::new(move |requester: &str| {
|
||||
if let Ok(uuid) = Uuid::parse_str(requester) {
|
||||
service_for_ready.release_agent_cold_start(AgentId::from_uuid(uuid));
|
||||
}
|
||||
});
|
||||
let handle = McpServerHandle::start(
|
||||
McpServer::new(Arc::clone(&self.orchestrator_service), project.clone())
|
||||
.with_events(events),
|
||||
.with_events(events)
|
||||
.with_ready_sink(ready_sink),
|
||||
endpoint,
|
||||
listener,
|
||||
project_id,
|
||||
@ -1355,7 +1418,10 @@ fn codex_mcp_config_target(profile: &AgentProfile) -> Option<(&str, &str)> {
|
||||
/// l'encodeur TOML partagé [`domain::McpServerWiring::to_config_toml`] (D2) — même
|
||||
/// source de wiring que la déclaration `.mcp.json` Claude, donc zéro dérive.
|
||||
fn mcp_server_entry_toml(profile: &AgentProfile, runtime: Option<&McpRuntime>) -> String {
|
||||
let transport = profile.mcp.as_ref().map_or(McpTransport::Stdio, |m| m.transport);
|
||||
let transport = profile
|
||||
.mcp
|
||||
.as_ref()
|
||||
.map_or(McpTransport::Stdio, |m| m.transport);
|
||||
let (command, args) = match runtime {
|
||||
Some(rt) => (
|
||||
rt.exe.clone(),
|
||||
|
||||
Reference in New Issue
Block a user