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 mode700. - Permissions: the file is written with mode
600, atomically, through a.tmpfile 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.
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
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.
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’sdocs/evidence/.
- fixed-v2 (ALLOW)
- broken-v1 (BLOCK)
- No report (shape)
g5-release-gate-fixed-v2.json
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
Full example: the fixed-v2 attestation
Full example: the fixed-v2 attestation
g5-release-gate-fixed-v2-attestation.json
GitHub step summary
When theGITHUB_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
### 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:Related
- selo test writes these files.
- GitHub Action reference uploads the summary and the attestation as an artifact.
- Objects documents the report and the attestation as the API returns them.