Semántica de ejecución, restricciones y seguridad
Esta página resume cómo se comporta un Script en tiempo de ejecución (orden, transacciones, errores, bloqueo), a qué restricciones estáticas está sujeto al guardarse y su modelo de seguridad. Para la sintaxis, consulte el Catálogo de statements y las Expresiones de valor; para combinaciones prácticas, consulte el Cookbook.
Orden de ejecución
- Los
statementsse ejecutan secuencialmente de arriba abajo. Cuando la ejecución alcanza unReturn, termina en ese punto. - La ejecución ocurre en línea, en la ruta que atiende la petición de la llamada. La respuesta de la llamada es el resultado de la ejecución (para la forma de la respuesta, consulte Petición y respuesta en la descripción general de Script), y el tiempo que recibe una ejecución se trata más abajo en Presupuesto de tiempo.
Semántica de ejecución
Guard (condiciones previas)
No existe un statement guard dedicado. Se expresa con If y then:[Return]. Cuando se incumple la condición, devuelve un resultado y no ejecuta los statements siguientes (por supuesto, también es posible un Script sin guard).
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "statusCode": 402 } ] }Sin transacciones y compensación best-effort
Un Script no es una transacción. Ante un fallo, el motor intenta compensar el trabajo realizado hasta ese momento y devuelve la causa del error, pero con las siguientes limitaciones (asumidas por diseño).
- Deshacer un borrado crea un nuevo
sys.id, por lo que las referencias que apuntaban a él se rompen. - Los efectos externos (
Http) son irreversibles (una llamada que ya ha salido, y su cobro, no se pueden deshacer). - Si la compensación no se ejecuta en absoluto, puede quedar un estado no compensado.
Si necesita atomicidad real, escriba la compensación en el Script usted mismo o coloque las operaciones irreversibles (como las llamadas externas) al final. El orden más peligroso es el que «encadena hasta el final pero no puede revertirse, y aun así parece seguro».
Bloqueo optimista
La contención de update/patch se acota con version en ResourceUpdate y ResourcePatch. Si se proporciona version (una expresión de valor, Int), la actualización se realiza solo cuando coincide con el sys.version actual del destino; una discrepancia aborta con un error de conflicto de versión (que se puede manejar localmente con Try/catch). Si se omite, es last-write-wins sin comprobación. Normalmente se lee primero con ResourceRead o ResourceFind y se pasa ese sys.version (véase CAS de bloqueo optimista en el Cookbook).
Escrituras en origin
Las escrituras siempre se aplican en origin (draft), y la exposición en delivery (CDA/ACDA) se controla con publish (el publish de ResourceCreate/ResourceUpdate/ResourcePatch, o ResourcePublish/ResourceUnpublish).
Qué se considera un fallo
- Un fallo real es un error de tiempo de ejecución de un statement: un status final de
Httpde 400 o superior (4xx·5xx; no es un fallo cuandoignoreStatusCode: true) o un tiempo de espera agotado, un cuerpo de respuesta que supera los 10MiB, o una operación de recurso fallida (destino inexistente, conflicto de versión, operación no admitida, etc.). Ante tales fallos, el motor aborta y compensa, y se pueden manejar localmente conTry/catch/finally. - Un
Returnno es un error, sino una salida anticipada normal. No es un objetivo decatch(no existe el concepto de throw de usuario). - Dentro de un
catch, se referencia{ message }mediante/error. No se incluye en qué statement se produjo el fallo.
En el servidor solo se agrega el recuento
El recuento lo hace ResourceCount en el servidor. Como no trae los elementos, no queda sujeto al máximo de elementos procesados.
Para sum y group-by no hay operaciones de servidor dedicadas. Esas agregaciones hay que calcularlas a mano recorriendo con ResourceForEach y usando SetVar y JsonLogic, por lo que quedan limitadas por el máximo de elementos procesados (no es adecuado para agregar millones de registros). Si solo hace falta el recuento, no se recorre: se usa ResourceCount.
Sin espera ni retardo
Un Script no tiene el statement Delay. Un Script se ejecuta una sola vez y termina, y no espera ni sondea internamente a que termine un job externo.
Restricciones estáticas (validadas al guardar)
Lo siguiente se comprueba cuando se guarda un Script (al crear/actualizar). Si algo se incumple, se rechaza el guardado (falla en el momento de la autoría, no en tiempo de ejecución). Con qué código se rechaza cada infracción se trata en Errores.
| Restricción | Valor |
|---|---|
Máximo de llamadas externas (Http·EmailSend) por definición | Según el plan (véase Planes de precios) |
Máximo total de elementos procesados por ResourceForEach (si no hay limit declarado, recorre hasta este valor; si llega a él quedando coincidencias, falla) | 10.000 |
Máximo de SetVar por definición (anidados incluidos) | 10 |
Máximo de Cache por definición (anidados incluidos, sumando con independencia de la operación) | 5. Si se supera, guardado rechazado |
Un Cache dentro del bloque de un Loop o de un ResourceForEach | Guardado rechazado |
Cache.key | Solo literal, 128 caracteres como máximo. Si es una expresión de valor, guardado rechazado |
Cache.ttl | Entre 1 y 30 segundos; si se omite, 5 segundos. Fuera de ese rango, guardado rechazado |
| Máximo total de statements por definición (anidados incluidos) | Según el plan (véase Planes de precios) |
Límite de Http.retry | 2 |
Longitud de Regex.pattern | 128 caracteres |
| Statements que modifican un ServiceUser | Guardado rechazado. Solo los tres statements de lectura aceptan este recurso |
Si anonymousCallEnabled es true, el createdBy: ":self" de where | Guardado rechazado |
Los límites fijos de la tabla anterior son valores que fija la plataforma, así que son los mismos con independencia del plan. En cambio, el número total de statements y el número de llamadas externas por definición son límites por plan. Estos dos no son errores de validación sino límites de plan, así que, al superarlos, el guardado o la modificación se rechazan por superación del límite del plan (la misma definición sí se permite en un plan superior) y se desbloquean actualizando el plan. Los valores por plan están en Planes de precios.
La ingesta de archivo de Media, a diferencia de las llamadas externas como
Http·EmailSend, no cuenta para el límite de llamadas externas por definición.
ResourceForEaches un statement compuesto que posee hijos, así que él mismo no cuenta en el número de llamadas externas. Son los statements de llamada externa dentro deonEach(Http·EmailSend) los que cuentan (de forma estática cuentan como 1, pero durante el recorrido se ejecutan realmente en cada elemento). EnonEachse pueden colocar llamadas externas o ingesta de archivos de Media, y lo mismo vale para el body deLoop. Cuántas veces gira realmente la iteración no entra en este recuento: entra como una multiplicación en el Presupuesto de tiempo de más abajo.
Límites de longitud de los valores (en tiempo de ejecución)
Los statements de firma y de tratamiento de texto, y también Cache, tienen un límite superior en el tamaño de los valores que manejan. No es la longitud de la expresión, sino la longitud del valor resuelto de esa expresión (los dieciséis caracteres de { /rawPayload } pueden apuntar a decenas de KB), y por eso se comprueba durante la ejecución y no en el momento de guardar.
- Los cuatro son como cualquier otro fallo en tiempo de ejecución, así que se pueden tratar de forma local con
Try/catch. - El límite de
Signatureestá ajustado al tamaño de cuerpo que envían los proveedores reales (un evento de pago llega a unos pocos KB, y un webhook de pedido a decenas de KB). El deHashes mucho más estrecho porque es el lugar donde se concatenan unos cuantos campos. - Los 128 caracteres de
Regex.patternson la comprobación al guardar que figura en Restricciones estáticas más arriba. Esa longitud no es un mecanismo para evitar una explosión de cómputo ((a+)+$es peligroso con solo seis caracteres). Lo que evita la explosión es la regla de escribir el patrón únicamente como literal y el presupuesto de tiempo de más abajo; lo único que promete la longitud es un tamaño que una persona pueda leer y revisar.
Presupuesto de tiempo (en tiempo de ejecución)
El tiempo que recibe una ejecución se decide con una sola fórmula: min(30 segundos + la suma de los tiempos que declaran los statements, 180 segundos).
- El presupuesto se calcula a partir de ese Script. Al presupuesto base se le suma únicamente el tiempo que la definición ha declarado. El único tiempo declarado es el
timeoutMsdeHttpy deEmailSend.Httpvuelve a emplear su propiotimeoutMsen cada reintento, así que se cuenta comotimeoutMs × (1 + retry);EmailSendno reintenta, así que se cuenta una sola vez. Si no se escribetimeoutMs, se cuenta con el valor predeterminado (30 segundos enHttp, 10 segundos enEmailSend). - Las operaciones sin tiempo declarado salen del presupuesto base de 30 segundos. Aquí entran la lectura y la escritura de recursos, la ingesta de archivos de Media y lo que una iteración hace en su interior. Por eso el presupuesto base no es una cifra formal, sino una parte real.
- La manera de sumarlos sigue la estructura de los statements. Los statements colocados en secuencia se suman; de un
Ifse toma la mayor de las dos ramas y de unParallel, la mayor de todas sus ramas. En unLoop, el body se multiplica por el número de iteraciones (maxIterations; 10.000 si no hay declaración), y en unResourceForEach, elonEachse multiplica por el número de elementos procesados (limit; 10.000 si no hay declaración). - Una iteración sin llamadas externas tiene un tiempo declarado de 0. Por eso el presupuesto base de 30 segundos pasa a ser el límite real, y ese es también el punto donde se atasca de verdad un Script que contiene una iteración.
- El límite superior de 180 segundos no impide el guardado: recorta. Aunque el resultado del cálculo supere el límite, ese Script se guarda y se ejecuta, y al alcanzar los 180 segundos se interrumpe ahí.
Límites de cantidad por plan
El número de Script por Organization está limitado por plan.
| Plan | Número de Script |
|---|---|
| Free | 10 |
| Basic | 30 |
| Pro | 100 |
| Enterprise | Ilimitado |
Con independencia de esto, el número de statements y el número de llamadas externas (Http·EmailSend) que una definición de Script puede contener también están limitados por plan. Al guardar o modificar una definición, si se supera el límite de ese plan, se rechaza; para los valores concretos, consulte Planes de precios.
Cuando se alcanza el límite, se rechaza la creación de un nuevo Script.
Modelo de seguridad
Cabeceras secret
Un elemento de Http.headers con secret:true es exclusivo de CMA (administrador): no se expone al usuario final (ServiceUser) y solo se descifra justo antes de la transmisión. Coloque aquí secretos como una clave de API de LLM (incluso al empaquetarse en un App Bundle, el valor secret se enmascara y nunca sale del Space de origen).
Dentro del Space, el secret de Signature no recibe este trato. No se cifra y se almacena tal cual se escribió en la definición, así que los roles que pueden leer ese Script ven el valor. Un miembro (ServiceUser) no puede leer la definición de un Script (la consulta y la autoría son exclusivas de CMA, y en ACMA no hay API de Script). En un Script que aloja una clave de verificación, es más seguro mantener acotados los roles que pueden leerlo.
Al salir del Space la cosa cambia. Cuando ese Script se empaqueta en un App Bundle, el secret de Signature se enmascara y no sale del Space de origen. En Http.headers se ocultan los elementos con el flag secret y la cabecera Authorization, mientras que el secret de Signature se oculta sin condiciones porque el campo en sí es la clave de firma. Un Signature anidado dentro de un If, un Loop o un Try también se oculta.
Identidad de ejecución y autorización
- Identidad de ejecución: durante la ejecución, toda operación de recurso se realiza bajo la identidad del usuario que llamó a
/execute. ElcreatedBy/updatedByde cualquier recurso que se cree o actualice es el llamante, y un ámbitocreatedBy: ":self"también se resuelve respecto al llamante. La excepción es la llamada anónima. Una ejecución que entra por/execute/anonymousno tiene llamante, así que ambas cosas se resuelven respecto al autor (Llamada anónima). - Hay dos límites de autorización y en tiempo de ejecución el motor no vuelve a comprobar los permisos de recurso en cada statement.
- En el momento de la autoría (guardado): al guardar un Script, se comprueba si el autor posee realmente los permisos de recurso y de acción que usan sus statements. Si falta aunque sea uno, se rechaza el guardado. Es decir, un Script que contiene una operación no autorizada no se guarda en primer lugar. Los statements que eligen un recurso pasan todos por esta comprobación, tanto los hoja como el
ResourceForEach, que posee un bloque. Una definición ya guardada se vuelve a comprobar al modificarla, así que, una vez retirados los permisos, esa definición no se puede corregir y guardar.- El directorio de miembros (ServiceUser) no se comprueba con el mapa de permisos, sino por el eje de la configuración. Para usar
resource: "ServiceUser"en los tres statements de lectura, elsettingsdel SpaceRole del autor debe tenerSETTING_SERVICE_LOGIN(oSETTING_ALL) (el settings de SpaceRole). Es porque el directorio de miembros, en todas las demás vías, es un recurso que gobierna la configuración del Space. - Los statements que modifican miembros no se guardan con ningún rol. Como en un Script no existe ninguna vía para crear, modificar ni eliminar miembros, el rechazo no es por falta de permisos (
403), sino por un statement mal escrito (400). Es decir, no es un hueco que se pueda cerrar añadiendo permisos.
- El directorio de miembros (ServiceUser) no se comprueba con el mapa de permisos, sino por el eje de la configuración. Para usar
- En el momento de la llamada (
/execute): solo se comprueba el permiso Execute de Script del llamante. Sin él, el resultado es403. Una vez que pasa, los permisos de recurso por statement no se vuelven a comprobar en tiempo de ejecución; la ejecución continúa. Funciona como el permiso de ejecución de una función en programación. Si se tiene permiso para ejecutar la función, no se vuelve a preguntar por el permiso de cada operación individual dentro de ella. La ruta de llamada anónima no tiene esta comprobación. Es porque no hay ningún llamante que comprobar, y por eso abrir esa ruta equivale a publicar un Script sin autenticación.
- En el momento de la autoría (guardado): al guardar un Script, se comprueba si el autor posee realmente los permisos de recurso y de acción que usan sus statements. Si falta aunque sea uno, se rechaza el guardado. Es decir, un Script que contiene una operación no autorizada no se guarda en primer lugar. Los statements que eligen un recurso pasan todos por esta comprobación, tanto los hoja como el
- Bloqueo de la invocación directa (
directCallEnabled): si eldirectCallEnableddel Script esfalse, la propia invocación directa de/executese rechaza. Esta puerta se aplica después de pasar la comprobación del permiso Execute, por lo que bloquea incluso si se tiene el permiso Execute. Un llamante que no tenga permiso recibe un403antes de llegar a esta puerta. Como esta puerta existe solo en ese endpoint, la acción vinculada (script) de un Webhook y un Scheduler lo ejecutan igualmente. El valor predeterminado estrue(invocación directa permitida). - Llamada anónima (
anonymousCallEnabled): el valor predeterminado esfalse. Si se pone entrue, solo ese Script se ejecuta también por una ruta dedicada sin autenticación (/execute/anonymous), y entonces la identidad de ejecución no es el llamante, sino el autor. De los dos límites anteriores, la comprobación en el momento de la llamada (el permiso Execute) no existe en esa ruta, así que la autenticación efectiva la hace el propio Script (verificando la firma de la petición recibida). Las condiciones para activarlo y las reglas de guardado se tratan en Llamada anónima. - Ámbito de propiedad:
createdBy: ":self"en un filtrowheresignifica «solo lo que ha creado el llamante actual» (por ejemplo, consultar solo la propia cartera). En un Script que permite la llamada anónima no se puede usar este filtro. Como no hay llamante y se resuelve como el autor, el sentido original de un ámbito de propiedad no se sostiene. - Permisos delegados (atención al autor): al combinar los dos límites anteriores, ejecutar un Script equivale a actuar con los permisos del autor delegados. El llamante solo necesita Execute, y los statements dentro del Script se ejecutan exactamente dentro del ámbito para el que se autorizó al autor al guardar. En consecuencia, una operación de recurso que el llamante no podría realizar por sí mismo puede ocurrir igualmente a través del Script. Dado que los permisos concedidos al autor son el alcance efectivo de ese Script, decida con cuidado qué operaciones incluye en un Script.
Lista de comprobación resumida
Antes de guardar, compruebe lo siguiente.
- Si ha activado la llamada anónima (
anonymousCallEnabled), enwhereno hay ningúncreatedBy: ":self"y ha colocado al principio el statement que verifica la petición recibida (Signature, etc.). - Las llamadas externas (
Http·EmailSend) y el número total de statements están dentro del límite del plan,SetVares 10 o menos yCache, 5 o menos. - Si ha usado
Cache, ha escrito lakeycomo literal y no lo ha colocado dentro de unLoopni de unResourceForEach. - Si recorre un conjunto grande con
ResourceForEach, ha declarado unlimito ha comprobado que el tamaño se puede recorrer por completo. - Si ha incluido una iteración (
Loop·ResourceForEach), ha comprobado que esa iteración entra como una multiplicación en el Presupuesto de tiempo (si no hay llamadas externas, el límite es el presupuesto base de 30 segundos). - Los valores secret se han colocado únicamente mediante
secret:trueenHttp.headers(comoSignature.secretno se almacena cifrado, ha revisado qué roles pueden leer ese Script). - El mensaje que se usa para verificar la firma se ha tomado con
{ /rawPayload }, no con/payload. - Si hay statements que leen miembros (ServiceUser), el autor tiene
SETTING_SERVICE_LOGINy no ha incluido statements que modifiquen ese recurso. - Las operaciones irreversibles (llamadas externas) se han colocado lo más tarde posible.
- Si preocupa la contención de update/patch, se usa
versionenResourceUpdateoResourcePatch. - Para devolver un resultado, se ha especificado
Return.value.
Errores
Códigos que aparecen cuando la forma de la definición infringe una restricción estática y el guardado se rechaza. Los códigos por infringir las reglas de las expresiones de valor están en los errores de Expresiones de valor, y los códigos que aparecen al invocar o eliminar, en los errores de los endpoints. Para los códigos comunes a todos los recursos, consulte Errores comunes.
| Código | Condición |
|---|---|
WGL400066 | La definición contiene más de 5 statements Cache. |
WGL400068 | La definición coloca un statement Cache dentro del bloque de un Loop o de un ResourceForEach. |
WGL400067 | El key de un statement Cache lleva una referencia { /pointer } en lugar de un literal. |
WGL400065 | El ttl de un statement Cache queda fuera del rango permitido. |
WGL400063 | Un statement Cache lleva un campo que no corresponde a su action (ttl en Get, defaultValue en Set). |
WGL400060 | El resource de un statement de escritura (ResourceCreate·ResourceUpdate·ResourcePatch·ResourceDelete y los statements de publicación y archivado) lleva "ServiceUser". |
WGL400061 | En un Script que permite la llamada anónima (anonymousCallEnabled), el where de un statement de lectura lleva createdBy: ":self". |
WGL400023 | La definición contiene más de 10 statements SetVar. |
WGL400026 | El retry de un statement Http supera el límite máximo de 2. |
WGL400036 | Un ResourceForEach supera el límite máximo de elementos que puede procesar. |
WGL429005 | El número total de statements de la definición supera el límite del plan de precios. |
WGL429006 | El número de llamadas externas (Http·EmailSend) de la definición supera el límite del plan de precios. |
WGL403015 | El autor no tiene los permisos de recurso y de acción que usan los statements de la definición. Aunque tenga el permiso, se rechaza si esa regla Allow lleva un filtro contentType, createdBy o tag. Tiene que ser una regla Allow sin condiciones. La única excepción es el Create de Content: en ese caso también se acepta una regla Allow acotada por contentType, que se contrasta con el contentType escrito en el statement (el Create de Media no tiene esta excepción). Este código cubre también el caso en que el resource de un statement de lectura lleva "ServiceUser" y el autor no tiene SETTING_SERVICE_LOGIN. |
Documentos relacionados
- Expresiones de valor: reglas de valores y condiciones.
- Catálogo de statements: los campos y resultados de cada statement.
- Cookbook: una colección de ejemplos completos.
- Recurso Script y endpoints: la estructura del recurso
Scripty endpoints HTTP como/execute. - Descripción general de Script: la estructura de nivel superior y el tiempo que recibe una ejecución.
