Workflows
Créer des définitions de workflows exécutables et les distribuer dans une extension Scrydon aux côtés d'un Process Flow
Il existe trois surfaces « workflow » : la construction interactive de workflows dans l'éditeur (Blocks, Triggers, Execution), la Référence YAML pour le format d'import/export de l'éditeur, et cette page — la création de définitions de workflow distribuées dans une extension. Pour le cycle de vie build/upload de l'extension, voir Extensions & SDK d'authoring.
Un Workflow est un graphe de blocs exécutable qui s'exécute aux côtés des Process Flows — une porte human-in-the-loop, un routage d'approbation, une automatisation multi-étapes. Le SDK d'authoring de Workflows vous permet de distribuer des workflows dans la même archive .scrydon-extension.tar.gz que votre Process Flow, de sorte qu'un seul téléversement installe l'ontologie, tous les workflows dont le flow a besoin, et le flow lui-même en une seule transaction atomique.
Les workflows distribués dans une extension sont du JSON pur — l'archive porte le graphe de blocs / arêtes / sous-blocs, jamais du code. Le runtime de workflow (blocs, exécuteurs, l'orchestrateur) réside dans la plateforme et est invoqué par workflowId quand une action workflow s'exécute.
Quand utiliser ce SDK
Utilisez le SDK d'authoring de Workflows quand :
- Un Process Flow que vous distribuez a une action de
actionType: "workflow"et vous voulez que le workflow réside dans la même extension (de sorte que ré-importer l'extension actualise également le workflow). - Vous créez une extension de démo / de démarrage et ne voulez pas demander aux opérateurs de configurer manuellement les workflows après l'installation.
- Vous voulez l'idempotence au niveau de l'extension — ré-uploader le même
.scrydon-extension.tar.gzmet à jour les lignes de workflow sur place.
Si le workflow existe déjà dans votre tenant (workflow système ou créé manuellement), continuez d'utiliser actionType: "workflow" + workflowId sur le modèle d'action — aucune modification de l'extension n'est nécessaire.
Installation
bun add -d @scrydon/sdk-authoring zodnpm install --save-dev @scrydon/sdk-authoring zodimport {
defineWorkflow,
defineBlock,
defineEdge,
} from '@scrydon/sdk-authoring/workflows'Anatomie d'un workflow
| Élément | Rôle |
|---|---|
| Package | Identité du manifeste du workflow : id, name, version |
| Workflow Entry | Un workflow nommé avec slug, name, version, definition — une extension peut en contenir plusieurs |
| Block | Un nœud dans le graphe exécutable (starter, human_in_the_loop, un bloc d'extension, …). Porte des subBlocks de configuration créée |
| Sub-block | Un seul champ créé sur un bloc (reviewerRole, resolutionMode, …). Le type est ouvert pour que l'éditeur puisse évoluer indépendamment |
| Edge | Une connexion orientée entre des blocs. Peut porter un sourceHandle / targetHandle et des data optionnels |
| Loop / Parallel | Descripteurs de sous-flow optionnels qui regroupent des nœuds pour l'itération ou le fan-out |
| Variable | Une variable de portée workflow affichée dans le popover Variables de l'éditeur |
| Metadata | name, description, exportedAt — champs non fonctionnels conservés par le runtime |
Un exemple complet — porte HITL
Cet exemple reflète la porte HITL démontrée par la PR #1208 : un seul bloc human-in-the-loop qui bloque la transition de Realize → Deploy. Le Process Flow (section suivante) y fait référence par slug.
import { defineWorkflow } from '@scrydon/sdk-authoring/workflows'
export const realizeGate = defineWorkflow({
slug: 'hitl-realize-gate',
name: 'HITL — Realize → Deploy Gate',
description: 'Approval gate before transitioning from Realize to Deploy.',
version: '1.0.0',
definition: {
blocks: {
'block-start': {
id: 'block-start',
type: 'starter',
name: 'Start',
position: { x: 0, y: 0 },
subBlocks: {},
outputs: {},
enabled: true,
},
'block-hitl': {
id: 'block-hitl',
type: 'human_in_the_loop',
name: 'Realize → Deploy Gate',
position: { x: 240, y: 0 },
subBlocks: {
reviewerRole: {
id: 'reviewerRole',
type: 'short-input',
value: 'admin',
},
resolutionMode: {
id: 'resolutionMode',
type: 'dropdown',
value: 'first_claim',
},
},
outputs: {
approved: 'boolean',
notes: 'string',
},
enabled: true,
},
},
edges: [
{ id: 'edge-start-hitl', source: 'block-start', target: 'block-hitl' },
],
loops: {},
parallels: {},
},
})defineWorkflow, defineBlock et defineEdge sont des fonctions d'identité à l'exécution — ils affinent les types pour que votre IDE détecte un champ manquant ou une faute de frappe avant que le build ne s'exécute.
Le contrat de rebind de slug
Au sein d'une extension, les workflows et les Process Flows sont découplés par un slug local à l'extension. Le Process Flow référence un workflow par workflowSlug ; l'importateur de l'extension matérialise le workflow dans le tenant, récupère un vrai identifiant de workflow et rebind le modèle d'action :
// In the Process Flow:
defineAction({
name: 'Realize → Deploy gate',
actionType: 'workflow',
isRequired: true,
workflowSlug: 'hitl-realize-gate', // ← references the workflow by extension-local slug
})L'importateur parcourt le manifeste, cherche hitl-realize-gate dans la correspondance slug → id résolue produite par la phase d'installation du workflow, et mute le modèle d'action sur place : workflowId est défini à l'identifiant matérialisé et workflowSlug est effacé. À partir de ce moment, l'action se comporte comme toute autre action workflow.
workflowId et workflowSlug sont mutuellement exclusifs sur un modèle d'action — le schéma rejette les manifestes qui déclarent les deux. Utilisez workflowId quand vous pointez vers un workflow pré-existant dans le tenant ; utilisez workflowSlug quand le workflow est distribué dans la même extension.
Un slug que le modèle de Process Flow déclare en ligne dans sa propre liste workflows[] n'est pas re-lié : l'action conserve son workflowSlug, et chaque exécution matérialise sa propre copie de ce workflow. Une déclaration en ligne a priorité sur un sous-répertoire workflow-<slug>/ portant le même slug. Les deux chemins d'import (Paramètres → Extensions et POST /api/extensions/import) acceptent les mêmes archives.
Si le slug ne se résout pas (le modèle ne le déclare pas en ligne, l'extension n'a pas distribué un sous-répertoire workflow-<slug>/ pour lui, ou le manifeste à l'intérieur l'a nommé différemment), l'installation échoue immédiatement avec UNKNOWN_WORKFLOW_SLUG. De même, deux sous-répertoires de workflow revendiquant le même slug échouent avec DUPLICATE_WORKFLOW_SLUG.
Packaging dans une extension
Une extension peut contenir une ontologie, un Process Flow, et zéro ou plusieurs workflows. Chaque workflow réside dans son propre sous-répertoire workflow-<slug>/ avec un manifest.json produit depuis defineWorkflow. Le fichier extension.json de niveau supérieur liste chaque workflow comme une entrée contents[] séparée :
import { defineExtension } from '@scrydon/sdk-authoring/extensions'
export default defineExtension({
manifestVersion: 1,
package: {
id: 'sap-activate-brabant',
name: 'SAP Activate (Brabant)',
version: '1.0.0',
},
contents: [
{ kind: 'ontology', path: 'ontology', version: '1.0.0', required: true },
{ kind: 'workflow', path: 'workflow-hitl-gate', version: '1.0.0', required: true },
{ kind: 'process-flow', path: 'process-flow', version: '1.0.0', required: true },
],
installOrder: ['ontology', 'workflow', 'process-flow'],
metadata: { isSystemExtension: false, isDemoExtension: true, tags: ['sap-activate'] },
})Quelques règles appliquées par le bundler :
ontologyetprocess-flowsont des singletons — chacun ne peut apparaître qu'une fois au maximum.workflowpeut se répéter.contents[].pathdoit être unique pour toutes les entrées.installOrderest une permutation des types distincts danscontents[]— une seule entrée"workflow"couvre tous les sous-répertoires de workflow ; ils s'exécutent tous dans la phase workflow, avantprocess-flowpour que la correspondance de slugs soit peuplée quand le rebind du process-flow s'exécute.
Structure de l'extension
<extension>.scrydon-extension.tar.gz
├── extension.json # ExtensionArchiveManifestSchema (lists every subdir)
├── ontology/
│ └── manifest.json # OntologyManifestSchema — may be empty
├── workflow-hitl-gate/
│ └── manifest.json # WorkflowManifestSchema (one per workflow)
├── workflow-cutover-gate/
│ └── manifest.json # (optional — extensions may ship many workflows)
└── process-flow/
├── manifest.json # ProcessFlowManifestSchema
└── assets/ # optional — JSON / image assetsL'inspecteur de l'extension valide le manifeste de chaque sous-répertoire contre son propre schéma Zod. Les manifestes de workflow font l'aller-retour comme du JSON autonome — vous pouvez en valider un de façon isolée avec bunx @scrydon/sdk-authoring extension validate contre un manifest.json de workflow.
Règles de validation des blocs
WorkflowDefinitionSchema applique deux invariants structurels au-delà des types par champ :
| Règle | Ce qu'elle détecte |
|---|---|
blocks[key].id doit être égal à la clé de l'enregistrement | Un identifiant mal orthographié qui briserait silencieusement les références d'arête |
Chaque source / target d'arête doit référencer un identifiant de bloc existant | Une arête pendante provenant d'un bloc supprimé |
Le type de bloc est intentionnellement une chaîne ouverte — le catalogue de blocs runtime (starter, human_in_the_loop, chaque bloc d'extension, …) évolue indépendamment du SDK et le schéma ne se verrouille pas sur un instantané. Pareil pour le type de sous-bloc : la valeur est préservée verbatim et exposée à l'éditeur.
Les références entre blocs sont vérifiées
Ces deux règles couvrent le graphe d'arêtes — la circulation du contrôle.
L'autre moitié, ce sont les références <nomDuBloc.champ> dans les valeurs des
sous-blocs — et dans forEachItems, whileCondition et doWhileCondition
d'une boucle —, par lesquelles circulent les données. Elles sont vérifiées
dans chaque workflow livré par votre extension : les workflows d'un manifeste
workflow et les template.workflows inline d'un process flow. Chaque
commande qui produit une extension livrable les résout toutes :
extension validate, extension build, build-dist et build-source.
Les erreurs font échouer la commande et sont signalées toutes à la fois :
un workflow comportant plusieurs mauvaises références se corrige en une seule
exécution, pas en une par référence. Les avertissements s'affichent sur stderr
et ne modifient jamais le code de sortie.
Une référence désigne un bloc par son name, en minuscules et sans
espaces — jamais par son id :
defineBlock({ id: 'block-intake-webhook', name: 'Report', /* … */ })
// reads as <report.body.subject> ✅ the name, normalized
// NOT <block-intake-webhook.body.subject> ❌ the id never resolvesLes noms ne sont pas limités à l'ASCII : un bloc nommé Résumé se lit
<résumé.content> et est vérifié comme les autres. Un segment de champ peut
contenir un tiret — <api.headers.content-type> lit la clé content-type. Un
élément de tableau se lit par sa position, écrite [0] ou .0 :
<api.data.users[0].name> et <api.data.users.0.name> sont la même référence,
et les deux sont vérifiées. Seul un nombre va entre crochets — <api.data[key]>
n'est pas une référence.
| Constat | Gravité | Signification |
|---|---|---|
Unknown reference root \x`` | erreur | Aucun bloc du workflow ne s'appelle x. Le plus souvent une faute de frappe, ou une référence restée après le renommage d'un bloc. |
Unknown workflow variable \v`` | erreur | Une référence <variable.v>, alors que le workflow ne déclare aucune variable v et qu'aucun bloc variables n'en assigne. |
Block \X` declares no output `f`` | avertissement | La racine se résout, mais le champ ne figure pas dans les outputs déclarés du bloc. Signalé uniquement si le bloc déclare des sorties, car la forme de sortie d'un bloc peut être dynamique. |
… reads loop context, but this block is not inside any loop | avertissement | Une référence <loop.…> depuis un bloc qui n'est dans aucun corps de loops. Un corps de parallels ne compte pas — voir la ligne Parallel. |
… the Dapr interpreter does not resolve the \variable` root` | avertissement | La variable est déclarée, l'écriture est donc correcte — mais voir la note ci-dessous. |
… reads from block \X`, which is disabled` | avertissement | La racine se résout, mais ce bloc est désactivé. Un bloc désactivé ne s'exécute jamais, ne produit donc aucune sortie, et le texte littéral est utilisé. Activez-le ou retirez la référence. |
| Toute erreur dans un bloc désactivé | avertissement | Un bloc désactivé est ignoré avant la résolution de ses entrées : une mauvaise référence qu'il contient ne peut pas casser une exécution. Elle est signalée comme avertissement et devient une erreur dès que le bloc est activé. |
Toute erreur dans un champ de boucle que son loopType n'utilise pas | avertissement | Seuls forEachItems (pour forEach), whileCondition (pour while) et doWhileCondition (pour doWhile) relèvent des erreurs. Une boucle for n'en utilise aucun. Les autres sont des restes d'un changement de type de boucle et ne sont jamais lus. |
parallels.<id>.nodes declares a Parallel container | avertissement | Le conteneur Parallel a été supprimé : son corps n'est exécuté ni par branche ni par élément, et <loop.…> ne s'y résout pas. Utilisez une boucle, ou des branches indépendantes. |
Les trois racines runtime sont circonscrites, pas acceptées en bloc
start, loop et variable ne désignent aucun bloc : elles sont donc résolues
d'après ce que le runtime fournit réellement, et non d'après blocks :
| Racine | Quand elle se résout |
|---|---|
start | Toujours. La charge utile du déclencheur est injectée avant l'exécution du premier bloc. |
loop | Uniquement dans un corps de loops. En dehors — y compris dans un corps de parallels —, le texte littéral est utilisé. |
variable | Uniquement dans un bloc de code aujourd'hui — voir ci-dessous. |
<variable.name> ne se résout pas actuellement en dehors d'un bloc de
code. La référence est substituée littéralement dans tous les autres
sous-blocs : une variable déclarée lue depuis, par exemple, le champ content
d'un bloc Teams envoie le texte <variable.name>. Lisez plutôt la sortie du
bloc qui l'assigne — un bloc variables nommé Set Vars qui assigne
greeting se lit <setvars.greeting>. extension validate le signale comme
un avertissement plutôt qu'une erreur, car l'écriture est correcte ; la lacune
se situe dans le runtime des workflows.
Une référence non résolue n'est pas une erreur à l'exécution — le texte
littéral est utilisé comme valeur. Un bloc Teams dont le content lit
<announcemnt.content> publie la chaîne <announcemnt.content> dans le
canal. C'est pourquoi il s'agit d'une erreur à l'écriture plutôt que d'un
problème découvert en production.
Deux formes ne sont délibérément pas vérifiées, car ce sont du texte ordinaire
que le runtime laisse lui aussi intact : une référence sans segment .champ
(<announcement> — impossible à distinguer de la balise HTML <div>), et tout
ce qui commence par une espace, est un nombre ou contient un opérateur, @, :
ou / (a < b && c > d, <0.5>, <someone@example.com>). Un nom peut
commencer par des chiffres pourvu qu'une lettre les suive directement : un bloc
nommé 2FA check, lu <2facheck.result>, est vérifié comme les autres, tandis
que <0.5> et <1.2.3> restent du texte.
Note de migration — nouvelles erreurs de build
Une extension qui compilait sans erreur avant cette vérification peut désormais
échouer à extension build / extension validate avec
workflow_reference_unresolvable. Chacune de ces références produisait déjà une
valeur fausse à l'exécution — le texte littéral de la référence —, le build
signale donc un défaut existant, il n'en introduit pas. Pour la même raison, la
vérification est livrée dans une version mineure de @scrydon/sdk-authoring,
et non majeure ; épinglez la version précédente si un build doit rester vert le
temps de corriger les références signalées.
| Ce que vous avez écrit | Pourquoi cela échoue désormais | Correction |
|---|---|---|
Une racine mal orthographiée ou obsolète — <announcemnt.content>, ou une référence restée après le renommage d'un bloc | Aucun bloc ne porte ce nom | Corrigez le nom, ou faites pointer la référence vers le bloc renommé |
Une référence par id de bloc — <block-intake-webhook.body> | Les références se résolvent par le name du bloc, jamais par son id | Utilisez le nom normalisé — name: 'Report' → <report.body> |
<parallel.currentItem> (ou tout <parallel.…>) | Le conteneur parallèle a été supprimé ; le runtime n'a pas de racine parallel | Utilisez une boucle et <loop.currentItem> / <loop.index> dans son corps |
Un <variable.x> non déclaré | Le workflow ne déclare aucune variable x et aucun bloc variables ne l'assigne | Déclarez-la, ou lisez la sortie du bloc qui l'assigne (<setvars.x>) |
Les mêmes vérifications couvrent désormais les forEachItems / conditions d'une
boucle et les template.workflows inline d'un process flow : une extension dont
les sous-répertoires workflow étaient déjà propres peut donc y trouver de
nouvelles erreurs.
Les extensions déjà publiées continuent de s'installer. L'import d'une
archive (l'aperçu de téléversement dans Settings → Platform → Extensions →
Sources → Upload an extension, l'API /api/extensions/import et
extension inspect) exécute les mêmes vérifications mais signale chaque constat
comme un avertissement — une racine non résolue garde le code
workflow_reference_unresolvable, les constats consultatifs utilisent
workflow_reference_advisory — et ne bloque jamais l'installation. Reconstruisez
l'extension pour les voir comme des erreurs et les corriger.
Portes Human-in-the-Loop
Chaque bloc human_in_the_loop est inspecté au build de l'extension, à son
import, et au déploiement du workflow — depuis le canevas, Monitor, une
automatisation, une promotion, ou POST /api/v1/workflows/{id}/deploy. Trois
constats bloquent, trois avertissent. Chaque constat bloquant bloque au build, à
l'import et au déploiement : une extension construite avant ces
contrôles est refusée tant que sa porte n'est pas corrigée et l'extension
reconstruite. Chaque avertissement est renvoyé dans hitlWarnings au
déploiement et affiché à côté du message de réussite. Un retour arrière vers une version
antérieure signale ses constats comme avertissements au lieu de refuser, car
cette version s'est déjà exécutée.
| Constat | Ce qu'il signifie | |
|---|---|---|
hitl_reserved_field_name | erreur | Un champ du formulaire de reprise porte un nom qui appartient à la sortie du bloc. Le runtime l'écrase, donc la valeur du relecteur est perdue — et un champ nommé approved enregistre l'inverse d'un « Non ». Renommez-le (recommendation, severity, vote). |
hitl_single_signoff_multi_reviewer | avertissement | Deux approbateurs nommés ou plus, mais une seule approbation libère la porte. La première réponse décide et les autres sont informés que la revue est close. |
hitl_quorum_exceeds_approvers | avertissement | Plus d'approbations requises que d'approbateurs nommés. Le runtime plafonne au lieu de se bloquer, donc la porte se libère sous le nombre que vous avez défini. |
hitl_decision_options_invalid | erreur | Une option de décision déclarée est inutilisable : pas de valeur, une valeur contenant autre chose que des lettres, chiffres, _ et -, une issue autre que approve / reject / other, une valeur en double, ou une seule option valide. |
hitl_decision_options_no_approve | avertissement | Des options de décision sont déclarées mais aucune n'a l'issue approve, donc la porte ne peut jamais approuver. Correct pour une porte de tri ; sinon, marquez l'option qui laisse l'exécution continuer. |
hitl_reviewer_not_org_role | erreur | Le reviewerRole de la porte n'est pas un rôle d'organisation (admin, member). Une persona de processus personnalisée n'est pas acceptée, même si le flux de l'extension la déclare : le workflow en cours d'exécution peut assigner des personnes à une persona, il choisirait donc son propre approbateur. Pour nommer des personnes précises, choisissez des approbateurs spécifiques. L'interpréteur de la porte refuse aussi un tel rôle à l'exécution. |
Les noms réservés sont approved, decision, reason, verdict,
responses, aggregate, submission, response, url et option. Vous
n'avez pas besoin d'un champ pour la décision elle-même — approuver ou
rejeter est la décision, lisible en aval via <gate.verdict> (et
<gate.approved>). Le formulaire sert à ce que l'enveloppe ne porte pas déjà.
Les deux avertissements sur le nombre d'approbateurs ne se déclenchent qu'en mode Personnes spécifiques, où la taille du panel est connue à la conception. Un rôle ou un persona se résout à l'exécution, donc rien ici ne peut savoir combien de personnes il atteint.
Options de décision
Une porte qui a besoin de plus qu'approuver / rejeter déclare son bulletin dans
le sous-bloc decisionOptions. Chaque option correspond à une classe d'issue —
approve compte pour les approbations requises, reject est un refus, other
n'est ni l'un ni l'autre — et il n'y a pas de pondération. Les lignes du tableau
de l'éditeur comme les objets simples sont acceptés :
decisionOptions: {
id: 'decisionOptions',
type: 'table',
value: [
{ value: 'go', label: 'Go', outcome: 'approve' },
{ value: 'no_go', label: 'No go', outcome: 'reject' },
{ value: 'defer', label: 'Reporter', outcome: 'other' },
],
},Les blocs en aval lisent <gate.verdict> (approve · reject · undecided),
<gate.aggregate.byOption.go> (un décompte par option, 0 compris) et — sur
une porte à approbation unique — <gate.option>. L'API de reprise et A2A
n'acceptent qu'une valeur déclarée, envoyée comme option ; toute autre est
refusée en HTTP 400 et n'est pas enregistrée. Voir Human in the Loop → Options
de décision pour la façon dont
chaque règle libère la porte.
Blocs IA et le champ model
Les blocs IA de base (agent, router, evaluator, translate) ont un champ model requis, mais les workflows fournis par une extension doivent le laisser vide. Coder un modèle en dur lierait votre extension à un fournisseur que l'organisation qui l'installe n'a peut-être pas activé. Lorsque le sous-bloc model est vide au moment de l'exécution, la plateforme résout l'extension LLM par défaut de l'organisation (celle configurée dans Settings → Agentic → Default LLM Extension) et utilise son modèle. Si l'organisation n'a pas de LLM par défaut configuré, l'exécution échoue avec <nom du bloc> is missing required fields: Model — configurer une extension LLM par défaut résout le problème.
Version du manifeste
Les manifestes de workflow portent manifestVersion: 1. L'extracteur au moment du build l'injecte de sorte que les auteurs n'ont pas à le faire.
Le schéma rejette :
| Erreur | Cause |
|---|---|
Slug must be lowercase alphanumeric with hyphens | Un slug de workflow a échoué /^[a-z0-9-]+$/ |
Must be a valid semver string | La version du workflow n'est pas semver |
workflows[] must not contain duplicate slugs | Deux entrées dans un manifeste partagent un slug |
blocks[key].id must equal the record key | L'id d'un bloc n'est pas d'accord avec la clé de l'enregistrement |
edges[].source / edges[].target must reference an existing block id | Une arête pendante après la suppression d'un bloc |
Construire, inspecter, téléverser
Créez chaque workflow avec defineWorkflow. Composez l'extension avec defineExtension et ajoutez une entrée contents[] par workflow.
bunx @scrydon/sdk-authoring extension build src/extension.ts --outDir dist
# → dist/<package.id>-<package.version>.scrydon-extension.tar.gzbunx @scrydon/sdk-authoring extension inspect dist/sap-activate-brabant-1.0.0.scrydon-extension.tar.gzL'inspecteur liste chaque sous-répertoire, valide chaque manifeste, et expose la correspondance de slugs que l'importateur va résoudre.
Connectez-vous en tant qu'administrateur de l'organisation → ouvrez Paramètres → Plateforme → Extensions → Sources dans l'application plateforme → cliquez sur Téléverser une extension (ponctuel) → déposez votre .scrydon-extension.tar.gz. Comme l'extension contient du contenu workflow, la boîte de dialogue affiche un sélecteur d'environnement d'espace de travail — choisissez l'environnement où les définitions de workflow doivent s'installer. Le contenu ontologie et process-flow s'installe au niveau organisation quel que soit le sélecteur.
Voir l'ADR 2026-05-21 Unified extension upload surface pour le raisonnement de conception.
La route agentic sous-jacente accepte également les téléversements directement. Une extension qui contient des workflows nécessite workspaceEnvironmentId sur l'appel — les workflows sont délimités à un environnement d'espace de travail, pas à l'organisation. Passez-le comme paramètre de requête :
curl -X POST "$AGENTIC_URL/api/extensions/import?organizationId=$ORG_ID&workspaceEnvironmentId=$ENV_ID" \
-H "Cookie: $SESSION_COOKIE" \
-F "file=@dist/sap-activate-brabant-1.0.0.scrydon-extension.tar.gz"La route retourne { ontology, workflows: { idsBySlug }, processFlow }. La correspondance idsBySlug montre chaque slug local à l'extension rebindé à son identifiant de workflow matérialisé, utile pour l'audit / la vérification après coup.
Idempotence
L'identifiant de ligne du workflow matérialisé est dérivé comme extension:<packageId>:<slug>:<workspaceEnvironmentId>. Re-uploader la même extension dans le même environnement d'espace de travail trouve la ligne existante et (quand replacePublished: true est passé) la supprime + la réinsère — workflow_blocks, workflow_edges et workflow_subflows en cascade. Des identifiants d'environnement différents produisent des lignes différentes, de sorte que la même extension peut être installée côte à côte dans des environnements dev / staging / prod.
workflow.variables est stocké comme Record<string, Variable> indexé par identifiant de variable. Le matérialiseur attache le workflowId résolu à chaque variable à l'insertion ; les entrées créées l'omettent.
Où aller ensuite
Référencez les workflows depuis les appels defineAction de votre Process Flow via workflowSlug (local à l'extension) ou workflowId (pré-existant).
Distribuez l'ontologie avec les workflows dans la même extension — l'importateur exécute d'abord l'ontologie, puis les workflows, puis le process-flow.
Extension vs Extension, la boucle build / upload, et les quatre surfaces de sous-chemins.