CI integration¶
Estamora is built to be run from a pipeline: a CLI with a documented exit-code contract, a machine-readable report on standard output, and renderings for the CI systems people already use.
The contract with CI¶
| Code | Meaning | What a gate should do |
|---|---|---|
0 |
A conformant verdict was reached. | Merge or publish. |
1 |
The contract violated a requirement. | Fail; the contract is at fault. |
2 |
The suite could not decide. | Fail, and re-run or fix what made a vector unmeasurable. |
3 |
The profile or corpus was unusable. | Fail the build; the requirements are at fault. |
4 |
The environment failed. | Retry, then fail. The contract is not implicated. |
5 |
The runner itself failed. | Report a bug. |
64 |
The command line was wrong. | Fix the invocation. |
A failure never produces 0, 1 or 2 unless it names the contract. That is the
property that makes 4 a reliable retry this signal: an unreachable node can
never be reported as non-conformance, and a broken profile can never be reported as
a broken contract.
2 is separate from 1 on purpose. INCONCLUSIVE says the suite could not decide —
a vector was skipped, or an error meant it contributed nothing. Treating that as a
contract failure would report a broken runner as a broken contract; treating it as
success would report a suite that measured nothing as a passing one. If a gate wants
to be strict, 2 fails the job; if it wants to be forgiving about a flaky
environment, it can distinguish the two by code.
Output formats¶
$ estamora run --profile sep-41@1.0 --contract ./my-token.wasm \
--format junit --out conformance.xml \
--report conformance.json
| Format | For |
|---|---|
json |
The normative document — the one the specification publishes a schema for. Anything another tool consumes should read this. |
markdown |
A reviewer reading a pull request. |
junit |
An existing CI gate that already understands JUnit XML. |
text |
A terminal. The default for run. |
--out and --report are separate because a pipeline usually wants both from one
run. Only --report writes the normative document.
Two properties of the renderings are worth relying on:
- An undecidable vector reaches JUnit as
error, never asfailure. A CI system that treatserroras infrastructure andfailureas a defect will then classify correctly without knowing anything about Estamora. --reportis stable and re-renderable. A stored JSON report re-renders to a byte-identical document, so a job can publish a machine-readable result and a reviewer can render the same result as Markdown without re-running anything.
The Action¶
The shortest integration, and the one this repository maintains as an interface rather than
as an example. action.yml builds the tool from the revision the uses: reference pins,
runs it, and exits with the same code the run exited with:
name: Estamora conformance
on:
pull_request:
push:
branches: [main]
jobs:
conformance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# The profiles are normative and live in the other repository. Pin the
# revision: which revision of which profile a verdict was produced against
# is part of what the verdict means.
- uses: actions/checkout@v7
with:
repository: Estamora-Soroban-Layers/estamora-conformance-spec
ref: v0.1.3
path: estamora-conformance-spec
- name: Build the contract
run: cargo build --target wasm32v1-none --release -p my-token
- name: Measure it
uses: Estamora-Soroban-Layers/estamora-conformance-runner@v1
with:
spec: estamora-conformance-spec
profile: sep-41@1.0
contract: ./target/wasm32v1-none/release/my_token.wasm
format: junit
out: conformance.xml
report: conformance.json
Two things about that step are worth knowing before it runs in a busy repository.
It compiles the tool from source. The action deliberately does not download a binary:
a verdict is only meaningful if the tool that produced it is identified, and building from
the revision uses: pins is what makes the revision the identity rather than whatever a
release channel happened to point at. The cost is a few minutes on a cold runner, which the
Swatinem/rust-cache step below removes on the second run. If the cost matters more than
the pinning, install a released binary instead and pass install: false.
Pin a release, not a major tag, when a verdict has to reproduce.
@v1 follows the current major version, so it moves; @v0.1.3 does not. Use the major tag
while you are adopting this, and a release tag once a result you have published has to be
reproducible from the workflow that produced it.
The action sets status, exit-code and report as outputs, so a later step can branch on
the verdict itself rather than on a bare failure:
- if: steps.conformance.outputs.status == 'INCONCLUSIVE'
run: echo "::warning::Estamora could not decide; this is not a contract defect"
Caching, and a longer example¶
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Measure it
uses: Estamora-Soroban-Layers/estamora-conformance-runner@v0.1.3
with:
spec: estamora-conformance-spec
profile: sep-41@1.0
contract: ${{ vars.CONTRACT_ID }}
network: testnet
tags: authorization events
report: conformance.json
- name: Publish the report
if: always()
uses: actions/upload-artifact@v7
with:
name: estamora-report
path: conformance.json
Without the Action¶
Not every pipeline is on GitHub, and a job that already installs its own tooling should not install a second one. The released binary and the CLI are the portable surface:
- name: Install Estamora
run: |
curl -fsSL https://raw.githubusercontent.com/Estamora-Soroban-Layers/estamora-conformance-runner/v0.1.3/scripts/install-binary.sh | sh
- name: Measure it
run: |
estamora run \
--spec "$GITHUB_WORKSPACE/estamora-conformance-spec" \
--profile sep-41@1.0 \
--contract ./target/wasm32v1-none/release/my_token.wasm \
--format junit --out conformance.xml \
--report conformance.json
The installer verifies the archive against the release's published SHA256SUMS and refuses
to install anything it cannot check — which matters more here than anywhere else, because
the tool it is installing is the one whose verdict you are about to trust.
Note what the job does not do: it does not make the conformance result a condition
for anything else in the workflow, and it does not pass --no-fail. The exit code is
the gate. --no-fail exists for a job whose purpose is to publish a report on every
push regardless of the verdict; using it on a gate would make the gate decorative.
Two things to be careful about when wiring this up:
- Pin the profile revision.
--profile sep-41@1.0names a profile version, and the repository revision it is read from is separate. A profile is a document with an author; measuring against a moving branch means the meaning of a passing run changes without a commit in your repository. - Do not gate on a skipped vector. If a job reports
2because vectors were skipped, the fix is to give the run a way to establish the vector's world, not to relax the gate.docs/local-testing.mdexplains the case.
Running it in this repository's own CI¶
formats, lints, builds, tests, validates the in-repository profile and corpus, runs the cross-repository tests when a specification checkout is present, and runs the release checks. It is the same sequence the workflow runs, so a contributor can reproduce a red job without pushing.