a3s-flow 0.4.1

Durable workflow engine and Rust SDK for A3S
Documentation
# Native TypeScript Workflows

A3S Flow remains a Rust SDK. `NativeTsRuntime` is an optional runtime adapter
for hosts that want workflow authors to write TypeScript while Rust still owns
run creation, event storage, replay, workers, scheduling, and inspection.

This is not a TypeScript SDK. The TypeScript code is source for a native runtime
artifact that a Rust host compiles and invokes.

## Compiler Contract

`NativeTsRuntime` expects a compiler executable with this command shape:

```sh
a3s-flow-native-compiler compile <entrypoint.ts> -o <artifact>
```

The produced artifact must:

- be executable by the host,
- accept `--a3s-flow-runtime`,
- read one `NativeRuntimeRequest` JSON object from stdin,
- write one `NativeRuntimeResponse` JSON object to stdout,
- dispatch workflow requests to the `exportName` function from the request,
- dispatch step requests by `payload.step_name`.

Set a custom compiler path in Rust:

```rust
let runtime = NativeTsRuntime::new(NativeTsRuntimeConfig::new(
    "/path/to/a3s-flow-native-compiler",
    ".a3s-flow/native-ts",
    ".",
));
```

The runnable example also accepts:

```sh
A3S_FLOW_NATIVE_TS_COMPILER=/path/to/a3s-flow-native-compiler \
  cargo run --example native_ts_greeting

A3S_FLOW_NATIVE_TS_COMPILER=/path/to/a3s-flow-native-compiler \
  cargo run --example native_ts_preflight
```

When that environment variable is not set, the examples print the missing
prerequisite and exits successfully so the normal Rust example suite remains
portable.

## Preflight And Diagnostics

Call `NativeTsRuntime::preflight(&spec)` before accepting user-authored source or
starting a run when a host wants early validation and compiler diagnostics.

```rust
let preflight = runtime.preflight(&spec).await?;

println!("entrypoint={}", preflight.entrypoint.display());
println!("artifact={}", preflight.artifact.display());
println!("source_hash={}", preflight.source_hash);
println!("cache_hit={}", preflight.cache_hit);
```

Preflight performs the same compile path used by workflow and step execution:

- validates that the `WorkflowSpec` is a valid `native_ts` spec,
- resolves the entrypoint relative to the runtime working directory,
- hashes the source and runtime identity fields into the artifact cache key,
- compiles the source only when the artifact cache is cold,
- returns `NativeTsRuntimePreflight` with entrypoint, artifact, source hash, and
  cache-hit metadata,
- returns a runtime error containing compiler stderr when compilation fails.

Use `examples/native_ts_preflight.rs` when testing compiler installation,
artifact cache paths, or CI diagnostics without starting a workflow run.

## Protocol Envelope

The request envelope is stable and versioned:

```json
{
  "protocol": "a3s.flow.native_ts.v1",
  "kind": "workflow",
  "exportName": "main",
  "sourceHash": "sha256...",
  "payload": {
    "run_id": "run-id",
    "input": {},
    "history": []
  }
}
```

The response envelope must mirror the request kind:

```json
{
  "protocol": "a3s.flow.native_ts.v1",
  "kind": "workflow",
  "ok": true,
  "output": {
    "type": "complete",
    "output": {}
  }
}
```

For step requests, `output` is the step output JSON value. For workflow
requests, `output` is a `RuntimeCommand`.

## Authoring Types

Use [`examples/native-ts/a3s-flow-runtime.d.ts`](../examples/native-ts/a3s-flow-runtime.d.ts)
as the authoring contract for workflow and step source. It mirrors the Rust
serde field names used in `NativeRuntimeRequest`, `WorkflowInvocation`,
`StepInvocation`, `RuntimeCommand`, and `FlowEventEnvelope`. The file is a type
contract, not a runtime helper module, so workflow source should define local
history helpers or rely on helpers injected by the compiler artifact.

The contract defines:

- `WorkflowInvocation<Input>`
- `StepInvocation<Input>`
- `RuntimeCommand`
- `RetryPolicy`
- `FlowEvent`
- `FlowEventEnvelope`
- `StepDefinition<Input, Output>`
- `NativeRuntimeRequest<Payload>`
- `NativeRuntimeResponse<Output>`

Important protocol details:

- `FlowEventEnvelope` includes `event_id`, `run_id`, `sequence`, `timestamp`,
  and `event`. It does not include a derived event key.
- `create_hook` commands and `hook_created` history events include a required
  `token` because callback routing must be stable across replay.
- `step_retrying.retry_after` is `string | null`, matching Rust's serialized
  `Option<DateTime<Utc>>`.
- `schedule_step.retry` and batched `StepCommand.retry` may be omitted; Rust
  applies the default retry policy.

The greeting source in
[`examples/native-ts/greeting.ts`](../examples/native-ts/greeting.ts) shows the
intended shape:

```ts
import type {
  RuntimeCommand,
  StepInvocation,
  WorkflowInvocation,
} from "./a3s-flow-runtime";

export async function main(
  invocation: WorkflowInvocation<GreetingInput>,
): Promise<RuntimeCommand> {
  // Inspect invocation.history and return the next deterministic command.
}

export const steps = {
  async greet_user(invocation: StepInvocation<GreetingStepInput>) {
    // Do side effects here and return persisted JSON output.
  },
};
```

## Determinism Rules

Workflow exports should be deterministic:

- read only `input` and `history`,
- return exactly one `RuntimeCommand`,
- do not perform network, clock, random, filesystem, or shell work,
- put side effects in step handlers,
- use stable step IDs, wait IDs, and hook IDs.
- set `retry.on_exhausted` to `"continue_workflow"` only when workflow replay
  explicitly handles the resulting `step_failed` history.

Step handlers may perform side effects, but their outputs are persisted before
workflow replay observes them.