feat(orchestrator): modèle de désignation d'orchestrateur + sink de diagnostic

Introduit le modèle AgentManifest { version, entries, orchestrator } et la
garde d'écriture directe may_write_directly(..., &OrchestratorDesignation) :
seul l'orchestrateur désigné peut écrire directement, les autres passent par
le rendez-vous médié. Câble la désignation à travers domain → application →
infrastructure → app-tauri (context_guard, service, lifecycle, ports).

Ajoute crates/application/src/diag.rs : sink de diagnostic best-effort, sans
dépendance, qui miroite les traces du rendez-vous inter-agents de
l'orchestrateur vers un fichier de log persistant (utile au lancement via
AppImage où stderr est jeté), avec la même discipline « zéro dépendance,
ne casse jamais le rendez-vous ».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 08:56:17 +02:00
parent 40982d44da
commit 287681c198
57 changed files with 1758 additions and 420 deletions

View File

@ -292,8 +292,14 @@ mod tests {
let deny = str_array(&json["permissions"]["deny"]);
// A Write capability fans out to both Edit(..) and Write(..) entries.
assert!(deny.contains(&"Edit(.ideai/**)".to_owned()), "deny={deny:?}");
assert!(deny.contains(&"Write(.ideai/**)".to_owned()), "deny={deny:?}");
assert!(
deny.contains(&"Edit(.ideai/**)".to_owned()),
"deny={deny:?}"
);
assert!(
deny.contains(&"Write(.ideai/**)".to_owned()),
"deny={deny:?}"
);
let allow = str_array(&json["permissions"]["allow"]);
assert!(
@ -330,7 +336,10 @@ mod tests {
"Bash(shutdown*)",
"Bash(reboot*)",
] {
assert!(deny.contains(&guard.to_owned()), "missing guardrail {guard}; deny={deny:?}");
assert!(
deny.contains(&guard.to_owned()),
"missing guardrail {guard}; deny={deny:?}"
);
}
}

View File

@ -2,9 +2,11 @@
//!
//! Produces the **permission-relevant** part of Codex's `config.toml`
//! (`sandbox_mode` / `approval_policy`) plus the matching launch args
//! (`--sandbox` / `--ask-for-approval`). The posture→mode derivation is extracted
//! verbatim from the former `codex_sandbox_mode` / `codex_approval_policy` /
//! `apply_codex_cli_permission_args` in `lifecycle.rs`.
//! (`--sandbox` / `--ask-for-approval`) and, for workspace-write postures, the
//! project root as an additional writable directory (`--add-dir`). The
//! posture→mode derivation is extracted verbatim from the former
//! `codex_sandbox_mode` / `codex_approval_policy` / `apply_codex_cli_permission_args`
//! in `lifecycle.rs`.
//!
//! Unlike Claude's seed, Codex's `config.toml` is **co-owned** (it also carries the
//! `mcp_servers.idea` table and the `projects.*` trust entries, which are MCP/trust
@ -44,7 +46,7 @@ impl PermissionProjector for CodexPermissionProjector {
fn project(
&self,
eff: Option<&EffectivePermissions>,
_ctx: &ProjectionContext,
ctx: &ProjectionContext,
) -> PermissionProjection {
// Product invariant: nothing posed ⇒ nothing projected. Codex keeps its
// native sandbox/approval defaults (no args, no managed keys written).
@ -63,6 +65,17 @@ impl PermissionProjector for CodexPermissionProjector {
toml_string(approval),
);
let mut args = vec![
"--sandbox".to_owned(),
sandbox.to_owned(),
"--ask-for-approval".to_owned(),
approval.to_owned(),
];
if sandbox == "workspace-write" && !ctx.project_root.is_empty() {
args.push("--add-dir".to_owned());
args.push(ctx.project_root.to_owned());
}
PermissionProjection {
files: vec![ProjectedFile::MergeToml {
rel_path: CONFIG_REL_PATH.to_owned(),
@ -70,12 +83,7 @@ impl PermissionProjector for CodexPermissionProjector {
managed_keys: MANAGED_KEYS.iter().map(|k| (*k).to_owned()).collect(),
contents,
}],
args: vec![
"--sandbox".to_owned(),
sandbox.to_owned(),
"--ask-for-approval".to_owned(),
approval.to_owned(),
],
args,
env: Vec::new(),
}
}
@ -135,7 +143,7 @@ mod tests {
}
// ---- (6) posture → sandbox_mode / approval_policy + (7) args↔contents
// coherence + MergeToml shape -------------------------------
// coherence + add-dir + MergeToml shape ---------------------
#[test]
fn posture_maps_sandbox_and_approval_in_file_and_args() {
@ -176,7 +184,7 @@ mod tests {
// -- (7) Args reflect the SAME values as the TOML, in CLI order.
assert_eq!(
proj.args,
&proj.args[..4],
vec![
"--sandbox".to_owned(),
sandbox.to_owned(),
@ -185,6 +193,21 @@ mod tests {
],
"posture {posture:?}: args must mirror the TOML values"
);
if sandbox == "workspace-write" {
assert!(
proj.args
.windows(2)
.any(|w| w == ["--add-dir".to_owned(), "/proj".to_owned()]),
"workspace-write posture must add the project root as writable: {:?}",
proj.args
);
} else {
assert!(
!proj.args.contains(&"--add-dir".to_owned()),
"read-only posture must not add writable dirs: {:?}",
proj.args
);
}
}
}
}