Semântica de execução, restrições e segurança
Esta página resume como um Script se comporta em tempo de execução (ordem, transações, erros, bloqueio), a quais restrições estáticas ele está sujeito no momento de salvar e o seu modelo de segurança. Para a sintaxe, consulte o catálogo de Statements e as expressões de valor; para combinações práticas, consulte o cookbook.
Ordem de execução
- Os
statementssão executados sequencialmente de cima para baixo. Ao alcançar umReturn, a execução termina nesse ponto. - A execução acontece inline, no caminho que processa a requisição da chamada. A resposta da chamada é o próprio resultado da execução (para o formato da resposta, consulte Requisição e resposta na visão geral do Script), e o tempo concedido a uma execução é abordado abaixo em Orçamento de tempo.
Semântica de execução
Guard (pré-condições)
Não existe um statement guard dedicado. Você o expressa com If e then:[Return]. Quando a condição é violada, retorna um resultado e não executa os statements seguintes (um Script sem guard também é perfeitamente possível).
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "statusCode": 402 } ] }Sem transações e compensação best-effort
Um Script não é uma transação. Em caso de falha, o engine tenta compensar (compensation) o trabalho realizado até então e retorna a causa do erro, mas com as seguintes limitações (assumidas por decisão de projeto).
- Desfazer uma exclusão cria um novo
sys.id, então as referências que apontavam para ele quebram. - Efeitos externos (
Http) são irreversíveis (uma chamada que já foi feita, e a cobrança dela, não podem ser desfeitas). - A compensação pode simplesmente não ser executada, deixando um estado não compensado.
Se você precisa de atomicidade real, escreva a compensação no Script por conta própria ou coloque as operações irreversíveis (como chamadas externas) por último. A ordenação mais perigosa é aquela que "encadeia até o fim, mas não consegue reverter, e ainda assim parece segura".
Bloqueio otimista
Você reduz a contenção de update/patch com o version em ResourceUpdate e ResourcePatch. Se fornecer version (uma expressão de valor, Int), a atualização acontece somente quando coincide com o sys.version atual do alvo; uma divergência aborta com um erro de conflito de versão (que você pode tratar localmente com Try/catch). Se omitir, vale last-write-wins, sem verificação. Normalmente você lê primeiro com ResourceRead ou ResourceFind e passa esse sys.version (consulte CAS de bloqueio otimista no cookbook).
Gravações vão para origin
As gravações sempre chegam ao origin (draft), e a exposição na delivery (CDA/ACDA) é controlada por publish (o publish em ResourceCreate/ResourceUpdate/ResourcePatch, ou ResourcePublish/ResourceUnpublish).
O que conta como falha
- Uma falha real é um erro em tempo de execução de statement: um status final de
Httpde 400 ou superior (4xx·5xx; não é uma falha quandoignoreStatusCode: true) ou timeout, um corpo de resposta que excede 10MiB, ou uma operação de recurso malsucedida (alvo inexistente, conflito de versão, operação não suportada, etc.). Nessas falhas o engine aborta e compensa, e você pode tratá-las localmente comTry/catch/finally. - Um
Returnnão é um erro, mas uma saída antecipada normal. Não é alvo decatch(não existe o conceito de throw pelo usuário). - Dentro de um
catch, você referencia{ message }via/error. Em qual statement a falha ocorreu não é incluído.
No servidor, apenas a contagem
A contagem é feita pelo ResourceCount no servidor. Como ele não traz os itens, não fica sujeito ao limite máximo de itens processados.
Para sum e group-by não há operações de servidor dedicadas. Essas agregações você precisa calcular por conta própria, percorrendo com ResourceForEach e usando SetVar e JsonLogic, portanto fica limitado pelo limite máximo de itens processados (inadequado para agregar milhões de registros). Se você precisa apenas da contagem, não percorra: use ResourceCount.
Sem espera ou atraso
Um Script não tem statement Delay. Um Script é executado uma vez e pronto, e não espera nem faz polling internamente até um job externo terminar.
Restrições estáticas (validadas ao salvar)
Os itens a seguir são verificados no momento em que um Script é salvo (na criação/atualização). Se algum for violado, o salvamento é rejeitado (a falha ocorre no momento da autoria, não em tempo de execução). A seção Erros mostra qual violação é recusada com qual código.
| Restrição | Valor |
|---|---|
Máximo de chamadas externas por definição (Http·EmailSend) | Por plano (veja Planos) |
Máximo de itens processados por ResourceForEach (sem um limit declarado, percorre até este valor; falha se atingi-lo ainda havendo correspondências) | 10.000 |
Máximo de SetVar por definição (incluindo aninhados) | 10 |
Máximo de Cache por definição (incluindo aninhados, somados independentemente da operação) | 5. Acima disso, salvamento recusado |
Cache dentro do bloco de um Loop·ResourceForEach | Salvamento recusado |
Cache.key | Somente literal, no máximo 128 caracteres. Se for uma expressão de valor, salvamento recusado |
Cache.ttl | Entre 1 e 30 segundos; se omitido, 5. Fora disso, salvamento recusado |
| Máximo total de statements por definição (incluindo aninhados) | Por plano (veja Planos) |
Limite de Http.retry | 2 |
Comprimento de Regex.pattern | 128 caracteres |
| Statement que altera um ServiceUser | Salvamento recusado. Somente os três statements de leitura aceitam este recurso |
Se anonymousCallEnabled for true, o createdBy: ":self" de where | Salvamento recusado |
Os limites fixos da tabela acima são valores definidos pela plataforma, portanto são os mesmos independentemente do plano. Já o número total de statements e o número de chamadas externas por definição são limites por plano. Esses dois não são erros de validação, mas limites de plano; se ultrapassados, o salvamento e a edição são recusados por estouro do limite do plano (a mesma definição é permitida em um plano superior) e são liberados com um upgrade. Os valores por plano estão em Planos.
A ingestão de arquivo de Media, ao contrário de chamadas externas como
Http·EmailSend, não entra no limite de chamadas externas por definição.
O
ResourceForEaché um statement composto que possui filhos, portanto ele próprio não conta no número de chamadas externas. Os statements de chamada externa dentro deonEach(Http·EmailSend) é que são contados (estaticamente contam como 1, mas são de fato executados a cada item durante o percurso). EmonEachvocê pode incluir chamadas externas ou ingestão de arquivo de Media, e o mesmo vale para o body deLoop. Quantas voltas a iteração dá de fato não entra nessa contagem: em vez disso, ela entra como multiplicação no Orçamento de tempo abaixo.
Limites de comprimento de valor (tempo de execução)
Os statements de assinatura e de tratamento de texto, e também o Cache, têm um limite máximo para o tamanho do valor que manipulam. Não é o comprimento da expressão, mas o comprimento do valor resolvido dessa expressão (os dezesseis caracteres de { /rawPayload } apontam para dezenas de KB), e é por isso que a verificação ocorre durante a execução, e não no momento do salvamento.
| Alvo | Limite máximo | Se exceder |
|---|---|---|
O value de Signature | 65.536 caracteres | Esse statement falha (status 422) |
O value de Hash | 128 caracteres | Esse statement falha (status 422) |
O value de Regex | 10.240 caracteres (10KiB) | Esse statement falha (status 400) |
O value de Cache | 10.240 bytes (10KiB) | Esse statement falha (status 422) |
- Os quatro são iguais a qualquer outra falha em tempo de execução, portanto podem ser tratados localmente com
Try/catch. - O limite máximo de
Signaturefoi ajustado ao tamanho dos corpos que os provedores realmente enviam (um evento de pagamento tem alguns KB; um webhook de pedido chega a dezenas de KB). O deHashé bem mais estreito, por ser a posição em que se concatenam alguns campos. - Os 128 caracteres de
Regex.patternsão a verificação de salvamento que está nas Restrições estáticas acima. Esse comprimento não é um dispositivo para impedir explosões de custo ((a+)+$já é perigoso com seis caracteres). O que impede a explosão é a regra que obriga o padrão a ser apenas literal, junto com o orçamento de tempo abaixo; o que o comprimento promete é somente um tamanho que uma pessoa consiga ler e revisar.
Orçamento de tempo (tempo de execução)
O tempo concedido a uma execução é definido por uma única fórmula: min(30 segundos + soma dos tempos declarados pelos statements, 180 segundos).
- O orçamento é calculado a partir daquele Script. Ao orçamento base soma-se apenas o tempo que a definição declarou. O único tempo declarado é o
timeoutMsdeHttpe deEmailSend. OHttpgasta o seutimeoutMsde novo a cada nova tentativa, portanto conta comotimeoutMs × (1 + retry); oEmailSendnão tenta de novo, portanto conta uma vez. Se você não escrevertimeoutMs, conta-se o valor padrão (30 segundos emHttp, 10 segundos emEmailSend). - O trabalho que não tem tempo declarado sai do orçamento base de 30 segundos. Entram aqui a leitura e a gravação de recursos, a ingestão de arquivo de Media e o que uma iteração faz por dentro. É por isso que o orçamento base não é um valor de formalidade, mas uma parcela real.
- A forma de somar segue a estrutura dos statements. Os statements dispostos em sequência somam-se; o
Iftoma o maior entre os dois ramos, e oParallel, o maior entre os branches. OLoopmultiplica o body pela quantidade de iterações (maxIterations; 10.000 se não houver declaração) e oResourceForEachmultiplica oonEachpela quantidade de itens processados (limit; 10.000 se não houver declaração). - Uma iteração sem chamada externa tem tempo declarado 0. Por isso o orçamento base de 30 segundos passa a ser o limite efetivo, e é também nesse ponto que um Script que contém uma iteração de fato é barrado.
- O limite de 180 segundos não impede o salvamento: ele corta. Mesmo que o cálculo ultrapasse o limite, aquele Script é salvo e executado, e ao alcançar os 180 segundos ele é interrompido ali.
Limites de quantidade por plano
Um Script tem a quantidade por Organization limitada por plano.
| Plan | Quantidade de Script |
|---|---|
| Free | 10 |
| Basic | 30 |
| Pro | 100 |
| Enterprise | Ilimitado |
À parte disso, o número de statements e o número de chamadas externas (Http·EmailSend) que uma definição de Script pode conter também são limitados por plano. Ao salvar ou editar uma definição, se ela ultrapassar o limite daquele plano, é recusada; para os valores concretos, consulte Planos.
Quando o limite é atingido, a criação de um novo Script é rejeitada.
Modelo de segurança
Cabeçalhos secret
Um item em Http.headers com secret:true é exclusivo do CMA (administrador): não é exposto ao usuário final (ServiceUser) e é descriptografado apenas imediatamente antes do envio. Coloque aqui segredos como uma chave de API de LLM (mesmo quando empacotado em um App Bundle, o valor secret é mascarado e nunca sai do Space de origem).
O secret de Signature não recebe esse tratamento dentro do Space. Ele não é criptografado: fica armazenado exatamente como escrito na definição, portanto seu valor é visível para os papéis que podem ler aquele Script. Um membro (ServiceUser) não consegue ler a definição de um Script (a consulta e a autoria são exclusivas da CMA, e a ACMA não tem a API de Script). Em um Script que guarda uma chave de verificação, é mais seguro manter estreito o conjunto de papéis capazes de lê-lo.
Sair do Space é diferente. Quando aquele Script é empacotado em um App Bundle, o secret de Signature é mascarado e não sai do Space de origem. Em Http.headers, o que fica oculto são os itens com a flag secret e o cabeçalho Authorization; no Signature, por outro lado, o secret fica oculto sem condição alguma, porque o próprio campo é a chave de assinatura. Um Signature aninhado dentro de If·Loop·Try também fica oculto.
Identidade de execução e autorização
- Identidade de execução: durante a execução, toda operação de recurso é realizada sob a identidade do usuário que chamou
/execute. OcreatedBy/updatedByde qualquer recurso criado ou atualizado é o chamador, e um escopocreatedBy: ":self"também é resolvido em relação ao chamador. A exceção é a chamada anônima. Uma execução que entra por/execute/anonymousnão tem chamador, portanto ambos são resolvidos em relação ao autor (veja Chamada anônima). - Há dois limites de autorização e, em tempo de execução, o engine não reverifica as permissões de recurso a cada statement.
- No momento da autoria (salvar): ao salvar um Script, verifica-se se o autor realmente possui as permissões de recurso e de ação que seus statements usam. Se faltar uma que seja, o salvamento é rejeitado. Ou seja, um Script que contém uma operação não autorizada nunca chega a ser salvo. Todo statement que seleciona um recurso passa por essa verificação, seja ele um leaf ou um
ResourceForEachque possui um bloco. Uma definição já salva também é verificada de novo ao ser editada, portanto, depois que uma permissão é revogada, não é possível corrigir e salvar aquela definição.- O diretório de membros (ServiceUser) é verificado pelo eixo de configurações, não pelo mapa de permissões. Para usar
resource: "ServiceUser"nos três statements de leitura, osettingsdo SpaceRole do autor precisa terSETTING_SERVICE_LOGIN(ouSETTING_ALL) (veja settings do SpaceRole). Isso porque o diretório de membros é, em todos os outros caminhos também, um recurso governado pelas configurações do Space. - Um statement que altera um membro não é salvo com nenhum papel. Como no Script não existe caminho algum para criar, editar ou excluir um membro, a recusa não é por falta de permissão (
403), mas por statement escrito de forma inválida (400). Ou seja, não é uma lacuna que se feche acrescentando permissões.
- O diretório de membros (ServiceUser) é verificado pelo eixo de configurações, não pelo mapa de permissões. Para usar
- No momento da chamada (
/execute): verifica-se apenas a permissão Execute do chamador sobre o Script. Sem ela, o resultado é403. Uma vez aprovado, as permissões de recurso por statement não são verificadas novamente em tempo de execução; a execução prossegue. Funciona como a permissão de execução de função na programação. Se você tem permissão para executar a função, a permissão de cada operação individual dentro dela não é perguntada de novo. No caminho de chamada anônima, essa verificação não existe. É porque não há chamador a verificar e, por isso, abrir aquele caminho equivale a publicar um Script sem autenticação.
- No momento da autoria (salvar): ao salvar um Script, verifica-se se o autor realmente possui as permissões de recurso e de ação que seus statements usam. Se faltar uma que seja, o salvamento é rejeitado. Ou seja, um Script que contém uma operação não autorizada nunca chega a ser salvo. Todo statement que seleciona um recurso passa por essa verificação, seja ele um leaf ou um
- Bloqueio de chamada direta (
directCallEnabled): se odirectCallEnabledde um Script forfalse, a própria chamada direta a/executeé rejeitada. Este gate é aplicado depois que a verificação da permissão Execute é aprovada, então ele bloqueia mesmo quem tem a permissão Execute. Um chamador sem essa permissão recebe403antes de chegar a este gate. Como esse gate existe apenas naquele endpoint, a ação de vínculo (script) de um Webhook e um Scheduler continuam executando o Script normalmente. O padrão étrue(chamada direta permitida). - Chamada anônima (
anonymousCallEnabled): o padrão éfalse. Se você deixá-lo comotrue, apenas aquele Script passa a ser executável também por um caminho dedicado e sem autenticação (/execute/anonymous), e nesse caso a identidade de execução é o autor, não o chamador. Dos dois limites acima, a verificação no momento da chamada (permissão Execute) não existe nesse caminho, portanto a autenticação efetiva é feita pelo próprio Script (verificando a assinatura da requisição recebida). As condições para ativá-lo e as regras de salvamento são abordadas em Chamada anônima. - Escopo de propriedade:
createdBy: ":self"em um filtrowheresignifica "apenas o que o chamador atual criou" (por exemplo, consultar apenas a própria carteira). Esse filtro não pode ser usado em um Script que permite chamada anônima. Como não há chamador e ele é resolvido para o autor, o sentido original de escopo de propriedade não se sustenta. - Permissões delegadas (atenção ao autor): combinando os dois limites acima, executar um Script equivale a agir com as permissões do autor delegadas a ele. O chamador precisa apenas de Execute, e os statements dentro do Script são executados exatamente dentro do escopo para o qual o autor foi autorizado no momento de salvar. Como consequência, uma operação de recurso que o chamador não conseguiria realizar sozinho ainda pode acontecer por meio do Script. Como as permissões concedidas ao autor são o alcance efetivo desse Script, defina com cuidado quais ações você coloca em um Script.
Checklist de resumo
Antes de salvar, verifique o seguinte.
- Se você ativou a chamada anônima (
anonymousCallEnabled), não hácreatedBy: ":self"emwheree você colocou no início o statement que verifica a requisição recebida (Signatureetc.). - O número de chamadas externas (
Http·EmailSend) e o total de statements estão dentro do limite do plano,SetVaré 10 ou menos eCacheé 5 ou menos. - Se você usou
Cache, escreveu akeycomo literal e não a colocou dentro de umLoopou de umResourceForEach. - Se você percorrer um conjunto grande com
ResourceForEach, declarou umlimitou verificou que o tamanho é conclusível. - Se você incluiu uma iteração (
Loop·ResourceForEach), confirmou que ela entra como multiplicação no Orçamento de tempo (sem chamadas externas, o limite é o orçamento base de 30 segundos). - Os valores secret foram colocados apenas via
secret:trueemHttp.headers(comoSignature.secretnão é armazenado criptografado, você conferiu quais papéis podem ler aquele Script). - A mensagem usada na verificação de assinatura foi tomada de
{ /rawPayload }, e não de/payload. - Se houver um statement que lê um membro (ServiceUser), o autor tem
SETTING_SERVICE_LOGINe você não incluiu nenhum statement que altere esse recurso. - As operações irreversíveis (chamadas externas) foram colocadas o mais tarde possível.
- Se você se preocupa com a contenção de update/patch, use o
versionemResourceUpdateouResourcePatch. - Para retornar um resultado, você especificou
Return.value.
Erros
São os códigos que aparecem quando o salvamento é recusado porque a forma da definição viola uma restrição estática. Os códigos que violam as regras das expressões de valor estão em Erros em Expressões de valor, e os que aparecem na chamada e na exclusão, em Erros em Recurso e endpoints do Script. Para os códigos comuns a todos os recursos, consulte Erros comuns.
| Código | Condição |
|---|---|
WGL400066 | A definição contém mais de 5 statements Cache. |
WGL400068 | A definição coloca um statement Cache dentro do bloco de um Loop ou de um ResourceForEach. |
WGL400067 | A key de um statement Cache traz uma referência { /pointer } em vez de um literal. |
WGL400065 | O ttl de um statement Cache está fora da faixa permitida. |
WGL400063 | Um statement Cache traz um campo que não corresponde ao seu action (ttl em um Get, defaultValue em um Set). |
WGL400060 | Um statement de escrita (ResourceCreate·ResourceUpdate·ResourcePatch·ResourceDelete e os statements de publicação e arquivamento) traz "ServiceUser" em resource. |
WGL400061 | Em um Script que permite chamada anônima (anonymousCallEnabled), um statement de leitura traz createdBy: ":self" em where. |
WGL400023 | A definição contém mais de 10 statements SetVar. |
WGL400026 | O retry de um statement Http ultrapassa o limite máximo de 2. |
WGL400036 | O ResourceForEach ultrapassa o limite máximo de itens que ele pode processar. |
WGL429005 | O número total de statements que a definição contém ultrapassa o limite do plano. |
WGL429006 | O número de chamadas externas (Http·EmailSend) que a definição contém ultrapassa o limite do plano. |
WGL403015 | O autor não possui as permissões de recurso e de ação que os statements da definição usam. Mesmo que ele tenha a permissão, o salvamento é recusado se essa autorização vier com um filtro contentType, createdBy ou tag. A autorização precisa ser incondicional. A única exceção é o Create de Content, para o qual também vale uma autorização com escopo de contentType, que é comparada com o contentType escrito no statement (o Create de Media não tem essa exceção). Um statement de leitura que traz "ServiceUser" em resource quando o autor não tem SETTING_SERVICE_LOGIN também recebe este código. |
Documentos relacionados
- Expressões de valor: regras de valores e condições.
- Catálogo de Statements: os campos e resultados de cada statement.
- Cookbook: uma coleção de exemplos completos.
- Recursos e endpoints do Script: a estrutura do recurso
Scripte endpoints HTTP como/execute. - Visão geral do Script: a estrutura de nível superior e o tempo concedido a uma execução.
