Capitalise la mémoire projet accumulée pendant les chantiers B7/B8 (tâches de fond first-class), le système de tickets V1 et le ticket #1 : design, cadrages d'archi, checkpoints d'avancement et verdicts QA/frontend. Mise à jour de l'index MEMORY.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.1 KiB
B8 — Runner de commandes couplé PTY (FIGÉ Architecture, 2026-07-03)
Base feature/background-tasks-first-class. Fait suite à background-tasks-first-class-design et b7-wiring-anchor-state-rs.
Trou constaté
BackgroundCompletionSink::new/start_from_runner n'existent QUE dans les tests. En prod state.rs, background_ready_tx n'est alimenté que par le reconcile boot ; aucun impl BackgroundTaskRunner concret. LocalProcessSpawner.run = one-shot Output, non couplé.
Décisions
- Le spawner ne crée pas la task. Concepts owner/project/wake_policy = application. On implémente le port FIGÉ
BackgroundTaskRunner(domain/ports.rs:1349) via nouvel adapter infraCommandBackgroundRunnercomposantArc<dyn PtyPort>(résolu via RemoteHost → Liskov SSH/WSL), PAS un PortablePtyAdapter en dur. - Application = use case
SpawnBackgroundCommand: alloue TaskId, store.create(Queued)→Running, runner.spawn(BackgroundTaskSpec). Le spec (ports.rs:207) porte déjà task_id/project_id/owner_agent_id/kind/wake_policy/command:Option/deadline. - Le runner n'écrit NI store NI inbox : il émet 1
BackgroundTaskCompletionsur subscribe_completions(). Le sink (single-writer) fait store.save PUIS ready_tx.send (persist-avant-signal). - Composition root ferme la boucle : construire runner +
BackgroundCompletionSink::new(store_port, background_ready_tx)+sink.start_from_runner(runner)surtauri::async_runtime::spawn(pas de tokio ambiant). Ancre state.rs ~1540-1660. - Frontière : seuls SpawnSpec/BackgroundTaskSpec/BackgroundTaskCompletion (domaine) franchissent ; portable-pty reste en infra. Rendu xterm = tee du même spawn vers PtyBridge, orthogonal au tracking.
Contrat complétion
BackgroundTaskResult Success/Failure { finished_at_ms, exit_code:Some, summary, stdout_tail, stderr_tail bornés (ring UTF-8-safe, cap IDEA_BG_TAIL_BYTES ~8-16KiB) }. Cancel→runner kill→Cancelled{reason}, pas de wake succès. Invariants tenus par le sink (dédup task_id, IgnoredAlreadyTerminal, mark_completion_delivered) + pont enqueue_message (jamais busy) + wake si idle. Final reste terminal-de-tour, la complétion est un InboxItem::BackgroundCompletion.
Commandes Tauri (dépend B8)
cancel_background_task({taskId}); retry_background_task({taskId})→BackgroundTaskDto (NOUVEAU task_id, jamais réutilisé); list_background_tasks({projectId,agentId?}). DTO camelCase {taskId,ownerAgentId,projectId,kind,state,exitCode?,summary?,stdoutTail?,stderrTail?,createdAtMs,updatedAtMs}.
Sous-tâches DevBackend (ordre)
1 util tail borné (infra). 2 CommandBackgroundRunner (infra/background_task.rs + lib.rs). 3 use cases SpawnBackgroundCommand/Cancel/Retry (application). 4 fermer boucle sink en composition root (app-tauri/state.rs). 5 handlers+DTO (commands.rs, events.rs). 6 tee PTY live (pty.rs). 7 tests (réutiliser tests/background_completion_sink.rs).