# Horizon Verify — integration guide

Horizon verifies business outcomes and recovery across sequential agent
workflows. The core is Rust, compiled to WebAssembly and executed by the
hosted API. The JavaScript SDK captures execution evidence and uses the Rust
result to gate subsequent steps. It works with your existing agent.

## Run the example

Requires Node.js 22 or newer:

```sh
curl -fsSLO https://horizon-orchestra.com/horizon-verify.mjs
curl -fsSLO https://horizon-orchestra.com/refund-quickstart.mjs
node refund-quickstart.mjs
```

This example mutates an in-memory simulated refund record. It does not move
money or call an LLM. Its refund deliberately times out after committing;
read-back verifies the refund and lets the ticket close without refunding twice.

## Connect your test workflow

1. Download `/refund-contract.json` and adapt its workflow steps, dependencies,
   assertions, and invariants to your business rules. Amounts use integer cents.
   Store the contract in source control and review it separately from the agent.
2. Provide a stable run ID. Each retry of a step uses the same key derived from
   the run ID and step ID; pass that key to a downstream API which actually
   enforces idempotency. The SDK cannot make a non-idempotent API idempotent.
3. Implement `readState()` as an independent read-only query to your application.
   Return a complete projection of the fields referenced by the contract. Do
   not derive it from agent text, a write acknowledgement, or requested changes.
4. Wrap each action in `verifier.step(id, async ({idempotencyKey}) => ...)`.
   Throw if the business API reports an error; for HTTP calls, check `res.ok`.
5. Call `finish()` and inspect `report.passed`. Export `evidence()` to your
   own storage for review and replay. A failed check must not be treated as done.

Read-state projections are sent to `https://horizon-orchestra.com/api/verify`.
Send only non-secret fields needed for assertions; replace identifying values
with opaque test identifiers. The beta does not persist or log request bodies.
Hosting infrastructure still receives the HTTP request.

## Verification semantics

The contract has `workflow`, ordered `steps`, and optional `invariants`.
Each step has `id`, `action`, `dependsOn`, `assertions`, and optional
`maxAttempts` (default 3). Assertions use JSON Pointer and typed `equals`.
Unknown steps, cycles, malformed pointers, and empty assertions are rejected.
All steps must retain their asserted state in the final complete read-back.

Evidence is `{id, initialState, events}`. Events, ordered by relative `atMs`, are:

- `call`: `callId`, `stepId`, `action`, `idempotencyKey`.
- `result`: `callId`, `status` (`success`, `error`, or `timeout`).
- `observe`: `callId`, `state` from independent read-back after the result.
- `finish`: completion claim after the preceding observations.

The SDK checks the contract and initial invariants before writes, requires
prerequisites, captures the action result, reads state, and calls Rust. A tool
response alone never verifies a step. A timeout may be recovered by a matching
read-back. Changed retry keys, retries after verification, prerequisite
violations, incorrect state, pending calls, and premature completion fail.

After a transient failure, repeat `step()` only after inspecting the reported
state and using the same run instance/key. If read-back itself fails, the SDK
stops; reconcile the business system rather than blindly repeating the write.
If verification is unavailable after a write, preserve `evidence()` and check
the business record. A network failure is not proof that the write failed.

## HTTP API

`POST /api/verify` with `Content-Type: application/json`:

```json
{"mode":"verify","contract":{"workflow":"your-workflow","steps":[]},"run":{"id":"run-1","initialState":{},"events":[]}}
```

The shape above is illustrative; valid contracts need 1–32 steps with
assertions and valid runs need 1–512 events. Use the complete downloaded
example rather than sending empty arrays. Maximum request size: 256 KiB.

Alternatively execute a built-in isolated sandbox:

```json
{"mode":"sandbox","scenario":"timeout_recovered"}
```

Available scenarios: `happy_path`, `timeout_recovered`, `false_success`,
`duplicate_refund`, `wrong_order`.

HTTP 200 means evaluation completed, **not** that the workflow passed. Check
`report.passed`. HTTP 400 is invalid input, 413 is oversized input, 415 is the
wrong media type, and 503 means verification is unavailable. Fail closed.
Results contain the report, contract, and evidence; exports can be reimported.
The endpoint accepts no URLs to fetch and never executes customer callbacks.

## Current boundaries

- The public `/api/verify` playground is stateless. Managed customer keys,
  isolated usage, and run allowances use `/api/v1/verify` after activation; see
  `/customer-guide.md`. Neither endpoint provides durable execution, hosted
  evidence history or an SLA. Approved pilots use a separate Stripe payment
  invitation after scope agreement.
- The SDK's run state is in memory. Store evidence yourself; process-restart
  recovery and concurrent/distributed execution require an external durable
  runner. Reusing a run ID does not restore earlier evidence automatically.
- Customer read-back is supplied evidence, not independently attested by
  Horizon. A dishonest or incorrect reader can fabricate state. Use a reader
  independent of the agent, server-side access controls, and reviewed contracts.
- Snapshot checks do not prove causality, completeness of your business policy,
  or absence of changes between observations. They are not a replacement for
  application transactions, authorization, or payment-provider guarantees.
- The sandbox is deterministic infrastructure testing, not an LLM benchmark.
  Unknown integrations are not advertised as supported connectors.

## Run Rust locally

In the repository, `site/rust/verify` is an independent Rust package. Install
the pinned toolchain from `rust-toolchain.toml`, then:

```sh
cargo test --locked
echo '{"mode":"sandbox","scenario":"timeout_recovered"}' | cargo run --locked --quiet
```

The native CLI reads one JSON request from stdin and prints a response.
Exit 0 means the workflow passed, exit 1 means verification found a failure,
and exit 2 means invalid input. Use this command in your CI quality gate.
