Daily SDK QA¶
The bench runs the actual reference strategies through the existing fork harness and backtest CLI. It validates coverage against the trusted connector support contracts before starting any provider work. The design contract owns its scope.
The accepted chain and connector expansion has a separate 96-cell delivery inventory. It records candidates and missing rollout evidence, not additional supported capabilities. The current QA plan still derives support from trusted connector declarations. New pairs require actual reference outcomes and prepared offline backtests before support promotion. The composed borrowing/trading report retains the current shared-inventory, process-recovery and historical evidence.
Run locally¶
Install the locked environment and hook, then inspect the complete matrix:
uv sync --locked
uv run --locked pre-commit install
uv run --locked python -m scripts.qa_bench --plan --output artifacts/qa-plan
Run all cases with explicit read-only provider bindings:
make fixtures
uv run --locked python -m scripts.qa_bench \
--env-file .env --output artifacts/qa-daily
make fixtures places the pinned evidence copies under tests/fixtures/evidence/
from the local evidence tree. The GMX keeper drivers read those copies, so run it
before the bench or any manual pytest --fork-env run in a fresh checkout and after
the manifest changes. make qa-daily runs it first.
Use a new output directory for each invocation. Existing output is never reused
as evidence for a new run. Plan mode performs no provider calls. Linux needs
Anvil and working Bubblewrap/unprivileged namespaces. macOS uses sandbox-exec;
Full QA runs locally with retained evidence. Hosted CI runs lint, types, the
offline suite with evidence-dependent modules skipped, the CRAP gate over that
reduced coverage scoped to the functions a pull request changed, the 87%
statement coverage floor over the whole src/ directory from that same reduced
coverage, and CLI smoke
checks on Linux and macOS; it does not run the full QA suite. The
Fork Qualification workflow (.github/workflows/fork_qualification.yml) runs
the discovered fork lifecycle and, where the checked-out connectors declare
production EOA support, the production rehearsal for named references on hosted
runners against the repository's provider key; it is the fork qualification a
reference listed in tests/strategy_fork/rehearsal.py::UNREHEARSED needs before
it joins the default rehearsal selection. It also runs
weekly on main. Each run retains the case evidence as a
workflow artifact and spends no funds. GMX keeper drivers settle requests with
authentic signed reports read from the pinned evidence copies in
tests/fixtures/evidence.json, which the workflow downloads from the staging evidence
bucket before pytest. The manual Cell QA Matrix workflow runs one
connector/chain cell's QA cases on hosted runners in parallel; see
Hosted cell matrix for what it does and does not establish.
For a focused diagnostic run, select exact case IDs from the plan:
uv run --locked python -m scripts.qa_bench \
--env-file .env --output artifacts/qa-focused \
--case backtest/pinned/uniswap_v4_trading/economic
--lane selects offline, pytest, fork, faults, backtest or blocked. Unselected cases remain
visible as skipped, so a focused run is never displayed as a passing full bench.
The exit code for an explicit selection describes its selected cases. The full
run exits nonzero if any required case did not pass. Unexpected pytest skips and
empty test collections fail the corresponding case.
Inputs and coverage¶
The current plan validates 82 connector/chain/profile/model/action obligations in 93 cases, including eight explicit blockers for unregistered rollout references. The executable cases include seven references through both Safe/Roles operator variants and EOA on pinned and newly resolved forks; twelve backtest reference/model combinations with pinned, recent and short native-cadence variants; the full offline gate; focused fork faults and multi-chain outages; support refusals; and supplemental prepared-model action checks. Counts change with declared support and reviewed proof mappings, not by hiding failed cases.
The catalog in tests/qa/catalog.py records exercised actions and model
revisions. Backtest mode is an explicit field checked against the declared model,
not inferred from its revision label. Required support comes from the existing
connector composition.
Adding a supported chain, profile, action or model without a proof fails plan
validation. Every discovered strategy needs a catalog mapping. Supplemental
model tests are labeled component proofs; they are not additional strategy
lifecycles.
An unregistered candidate reference has an explicit blocked case and contributes no supported-action claims. The case remains in the full result denominator; omitting it or clearing its blocker fails catalog validation. Candidate blockers prevent a passing full bench even when all executable cases pass. Promoting its chain support requires replacing the blocker with reviewed proof mappings.
Author economic choices and named markets remain in each strategy's config.yaml.
Backtests use a separate backtest configuration. Every reference keeps providers,
account bindings and fork fixtures out of its author files: QA derives one operator
file per case from
examples/operators/, varying the pinned or fresh anchor, the Safe or EOA profile
and the shared or distinct operator there, and drives its fork lifecycle through the
public almanak run, pause, resume, teardown and shutdown commands.
QA writes and validates full resolved configurations, preserving all changes as
evidence:
- Fresh forks resolve one block identity per chain before case execution.
- Recent historical windows preserve the configured duration and cleanup period.
The explicit source lag in
tests/qa/policy.jsonis currently two hours on each chain, measured after the cleanup endpoint. Missing history is refused; the bench does not shorten the window or choose another provider. - Native-cadence probes use a ten-minute window and ten-minute cleanup, with the author interval and explicit 120-second hold / 60-second collection parameters where those parameters apply. The ordinary pinned/recent variants retain their authored holding parameters and explicit backtest cadence overrides.
- Detailed Aave history uses an explicit debt-free author variant and an hourly override in the long runs. Its native probe has no interval override.
- The EOA rehearsal harness supplies the explicit Ethereum test gas policy in
tests/strategy_fork/rehearsal-policy.jsonwhen a reference has no production policy. Provider selection still comes from that reference. Generated production configurations run only against identified local forks with Foundry test keys; this does not change author production authority.
Prepared backtests use a new dataset directory for cold preparation, then repeat preparation and run execution/reproduction with OS network isolation. Local IPC and loopback gateway calls remain available. Detailed history captures online reads and reproduces them in the same network isolation. Successful cases must show closure, delivered outcomes and the expected economic actions on each chain. Uniswap prepared runs also compare integer calculations with independent Quoter reads at the same sampled blocks, in both directions and several sizes.
Existing frozen fixture, recovery, integer boundary, vendor-source and model
regressions run in the full offline gate. The online fault lane runs tests/fork
and the multi-chain outage tests, including Aave liquidation/contract comparisons.
Results and limits¶
public/results.json is the versioned complete matrix. summary.md, index.html
and junit.xml present the same outcomes. private/ holds working files and
credentials generated by the harness. Both directories remain local. Evidence
export preserves dataset bytes or refuses to export a file containing known
credential material. Logs are redacted. A refused required artifact blocks the
case. The source archive and hashes retain the tested implementation, including
uncommitted changes; source changes during execution invalidate passing results.
public/proof-ledger.json links every declared connector, chain, action, account
profile, model and protocol variant obligation to its scheduled case IDs. An
obligation passes only when every mapped case passes. Failed, skipped, unstarted
or missing cases remain outstanding, including separate fresh-input and recovery
cases. The ledger preserves the tested source identity. It does not turn an
undeclared candidate into supported scope or replace the 96-cell rollout inventory.
Each case has isolated journals, gateways, forks and retained evidence. Provider
cases run serially initially; --workers controls only the offline pytest worker
count and accepts auto (the default) or an explicit positive integer.
The default runs the combined coverage/CRAP gate with -n auto. The fault lane
uses one pytest worker. The runner does not automatically
retry failures. Existing gateway collection limits retain their own explicit
bounded provider retry behavior. This is deliberately conservative until measured
complete daily runs establish useful concurrency and timeout settings.
Per-command records measure cold preparation, cached preparation, execution and reproduction wall time independently. Quoter comparisons and source resolution remain separate activities. The SDK's retained dataset and progress reports provide provider activity, model and cadence detail. No throughput or ETA claim is inferred from test counts.
The case timeout defaults to one hour. This is an operational bound, not a measured runtime target. A backtest case's execute and reproduce steps share that bound; dataset preparation keeps its own thirty minute bound. Timeouts, missing providers, unstarted cases and source-resolution failures remain visible. Unknown failures are labeled without guessing that the SDK or provider caused them.
Pass --previous path/to/results.json to write changes.json, comparing case
status/category with the previous run. Unchanged cases are absent from this file.
Supply the preceding local result explicitly. The bench sends no Slack/email
messages. An absent prior artifact is reported as a fresh comparison baseline.
Hosted cell matrix¶
The manual Cell QA Matrix workflow (.github/workflows/cell_qa_matrix.yml)
runs one connector/chain cell's QA matrix on hosted runners, one job per case,
through the same scripts.qa_bench --case entry point as a local run. It spends
no funds and installs no production permissions: fixture accounts are public
Foundry test keys on identified Anvil forks of the repository's read-only
Alchemy provider. Dispatch it with a connector ID and a CAIP-2 chain, or with
explicit case IDs:
Selection. scripts/ci/cell_qa_matrix.py selects every catalog case whose
obligations name the connector on the chain, which are the cases the proof
ledger maps to the cell. The fork and backtest lanes run by default and the
pytest lane can be added. The offline, faults and blocked lanes are
refused: the offline gate needs the retained evidence tree, fork/faults runs
every connector's fault suite, and a candidate blocker cannot pass. A selected
cell case outside the chosen lanes stays listed as not run. The reverse-fault
entries are the reversal tests in tests/fork/test_*reversal*.py that are
parametrized by one of the cell's references, or whose module declares it as
REFERENCE. Each one runs as its own pytest job. When no such test exists, the
run reports that gap and does not pass. The nodes input adds explicit
tests/fork or tests/strategy_fork node IDs, for example a cell's
recovery-foundation nodes. An unknown case, an empty cell and more than 256
entries are refused.
Execution. Each job runs on ubuntu-latest-l with Bubblewrap and Anvil and
installs the locked environment on CPython 3.12.13. At most eight jobs run at
once. Every job is a separate deployment with its
own gateway, so the per-deployment provider caps apply to each job, and the
Alchemy plan limit is the only ceiling they share. Nothing is retried.
Source and input identity. Every job checks out the run's commit. Before and
after its entry, each job records the commit, the bench's tree hash, the lock
hash, the Python build and the tool versions. The aggregate job recomputes the
tree hash from a clean checkout of the same commit. Any mismatch, or any change
during a job, blocks that entry. The plan job resolves the fresh fork origins
and the input time once and passes them to every job through
qa_bench --frozen-inputs. All fresh forks in one run therefore start from the
same block per chain, and all recent historical windows end at the same instant,
as in one local run.
Time budgets. Fork and pytest-lane cases keep the bench's one-hour case bound,
in a 90-minute job. A backtest case gets its cold-preparation budget from
tests/qa/policy.json plus 7200 seconds. The policy budget is 1800 seconds by
default and 7200 seconds for the five composed references it lists; detailed
historical cases prepare nothing. The extra 7200 seconds cover cached
preparation, execution, reproduction and model comparisons, and match the bound
the local runbook recommends for slow windows. The job timeout adds 30 minutes
for setup and upload. The largest case, a 7200-second native preparation, gets
14400 seconds in a 270-minute job, inside the six-hour hosted job limit. The
planner refuses any case whose bound cannot fit. The preparation budgets are
unchanged. Each job prepares its dataset cold from the provider, exactly as a
local case does, so no retained dataset is needed or reused. These are
operational bounds, not measured runtimes, and a timeout remains a blocked case.
Evidence. Only the bench's sanitized public/ tree leaves the runner. The
bench's export boundary redacts logs, drops credential files and refuses any
other file that contains a known secret. private/ never leaves the runner.
Reversal-node files pass through the same export. Before upload, each job scans
file names and bytes, including gzip members, for the provider keys. The key
values come from the environment only. On a match, the job uploads nothing but
its record, and the entry is blocked as unsafe evidence. The per-case source
snapshot is dropped from the job artifacts. The aggregate archives one
source.tar.gz for the verified commit. All artifacts are retained for 90 days.
Results. The aggregate job writes results.json, marked
"execution": "hosted_cell_matrix", and a job summary. The summary has one row
per planned entry, the cell's cases that were not run and a cell-scoped proof
ledger. The workflow fails unless every planned entry passed on one source. A
skipped, missing, timed-out, unsafe or mixed-identity entry keeps the result
incomplete_or_failed.
What a hosted cell run establishes¶
It can satisfy the first item of the per-cell package: the cell's fork and backtest cases and its reverse-fault node, passed on one commit. That commit must be the frozen integrated source, or the proof must survive the amendment's path-level renewal check. It is not the local retained-evidence gate and does not establish:
- Pytest-lane component proofs that read retained evidence, such as
model-actions/uniswap_v4/eip155:56. They cannot pass on hosted runners; run them locally with the evidence tree present. - The full offline suite with retained evidence, combined coverage, CRAP at most 30, Ruff, formatting and mypy (package item 2). Hosted CI's offline suite skips the evidence modules and is not a substitute.
- The recovery-foundation nodes (item 3), unless they were passed as
nodesand do not need retained evidence. Otherwise they remain local work. - Append-only sanitized evidence archival with checksum verification (item 4). Download the artifacts, verify their digests and seal them locally. The workflow writes nothing to the evidence bucket.
- GMX cells. Their keeper drivers settle requests with signed reports read from the
pinned evidence copies, which this workflow does not fetch, so those cases fail here.
The
Fork Qualificationworkflow fetches them.
Hosted weekly fork qualification¶
The Fork Qualification workflow also runs every Sunday at 03:17 UTC on main.
The scheduled run has no inputs: scripts.ci.fork_qualification_matrix
--production-cells selects every converted reference with a market on a
connector/chain cell that the checked-out connectors declare for production
(almanak support), and runs the lifecycle and the rehearsal for each. It
refuses an empty selection and a declared cell that no reference exercises.
Keeper-settled GMX references are selected like any other and replay their
signed reports from the pinned copies. At most 24 references run at once.
The select job summary lists each cell with its references. When a scheduled
run fails, the report job opens the issue "Weekly Fork Qualification failed on
main", or comments on it if it is open, naming the jobs that did not pass; the
next passing scheduled run closes it. A failed run is not proof of support, and
a passing run is not a complete per-cell evidence package: it holds no
reverse-fault case, recovery-foundation node, backtest or offline gate.
Local evidence and scheduling¶
Generated captures under docs/internal/reports/evidence/ and bench output under
artifacts/ are ignored by Git and remain local. Full offline QA requires the
retained captures at their original paths. A fresh clone needs a local copy of
those inputs; missing evidence must not be converted into passing or skipped
required cases. See the evidence policy.
Hosted daily QA and its GitHub watchdog have been removed. There is no hosted daily coverage or automatic replacement local schedule; the only hosted schedule is the weekly fork qualification, which covers the fork lifecycle and the rehearsal only. Operators can invoke the commands above with explicit provider bindings and retained results. Hosted CI skips evidence-dependent tests and reports that scope in its job summary.