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

@ -187,7 +187,10 @@ async fn structured_run_turn_without_plan_does_not_sandbox() {
.expect("run_turn natif réussit");
assert_eq!(lines, vec![RESULT_LINE.to_owned()]);
assert!(allowed_marker.exists(), "écriture in-grant réussit (sans plan)");
assert!(
allowed_marker.exists(),
"écriture in-grant réussit (sans plan)"
);
assert!(
denied_marker.exists(),
"sans plan, l'écriture hors-grant DOIT réussir: {denied_marker:?} absent — restriction ambiante anormale"
@ -278,7 +281,10 @@ async fn structured_run_turn_fail_closed_no_child_on_enforce_err() {
async fn structured_run_turn_none_plan_is_native_path() {
let dir = fresh_dir("p4");
let marker = dir.join("native.txt");
let script = format!("printf '%s\\n' '{RESULT_LINE}'; echo ok > '{}'", marker.display());
let script = format!(
"printf '%s\\n' '{RESULT_LINE}'; echo ok > '{}'",
marker.display()
);
let spec = SpawnLine {
command: "sh".to_owned(),
args: vec!["-c".to_owned(), script],
@ -327,7 +333,10 @@ async fn structured_two_turns_disjoint_grants_are_confined() {
run_turn(&spec_a, None, Some(&enforcer))
.await
.expect("tour A ok");
assert!(a_in.exists(), "tour A: écriture dans son grant (A) doit réussir");
assert!(
a_in.exists(),
"tour A: écriture dans son grant (A) doit réussir"
);
assert!(
!a_into_b.exists(),
"tour A: écriture dans B (hors grant A) doit être bloquée"
@ -419,7 +428,9 @@ async fn structured_sandboxed_turn_preserves_conversation_id() {
use super::factory::StructuredSessionFactory;
if !landlock_is_enforced() {
eprintln!("skip structured_sandboxed_turn_preserves_conversation_id: Landlock indisponible");
eprintln!(
"skip structured_sandboxed_turn_preserves_conversation_id: Landlock indisponible"
);
return;
}
@ -448,6 +459,7 @@ async fn structured_sandboxed_turn_preserves_conversation_id() {
let ctx = PreparedContext {
content: MarkdownDoc::new("# ctx"),
relative_path: "CLAUDE.md".to_owned(),
project_root: run_dir.to_string_lossy().into_owned(),
};
let cwd = ProjectPath::new(run_dir.to_string_lossy().into_owned()).expect("cwd absolu");
let plan = rw_plan(&run_dir); // plan write-only ⇒ reads/exec du fake non gênés
@ -466,7 +478,10 @@ async fn structured_sandboxed_turn_preserves_conversation_id() {
.iter()
.filter(|e| matches!(e, ReplyEvent::Final { .. }))
.count();
assert_eq!(finals, 1, "un Final attendu malgré le sandbox, vu: {events:?}");
assert_eq!(
finals, 1,
"un Final attendu malgré le sandbox, vu: {events:?}"
);
// LE POINT : l'id de conversation a bien été capté sous enforcement actif.
assert_eq!(