Sémantique d'exécution, contraintes et sécurité

Dernière mise à jour : 21 juillet 2026

Cette page résume comment un Script se comporte à l'exécution (ordre, transactions, erreurs, verrouillage), à quelles contraintes statiques il est soumis à l'enregistrement et son modèle de sécurité. Pour la syntaxe, consultez le Catalogue des statements et les Expressions de valeur ; pour les combinaisons concrètes, consultez le Cookbook.

Ordre et mode d'exécution

  • Les statements s'exécutent séquentiellement de haut en bas. Lorsque l'exécution atteint un Return, elle se termine à ce point.
  • Sync s'exécute sur le chemin qui traite la requête ; Async s'exécute en arrière-plan. Ce n'est qu'une distinction de l'endroit où a lieu l'exécution ; dans les deux cas, le résultat est la valeur de Return (pour la forme de la réponse de l'appel, voir Requête et réponse dans l'aperçu de Script et Modes d'exécution).
  • De la capacité (capability) au mode : si l'arbre des statements contient l'un de ExternalIo (un appel externe Http), MediaIngest (ingestion de fichier Media ; { source, encoding } sous fields.file ; commun à url et base64) ou LongRunning (un Loop volumineux, etc.), alors executionMode est forcé à Async. Ces trois-là sont des capacités distinctes et, dans les Contraintes statiques plus bas, elles comptent pour des limites différentes.

Sémantique d'exécution

Guard (conditions préalables)

Il n'existe pas de statement guard dédié. On l'exprime avec If et then:[Return]. En cas de violation de la condition, il renvoie un résultat et n'exécute pas les statements suivants (un Script sans guard est bien entendu également possible).

{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
  "then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "statusCode": 402 } ] }

Sans transaction et compensation best-effort

Un Script n'est pas une transaction. En cas d'échec, le moteur tente de compenser (compensation) le travail effectué jusque-là et renvoie la cause de l'erreur, mais avec les limites suivantes (assumées par conception).

  • Annuler une suppression crée un nouveau sys.id, de sorte que les références qui pointaient dessus se brisent (annuler une création est facile ; une modification nécessite une before-image).
  • Les effets externes (Http) sont irréversibles (un appel déjà parti, ainsi que sa facturation, ne peuvent pas être annulés).
  • En cas de plantage du processus, un état non compensé (un orphan) peut subsister.

Si vous avez besoin d'une véritable atomicité, écrivez la compensation dans le Script vous-même ou placez les opérations irréversibles (les appels externes, etc.) tout à la fin. L'ordre le plus dangereux est celui qui « s'enchaîne jusqu'au bout mais ne peut pas être annulé, tout en paraissant sûr ».

Verrouillage optimiste

La contention d'update/patch se maîtrise avec le version de ResourceUpdate et ResourcePatch. Si l'on fournit version (une expression de valeur, Int), la mise à jour n'a lieu que si elle correspond au sys.version actuel de la cible ; une non-correspondance avorte avec une erreur de conflit de version (gérable localement avec Try/catch). Si on l'omet, c'est du last-write-wins sans vérification. En général, on lit d'abord avec ResourceRead ou ResourcePageRead et on transmet ce sys.version (voir CAS de verrouillage optimiste dans le Cookbook).

Écritures sur origin

Les écritures s'appliquent toujours sur l'origin (draft), et l'exposition en delivery (CDA/ACDA) se contrôle avec publish (le publish de ResourceCreate/ResourceUpdate/ResourcePatch, ou ResourcePublish/ResourceUnpublish).

Qu'est-ce qui constitue un échec

  • Un vrai échec est une erreur d'exécution de statement : un statut final Http supérieur ou égal à 400 (4xx·5xx ; pas un échec lorsque ignoreStatusCode: true) ou un dépassement de délai, un corps de réponse dépassant 10MiB, ou une opération de ressource en échec (cible inexistante, conflit de version, opération non prise en charge, etc.). Face à de tels échecs, le moteur avorte et compense, et on peut les gérer localement avec Try/catch/finally.
  • Un Return n'est pas une erreur, mais une sortie anticipée normale. Il n'est pas une cible de catch (il n'existe pas de concept de throw utilisateur).
  • Dans un catch, on référence { message, statement } via /error.

Pas d'agrégation côté serveur

Il n'existe pas d'opérations serveur dédiées à count, sum ou group-by. Le calcul se fait en parcourant avec ResourcePageRead et en utilisant SetVar/JsonLogic ; on est donc lié à la taille du fetch et à maxIterations (inadapté pour agréger des millions d'enregistrements).

Pas d'attente ni de délai

Un Script n'a pas de statement Delay. Un Script s'exécute une seule fois puis se termine, que ce soit sur le chemin de la requête (Sync) ou en arrière-plan (Async), et il n'attend ni ne sonde en interne la fin d'un job externe (le résultat Async est distinct : l'appelant sonde avec le requestId reçu dans le 202 pour obtenir la valeur de Return).

Contraintes statiques (validées à l'enregistrement)

Les points suivants sont vérifiés au moment où l'on enregistre un Script (création/modification). En cas de violation, l'enregistrement est refusé (échec au moment de l'enregistrement, pas à l'exécution).

ContrainteValeur par défaut
S'il y a des I/O externes, executionMode doit être AsyncSans objet
Dans le body d'un Loop, les appels externes Http et l'ingestion de fichier Media sont interditsSans objet
Maximum d'appels externes Http par définition3 (maxExternalIo)
Maximum de SetVar par définition (imbriqués inclus)5 (maxSetVar)
Maximum total de statements par définition (imbriqués inclus)15 (maxStatements)
Plafond de Http.retry2 (maxHttpRetry)

Ces limites peuvent être ajustées via la configuration du serveur (weegloo.core.script.*) ; les valeurs ci-dessus sont les valeurs par défaut.

L'ingestion de fichier Media relève de la capacité MediaIngest et, contrairement à un appel externe Http (ExternalIo), ne compte pas dans le plafond de maxExternalIo (3). En revanche, l'obligation d'Async et l'interdiction dans le body d'un Loop s'appliquent exactement comme pour Http.

Budget de temps (à l'exécution)

ModeBudget par défaut
Sync10 secondes (syncTimeoutMs)
Async60 secondes (asyncTimeoutMs)

Limites de quantité par plan

Script est une ressource Billable, et son nombre par Organization est limité selon le plan.

PlanNombre de Script
Free3
Basic10
Pro50
EnterpriseIllimité

Lorsque la limite est atteinte, la création d'un nouveau Script est refusée (même voie que pour les autres ressources Billable).

Modèle de sécurité

En-têtes secret

Un élément de Http.headers avec secret:true est réservé à CMA (l'administrateur) : il n'est pas exposé à l'utilisateur final (ServiceUser) et n'est déchiffré qu'au tout dernier moment avant la transmission. Placez ici les secrets comme une clé d'API LLM (même lorsqu'il est empaqueté dans un App Bundle, la valeur secret est masquée et ne sort jamais du Space d'origine).

Identité d'exécution et autorisation

  • Identité d'exécution : pendant l'exécution, toute opération de ressource est effectuée sous l'identité de l'utilisateur qui a appelé /execute. Le createdBy/updatedBy de toute ressource créée ou modifiée est l'appelant, et une portée createdBy: ":self" se résout elle aussi par rapport à l'appelant.
  • Il y a deux limites d'autorisation, et à l'exécution, le moteur ne revérifie pas les permissions de ressource à chaque statement.
    1. Au moment de la rédaction (enregistrement) : lors de l'enregistrement d'un Script, on vérifie si l'auteur possède réellement les permissions de ressource et d'action qu'utilisent ses statements. S'il en manque ne serait-ce qu'une, l'enregistrement est refusé (WGL403015). Autrement dit, un Script contenant une opération non autorisée n'est jamais enregistré au départ.
    2. Au moment de l'appel (/execute) : on ne vérifie que la permission Execute de Script de l'appelant. Sans elle, le résultat est 403. Une fois qu'elle est validée, les permissions de ressource par statement ne sont pas revérifiées à l'exécution ; l'exécution se poursuit. C'est le même principe que le droit d'exécuter une fonction en programmation. Si l'on a le droit d'exécuter la fonction, on ne redemande pas la permission de chaque opération individuelle qu'elle contient.
  • Portée de propriété : dans un filtre where, createdBy: ":self" signifie « uniquement ce qu'a créé l'appelant actuel » (par exemple, ne consulter que son propre portefeuille).
  • Permissions déléguées (prudence à la rédaction) : en combinant les deux limites ci-dessus, exécuter un Script revient à agir avec les permissions de l'auteur déléguées. L'appelant n'a besoin que de Execute, et les statements à l'intérieur du Script s'exécutent tels quels dans le périmètre pour lequel l'auteur a été autorisé au moment de l'enregistrement. Par conséquent, une opération de ressource que l'appelant ne pourrait pas effectuer lui-même peut tout de même se produire à travers le Script. Comme les permissions accordées à l'auteur constituent la portée effective de ce Script, il convient de définir avec soin les opérations que l'on place dans un Script.

Liste de contrôle récapitulative

Avant d'enregistrer, vérifiez les points suivants.

  • S'il y a un appel externe (Http) ou une ingestion de fichier Media, executionMode vaut "Async".
  • Aucun appel externe n'a été placé dans le body d'un Loop.
  • Il y a au maximum 3 appels externes, 5 SetVar et 15 statements au total.
  • Les valeurs secret ont été placées uniquement via secret:true dans Http.headers.
  • Les opérations irréversibles (les appels externes) ont été placées le plus tard possible.
  • Si la contention d'update/patch est une préoccupation, on utilise le version de ResourceUpdate ou ResourcePatch.
  • Pour renvoyer un résultat, Return.value a été spécifié.