> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construa/conceitos/replay-e-determinismo.md).

# Replay e determinismo

Uma function é reexecutada do início toda vez que a execução é retomada. Esta página explica por que isso acontece, o que pode e o que não pode estar dentro de uma function, e o que significa o erro de não determinismo.

## Por que a função é reexecutada

O skail não guarda a pilha de execução do seu método. O que existe é o histórico da execução: quais commands foram chamados, com quais argumentos, o que devolveram, quais delays e esperas foram alcançados. Quando a execução precisa continuar (o delay venceu, o evento chegou, o processo reiniciou, um command falhou e vai ser retentado), o runtime chama o método de novo, desde a primeira linha, e vai consultando o histórico a cada `await`:

```
execução chega (início ou retomada)
  → runtime carrega o histórico da execução
  → runtime chama a function desde o início
  → a cada await alcançado:
       • o passo já está no histórico → devolve o resultado gravado, sem executar
       • não existe → executa de verdade (command: executa a operação; delay: agenda; evento: hiberna) e grava um passo
  → quando a function termina ou hiberna, o runtime confirma ao skail
```

Esse processo é o replay. Ele só funciona se o método, dado o mesmo histórico, percorrer o mesmo caminho e alcançar os mesmos `await`s na mesma ordem. É isso que "determinístico" quer dizer aqui.

## O que pode estar dentro de uma function

* Lógica pura: `if`, `switch`, laços, cálculos sobre argumentos e sobre resultados de commands, variáveis locais.
* Chamadas a commands e a outras functions (que viram orquestrações filhas).
* As primitivas de espera do SDK: delay, espera por evento, esperar todos e esperar o primeiro que terminar.
* O log da aplicação e o TaskId da execução, lido do contexto do SDK.

Tudo isso ou não muda entre execuções, ou é gravado no histórico e devolvido igual no replay.

## O que não pode

Qualquer coisa cujo valor ou ordem possa ser diferente na segunda execução:

{% tabs %}
{% tab title="C#" %}
Estas chamadas compilam dentro da function, mas quebram o replay: produzem valor ou sequência diferente.

| Dentro da function                                                           | Por quê                                                                                         | Onde colocar                                  |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `DateTime.Now`, `DateTime.UtcNow`, `DateTimeOffset.Now`                      | O relógio muda entre o primeiro run e o replay; um `if (agora > vencimento)` toma outro caminho | Em um `[SkailCommand]` que retorna `DateTime` |
| `Guid.NewGuid()`, `Random`, `Environment.TickCount`                          | Valor diferente a cada execução                                                                 | Command, ou receber como parâmetro            |
| `Environment.GetEnvironmentVariable`, ler arquivo, config, banco, HTTP, fila | É I/O: o resultado pode mudar e o efeito se repete                                              | Command                                       |
| `Task.Delay`, `Thread.Sleep`                                                 | Não registra passo; no replay o runtime não encontra o passo                                    | `SkailTask.Delay`                             |
| `Task.WhenAll`, `Task.WhenAny`, `Task.Run`, `Parallel.ForEach`               | Concorrência fora do controle do runtime, ordem não reproduzível                                | `SkailTask.WhenAll` / `WhenAny`               |
| {% endtab %}                                                                 |                                                                                                 |                                               |

{% tab title="TypeScript" %}
Dentro da function, as chamadas de relógio, aleatoriedade, timer e `fetch()` lançam erro na hora, com a mensagem `... is not available in a deterministic Skail function. Use a command.`

| Dentro da function                                                        | Por quê                                                                                                                  | Onde colocar                                                      |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
| `new Date()` sem argumento, `Date.now()`                                  | Lança erro na hora; o relógio muda entre o primeiro run e o replay                                                       | Em um command que retorna a data como string ISO-8601             |
| `Math.random()`, `crypto.randomUUID()`, `crypto.getRandomValues()`        | Lança erro na hora; valor diferente a cada execução                                                                      | Command, ou receber como parâmetro                                |
| `fetch()`, `process`, `require`, imports estáticos de módulos Node de I/O | `fetch()` lança erro na hora; `process` e `require` não existem dentro da function; o build rejeita os imports estáticos | Command, com os módulos de I/O carregados por `await import(...)` |
| `setTimeout()`, `setInterval()`                                           | Lança erro na hora; um timer comum não registra passo no histórico                                                       | `Skail.delay`, `Skail.delaySeconds`, `Skail.delayMinutes`         |
| `Promise.all`, `Promise.race`, `.then`                                    | A composição fica fora do controle do runtime                                                                            | `Skail.whenAll` / `Skail.whenAny`                                 |

{% hint style="info" %}
`new Date(texto)`, `Date.parse` e `getTime()` são permitidos.
{% endhint %}
{% endtab %}
{% endtabs %}

A regra que resume a tabela: se o resultado pode diferir entre duas execuções com as mesmas entradas, é command.

Um exemplo que parece inocente e quebra:

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailFunction]
public async SkailTask EnviarLembrete(Assinatura assinatura)
{
    var dias = (assinatura.Vencimento - DateTime.UtcNow).TotalDays; // não determinístico
    await SkailTask.Delay(TimeSpan.FromDays(dias - 3));
    await Notificar(assinatura.UsuarioId);
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

interface Assinatura {
  vencimento: string;
  usuarioId: string;
}

const UM_DIA_MS = 24 * 60 * 60 * 1000;

export class Lembretes {
  @SkailFunction()
  async enviarLembrete(assinatura: Assinatura): Promise<void> {
    const dias = (new Date(assinatura.vencimento).getTime() - Date.now()) / UM_DIA_MS; // não determinístico
    await Skail.delay((dias - 3) * UM_DIA_MS);
    await this.notificar(assinatura.usuarioId);
  }

  @SkailCommand()
  async notificar(usuarioId: string): Promise<void> {
    /* ... */
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Em C#, esse código compila e só falha no replay: na primeira execução `dias` vale, digamos, 30, e o delay agendado é de 27 dias. Vinte e sete dias depois a execução acorda, o método roda de novo, a leitura do relógio agora dá `dias = 3`, e o código pede um delay de zero dias: o passo gravado (delay de 27 dias) não bate com o passo que o código quer executar. Em TypeScript, a leitura do relógio lança erro já na primeira execução. O mesmo método, correto:

<figure><img src="https://1898946878-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJmS5ILPG5x2x5j0WyiV%2Fuploads%2FNqcOEPBvAtOJVYOzTJdF%2Fap-determinismo-replay.svg?alt=media" alt="Replay do método correto, que lê a data atual em um command. Na entrega 1, o command grava agora, a function calcula dias = 30 e o delay de 27 dias é agendado e gravado. Na entrega 2, 27 dias depois, a function roda desde o início e recalcula dias = 30 a partir de agora: a data e o delay voltam do histórico e só a notificação executa e grava."><figcaption></figcaption></figure>

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailFunction]
public async SkailTask EnviarLembrete(Assinatura assinatura)
{
    var agora = await ObterDataAtual();            // gravado no histórico; igual em todo replay
    var dias = (assinatura.Vencimento - agora).TotalDays;
    await SkailTask.Delay(TimeSpan.FromDays(dias - 3));
    await Notificar(assinatura.UsuarioId);
}

[SkailCommand]
public async SkailTask<DateTime> ObterDataAtual() => await Task.FromResult(DateTime.UtcNow);
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

interface Assinatura {
  vencimento: string;
  usuarioId: string;
}

const UM_DIA_MS = 24 * 60 * 60 * 1000;

export class Lembretes {
  @SkailFunction()
  async enviarLembrete(assinatura: Assinatura): Promise<void> {
    const agora = await this.obterDataAtual(); // gravado no histórico; igual em todo replay
    const dias = (new Date(assinatura.vencimento).getTime() - new Date(agora).getTime()) / UM_DIA_MS;
    await Skail.delay((dias - 3) * UM_DIA_MS);
    await this.notificar(assinatura.usuarioId);
  }

  @SkailCommand()
  async obterDataAtual(): Promise<string> {
    return new Date().toISOString();
  }

  @SkailCommand()
  async notificar(usuarioId: string): Promise<void> {
    /* ... */
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Dentro do command vale tudo

O command roda uma vez por resultado e o resultado é gravado. Por isso dentro dele você usa normalmente o cliente HTTP, o acesso ao banco, pausas e timers da linguagem, concorrência comum, ids gerados e a hora atual (em TypeScript, os módulos Node de I/O entram com `await import(...)`). A function fica determinística porque esses valores são lidos no command e gravados antes de voltar para ela. A exceção é chamar outro command ou uma function: quem compõe os passos é sempre a function, e commands são chamados só de dentro de functions; para reaproveitar lógica dentro de um command, use métodos comuns, que não são function nem command.

## Erro de não determinismo

É o erro que o runtime lança quando o replay diverge do histórico: o passo que o código quer executar não é o passo que o histórico tem naquela posição (índice diferente, ou tipo de evento e URI do método diferentes do passo gravado). Em C#, ele aparece como `SkailNonDeterministicException`; em TypeScript, como `SkailNonDeterministicError`. As causas, em ordem de frequência:

1. Relógio, aleatoriedade, variável de ambiente ou I/O dentro da function.
2. Código mudou depois que a execução já tinha histórico: ordem, quantidade ou tipo dos `await`s diferente (inseriu ou removeu um command, um delay, uma execução em paralelo, uma espera por evento). Ver [Versionamento de código com execuções em andamento](/construa/conceitos/versoes-com-execucoes-em-andamento.md).
3. Método renomeado, ou `SKAIL_WORKLOAD` diferente do que gerou a execução.
4. Pausas, timers ou concorrência comuns da linguagem usados para esperar ou paralelizar, sem passar pelas primitivas de espera do SDK.

{% hint style="info" %}
Em TypeScript, as causas 1 e 4 costumam aparecer antes: relógio, aleatoriedade, `fetch()` e timers dentro da function lançam erro já na primeira execução, antes de qualquer replay.
{% endhint %}

{% hint style="warning" %}
Não capture esse erro para "seguir em frente". Ele indica que o código e o histórico não correspondem mais; capturar esconde o problema e corrompe o fluxo. Corrija a causa e, se houver execuções em andamento com o histórico antigo, versione.
{% endhint %}

## O que isso muda para quem escreve código

A function só decide e encadeia; o command concentra as operações de I/O e toda fonte de não determinismo. Nomeie os commands pelo efeito (`CobrarCartao`, `ObterDataAtual`) porque é assim que eles aparecem no Monitor. E lembre que o corpo da function roda várias vezes: uma linha de log no meio dela pode aparecer repetida nos logs; use o `TaskId` para agrupar. Em C#, os logs do `ILogger` dentro de functions e commands já saem correlacionados pelo `TaskId`; em TypeScript, inclua `SkailContext.current.taskId` na linha do `console`.

## Próximos passos

[Falhas, retries e idempotência](/construa/conceitos/retries-e-idempotencia.md) explica o que acontece quando um command lança exceção. [Antipadrões](/construa/como-fazer/padroes-e-antipadroes/antipadroes-o-que-nao-fazer-e-por-que.md) lista os erros mais comuns com a correção de cada um. A referência de [function](/referencia/sdk-csharp/skailfunction.md) tem as regras de assinatura que o SDK cobra.
