Workflows

Un Workflow est une automatisation réutilisable et versionnée qu'un opérateur rédige une fois et exécute de nombreuses fois. Il s'agit d'un graphe orienté d'étapes typées — LLM call sites, lectures système, actions d'écriture inline, opérations de service, approval gates (portes d'approbation) humaines et constructions de contrôle de flux (branche / boucle / fan-out dynamique) — reliées entre elles par des pins. Lorsque vous exécutez un workflow, Genesis compile le graphe en une mission dont les tasks s'exécutent sur le moteur de mission existant, chaque gate étant mise en pause derrière une décision humaine et le workflowId@version d'origine + checksum étant épinglés sur le run comme provenance de compliance.

Les workflows sont des artefacts d'authoring propres à chaque tenant : chaque tool et endpoint est protégé par la politique tenant-admin, et la tenancy est implicite via la base de données par tenant (pas de colonne TenantId — ADR-008), exactement comme pour les missions et les tasks.

Cette page couvre les deux surfaces :

  • Opérateur / produit — ce qu'est un workflow, son cycle de vie, la surface d'outils MCP, l'API REST et les pages du dashboard.
  • Architecture — le modèle de données, l'IR du graphe et le modèle de pins, le catalogue d'étapes, les mécanismes internes materialize → publish → invoke → run, les workflows système vs tenant, les triggers, la gouvernance de compliance, la couche d'acceptation des mission runs (runs → changements) et la relation entre workflows, missions et agents.

1. Guide de l'opérateur

1.1 Ce qu'est un workflow

ConceptSignification
WorkflowUne identité logique stable (workflowId, ex. wf3f9ac21b04) qui possède une série ordonnée de versions immuables.
VersionUne révision monotone (1, 2, 3, …) d'un workflow. Une version est une charge utile de graphe plus un status de cycle de vie.
GrapheUn objet JSON { irVersion, nodes, edges, params } décrivant les étapes et la manière dont leurs pins se connectent.
Étape (node)Une unité de travail : un LLM call site, une lecture, une action d'écriture, une opération de service, une gate ou une construction de contrôle de flux.
PinUn point de connexion typé sur une étape. Les pins Exec (in/out) ordonnent l'exécution ; les pins data (Json/String/Number/Bool/Document/Artifact/Context) transportent des valeurs typées.
GateUne étape d'approbation humaine. Toute étape capable d'écriture doit se situer derrière une gate (imposé au publish).
RunUne mission lancée depuis une version publiée. Il n'existe pas de table « run » distincte — un run est une mission estampillée avec la provenance du workflow.
TunnelLa signature de pins d'entrée/sortie propre à un workflow, utilisée lorsqu'il est intégré comme étape à l'intérieur d'un autre workflow (composition FlowRef).

1.2 Le cycle de vie

                 update_draft / materialize / waive_control / validate_draft
                 ┌───────────────────────────┐
                 ▼                            │
  create_draft ───►  DRAFT  ──── publish ───►  PUBLISHED  ──── archive ───►  ARCHIVED
       │              │  (validate + freeze)        │
       │              │                             │
       │          delete_draft                   invoke ───► RUN (a mission)
       │         (permanent)                         │
       └─ clone_from_mission / clone_from_system ────┘ (always lands a new DRAFT)

Les seules transitions de status légales sont Draft → Published et Published → Archived :

  1. Draft — la version de travail mutable ; le seul état qui accepte les éditions de graphe. Le premier draft d'un nouveau workflow est la version 1 ; versionner un workflow existant ajoute max(version)+1.
  2. Update — remplacer la charge utile graphe/tunnels du draft autant de fois que nécessaire.
  3. Materialize (optionnel, workflows gouvernés) — injecter les étapes gate/review de compliance requises depuis le catalogue de contrôles actif avant le publish, afin que l'opérateur les examine sur le canvas.
  4. Publish — valider, puis figer. La validation signale tous les problèmes d'un coup (vérifications catalogue/site, compatibilité de pins, pins requis, cycles, dominance de gate, plancher de compliance). En cas de succès, la version devient immuable, reçoit un checksum SHA-256, estampille PublishedAt et devient invocable. À tout moment avant cela, workflow_validate_draft exécute la validation identique en dry run — la même liste agrégée de problèmes, sans figer, sans changement de status.
  5. Invoke — exécuter une version publiée comme une nouvelle mission. Plusieurs versions publiées du même workflow peuvent coexister ; vous choisissez laquelle exécuter.
  6. Runs — observer les missions lancées par workflow, par version et par node.
  7. Archive — retirer une version publiée (terminal ; plus invocable, conservée pour la provenance). Le Delete n'est autorisé que sur les drafts ; les versions publiées sont immuables et doivent être archivées à la place.

1.3 La surface d'outils MCP

Tous les outils résident dans WorkflowMcpTools (noms différés workflow_*), sont protégés par TenantAdminPolicy et appellent les mêmes services que les contrôleurs REST via une portée in-process (Core est le service — pas de saut HTTP). Chaque outil renvoie une enveloppe { success, data } (ou { success:false, error, code }).

Authoring

ToolObjetEntrées clésRetourne
workflow_create_draftCréer une nouvelle version draft à partir d'une charge utile de graphe.graphJson ; optionnels workflowId (omis = nouvelle identité, version 1 ; fourni = version suivante), tunnelsJson{ id, workflowId, version, status }
workflow_update_draftRemplacer le graphe/tunnels d'un draft. Drafts uniquement.id (GUID), graphJson, optionnel tunnelsJson{ id, workflowId, version, status }. Erreurs : NOT_FOUND, CONFLICT (pas un draft)
workflow_materializeInjecter les contrôles de compliance requis (nodes human-gate + regulatory-review) dans un draft depuis le catalogue de contrôles actif, en s'appuyant sur les stades de développement dont les nodes sont taggés. S'exécute avant le publish ; déterministe + idempotent.id{ id, workflowId, version, status }
workflow_waive_controlEnregistrer une dérogation explicite et auditée autorisant le publish malgré un contrôle requis manquant.id, controlId (ex. DORA-P1-PROT-004), reason{ id, workflowId, version, status }
workflow_validate_draftExécuter en dry run la validation complète de publish sur un draft sans le figer — le même validateur composite que celui du publish, chaque problème signalé d'un coup ; le draft conserve son status.idLa liste agrégée de problèmes (vide quand le draft publierait proprement)
workflow_publishValider + figer un draft.id{ id, workflowId, version, status, checksum, publishedAt }. Erreur : VALIDATION_ERROR (liste chaque problème)
workflow_renameDéfinir le nom d'affichage du workflow (partagé par toutes les versions ; hors du checksum, donc renommer un workflow publié est autorisé).workflowId, name{ workflowId, name, updatedVersions }
workflow_archiveArchiver une version publiée (terminal). Gardé : refusé tant qu'un consommateur FlowRef publié et vivant référence encore le workflow — vérifiez workflow_referenced_by d'abord.id{ id, workflowId, version, status }. Erreur : CONFLICT (pas Published)
workflow_delete_draftSupprimer définitivement une version draft.id{ id }. Erreur : CONFLICT (pas Draft). Marqué destructif.

Discovery / lectures

ToolObjetRetourne
workflow_catalogLister la palette de types d'étapes — chaque type de node que vous pouvez placer, avec ses pins d'entrée/sortie typés. À utiliser avant l'authoring pour câbler des pins compatibles. category est la famille de taxonomie de la palette (p. ex. Missions/Research/Validation) ; tags est un axe de sous-familles réservé — vide sur chaque node aujourd'hui ; lifecycle vaut Standard ou Legacy (placer un node Legacy dans un nouveau brouillon est bloqué à la publication).{ catalog: [{ nodeTypeId, kind, siteId, category, tags, lifecycle, inputs:[{name,kind,required,defaultValue,variadic}], outputs:[…] }] }
workflow_listLister toutes les versions d'un workflow, les plus récentes d'abord.{ versions: [{ id, workflowId, version, status, checksum, createdBy, createdAt, publishedAt }] }
workflow_list_allLister la dernière version de chaque workflow du tenant.{ workflows: [{ id, workflowId, name, version, status, … }] }
workflow_getUne version avec sa charge utile de graphe complète. version=0 (ou omis) = dernière publiée.{ id, workflowId, version, status, checksum, graphJson, tunnelsJson, … }
workflow_runsMissions lancées depuis un workflow (ses runs), les plus récentes d'abord ; filtre de version optionnel.{ runs: [{ missionId, title, status, version, createdAt }] }
workflow_run_nodesStatus par étape d'un run : mappe les tasks de la mission vers les ids de node d'authoring (la vue run-debugger).{ nodes: [{ nodeId, taskDefId, status, resultSummary }] }

Composition

ToolObjetRetourne
workflow_tile_catalogLister les workflows publiés disponibles comme tuiles FlowRef intégrables (y compris les compositions sys- seedées), chacune avec la signature de tunnel contre laquelle vous câblez. À utiliser avant de placer une étape FlowRef.La liste de tuiles (identité de workflow, version, nom, pins de tunnel)
workflow_rebind_flowrefRepointer l'étape FlowRef d'un draft vers un autre workflow/version cible (déplacer un consommateur hors d'un ancien sous-workflow). Le rebinding invalide la ratification antérieure des gates — elles doivent être ré-approuvées.L'enveloppe du draft mis à jour
workflow_referenced_byLister les workflows publiés dont les étapes FlowRef référencent un workflow donné — la garde d'archive : à vérifier avant workflow_archive.La liste des consommateurs (vide = archivage sûr)

Execution

workflow_invoke — exécuter une version publiée.

  • Entrées : workflowId, version (doit être ≥ 1 — il n'y a pas de raccourci version=0 ici ; exécuter « la plus récente quelle qu'elle soit » doit être un choix explicite), optionnels objective, model, provider, paramsJson (un objet JSON de valeurs de paramètres de run) et projectId (project cible optionnel dans lequel le coder du run écrit — GUID ou slug ; inconnu → 404, non Active → 409, omis → project par défaut du tenant, consigné dans la provenance comme projectScope).
  • Comportement : compile le graphe en une mission dont les tasks portent le câblage des étapes ; chaque étape gate est mise en pause derrière une décision d'approbation en attente (résolue avec decision_respond : approve libère la gate une fois les étapes antérieures terminées, skip l'annule, fail la fait échouer). model + provider sont requis sauf si les valeurs par défaut du tenant les résolvent — il n'y a pas de repli silencieux de modèle.
  • Retourne : { missionId, workflowId, version, checksum, stepCount, gateCount, controlCount }.
  • Suites : mission_get pour observer le run ; decision_list pour trouver les approbations de gate en attente ; workflow_run_nodes pour le status par étape.

Cloning (sauvegarder un run, ou forker un template)

ToolObjetRetourne
workflow_clone_from_mission« Sauvegarder ce run comme workflow. » Clone une mission dans un nouveau draft (ne publie jamais). Les missions nées d'un workflow sont re-draftées à l'identique depuis leur source ; les autres missions sont reconstruites à partir de leurs lignes de task, en ajoutant un trigger et (uniquement si nécessaire) une seule gate pour que le résultat soit publiable.{ id, workflowId, version, status, source, notes, suggestedInvoke }. source = workflow-born | organic ; notes liste chaque réparation
workflow_clone_from_systemCloner un template de system-workflow de la plateforme dans un nouveau draft éditable détenu par votre tenant.{ id, workflowId, version, status }

Templates de system-workflow

ToolObjetRetourne
workflow_system_listLister les templates de system-workflow en lecture seule de la plateforme (les flux d'orchestration intégrés : cycle de vie de mission, research, onboarding, support, review, …).{ workflows: [{ workflowKey, name, description, category, checksum, isSystemSeed }] }
workflow_system_getUn template avec sa charge utile de graphe complète, pour inspecter le flux avant de cloner.{ workflowKey, name, description, category, checksum, graphJson, tunnelsJson }

Node instructions

Chaque node de workflow peut porter des node instructions — du texte supplémentaire superposé au prompt du node au dispatch. Les instructions vivent dans des slots à portée, avec la précédence run > project > tenant > global (le slot le plus spécifique gagne). Trois outils les gèrent, protégés par la même politique tenant-admin que le reste de la surface :

ToolObjet
workflow_node_instructions_getLire les slots d'instructions d'un node à travers les portées.
workflow_node_instructions_setÉcrire une instruction à une portée. Seuls les slots de source MANUAL sont inscriptibles — un slot détenu par le chemin PromptEngineer renvoie CONFLICT.
workflow_node_instructions_clearRetirer un slot d'instruction. Même règle MANUAL-only.

Aujourd'hui, chaque slot vivant est défini à la main ; le chemin de proposition PromptEngineer (ARDS proposant lui-même des améliorations de prompt) n'est pas actif en v1 — la garde CONFLICT réserve ses slots.

1.4 API REST

Route de base api/v1/workflows (tenant-admin). Reflète la surface MCP pour le composeur du dashboard :

  • GET /workflows — une ligne par identité de workflow (dernière version).
  • GET /workflows/catalog — la palette de types d'étapes.
  • GET /workflows/{workflowId}/versions — toutes les versions, les plus récentes d'abord.
  • GET /workflows/{workflowId}/{version} — une version avec la charge utile complète.
  • POST /workflows — créer un draft (corps : graphJson, optionnels workflowId/tunnelsJson/name).
  • PUT /workflows/{id} — remplacer la charge utile d'un draft.
  • POST /workflows/{workflowId}/rename — définir le nom d'affichage sur toutes les versions.
  • POST /workflows/{id}/materialize · POST /workflows/{id}/waive — overlay de compliance + dérogation.
  • POST /workflows/{id}/publish — valider + figer. Note : une validation menée à terme est toujours un HTTP 200 avec une enveloppe { success, workflow, problems[] } (success:false + une liste de problèmes structurés { nodeId, pin, message, raw } en cas d'échec) — le dashboard mappe tout non-2xx vers « request failed », donc un échec de validation doit rester une enveloppe 200. Les 404/409 restent pour les lignes manquantes/non-draft.
  • POST /workflows/{id}/archive · DELETE /workflows/{id} — archiver (publié) / supprimer (draft).
  • POST /workflows/{workflowId}/{version}/invoke — exécuter.
  • POST /workflows/clone-from-mission — cloner une mission vers un draft.
  • GET/POST /workflows/system[...] — lister/récupérer/cloner les templates système.
  • GET /workflows/{workflowId}/runs et /runs/{missionId}/nodes et GET /workflows/runs (fleet) — observabilité des runs (§2.10).

Le CRUD des triggers réside sous api/v1/workflows/{workflowId}/triggers (§2.9) ; la surface de lecture centrée run dispose aussi de api/v1/workflow-runs/{missionId}/graph-status et GET /workflow-runs (runs en cours).

1.5 Pages du dashboard

Le composeur Blazor (Genesis.UI) fournit :

RoutePageCe qu'elle fait
/workflowsWorkflowsDashboardLister tous les workflows ; créer, ouvrir, cloner.
/workflows/{WorkflowId}/{Version}WorkflowEditorPageL'éditeur de graphe / canvas : placer des étapes depuis le catalogue, câbler des pins, materialize, waive, publish, rename, gérer les triggers (WorkflowTriggersDialog).
/workflows/system/{Key}SystemWorkflowPreviewPagePrévisualiser un template système avant de cloner.
/workflows/runs/{MissionId}WorkflowRunViewerPageVue live du run par node (le run debugger), avec WorkflowGateReviewPanel pour résoudre les gates.
/workflows/activeWorkflowActiveRunsPageListe inter-workflows des runs en cours.

2. Architecture

2.1 Modèle de données

Les workflows persistent à trois endroits. Point crucial : les runs ne sont pas une table de workflow — un run est un Mission + ses lignes TaskDefinition, estampillé avec la provenance du workflow dans Mission.Metadata["workflow"].

workflow_records (tenant DB — WorkflowRecord) : une ligne par (workflowId, version).

ColonneTypeNotes
idguid (PK)
workflow_idvarchar(64)identité logique stable (wf + 10 hex) ; partagée par toutes les versions
versionintmonotone, base 1 ; index unique (workflow_id, version)
namevarchar(200) nullnom d'affichage, logique par workflow id, hors du checksum
statusvarchar(50)Draft / Published / Archived (enum→chaîne) ; indexé
graph_jsonjsonbla charge utile de l'IR du graphe ; le schéma est versionné à l'intérieur (irVersion)
tunnels_jsonjsonb nullla signature de pins du workflow pour la composition FlowRef
checksumvarchar(64)SHA-256 hex en minuscules de graph_json, calculé au publish ; vide tant qu'en Draft
created_by, created_at, published_atcreated_at indexé
compliance_catalog_versionint nullla version du catalogue de contrôles épinglée au publish (provenance)
compliance_waiversjsonb nulldérogations enregistrées [{controlId, reason, grantedBy, grantedAt}]

workflow_triggers (tenant DB — WorkflowTrigger) : liaisons de trigger automatiques, non intégrées au graphe (elles survivent donc au re-versioning). Colonnes : id, workflow_id, version_policy (LatestPublished/Pinned), pinned_version, trigger_type (Schedule/Webhook/Event), config_json (jsonb), webhook_secret (write-only), selected_model, selected_provider (les deux requis — invoke n'a pas de repli), objective, params_json, target_project_id, is_active, last_run_at, next_run_at, colonnes d'audit. Indexé (workflow_id, is_active) et (trigger_type, is_active, next_run_at).

system_workflows (control-plane DB — SystemWorkflowRecord) : templates de plateforme exposés en lecture seule à chaque tenant. Colonnes : id, workflow_key (unique), name, description, category, scope (Tenant/Platform), is_system_seed, status, graph_json, tunnels_json, checksum, horodatages. Réconciliés depuis le catalogue de code au démarrage ; les tenants peuvent cloner mais pas éditer.

Le catalogue de contrôles de compliance (lignes workflow_control_catalog, control-plane + tenant) alimente les vérifications materialize/plancher ; il est documenté sous Compliance.

2.2 L'IR du graphe et le modèle node/pin

WorkflowRecord.GraphJson se parse (via WorkflowIrSerializer) en un record WorkflowIr :

WorkflowIr { int IrVersion, IrNode[] Nodes, WorkflowEdge[] Edges, WorkflowParam[]? Params }
WorkflowEdge { PinRef From, PinRef To }   PinRef { string NodeId, string Pin }

Genres de node (IrNodeKind) — 16 genres :

GenreAuthorable ?Rôle
EventouiLe trigger. Au plus un par graphe ; pas de pins d'entrée ; ses successeurs exec sont les étapes d'entrée. Ne s'abaisse en rien.
CallSiteouiUn LLM call site latent issu du catalogue organisé. Classé écriture (il dispatche des coders).
ReadouiUne lecture système pure, sans effet de bord. Jamais classée écriture, jamais gatée.
ActionouiUne écriture workspace:* exécutée inline (W4). Classée écriture ; s'exécute inline, ne dispatche jamais de coder.
ServiceAdapterouiUne opération de service C# exécutée inline (W5). Classée écriture ; s'exécute inline.
GateouiUne gate d'approbation humaine. S'abaisse sur le trio Paused / AttentionRequest / DecisionResolution. Porte des decisions[] nommées (arms à libellé humain), des fields[] de capture typés exposés sur le pin data response, et un timeout optionnel résolu en fail ou skip.
BranchouiRoutage first-match sur une valeur de sélecteur ; cas ordonnés + un else. Abaissée en étapes Control par cas.
LoopouiRetry-until-acceptance sur un workflow de corps publié (flowId@version). Déroulée en N itérations.
MapFanOutouiFan-out dynamique : à l'exécution, lit un tableau JSON depuis un résultat amont et crée une task de corps par élément (nombre inconnu au moment de la compilation). Config : sourcePin (le tableau amont), itemVar, un corps qui est soit une seule étape du catalogue (bodyTemplate) soit un sous-workflow (flowRef), maxItems (1..1000), et des dépendances entre éléments frères via itemDependsOnPath (les éléments forment un DAG, pas seulement un lot plat).
FlowRefouiUne étape composite référençant un autre workflow par flowId@version ; inlinée à la compilation.
ForEachouiFan-out séquentiel : itère un tableau JSON un élément à la fois, dans l'ordre, avec prise en charge de break (une condition de break satisfaite saute les éléments restants). Retire Loop pour le nouvel authoring ; les graphes Loop existants continuent de tourner.
WaitouiGare le run sur un timer (durationMinutes, 1..40320 — d'une minute à 28 jours) ou sur un event (l'une des six coutures d'événement vivantes — §2.9) ; exactement un des deux modes par node.
LlmouiL'étape LLM générique à tour unique borné : un appel, pas de tools. Son siteId de config doit nommer un site deny-all enregistré (vérifié au publish).
AgentouiL'étape agent générique multi-tour in-process avec un toolset explicite + budget ; le toolset doit être ⊆ de la politique effective du site. Bi-classe : classée lecture ou écriture selon son toolset (tout tool d'écriture ⇒ classée écriture, dominée par une gate). Disponible mais récent — aucun flux intégré ne l'utilise encore.
FinallyouiUne région de nettoyage qui s'exécute exactement une fois, que la région gardée réussisse, échoue ou soit annulée.
ControlnonRoutage synthétisé par le compilateur (cas de branche, continue/exit de boucle, arm de skip de gate, arms de décision, arms d'outcome, materializer/join de map). Son authoring est rejeté.

Un LLM site latent qui déclenche exactement un outcome terminal (onSuccess/onFailure/onRefusal) est un call site asynchrone ; son descripteur expose des pins d'outcome au lieu d'un unique out, et le runtime déclenche exactement un arm (OutcomeRole).

Pins (PinKind) : Exec (séquencement seul, ne transporte jamais de données, ne s'élargit jamais), Context, Document, Artifact, Json (peut porter un raffinement JSON-Schema), String, Number, Bool. Le câblage est légal lorsque les genres correspondent (plus les élargissements scalaire→Json) ; lorsque les deux côtés d'un edge Json déclarent un schéma, la compatibilité de sous-ensemble structurel est imposée au publish. Un PinDescriptor porte Name, Schema (genre + schéma optionnel), Required, une DefaultValue optionnelle réservée à l'éditeur et Variadic (un pin data qui accepte >1 edge entrant — un fan-in N-vers-1 collecté en tableau à l'exécution).

Params (WorkflowParam { Name, JsonSchema?, Required, default? }) : paramètres d'invocation au niveau du graphe. Les valeurs passées à workflow_invoke(paramsJson) sont résolues à la compilation en littéraux des pins liés — il n'y a pas de plomberie de paramètre à l'exécution. Un param lié à un pin requis doit lui-même être requis ou porter une valeur par défaut (vérifié au publish), de sorte qu'un workflow publié ne peut jamais se compiler en une task à laquelle manquerait une entrée requise.

Note de vocabulaire / format de fil : l'IR persiste le track d'un node sous un nom de champ hérité, maintenu stable pour que les checksums publiés restent octet-identiques. Dans toute la prose, l'UI et l'outillage, ce champ est le Track de l'étape (Governance / Research / Planning / Build) ; le compilateur lui donne par défaut la valeur Build.

2.3 Le catalogue d'étapes (palette)

WorkflowNodeCatalog.All est la palette organisée et définie en code de 128 types d'étapes (recensement vivant épinglé). Chaque entrée est un WorkflowNodeDescriptor { NodeTypeId, Kind (LlmSite/ReadAdapter/ActionAdapter/ServiceAdapter), SiteId, Inputs[], Outputs[], Outcomes?, ConfigFields? }. Le descripteur attache des pins typés à côté du registre de call-site existant — il ne modifie jamais LlmCallSite dans Genesis.Contracts ; un site non annoté n'est simplement pas éligible à la palette. Entrées représentatives :

  • LLM call sites : Missions:Decomposition, Coder:Default (sync) et Coder:AsyncDefault (arms d'outcome), ReviewEngine:Default. Chaque LLM site porte aussi deux pins optionnels de ghost-evaluation (ghostEval, ghostEvalModel). Les sites Coder exposent des ConfigFields (Task Type, Track, Coder Variant, id de Preset épinglé) que le panneau Properties affiche et que le compilateur/validateur lisent.
  • Read adapters (purs, jamais gatés) : read:mission_get, read:gitlab_get_file, read:intelligence_knowledge_query, read:loki_query_range, read:task-deps:resolve, read:boundary:validate, read:task-routing:route, workspace:read_file, workspace:list_files.
  • Action adapters (écritures workspace inline, dominées par une gate) : workspace:write_file, workspace:create.
  • Service adapters (opérations C# inline, dominées par une gate) : workspace:merge, service:decomposition:propose, la famille service:research:* (investigate-codebase/docs/web, synthesize, critique, llm), les service:review-engine:*, service:ux-review:*, service:canary:*, service:improvement:*, service:support:*, service:onboarding:*, service:aspect-onboarding:*, service:validation-runs:*, service:batch-processing:*, service:satisfaction:score, service:git-push:default, et les évaluateurs service:compliance:* (SCA, clause-map, complétude ROI, ponctualité d'incident, et les familles CRA / PLD).

Les schémas de result des read adapters proviennent des fichiers DTO d'adaptateur (source unique de vérité). La librairie Genesis.Missions reste libre de la dépendance Contracts — le validateur de publish Genesis.Core recroise chaque SiteId contre le LlmSiteRegistry vivant.

Le recensement par famille de taxonomie (17 familles) : ComplianceChecks 24 · Validation 20 · Missions 11 · Scaffolding 9 · Onboarding 9 · Support 8 · Review 7 · Improvement 7 · Workspace 6 · Research 6 · Incidents 5 · Agents 4 · Git 3 · Knowledge 3 · ComplianceAssess 3 · Communication 2 · Legacy 1. Par genre de descripteur : ServiceAdapter 102, ReadAdapter 18, LlmSite 4, ActionAdapter 2 (workspace:write_file, workspace:create), plus les entrées génériques Llm et Agent (une chacune). Exactement un node est Legacy — service:research:llm, supplanté par le node llm générique et bloqué à la publication dans les nouveaux drafts.

Conventions à l'échelle du catalogue : chaque node suit l'épine Exec (in/out) — la seule exception est Coder:AsyncDefault, l'unique node multi-arm, dont les sorties exec sont ses arms d'outcome ; 122 des 128 émettent une sortie result:Json requise ; les six brain nodes (Missions:Decomposition, Coder:Default, Coder:AsyncDefault, ReviewEngine:Default, llm, agent) sont les seuls à porter le pin context:Context, et les quatre LLM sites câblés parmi eux (Missions:Decomposition, Coder:Default, Coder:AsyncDefault, ReviewEngine:Default) sont ceux où la paire de pins ghost-evaluation (ghostEval/ghostEvalModel) est vivante ; les familles de fan-out suivent une triade prepare → per-item → reduce (la première entrée du node de corps per-item est item:Json, requise) ; et quelques nodes émettent des handles typés à côté de — ou à la place de — result : runId (service:compliance-assess:prepare), incidentId (service:incident:create), leaseId (service:run-target:lease), workspace (workspace:create/workspace:merge).

2.4 Authoring et l'overlay de compliance (materialize)

materialize (ComplianceOverlayService.MaterializeDraftAsync) s'exécute avant le publish pour que l'opérateur examine les contrôles injectés sur le canvas. Il :

  1. Parse l'IR du draft.
  2. Lit le plancher — le catalogue de contrôles actif (IWorkflowControlCatalog.GetActiveAsync) — plus les augmentations de gouvernance additives uniquement (IOverlayGovernanceAugmenter ; par défaut ne lit rien, la couture permet à un project d'ajouter des contrôles, jamais de retirer un contrôle du plancher — la conception à cliquet).
  3. Exécute le ComplianceOverlayMaterializer déterministe, qui injecte les nodes human-gate et regulatory-review/service:compliance:* requis en s'appuyant sur les stades de développement dont les nodes du graphe sont taggés (Design / Implementation / Integration / Verification / PreDeployment), plus les contrôles de base toujours requis. Les nodes injectés portent une provenance d'overlay ({ controlId, catalogVersion }).
  4. Idempotent : si le JSON canonique est inchangé, il renvoie le draft intact (pas de churn de checksum). Sinon il persiste la forme de fil compatible-parse draft→draft.

waive_control ajoute une dérogation auditée { controlId, reason, grantedBy, grantedAt } au draft pour que le plancher de publish considère ce contrôle comme satisfait — sauf si le contrôle est non-waivable, auquel cas la présence d'une dérogation est elle-même une violation de publish.

2.5 Publish et validation

IWorkflowRepository.PublishAsync exécute un unique IWorkflowPublishValidator injecté — un CompositeWorkflowPublishValidator qui exécute chaque validateur enregistré et agrège tous les problèmes (pas de court-circuit), de sorte que les problèmes structurels, de registre et de plancher de compliance surgissent ensemble. Les trois validateurs :

  1. Structurel — WorkflowIrValidator (pur ; pas de DB/registre). Il s'exécute comme le fera le compilateur : expansion FlowRef → Validate(Authored) → expansion contrôle-de-flux → Validate(Final). Il vérifie : ids bien formés (uniques, pas de segments réservés __/#), au plus un Event, modèles de pins par genre, compatibilité genre/schéma des edges, edge-unique-par-pin-data (assoupli pour variadic), connectivité des pins requis, règles de liaison littéral/param, configuration de branche/boucle/fan-out, isolation de reconvergence des arms de branche et d'outcome, acyclicité (avec un chemin de cycle explicite) et — la pierre angulaire — la dominance de gate.
  2. Registre — LlmSiteRegistryWorkflowPublishValidator (Genesis.Core) : ré-exécute la chaîne structurelle sur le graphe expansé FlowRef, recroise le SiteId de chaque call site contre LlmSiteRegistry.GetAll(), valide la signature de tunnel du draft (noms/genres/liaisons), et valide que tout id de LLM preset épinglé à un node est connu et éligible.
  3. Plancher de compliance — ComplianceFloorPublishValidator (Genesis.Core, fail-closed). Un graphe est gouverné ssi un node porte un tag stage ou un node injecté par overlay. Les graphes non gouvernés se publient inchangés. Pour un graphe gouverné : chaque node classé écriture doit porter un stage ; un contrôle requis n'est satisfait que par un node correspondant à son control id et à son genre de node ; un contrôle manquant ne passe qu'avec une dérogation explicite (et seulement si le contrôle est waivable). En cas de succès, il épingle la version de catalogue active sur le record.

Dominance de gate. Le wave executor n'exécute une task que lorsque toutes ses dépendances sont terminées. Ainsi une étape classée écriture (CallSite / Action / ServiceAdapter) est bloquée en exécution par une gate ssi un node Gate est un ancêtre dans le graphe de dépendances de l'union (exec + data). Le validateur impose exactement cela : chaque chemin de dépendance vers une écriture doit traverser une Gate. Les étapes Read ne sont jamais classées écriture ni gatées. La seule exemption est une vérification service:compliance:* injectée par overlay — c'est le mécanisme de protection (délibérément placé avant la gate de stage pour qu'une vérification échouée annule en cascade la gate→push), pas une écriture d'auteur.

L'échec de publish est signalé comme VALIDATION_ERROR en MCP et comme une enveloppe 200 { success:false, problems:[…] } en REST ; le contrôleur parse chaque message de validateur en un { nodeId, pin, message, raw } structuré pour la mise en évidence sur le canvas.

2.6 Invocation : compiler un graphe en une mission

WorkflowInvocationService.InvokeAsync est compile-first pour qu'un graphe qui ne compile plus (ex. un sous-flux référencé a été dépublié) ne laisse jamais une mission orpheline :

  1. Chargement + intégrité. Récupérer (workflowId, version) ; exiger Published ; recalculer le SHA-256 de graph_json et refuser s'il ne correspond pas au checksum stocké.
  2. Compilation (pure). Parser l'IR, construire un FlowResolver (résout les sous-flux FlowRef ; seules les versions Published se résolvent), générer un seed frais par invocation, parser paramsJson, et exécuter WorkflowCompiler.Compile. Le compilateur est pur et déterministe — pas de DB, pas d'horloge, pas de GUID frais ; même graphe + même seed → jeu de tasks octet-identique. Il :
    • Expanse FlowRef, valide (Authored), expanse le contrôle de flux (Branch/Loop/MapFanOut → nodes Control), valide (Final), puis applique les params en littéraux.
    • Assigne des TaskDefIds déterministes (DeterministicIdGenerator.TaskDefId(seed, nodeId)), calcule les vagues topologiques de Kahn (l'ExecutionOrder), et abaisse chaque node en un CompiledTask portant TaskType, Track, CoderVariant, DependsOn, status, tools autorisés, critères d'acceptation, un porteur workflow (le JSON décrivant node id/kind/site/inputBindings/outputSchema/config de contrôle, utilisé au dispatch) et — pour les gates — un CompiledGate.
    • Abaissement par genre : Gate → gate/Paused ; Control → workflow-control/Paused ; Read → analyze/Pending ; Action → workflow-action/Pending ; ServiceAdapter → workflow-service/Pending ; CallSite → code (ou configuré)/Pending. Une garde de défense-en-profondeur lève une erreur contre une ligne non-gate portant les task types réservés gate/workflow-control/workflow-service (qui s'auto-approuveraient ou se mal-routeraient).
  3. Résoudre le project cible optionnel (fail-loud avant la création de la mission ; une cible omise est journalisée et consignée comme projectScope=tenant-default).
  4. Dériver model/provider pour un invoke UI en un clic depuis le preset épinglé d'un node coder (résolu via le même dispatch de site Coder:Default) — additif et appliqué uniquement lorsque model/provider de l'appelant sont vides, de sorte qu'un model/provider fourni par l'appelant est un no-op ici.
  5. Créer la mission avec Metadata.skipDecomposition=true et un blob de provenance workflow (workflowId, version, irChecksum, invocationSeed, paramsJson, targetProjectId, projectScope). Model/provider sont transmis à CreateMissionAsync, qui échoue bruyamment quand les deux sont vides.
  6. Matérialiser les tasks en une seule sauvegarde (le pattern ProposalMaterializer) avec des TaskDefIds pré-définis et un status par task. Les lignes gate et control reçoivent MaxRetries=0 (une gate rejetée doit rester rejetée ; une loop-exit résolue en échec doit rester en échec — l'auto-retry les ressusciterait sinon en « approuvées »).
  7. Mettre en file une demande d'approbation par gate via la file de décisions (IDecisionQueueService), portant les arms de décision / champs typés de la gate dans les metadata de l'AttentionRequest, plus une deadline optionnelle + action de timeout. Si quoi que ce soit échoue en cours de matérialisation, la mission est compensée à Failed (un run à moitié matérialisé avec une gate non-débloquable ne doit jamais se garer silencieusement).

Le résultat est { missionId, workflowId, version, checksum, stepCount, gateCount, controlCount } (les lignes control synthétisées par le compilateur sont exclues de stepCount et exposées séparément).

2.7 Exécution runtime d'un run

Un run s'exécute sur le moteur de mission existant (ArdsMissionOrchestrator) — les workflows n'ajoutent aucun nouveau runtime. Parce que la mission est créée skipDecomposition=true, l'orchestrateur ne la décompose jamais automatiquement ; il dispatche les tasks pré-matérialisées par vague. Pour les missions à provenance workflow, chaque tick de l'orchestrateur exécute une passe de contrôle déterministe (IWorkflowControlService.EvaluateAsync) avant les boucles de dispatch Ready et de promotion Pending→Ready :

  • Elle lit le porteur résolu de chaque task workflow-control et la liste de tasks en mémoire, et s'exécute jusqu'à un point fixe (plafonné par une garde), ne faisant jamais que transitionner les status de tasks déjà matérialisées — elle ne crée jamais de task (sauf le materializer MapFanOut, qui ajoute des tasks de corps de manière idempotente). Les routeurs : RouteBranches, RouteLoopContinues, RouteLoopExits, RouteGateSkipArms, RouteGateDecisionArms, RouteOutcomeArms, RouteMapFanOut, RouteMapJoins, et CascadeSkip (annulation de région morte, marquée workflow-cascade pour la distinguer d'un skip opérateur).
  • Les conditions privilégient une expression booléenne RulesEngine rédigée quand elle est présente, sinon reviennent à une égalité scalaire chemin-pointé-plus-attendu — de sorte que chaque graphe pré-expression se comporte de manière octet-identique.

Exécution des étapes par genre :

  • Gate — née Paused avec une AttentionRequest ouverte. L'opérateur la résout via decision_respond/decision_create : approve libère les dépendants une fois les étapes antérieures terminées (la pré-approbation est autorisée) ; skip annule la région gatée ; fail la fait échouer. Les gates à N arms routent sur le pin de décision (jamais le libellé d'affichage) ; les champs de gate typés sont capturés sur le ResultSummary de la gate et exposés via un pin data response.
  • Read / Action / ServiceAdapter — s'exécutent inline dans le poller de dispatch (ne dispatchent jamais de coder) : l'orchestrateur résout l'IWorkflowReadAdapterExecutor / IWorkflowActionExecutor / IWorkflowServiceAdapterExecutor correspondant, passe les inputBindings résolus du porteur (littéraux, références de sortie amont, et liaisons de motif de nom au moment du dispatch via WorkflowReadInputResolver), et réécrit le résultat. Les lignes Action/ServiceAdapter sont gardées : une ligne atteignant le dispatch sans provenance de porteur du compilateur est mise en échec plutôt qu'exécutée.
  • CallSite — matérialisée en une coding task et dispatchée à un coder comme d'habitude ; le preset épinglé du node du porteur pilote le routage de provider, et un async call site doit émettre une ligne finale {"outcome":…} parsable (le consommateur de résultat échoue+retente un résultat qui n'en déclare aucun).

2.8 Workflows système vs tenant

La plateforme livre 22 templates de system-workflow qui représentent les flux d'orchestration codés en dur sous forme de graphes composables, définis en code (SystemWorkflowCatalog) et réconciliés dans la table control-plane system_workflows au démarrage. La ventilation par catégorie : Mission ×6 (sys-mission-lifecycle, sys-research-mission, sys-decompose-direct, sys-mission-decompose-grounded, sys-decompose-research, sys-task-dispatch), Onboarding ×4 (sys-first-run-onboarding, sys-aspect-onboarding, sys-intelligence-scaffolding, sys-project-onboarding), Ops ×3 (sys-batch-processing, sys-canary, sys-run-acceptance), Compliance ×2 (sys-compliance-assess, sys-incident-pipeline), Review ×2 (sys-review-engine, sys-ux-review), Validation ×2 (sys-llm-preset-validation, sys-validation-runs), Evaluation ×1 (sys-satisfaction-scoring), Improvement ×1 (sys-improvement-cycle), Support ×1 (sys-support-case-triage). Ceux-ci exercent les constructions réelles — ex. sys-mission-lifecycle est un MapFanOut piloté par le moteur (un coder par task proposée, nombre inconnu jusqu'à l'exécution de la décomposition) ; sys-research-mission fait tourner 3 investigateurs parallèles (un diamant) qui convergent dans une boucle de révision bornée synthesize→critique (max 3 passes, rompue dès que le critic approuve).

Les lignes système sont en lecture seule pour les tenants (ISystemWorkflowStore) : les tenants peuvent lister/récupérer et cloner dans un draft détenu par le tenant (workflow_clone_from_system → identité fraîche, graphe à l'identique), puis éditer/publier/invoquer librement. Les templates Scope="Platform" sont masqués des surfaces tenant.

Deux catalogues, deux vocations. SystemWorkflowCatalog contient les 22 templates représentationnels ci-dessus — des clonables en lecture seule : cloner l'un d'eux vous donne le graphe comme draft tenant éditable, pas un branchement dans le flux codé en dur de la plateforme. Séparément, SysWorkflowInventory seede 19 compositions sys- exécutables, publiées par tenant comme de vraies versions de workflow et exposées comme tuiles FlowRef pour l'intégration (ex. sys-research-codebase, sys-decompose-propose) ; la plupart sont seed-only aujourd'hui — les call sites legacy qu'elles reflètent n'ont pas encore été re-pointés dessus. L'énumération complète des templates réside dans la documentation utilisateur : the templates page.

Une capacité connexe, la décomposition-comme-workflow (MaterializeDecompositionWorkflowAsync), matérialise un template système decompose (sys-decompose-direct / sys-decompose-research) directement sur une mission existante et résout automatiquement la simple gate initiale via le chemin de décision vivant — la couture flag-gated qui remplace l'invocateur de phase inline pour les missions opt-in.

2.9 Triggers (invocation automatique)

Un WorkflowTrigger lie un workflow publié (par workflowId stable + une politique de version : LatestPublished ou Pinned) à une source automatique. Chaque source aboutit au même appel — résoudre la version publiée et InvokeAsync — ne différant que par la façon dont « déclencher maintenant » est détecté :

  • Schedule — WorkflowTriggerScheduler (un BackgroundService, boucle d'environ 1 minute avec un délai de démarrage) balaie le registre des tenants et déclenche les lignes dont le NextRunAt est échu, puis avance NextRunAt via le parseur cron partagé. Politique de fenêtre manquée : déclencher une fois et recalculer vers l'avant (jamais rattraper N occurrences) ; NextRunAt est la garde d'idempotence.
  • Webhook — un événement GitLab entrant vérifié contre le webhook_secret par trigger (temps constant, jamais journalisé), filtré par eventType + un filterJson.
  • Event — WorkflowTriggerDispatcher recherche les triggers Event actifs pour un événement de domaine in-process (les six coutures vivantes : mission.created, conversation.completed, support-case.created, incident.created, batch.completed, mr.merged), évalue le matchJson de chaque trigger (filtres projectId / mots-clés) contre le contexte de l'événement, et invoque les correspondances. Un ensemble borné (trigger, eventId) donne une livraison at-most-once par processus ; une garde de boucle saute entièrement le dispatch quand le contexte porte triggerSpawned:true, de sorte qu'une mission engendrée par un trigger ne peut re-déclencher mission.created.

Les triggers sont validés fail-loud à la création (le workflow doit avoir une version publiée ; selectedModel + selectedProvider sont obligatoires car invoke n'a pas de repli ; un cron Schedule doit parser ; un Webhook a besoin d'un secret + eventType). Le déclenchement ne contourne ni n'auto-approuve jamais une gate — la discipline fail-closed est préservée. Les triggers ne sont pas intégrés au graphe, ils survivent donc au re-versioning.

2.10 Observabilité des runs

Parce qu'un run est une mission, l'observabilité est une projection sur ses tasks plutôt qu'un store dédié. WorkflowRunsReader lit la provenance Mission.Metadata["workflow"] et mappe chaque TaskDefinition vers son node id d'authoring via le porteur (partagé par MCP et REST pour qu'ils ne divergent pas). Au-delà de la forme brute workflow_run_nodes, les services de lecture projettent chaque node vers un NodeRunState — les huit valeurs TaskDefinitionStatus plus un AwaitingGate dérivé (une task Paused qui a une AttentionRequest ouverte et non résolue). Les surfaces :

  • GET /workflows/{workflowId}/runs/{missionId}/nodes — états projetés par node, filtrables par state / nodeType / gateLabel.
  • GET /workflows/runs — vue fleet : runs inter-workflows avec un rollup NodeRunState par run, provenant du GetMissionsAsync F003-safe, filtrable par la grammaire RunFilter partagée (template/missionId/fenêtre-temps/providerModel au niveau run ; state/nodeType/gateLabel au niveau node).
  • GET /workflow-runs/{missionId}/graph-status et GET /workflow-runs — la surface FE centrée run (status par node + runs en cours). La forme du graphe elle-même est récupérée via l'API de version existante en utilisant le workflowId + version retournés — elle n'est délibérément pas dupliquée ici.

(Les tasks de corps de fan-out sont incomplètes au grain node dans la lecture par run jusqu'à ce que le porteur de corps porte les champs de provenance ; le rollup fleet compte toutes les lignes de task réelles.)

2.11 Clone-from-mission

WorkflowCloneService.CloneFromMissionAsync transforme un run en draft via deux chemins choisis selon ce que porte la mission :

  • Workflow-born — la mission épingle la provenance workflowId@version et le WorkflowRecord source existe encore : son GraphJson est re-drafté à l'identique sous TargetWorkflowId ?? sourceWorkflowId (une dérive de checksum n'ajoute qu'une note).
  • Organic — pas de provenance utilisable (une mission construite à la main/décomposée, ou une dont le record source a été supprimé) : MissionGraphReconstructor reconstruit un graphe à partir des lignes de task de la mission, en ajoutant un trigger et — seulement si nécessaire — une seule gate d'approbation pour que le résultat soit publiable, signalant chaque réparation dans les notes.

Il ne publie jamais (aboutit toujours à un Draft que l'auteur examine) et expose l'objective + model/provider de la mission comme indices de run seulement — aucun paramètre au niveau des pins n'est extrait.

2.12 Relation avec les missions et les agents

  • Les missions sont le substrat. Un workflow publié se compile de manière déterministe en le jeu de TaskDefinition d'une mission ; le run s'exécute sur le wave executor, la file de décisions et les balayages d'orchestrateur existants. Les workflows ajoutent la couche authoring/versioning/compliance ; les missions ne réinjectent rien dans le graphe.
  • Les gates réutilisent le système de décision. Une gate n'est pas une nouvelle entité — c'est une ligne AttentionRequest + task Paused + mission PendingOperatorDecision, résolue par les mêmes outils decision_* que les agents et les opérateurs utilisent déjà.
  • Les agents sont les exécuteurs. Les étapes CallSite dispatchent des coders/agents exactement comme les tasks de mission décomposées, honorant les LLM presets par node via le même routage de provider ; les étapes read/action/service s'exécutent inline contre les mêmes services. Un workflow est donc une manière gouvernée, réutilisable et gate-enforced d'orchestrer la même fleet d'agents que les missions ad-hoc utilisent — avec la provenance (workflowId@version + checksum) épinglée sur chaque run comme artefact de compliance.

2.13 Mission runs et changements (la couche d'acceptation)

workflow_invoke crée une nouvelle mission (§2.6). Une version publiée peut aussi être exécutée à l'intérieur d'une mission existante comme une unité discrète et acceptable — un MissionRun — via mission_run_start (protégé par le flag Workflows:MissionRuns:StartEnabled ; activé sur les déploiements actuels). Il réutilise le même pipeline compile-first, mais le run naît sur sa propre branche git issue du tip de la mission (strict-sequential : le base SHA du run doit toujours égaler le tip), et chaque task matérialisée est estampillée avec l'id/la branche du run.

mission_run_accept boucle la boucle et constitue l'unité d'acceptation :

  1. Revérifier que la base du run égale toujours le tip de la mission (sinon un StaleRefused — relancer depuis le tip courant).
  2. Exécuter le plancher de validation physique non-waivable contre la branche du run. C'est fail-closed : sans backend d'exécution vivant pour valider, l'accept est refusé (ValidationRefused), jamais passé sans validation.
  3. Merger la branche du run sur le tip de la mission et enregistrer le résultat comme un Changement — une unité réversible portant un ordinal, ses fichiers touchés et ses dépendances envers les changements antérieurs (dérivées par recouvrement de fichiers). Le tip accepté est scellé, de sorte que le tip de la mission reste toujours vert.

mission_changement_stack liste la pile dans l'ordre ordinal. mission_revert_preview + mission_revert annulent un changement ainsi que la fermeture transitive de tout ce qui en dépend, dans l'ordre inverse, chacun comme un merge-revert — fail-closed en cas de conflit (un conflit avorte proprement la cascade et nomme le membre en échec). mission_run_discard abandonne un run non accepté (le marque rejected, supprime sa branche) sans toucher à la pile ; un run de compétition se résout en exactement un gagnant (les frères sont rejetés, leurs branches supprimées). C'est ainsi que la sortie d'un workflow devient examinable, acceptable et réversible sur la mission dans laquelle il s'exécute.

mission_run_start accepte un groupId optionnel. Omettez-le et les runs sont strict-sequential : un second démarrage alors qu'un run est encore ouvert est refusé (RunAlreadyOpen) — acceptez ou écartez d'abord le run ouvert. Passez le même groupId à N démarrages et les runs concourent depuis le même base SHA : exactement un peut être accepté ; les frères sont rejetés et leurs branches supprimées (GroupAlreadyWon pour un accept tardif). mission_run_get retourne le roll-up par node plus les ancres d'acceptation — le changement, le seal de validation physique (SHA du tip + verdict) et isAnchored, vrai ssi le run est Accepted et son changement appliqué et son seal passé.

Les deux vocabulaires de status, verbatim. mission_run_start retourne l'un de 11 statuts : Started · FeatureDisabled · AgenticMissionUnsupported · MissionTerminal · MissionNotPlanned · ChildSpawnerDisallowed · RunAlreadyOpen · TipUnavailable · WorkflowNotFound · NotInvocable · MaterializationFailed. mission_run_accept retourne l'un de 7 : Accepted · StaleRefused · MergeConflict · GroupAlreadyWon · ValidationRefused · NotProposed · TipUnavailable.