Câble un canal attachable au flux de sortie d'une tâche de fond (runner
infrastructure + commande app-tauri + port/adaptateurs frontend) et le
panneau ProjectWorkStatePanel s'y abonne pour un rendu live au lieu d'un
état figé au dernier snapshot.
Validations obtenues avant commit :
- cargo test -p infrastructure --test background_task_runner : vert
- cargo check -p backend -p app-tauri : vert
- npx vitest run src/features/workstate/workstate.test.tsx : vert
- npm run typecheck : vert
- verdict QA #58 : vert
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajout du catalogue enrichi pour les modèles Codex et Claude avec:
- Compatibilité estimée avec la version CLI locale détectée
- Source d'origine (catalogue/Provider) pour chaque entrée
- Support du catalogue Provider API externe
- Matrice de compatibilité embarquée dans l'application
Frontend:
- UI de configuration des modèles avec affichage des états de compatibilité
- Suggestions dynamiques avec badges de compatibilité
- Messages d'aide contextuels (compatible/unknown/likelyTooRecent)
- Alertes non-bloquantes pour les modèles trop récents
- Gestion des échecs de catalogue avec saisie manuelle conservée
Backend:
- Ports CliVersionReader, ProviderModelCatalogue, CompatibilityMatrixSource
- Implémentations: ProcessCliVersionReader, HttpProviderModelCatalogue, EmbeddedCompatibilityMatrix
- Enrichissement des DTOs avec compatibility, cli_version, warnings
- Tests unitaires complets pour le resolver de catalogue
Les builders `with_opencode` / `with_opencode_provider` s'évacuent
réciproquement (dernier appel gagnant) pour honorer l'invariant
`opencode_backend_is_consistent`. Le use case SaveOpenCodeProviderProfile
route désormais via le builder au lieu de muter le champ directement — c'est
ce qui dupliquait `opencode` + `opencodeProvider` et forçait un repli sur
llamacpp même quand l'utilisateur choisissait un provider cloud.
Défense en profondeur côté store :
- `read_doc` répare les profils corrompus sur disque (drop du stale `opencode`)
- `save` refuse de persister un profil violant l'invariant via le nouveau
variant `StoreError::Invalid` (mappé vers `AppError::Invalid`)
Tests verts : builders last-wins (domain), save rejets + read repair
(infra, profile_store 10/10).
Refs #97
Le timeout hardcoded 15000ms (15s) dans le bloc mcp.idea de l'opencode.json
généré par IdeA était far too short pour idea_ask_agent, qui bloque pendant
que l'agent cible travaille. OpenCode tuait la requête à 15s avec l'erreur
-32001 « Request timed out », même si la cible répondait correctement
(visible dans le workstate). Claude/Codex n'étaient pas affectés car leur
config MCP n'a pas de champ timeout.
Désormais le timeout est résolu par resolve_opencode_mcp_timeout_ms() :
- défaut 4h (14_400_000 ms), aligné sur DEFAULT_RENDEZVOUS_CEILING ;
- overridable via IDEA_OPENCODE_MCP_TIMEOUT_MS ;
- fallback sûr sur 0/parse-error (jamais d'expiration instantanée).
Les 4 occurrences (lifecycle.rs x2 + assistant/mod.rs x2) utilisent la même
fonction exportée depuis application::agent. Aucune modification des configs
MCP Claude/Codex.
Les 4 générateurs opencode.json/opencode_provider.json codaient en dur
{"bash":"ask","edit":"ask"}, ignorant les permissions configurées côté
IdeA pour l'agent. Claude et Codex appliquaient déjà PermissionProjector,
seul OpenCode passait à côté.
Ajoute domain::opencode_permission_block(eff: Option<&EffectivePermissions>)
qui mappe bash ← posture bash effective, edit ← posture Write effective
(Read/Delete non exprimables dans le schéma OpenCode, déjà enforcées par
le sandbox Landlock). eff == None omet la clé permission entièrement,
préservant le prompting natif OpenCode — même invariant que Claude/Codex.
Câble eff jusqu'aux 4 sites d'appel (lifecycle.rs + assistant/mod.rs,
variantes llamacpp et provider cloud). Le chemin ticket-assistant
(assistant/mod.rs) n'a pas de PermissionStore par agent pour l'instant,
donc eff y reste None (comportement inchangé, pas de régression).
Réf ticket #94.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le parseur lisait value.text/value.content à la racine alors que
`opencode run --format json` niche le texte réel sous value.part.text
(cf. packages/opencode/src/cli/cmd/run.ts). Conséquence : idea_ask_agent
vers un profil OpenCode échouait systématiquement avec « OpenCode n'a
produit aucun final textuel » malgré une réponse CLI normale.
Ajoute aussi la gestion des événements type "error" (ParsedEvent::Error,
ReplyEvent::Error) pour distinguer un échec explicite d'un final vide,
et des tests de conformité sur le format JSONL réel confirmé.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Quand un repo HuggingFace héberge à la fois le fichier GGUF fusionné et
ses shards pour une même quantisation, select_gguf_file (tri alphabétique
naïf) pouvait retenir un shard partiel plutôt que le fichier fusionné ou
l'ensemble complet des shards, causant un échec immédiat au démarrage du
serveur llama.cpp (agent Context).
select_gguf_files renvoie désormais soit le fichier fusionné seul (priorité),
soit tous les shards correspondants triés par index — jamais un shard isolé.
Le téléchargement, le cache et la reprise gèrent le cas multi-fichiers
(manifest de shards, chemins locaux préservant le nom distant pour
l'autoload de llama.cpp).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le catalogue de providers OpenCode lit désormais le cache local
~/.cache/opencode/models.json pour refléter les providers réellement
disponibles, avec repli garanti sur le catalogue statique en cas
d'absence ou d'erreur de lecture du cache. Ajout d'un champ additif
`custom` sur OpenCodeProviderConfig pour permettre à l'utilisateur de
déclarer un provider hors catalogue (id + clé API en saisie libre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute le catalogue statique de providers OpenCode (lot B3), le stockage
sécurisé des secrets (SecretStore + adapter infrastructure), et les
use cases SaveOpenCodeProviderProfile/DeleteProfile câblés en composition
root. Couvre le fix B1 et les tests de régression demandés par QA.
cargo build --workspace propre, cargo test --workspace -- --test-threads=1
intégralement vert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lots B1-B4 : modèle de domaine des plugins (manifeste, capacités menus/layouts/MCP),
port et registre applicatif, chargement/validation en infrastructure, exposition DTO
et commandes Tauri. Tests cargo verts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute le MCP dédié à l'édition de templates : catalogue et classification
des templates (mcp/templates.rs infrastructure + app-tauri), use cases et
provider (application/template), enforcement de la policy des tools (mod.rs,
server.rs, tools.rs), avec la parité côté chemin OpenAI-compatible
(openai_tools.rs x2).
Lots B1 (catalogue/classification), B2 (use cases/provider) et B3
(enforcement policy) livrés en un seul commit cohérent.
QA vert (seul l'échec de bind loopback #80, connu et non-régression, écarté).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
orchestrator/mcp/server.rs applique désormais les règles de permission du
domaine mcp_tool_permissions (lot B1) à l'invocation d'un tool, y compris
le cas requester vide.
Lot B2 du ticket #82 : ferme la boucle enforcement sur le socle B1, la
parité OpenAI-compatible suit en B3 sur la même branche.
QA vert (32 tests dont le nouveau cas requester vide).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduit le domaine mcp_tool_permissions (règles de permission par tool
MCP) et étend le port de store correspondant. Le catalogue read/write des
tools MCP (orchestrator/mcp/tools.rs) s'appuie désormais sur ces règles, et
un store durable (mcp_tool_permission.rs) persiste les permissions au-delà
d'une session.
Lot B1 du ticket #82 : pose le socle domaine/store, l'enforcement suit en
B2 sur la même branche.
QA vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AgentSessionFactory::start propage désormais l'identité du requester aux
sessions structurées ; OpenTicketAssistant et LaunchAgent la portent
correctement de bout en bout. ToolPolicyRegistry est branché sur
AppOpenAiToolInvoker pour combler le trou de parité : TicketToolProvider
n'appliquait pas la policy des tools sur le chemin OpenAI-compatible,
contrairement au chemin structuré natif.
QA vert (échecs de bind loopback écartés comme non-régression préexistante,
tracés en #80).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
L'appairage ne survivait pas au redémarrage et son code, permanent, était
imprimé sur la sortie standard. Un appareil appairé devient une entité
persistante, nommée et révocable, derrière un code désormais éphémère.
- B1 : port DeviceSessionStore et adapter FsDeviceSessionStore, entités de
domaine (PairedDevice, DeviceId, SessionTokenHash, DeviceName). Les tokens
sont hachés en SHA-256 et comparés en temps constant (subtle) : le store
ne peut pas rejouer une session qu'il a servie. Cookie Max-Age 400 j à
renouvellement glissant, lastSeenAtMs throttlé.
- B2 : code éphémère en mémoire, TTL 10 min et usage unique, toute
génération invalidant la précédente. POST /api/pairing-code authentifiée,
flag --new-code. Le code est retiré du boot et l'eprintln! qui l'imprimait
est supprimé.
- B3 : endpoints devices (list/rename/revoke/revoke-all/logout), event
DeviceRevoked et ActiveConnectionRegistry par device_id, fermant sans
délai les WebSockets d'un appareil révoqué.
- B4 : port PairAttemptLimiter et adapter mémoire, rate-limit par origine et
global sur horloge injectée, donc testable sans attente réelle.
La normalisation du code passe côté serveur : elle absorbe la dette #76, que
la seule normalisation frontend de #75 ne faisait que masquer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rendre visible l'échec « aucune réponse » de l'assistant de ticket :
stream sans Final ou Final vide → ReplyChunk::Error terminal visible,
plus jamais de tour muet. Backend + frontend, tests verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Corrige la régression high « Error on loading local model » : le premier chargement
du serveur modèle local (cold-start llama.cpp) dépassait la fenêtre de readiness et
échouait. Le warmup dispose désormais d'un deadline par défaut de 600 s, surchargable
par config optionnelle `warmup_deadline_secs` (validée dans [30, 1800]).
- domain: champ `warmup_deadline_secs: Option<u64>` + validation de borne
- application: policy de readiness effective (défaut 600 s, override par config)
- app-tauri: DTO `warmupDeadlineSecs`
- infrastructure: application de la deadline effective au warmup
Verdict QA (vert) : domain 252, application 81 + 22 model_server, app-tauri
dto_model_servers 6, infrastructure model_server 2, build OK. Contrat readiness
couvert sur ports mockés.
Caveat : le cold-start end-to-end réel (llama-server) sort du sandbox de test ->
vérification manuelle utilisateur restante, non couverte par les tests unitaires.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduit un variant terminal `ReplyEvent::Error { message }` (domain/ports)
propagé jusqu'au chat : l'adapter OpenAI-compat récupère `reasoning_content`
quand `content` est vide, et un `Final` vide est converti en `Error` visible
plutôt qu'un tour silencieux qui pend. Le front (`ReplyChunk` + useTicketAssistant)
rend ce message d'erreur dans la cellule.
- domain: variant `ReplyEvent::Error`, bras non terminal (readiness, structured drain)
- infra/session: parse `reasoning_content` (delta/completion), fallback visible
- app-tauri: mapping ReplyEvent::Error -> ReplyChunk::Error, Final vide -> Error
- frontend: ReplyChunk `error`, rendu dans useTicketAssistant (+ test .test.tsx)
NB: commit sur la feature branch, PAS un merge. #60 reste inProgress.
Les 10 tests `cargo test -p infrastructure --lib session` échouent SOUS SANDBOX
uniquement (bind loopback 127.0.0.1:0 interdit -> Os PermissionDenied), pas une
régression : revérification hors-sandbox requise avant tout merge vers develop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le runner de tâches de fond détectait la fin d'un process via l'EOF du drain de
sortie PTY, couplant le cycle de vie du process au flux d'output. Un output
encore ouvert (ou drainé ailleurs) pouvait masquer ou retarder la détection de
fin.
Ajoute wait/try_wait au port figé PtyPort :
- wait : bloquant, rend un ExitStatus idempotent ;
- try_wait : non bloquant, rend Option<ExitStatus> ;
- exit status mémorisé pour garder wait/try_wait/kill cohérents.
Implémentation dans PortablePtyAdapter (état d'exit mémorisé). Le
CommandBackgroundRunner détecte désormais la fin via pty.wait (select sur
cancel/deadline) au lieu de l'EOF du drain. Tous les fakes PtyPort du workspace
sont complétés en conséquence.
Le tee live UI reste hors périmètre (traité en #58). Aucun breaking IPC/front.
Tests : infrastructure/tests/background_task_runner.rs (nouveau) + pty_adapter.rs
verts, non-régression application/app-tauri OK (hors échecs réseau du sandbox).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Épurement de la dette de ticket_list sur deux axes :
Recherche texte — matching exact du numéro/#ref via parse_issue_search_ref,
court-circuité avant load_issue, sans matching par substring numérique
(« 1 » ne remonte plus « 12 », « 123 »…).
Pagination — curseur opaque et stable anchor-based au lieu d'un offset fragile :
token v1.<base64url-no-pad-json> encodant le tri + l'ancre {number, sortKey},
reprise strictement après l'ancre. Curseur legacy / invalide / de version
inconnue / avec sort divergent rejeté par une erreur explicite « Invalid
cursor ». Le DTO cursor reste String → non-breaking côté UI.
Fichiers : infrastructure/src/issues.rs, app-tauri/src/tickets.rs,
app-tauri/Cargo.toml (+ base64 0.22). Tests : issue_store_text_filter,
ticket_list_ 7/7 (anchor-based + rejets).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stretch B2/F2 de #54, par-dessus le MVP déjà mergé (B1/F1).
Backend : le port de téléchargement HF publie une progression débouncée
(bytes reçus / total, pourcentage) via le stream de statut du serveur
modèle, avec gestion du total inconnu (pas de faux %), du cache hit,
de l'annulation et du timeout.
Frontend : l'overlay plein-cellule de préparation du serveur affiche la
progression réelle (barre, %, octets, source) en mappant le fil de
statut, avec la règle « pas de faux % » quand le total est inconnu.
Tests : application + infrastructure (téléchargement débouncé, cancel,
timeout, cache hit, total inconnu) et vitest (overlay + formatage pur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'assistant IA d'édition de ticket éditait les fichiers du ticket en direct,
hors de tout contrôle. Il passe désormais par les tools MCP idea_ticket_* :
préparation d'un environnement structuré dédié et policy d'enforcement scopée
au ticket courant, de sorte que l'assistant ne peut agir que sur son ticket
via la surface MCP plutôt que sur le système de fichiers.
Couvert par de nouveaux tests QA (mcp_server, assistant_context_store).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persiste l'état des fenêtres (layout/position) et le restaure au
relancement de l'application. Découpage hexagonal :
- domaine : modèle et port d'état des fenêtres (layout, ports)
- application : use cases de persistance/restauration
- infrastructure : store window_state (adapter de persistance)
- présentation : câblage app-tauri (state, commands, lib)
Couvert par des tests ciblés domaine/application/infrastructure.
Depend de #39 (fermeture des fenêtres auxiliaires).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend du support natif d'une source modèle Hugging Face en alternative au
chemin .gguf local pour le serveur llama.cpp intégré, et refonte des options.
- domaine : ModelSource {LocalPath|HuggingFace} + HfModelRef,
LlamaCppOptions {host,gpu_layers,context_size,jinja}, invariant auto_start
exigeant une source, validate_free_args (rejet des flags réservés).
- infra : build_argv partagé avec build_spawn_spec, émission -hf vs --model,
ordre argv figé ; migration model-servers.json V1 -> V2.
- app-tauri : DTO V2 (modelSource, compat modelPath, conflit = INVALID),
commande preview_model_server_command.
QA : cargo test ciblés verts (domain 7, application 8, infra model_server 4,
app-tauri dto 5). Échecs des suites complètes infra/app-tauri environnementaux
(sandbox bind/socket), hors périmètre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le tool-calling local ne fonctionnait jamais via Ollama. Refonte du
support local d'OpenCode autour de llama.cpp: profil, catalogue,
matérialisation de la config OpenCode et surface first-run alignés sur
llama-server (backend + frontend).
QA vert (commandes réelles): domain 244, application 81+64, infra 263
(10 échecs = bind-port sandbox identiques sur develop, non-régression),
frontend 574, tsc propre. Réserve E2E live non bloquante faute de
llama-server joignable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le first-run wizard restait figé : `CliAgentRuntime::detect` construisait un
runtime tokio courant-thread via `futures_block_on` pour piloter un
`ProcessSpawner` async. Appelé depuis le runtime async de Tauri, ce
`block_on` imbriqué panique ; la commande IPC ne répond alors jamais, la
promesse `detectProfiles` reste pendante et `busy` ne redescend plus.
Ce commit s'attaque à la cause côté backend :
- `AgentRuntime::detect` devient `async fn` (`#[async_trait]`) : le port cesse
de mentir sur sa nature. Il pilote un spawner async, il est async. Le
`futures_block_on` disparaît, et avec lui le runtime imbriqué.
- La sonde CLI est bornée par `tokio::time::timeout(DETECTION_TIMEOUT)` :
un binaire qui ne rend jamais la main dégrade la détection en `Err`, il
ne gèle plus l'appelant.
- Les profils `StructuredAdapter::OpenAiCompatible` n'ont pas de CLI à
spawner : les sonder revenait à tester un binaire inexistant. Ils sont
désormais sondés par un GET HTTP sur l'endpoint `/models` dérivé de
`chat_http.endpoint`, borné par le même timeout.
`DetectProfiles` séquence les `await` sur les candidats. Les fakes
`AgentRuntime` des crates application/infrastructure/app-tauri suivent la
nouvelle signature.
Tests: cargo test -p domain (241), -p application (81), -p infrastructure
(269), -p app-tauri (63+15) — verts. `rg futures_block_on crates` sans match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute le paramètre de tri dans TicketListQuery et l'expose via
ticket_list (surface app-tauri + MCP). Ports et adaptateur mock
frontend alignés sur le contrat de tri. La partie UI (contrôle de tri
dans TicketFacetsBar) suivra dans un commit frontend dédié.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Passe les filtres de la liste des tickets en sélection multiple (#12).
Backend Rust :
- IssueListFilter : statuses/priorities en Vec, filter_matches en OR intra-champ
et AND inter-champs
- TicketListRequestDto en tableaux + from_request (parse/déduplication)
- MCP idea_ticket_list aligné sur le nouveau contrat
Frontend :
- TicketListQuery.statuses/priorities
- UI de cases à cocher, toggle/clear des filtres
Tests verts : issue_store (7), app-tauri --lib (59), mcp_server (24),
issue_usecases (6), sprint_usecases (6), frontend vitest (505), npm run build (exit 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute la suppression complète d'un ticket (#6).
Backend Rust :
- port IssueStore::delete (NotFound si absent) + event IssueDeleted{freed_sprint}
- FsIssueStore::delete : supprime .ideai/tickets/<N>/ et l'index sous lock
- use case DeleteIssue + adaptations des tests sprint/ticket_assistant au port
- commande Tauri ticket_delete + câblage events/state/lib
Frontend :
- gateway delete (ports, adapter ticket + mock, domain)
- useTicketDetail : retrait de la liste et fermeture du détail via event issueDeleted
- intégration TicketDetail + tests
Tests verts : application/issue_usecases (6), infrastructure/issue_store (7),
app-tauri --lib (56), frontend vitest (503), npm run build (exit 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ticket #10 — introduction du modèle de sprints côté backend.
- Domaine : nouvel agrégat Sprint (sprint.rs), IDs, événements et invariants ;
rattachement des issues à un sprint (issue.rs) et ports associés.
- Application : use-cases sprints (application/src/sprints) + erreurs dédiées.
- Infrastructure : store de sprints (infrastructure/src/sprints.rs), adaptation
du store d'issues et exposition MCP via orchestrator/mcp/tickets.rs.
- app-tauri : commandes, state et events pour piloter les sprints depuis l'UI.
Tests domaine/application/infra/app-tauri verts (sprint_usecases, sprint_store,
issue_store, mcp_server).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Redémarre le ticket #4 sur la base develop. Émission d'annonces live d'un
agent vers l'UI, distinctes du Final de délégation :
- domain: ReplyEvent::Announcement/Final et DomainEvent::AgentAnnouncement
(events.rs), port d'émission (ports.rs), gating de readiness (readiness.rs).
- application: mapping des événements structurés en annonces
(agent/structured.rs, agent/mod.rs, lib.rs) et relais côté orchestrateur
(orchestrator/service.rs).
- infrastructure/session: parse des annonces + fix du Final pour Claude et
Codex, propagé aux adaptateurs et à la conformance
(claude.rs, codex.rs, conformance.rs, mod.rs, process.rs, sandbox_e2e.rs).
- app-tauri: relais Tauri des annonces vers le front (events.rs, chat.rs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
T1 — Wake automatique du propriétaire à la complétion.
La complétion d'une tâche de fond est désormais livrée à l'agent
propriétaire dès que la session accepte l'envoi (wake.rs :
mark_completion_delivered au send accepté), via un drain de flux dédié
(structured.rs : drain_reply_stream_with_readiness). L'inbox médiée
enfile l'item sans démarrer de tour ni marquer l'agent busy
(input/mod.rs : enqueue FIFO silencieux). Régression couverte
(tests/agent_wake.rs, tests input/mod.rs).
T3 — Tâches de fond projetées dans le read-model du panneau Work.
AgentWorkState porte désormais background_tasks
(VO AgentBackgroundTaskState), alimenté par un builder best-effort
with_background_tasks(store) : union list_open_for_agent + dispatch des
completions non livrées par owner_agent_id, erreur store => Vec vide
(aucune régression live/busy/tickets). DTO Tauri backgroundTasks et
wiring du BackgroundTaskStore côté state.rs. Le frontend, déjà câblé,
affiche Cancel/Retry (mapping queued/waiting -> pending, tri sur
updatedAtMs). Borne V1 : une tâche terminale déjà livrée n'est plus
énumérable (Retry limité à la fenêtre non livrée).
Tests : cargo build --workspace OK ; cargo test -p application /
-p app-tauri / -p infrastructure verts ; frontend build + vitest verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Intègre le système de tickets/issues V1 (validé QA vert de bout en bout,
mémoire tickets-v1-e2e-validated-qa) dans la branche des tâches de fond.
Conflits résolus : app-tauri/state.rs, domain/events.rs, domain/lib.rs, domain/ports.rs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Livre le lot backend B8 des tâches de fond :
- infrastructure : runner de commandes concret (CommandBackgroundRunner)
sur le port BackgroundTaskRunner, tail borné (bounded_tail/BoundedTail),
éclatement du module background_task en sous-modules (mod/runner/tail,
sink extrait de l'ancien background_task.rs).
- application : nouveau module background exposant les cas d'usage
SpawnBackgroundCommand, CancelBackgroundTask, RetryBackgroundTask et le
port BackgroundCommandArchive.
- domain : refactor point-2 de l'arbitrage Architect — sortie du trait
BackgroundCommandArchive de la couche domaine vers application.
- app-tauri : câblage runtime (commands, dto, state, lib) des commandes
spawn/cancel/retry et de la boucle de complétion sink fermée en
composition root.
Build workspace + tests application/infrastructure verts (QA).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>