Sémantique d'exécution, contraintes et sécurité
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 d'exécution
- Les
statementss'exécutent séquentiellement de haut en bas. Lorsque l'exécution atteint unReturn, elle se termine à ce point. - L'exécution se déroule en ligne, sur le chemin qui traite la requête d'appel. La réponse de l'appel est le résultat de l'exécution (pour la forme de la réponse, voir Requête et réponse dans l'aperçu de Script), et le temps alloué à une exécution est traité plus bas dans Budget de temps.
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. - Les effets externes (
Http) sont irréversibles (un appel déjà parti, ainsi que sa facturation, ne peuvent pas être annulés). - Il arrive que la compensation ne s'exécute pas du tout, laissant subsister un état non compensé.
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 ResourceFind 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
Httpsupérieur ou égal à 400 (4xx·5xx ; pas un échec lorsqueignoreStatusCode: 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 avecTry/catch/finally. - Un
Returnn'est pas une erreur, mais une sortie anticipée normale. Il n'est pas une cible decatch(il n'existe pas de concept de throw utilisateur). - Dans un
catch, on référence{ message }via/error. Le statement où l'échec s'est produit n'y figure pas.
L'agrégation côté serveur se limite au décompte
Le décompte, c'est ResourceCount qui l'effectue côté serveur. Comme il ne va pas lire les éléments, il n'est pas soumis au plafond du nombre d'éléments traités.
Pour sum et group-by, il n'existe pas d'opération serveur dédiée. Une telle agrégation doit se calculer soi-même en parcourant avec ResourceForEach et en utilisant SetVar et JsonLogic ; on est donc lié au plafond du nombre d'éléments traités (inadapté pour agréger des millions d'enregistrements). Si seul le décompte est nécessaire, on ne parcourt pas : on utilise ResourceCount.
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, et il n'attend ni ne sonde en interne la fin d'un job externe.
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). Quelle violation entraîne quel code de refus est traité dans Erreurs.
| Contrainte | Valeur |
|---|---|
Maximum d'appels externes par définition (Http, EmailSend) | Selon le plan (voir Tarifs) |
Maximum d'éléments traités par ResourceForEach (en l'absence de limit déclaré, parcourt jusqu'à cette valeur ; échec s'il reste des correspondances à ce plafond) | 10 000 |
Maximum de SetVar par définition (imbriqués inclus) | 10 |
Maximum de Cache par définition (imbriqués inclus, toutes opérations confondues) | 5. Au-delà, enregistrement refusé |
Un Cache dans le bloc d'un Loop ou d'un ResourceForEach | Enregistrement refusé |
Cache.key | Littéral uniquement, 128 caractères au maximum. Une expression de valeur fait refuser l'enregistrement |
Cache.ttl | Entre 1 et 30 secondes ; en son absence, 5 secondes. Hors de cet intervalle, enregistrement refusé |
| Maximum total de statements par définition (imbriqués inclus) | Selon le plan (voir Tarifs) |
Plafond de Http.retry | 2 |
Longueur de Regex.pattern | 128 caractères |
| Statement qui modifie un ServiceUser | Enregistrement refusé. Seuls les trois statements de lecture acceptent cette ressource |
Si anonymousCallEnabled vaut true, le createdBy: ":self" de where | Enregistrement refusé |
Les limites fixes du tableau ci-dessus sont des valeurs fixées par la plateforme : elles sont identiques quel que soit le plan. En revanche, le nombre total de statements et le nombre d'appels externes par définition sont des limites propres au plan. Comme ces deux-là ne sont pas des erreurs de validation mais des limites de plan, un dépassement fait refuser l'enregistrement ou la modification pour dépassement de la limite du plan (une même définition est admise sur un plan supérieur) et se débloque par une montée en gamme de plan. Les valeurs par plan figurent dans Tarifs.
L'ingestion de fichier Media, contrairement aux appels externes comme
HttpetEmailSend, ne compte pas dans le plafond d'appels externes par définition.
ResourceForEachétant un statement composite qui possède des enfants, il ne compte pas lui-même dans le nombre d'appels externes. Ce sont les statements d'appel externe (Http,EmailSend) à l'intérieur deonEachqui sont comptés (comptés pour 1 statiquement, mais réellement exécutés à chaque élément lors du parcours).onEachpeut contenir des appels externes ou l'ingestion de fichier Media, tout comme le body d'unLoop. Le nombre de tours qu'une itération effectue réellement n'entre pas dans ce comptage : il entre en revanche, sous forme de multiplication, dans le Budget de temps ci-dessous.
Limites de longueur des valeurs (à l'exécution)
Les statements de signature et de traitement de texte, ainsi que Cache, ont une limite sur la taille des valeurs qu'ils manipulent. Il s'agit de la longueur de la valeur résolue de l'expression, et non de la longueur de l'expression (les seize caractères de { /rawPayload } peuvent désigner plusieurs dizaines de KB) : la vérification a donc lieu pendant l'exécution et non à l'enregistrement.
| Cible | Limite | En cas de dépassement |
|---|---|---|
Le value de Signature | 65 536 caractères | Ce statement échoue (status 422) |
Le value de Hash | 128 caractères | Ce statement échoue (status 422) |
Le value de Regex | 10 240 caractères (10KiB) | Ce statement échoue (status 400) |
Le value de Cache | 10 240 octets (10KiB) | Ce statement échoue (status 422) |
- Tous quatre se comportent comme les autres échecs d'exécution : ils se traitent localement avec
Try/catch. - La limite de
Signatureest calée sur la taille des corps que les fournisseurs envoient réellement (un événement de paiement fait quelques KB, un webhook de commande atteint plusieurs dizaines de KB). Celle deHashest bien plus étroite, car c'est l'emplacement où l'on concatène quelques champs. - Les 128 caractères de
Regex.patternrelèvent de la vérification à l'enregistrement des contraintes statiques ci-dessus. Cette longueur n'est pas un dispositif contre l'emballement ((a+)+$est dangereux avec six caractères seulement). Ce qui empêche l'emballement, c'est la règle qui n'autorise le motif qu'en littéral et le budget de temps ci-dessous ; la longueur ne promet rien de plus qu'une taille qu'un humain peut lire et relire.
Budget de temps (à l'exécution)
Le temps alloué à une exécution est fixé par une seule formule : min(30 secondes + la somme des temps déclarés par les statements, 180 secondes).
- Le budget se calcule sur le Script lui-même. Au budget de base ne s'ajoutent que les temps que la définition déclare. Le seul temps déclaré est le
timeoutMsdeHttpet deEmailSend.Httpréemployant son propretimeoutMsà chaque nouvelle tentative, il compte pourtimeoutMs × (1 + retry);EmailSendne réessayant pas, il compte pour une seule fois. En l'absence detimeoutMs, c'est la valeur par défaut qui est comptée (30 secondes pourHttp, 10 secondes pourEmailSend). - Les opérations sans temps déclaré sortent du budget de base de 30 secondes. Les lectures et écritures de ressource, l'ingestion de fichier Media et ce qu'une itération effectue en son sein en relèvent. Le budget de base n'est donc pas une valeur de forme, mais une part réelle.
- La façon de les additionner suit la structure des statements. Les statements placés en séquence s'additionnent ; pour
If, on retient la plus grande des deux branches et, pourParallel, la plus grande des branches.Loopmultiplie son body par le nombre d'itérations (maxIterations, ou 10 000 à défaut de déclaration) etResourceForEachmultiplie sononEachpar le nombre d'éléments traités (limit, ou 10 000 à défaut de déclaration). - Une itération sans appel externe a un temps déclaré de 0. Le budget de base de 30 secondes constitue donc la limite effective, et c'est aussi là que se heurte réellement un Script qui contient une itération.
- Le plafond de 180 secondes ne bloque pas l'enregistrement : il tronque. Même si le résultat du calcul dépasse le plafond, ce Script est enregistré et exécuté, et il s'interrompt là où il atteint 180 secondes.
Limites de quantité par plan
Le nombre de Script par Organization est limité selon le plan.
| Plan | Nombre de Script |
|---|---|
| Free | 10 |
| Basic | 30 |
| Pro | 100 |
| Enterprise | Illimité |
Indépendamment de cela, le nombre de statements et le nombre d'appels externes (Http, EmailSend) qu'une définition de Script peut contenir sont eux aussi limités selon le plan. Lors de l'enregistrement ou de la modification d'une définition, un dépassement de la limite de ce plan est refusé ; pour les valeurs précises, consultez Tarifs.
Lorsque la limite est atteinte, la création d'un nouveau Script est refusée.
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).
Le secret de Signature ne bénéficie pas de ce traitement à l'intérieur du Space. Il est stocké sans chiffrement, tel qu'il est écrit dans la définition : sa valeur est donc visible pour les rôles capables de lire ce Script. Un membre (ServiceUser) ne peut pas lire la définition d'un Script (la consultation et la rédaction sont réservées à CMA, et ACMA n'a pas d'API Script). Pour un Script qui porte une clé de vérification, il est plus sûr de restreindre les rôles capables de le lire.
Il en va autrement lorsque la définition sort du Space. Lorsque ce Script est empaqueté dans un App Bundle, le secret de Signature est masqué et ne sort jamais du Space d'origine. Là où Http.headers masque les éléments portant le drapeau secret ainsi que l'en-tête Authorization, le secret de Signature est masqué sans condition, le champ étant lui-même la clé de signature. Un Signature imbriqué dans un If, un Loop ou un Try est masqué de la même manière.
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. LecreatedBy/updatedByde toute ressource créée ou modifiée est l'appelant, et une portéecreatedBy: ":self"se résout elle aussi par rapport à l'appelant. L'appel anonyme fait exception. Une exécution arrivée par/execute/anonymousn'a pas d'appelant : les deux se résolvent donc par rapport à l'auteur (voir Appel anonyme). - Il y a deux limites d'autorisation, et à l'exécution, le moteur ne revérifie pas les permissions de ressource à chaque statement.
- 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é. Autrement dit, un Script contenant une opération non autorisée n'est jamais enregistré au départ. Tout statement qui désigne une ressource subit cette vérification, qu'il s'agisse d'un leaf ou d'un
ResourceForEachqui possède un bloc. Une définition déjà enregistrée est revérifiée lors de sa modification : après une révocation de permission, il n'est donc plus possible de la corriger et de l'enregistrer.- L'annuaire des membres (ServiceUser) est vérifié sur l'axe des paramètres et non sur les mappages de permissions. Pour écrire
resource: "ServiceUser"dans les trois statements de lecture, lesettingsdu SpaceRole de l'auteur doit contenirSETTING_SERVICE_LOGIN(ouSETTING_ALL) (voir le settings de SpaceRole). C'est que l'annuaire des membres est, sur tous les autres chemins également, une ressource que gouvernent les paramètres du Space. - Aucun rôle ne permet d'enregistrer un statement qui modifie un membre. Comme il n'existe absolument aucun moyen, dans un Script, de créer, modifier ou supprimer un membre, le refus n'est pas un manque de permission (
403) mais un statement mal écrit (400). Ce n'est donc pas une lacune qu'on pourrait combler en ajoutant une permission.
- L'annuaire des membres (ServiceUser) est vérifié sur l'axe des paramètres et non sur les mappages de permissions. Pour écrire
- Au moment de l'appel (
/execute) : on ne vérifie que la permission Execute de Script de l'appelant. Sans elle, le résultat est403. 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. Le chemin d'appel anonyme n'a pas cette vérification. C'est qu'il n'y a pas d'appelant à vérifier : ouvrir ce chemin équivaut donc à publier un Script sans authentification.
- 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é. Autrement dit, un Script contenant une opération non autorisée n'est jamais enregistré au départ. Tout statement qui désigne une ressource subit cette vérification, qu'il s'agisse d'un leaf ou d'un
- Blocage de l'appel direct (
directCallEnabled) : si ledirectCallEnabledd'un Script vautfalse, l'appel direct à/executelui-même est refusé. Cette barrière s'applique une fois la vérification de la permission Execute franchie, si bien que le blocage a lieu même lorsque l'on possède la permission Execute. Un appelant qui n'a pas cette permission reçoit un403avant d'atteindre cette barrière. Cette barrière n'existant que sur cet endpoint, l'action de liaison (script) d'un Webhook et un Scheduler l'exécutent tel quel. La valeur par défaut esttrue(appel direct autorisé). - Appel anonyme (
anonymousCallEnabled) : la valeur par défaut estfalse. En la passant àtrue, ce Script et lui seul s'exécute aussi par un chemin dédié sans authentification (/execute/anonymous), et l'identité d'exécution est alors non pas l'appelant mais l'auteur. Des deux limites ci-dessus, la vérification au moment de l'appel (la permission Execute) est absente de ce chemin : l'authentification effective est donc assurée par le Script lui-même (la vérification de signature de la requête reçue). Les conditions pour l'activer et les règles d'enregistrement sont traitées dans Appel anonyme. - 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). Ce filtre est inutilisable dans un Script qui autorise l'appel anonyme. Faute d'appelant, il se résout sur l'auteur, si bien que le sens même de portée de propriété ne tient plus. - 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.
- Si l'appel anonyme (
anonymousCallEnabled) est activé,wherene contient pascreatedBy: ":self"et le statement qui vérifie la requête reçue (Signature, etc.) est placé tout au début. - Le nombre d'appels externes (
Http,EmailSend) et le nombre total de statements restent dans la limite du plan, il y a au maximum 10SetVaret au maximum 5Cache. - Si l'on a utilisé
Cache, lekeya été écrit en littéral et le statement n'a été placé ni dans unLoopni dans unResourceForEach. - Si l'on parcourt un grand ensemble avec
ResourceForEach, on a déclaré unlimitou vérifié que la taille permet d'aller au bout. - Si l'on a placé une itération (
Loop,ResourceForEach), on a vérifié qu'elle entre sous forme de multiplication dans le Budget de temps (sans appel externe, la limite est le budget de base de 30 secondes). - Les valeurs secret ont été placées uniquement via
secret:truedansHttp.headers(Signature.secretn'étant pas stocké chiffré, on a vérifié quels rôles peuvent lire ce Script). - Le message utilisé pour la vérification de signature a été pris sur
{ /rawPayload }et non sur/payload. - S'il y a un statement qui lit un membre (ServiceUser), l'auteur possède
SETTING_SERVICE_LOGINet aucun statement ne modifie cette ressource. - 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
versiondeResourceUpdateouResourcePatch. - Pour renvoyer un résultat,
Return.valuea été spécifié.
Erreurs
Ce sont les codes qui surviennent lorsque l'enregistrement est refusé parce que la forme de la définition enfreint une contrainte statique. Les codes qui sanctionnent une infraction aux règles des expressions de valeur figurent dans Erreurs des Expressions de valeur, et les codes qui surviennent lors de l'appel ou de la suppression dans Erreurs de Ressource Script et endpoints. Pour les codes communs à toutes les ressources, consultez Erreurs communes.
| Code | Condition |
|---|---|
WGL400066 | Une définition contient plus de cinq statements Cache. |
WGL400068 | Un statement Cache a été placé dans le bloc d'un Loop ou d'un ResourceForEach. |
WGL400067 | Le key d'un statement Cache porte une référence { /pointer } au lieu d'un littéral. |
WGL400065 | Le ttl d'un statement Cache sort de l'intervalle autorisé. |
WGL400063 | Un statement Cache porte un champ qui ne correspond pas à son action (un ttl sur Get, un defaultValue sur Set). |
WGL400060 | Le resource d'un statement d'écriture (ResourceCreate, ResourceUpdate, ResourcePatch, ResourceDelete, ainsi que les statements de publication et d'archivage) porte "ServiceUser". |
WGL400061 | Dans un Script qui autorise l'appel anonyme (anonymousCallEnabled), le where d'un statement de lecture porte createdBy: ":self". |
WGL400023 | Une définition contient plus de dix statements SetVar. |
WGL400026 | Le retry d'un statement Http dépasse la limite supérieure de 2. |
WGL400036 | Un ResourceForEach dépasse la limite supérieure du nombre d'éléments traitables. |
WGL429005 | Le nombre total de statements d'une définition dépasse la limite du plan. |
WGL429006 | Le nombre d'appels externes (Http, EmailSend) d'une définition dépasse la limite du plan. |
WGL403015 | L'auteur ne possède pas les permissions de ressource et d'action qu'utilisent les statements de la définition. Même lorsque la permission existe, l'enregistrement est refusé si l'autorisation porte un filtre contentType, createdBy ou tag. L'autorisation doit être inconditionnelle. La seule exception est le Create de Content : dans ce cas, une autorisation limitée à une portée contentType est également acceptée, et cette portée est comparée au contentType indiqué dans le statement (le Create de Media ne bénéficie pas de cette exception). Ce code survient aussi lorsque le resource d'un statement de lecture porte "ServiceUser" alors que l'auteur n'a pas SETTING_SERVICE_LOGIN. |
Documents connexes
- Expressions de valeur : règles de valeurs et de conditions.
- Catalogue des statements : les champs et résultats de chaque statement.
- Cookbook : une collection d'exemples complets.
- Ressource Script et endpoints : la structure de la ressource
Scriptet les endpoints HTTP comme/execute. - Aperçu de Script : la structure de niveau supérieur et le temps alloué à une exécution.
