Skip to main content
selo test writes up to four outputs. Three are safe to publish. The run state file is not, because it holds the private report link. A run that stops at input validation, at the run state check or at the preflight writes none of them. Relative paths resolve against the directory you ran the command in.

The run state file

selo test records each paid attempt in .selo/runs/<release>.json, where <release> is the --release value. The CLI uses it to refuse a second payment while an earlier outcome is unconfirmed, and to resume that attempt with --resume.
  • Location: .selo/runs/ under the directory you ran the command in. The directory is created with mode 700.
  • Permissions: the file is written with mode 600, atomically, through a .tmp file that is renamed into place.
  • One file per release: a new run for the same release replaces the file.
  • Format: pretty-printed JSON. Any extra or missing field makes the file unreadable to the CLI.
Never upload .selo/ as a CI artifact or commit it. The reportUrl field holds the full private report link.

Fields

string
required
Always selo.cli.run.v1.
string
required
The --release value. Also the file name.
string
required
The --api origin, for example https://api.useselo.xyz.
object
required
The endpoint under test.
string
required
algorand-mainnet or algorand-testnet.
string
required
The payer wallet’s Algorand address. Never the mnemonic.
string
required
The preflight the attempt pays for. --resume reuses it.
string
required
selo-cli- followed by the first 48 hex characters of the SHA-256 of <preflightId>:<payer>. --resume sends it again so Selo never settles a duplicate.
string
required
paying, unresolved, rejected or concluded. See Phases.
string
required
Why the run is in this phase: the paid request is being sent while paying, otherwise the same reason the gate line and the summary show.
string | null
required
The transaction id of your payment to Selo, computed from the signed payment before it was sent. Look it up on an Algorand explorer when an outcome is unconfirmed. null when nothing was signed. A resumed run keeps the id recorded first.
string | null
required
Selo’s job id, when a report came back.
string | null
required
PASS, FAIL or INCONCLUSIVE as reported, when a report came back.
string | null
required
The full, private report link, when a report came back. This is the only place the CLI keeps it.
string
required
When the file was last written, as an ISO 8601 timestamp.
.selo/runs/<release>.json

Phases

While the phase is paying or unresolved, every selo test for that release prints recovery instructions and exits 2 without contacting Selo. See Resuming an unconfirmed run and Recover an interrupted run.
The CLI never clears a paying or unresolved run on its own. Removing or editing the file removes that protection: the next run starts a new preflight and pays again. Do it only after you have checked on chain whether the earlier payment settled.
If the file exists but cannot be read or does not match the shape above, every run for that release exits 2 with the run state <path> is unreadable; inspect it before paying again.

The gate summary

--summary <file> writes a selo.gate.v1 JSON summary: the gate decision, the verdict, every check, both payment legs and the job id. The report link is redacted, so the file is safe to upload as a CI artifact. Parent directories are created, and an existing file is overwritten.

Example

These are the summaries of the two Mainnet runs against the demo merchant on 26 October 2026, as committed in the Selo repository’s docs/evidence/.
g5-release-gate-fixed-v2.json
In the broken-v1 run, discovery_contract was INCONCLUSIVE because the Bazaar catalog could not be read at the time. The proven response_contract FAIL decides the verdict regardless.

Fields

string
required
Always selo.gate.v1.
object
required
The release you named.
object
required
string
required
algorand-mainnet or algorand-testnet, from --network.
string
required
ALLOW only for a fully evidenced PASS, otherwise BLOCK.
number
required
The exit code of the run: 0, 1, 2 or 3.
string
required
The gate’s reason, the same text as the gate line in the terminal. For example PASS on every release check, FAIL: response_contract, INCONCLUSIVE: EVIDENCE_INCOMPLETE, or a refusal message. See Exit paths.
string | null
required
PASS, FAIL or INCONCLUSIVE as reported by Selo, or null when no report came back.
string | null
required
Why the verdict is INCONCLUSIVE: NETWORK_UNAVAILABLE, FACILITATOR_UNAVAILABLE, TARGET_TIMEOUT, BUDGET_EXHAUSTED, PAYMENT_UNRESOLVED, EVIDENCE_INCOMPLETE, AUTHORIZATION_LAPSED or INTERNAL_ERROR. Otherwise null.
object[]
required
One entry per release check in the report, empty when no report came back. Evidence ids are not included; they are in the attestation.
string[]
required
Warnings from the report, empty when there are none or no report came back.
object
required
Both payment legs.
string | null
required
Selo’s job id, or null when no report came back.
string | null
required
The report link with its private token replaced by <private>, or null when no report came back. A link that cannot be parsed becomes <private report link>.
string
required
When the CLI decided the gate, as an ISO 8601 timestamp.

The attestation file

--attestation <file> saves the signed attestation that Selo returns with the report, exactly as received, as pretty-printed JSON. It holds no secret and is safe to publish. selo verify checks it offline. When Selo returns no attestation, the CLI prints attestation none: Selo issued no signed attestation for this run and writes nothing. The file is an envelope { payload, signature }. The signature is Ed25519 over the canonical JSON of payload: object keys sorted at every level, no whitespace. See Signed attestations.

Fields

object
required
The signed statement.
object
required
g5-release-gate-fixed-v2-attestation.json

GitHub step summary

When the GITHUB_STEP_SUMMARY environment variable is set, selo test appends the summary to that file as Markdown. GitHub Actions sets the variable for every step and renders the file on the run’s summary page. Each run appends one block: a heading with the gate, the reason in bold, a table of the release, target, network, verdict, both payment legs and the job, then one row per check. The checks table is left out when no report came back. Pipes in values are escaped and line breaks become spaces. This is the block for the fixed-v2 run, generated from its committed summary:
GITHUB_STEP_SUMMARY
A blocked run starts with ### Selo release gate: BLOCK: do not promote. When there is no data, the rows read none, no settlement recorded or no payment recorded.

What is redacted

Input files

For completeness, the files the CLI reads: