* feat(stress): add durable memory parity matrix * refactor(stress): split native search behavior * fix(stress): fail closed on delayed retries and parity * fix(stress): keep delayed retries in-flight * fix(stress): address final review edge cases
CI Contract
This directory implements a tiered CI contract. Each tier has a distinct job; a check belongs to exactly one tier on purpose.
| Tier | Event | Job |
|---|---|---|
| PR feedback | pull_request |
Fast, scoped signal for the author. May run slim matrices and path-scoped subsets. |
| Production gate | merge_group (merge queue) |
The authority on what reaches main. Runs deterministic checks in the same shape as push on the merged state. |
| Post-merge confirm | push to main |
Confirms the queue's verdict, warms shared caches, feeds Codecov/canaries. Should never be the first place a deterministic failure appears. |
| Deep / scheduled | schedule (nightly) |
Exhaustive suites too slow for the queue: legacy v1 matrix, full browser E2E, stress scans. |
The invariant
No deterministic failure may be main-only. If a check runs deterministically
on push to main, the merge queue must run it in the same shape first
(merge_group does not support paths: filters — use a changes scope job
instead). External/live checks (canaries, deploys, releases, benchmark
thresholds) are exempt: they stay out of the queue by design.
The canonical local composition of the deterministic Reborn gates is
scripts/ci/run-hermetic-deterministic-suite.sh all. CI invokes the same
checked-in stages through that runner so credentials, ambient behavior,
mutable roots, clock/seed inputs, and non-loopback egress have one mechanical
boundary. Setup and exclusions are documented in
docs/internal/hermetic-deterministic-suite.md.
The WASM WIT compatibility lane uses two risk scopes. Pull requests run it only
for direct WIT, WASM host, extension, compatibility-test, or lane-workflow
changes. Root Cargo.toml and Cargo.lock changes are broader workspace risk:
they run the lane in the merge queue, before landing, without adding the full
WASM build to ordinary PR feedback. Push and deep-CI runs remain exhaustive.
reborn-tests.yml follows the same PR-versus-queue contract. Pull requests use
reborn_pr_test_plan.py to run affected crate buckets and exact changed root
and integration suites without LLVM instrumentation. Recorded QA
replay remains a baseline on every pull request because it detects ordering
and cross-surface regressions that cannot be inferred from changed paths. The
full transitive reverse workspace dependency closure is included in PR crate
selection for production-source changes. Package-owned tests, examples, and
benches run only their owning package because they cannot alter a dependent
package's production behavior. A changed top-level Cargo test, example, or
bench target runs directly; nested support changes retain all owning-package
targets. Foundational-crate changes that span more than
three canonical buckets coalesce every changed and dependent package into at
most three PR jobs instead of omitting consumer tests. The merge queue and
pushes to main still run every crate bucket, root partition, group suite,
integration lane, recorded replay, and coverage gate. Shared root or
integration support changes run one
representative PR partition or lane, with the exhaustive fan-out required in
merge queue. CI workflow, CI script, coverage-policy, toolchain, and workspace
topology changes use their owning static/compile gates on the PR and receive
the exhaustive Reborn matrix in merge queue. Unknown paths and empty diffs
fail quickly at planning instead of silently launching or skipping an
unbounded matrix. A planner execution or schema failure also fails the
required check loudly.
Cargo.lock is scoped only when a structured base/head comparison proves that
the lockfile changed solely in dependency lists for workspace manifests changed
by the same PR. Package additions/removals, versions, checksums, unrelated
workspace edges, and unreadable base state receive their full dependency
breadth in merge queue; changed workspace manifests still select their
production dependency closure on the PR. The stress tool
is owned by ironclaw-stress.yml, and changed-line coverage exemptions are
schema-checked in Code Style instead of launching unrelated integration lanes.
The queue therefore preserves exhaustive deterministic evidence while
ordinary PRs avoid consuming 20-plus runners for unrelated lanes. Pull-request
parallelism is capped at three crate buckets, one root partition, and one
integration lane; merge queue and main retain full matrix parallelism so this
feedback optimization does not serialize the production gate.
Full-coverage crate buckets run one multi-package cargo llvm-cov invocation
per bucket, preserving every package test and the bucket LCOV artifact while
sharing dependency compilation across packages in the same job.
Tests (Reborn) owns Rust crate, root, architecture, runtime, and coverage
contracts. Code Style owns WebUI lint, Vitest, and the production build on all
code events. Pull-request Clippy covers production libraries and binaries for
directly changed workspace packages with all features. Test-only and CI-only
PRs do not compile an unchanged workspace solely for linting. Root
Cargo.toml and Cargo.lock changes lint every workspace package because
their dependency and feature impact is workspace-wide. Merge queue and main
lint the full workspace, add test and example targets, and run the
default-feature matrix. Code Style's CLI Rust smoke and Reborn E2E's
four Rust groups run on merge queue and main, where they validate the
exhaustive merged state, but do not repeat those contracts on PR runners. The release-binary
smoke harness self-test remains in Code Style's fast deterministic job on every
code PR. Reborn E2E continues to build the real product binary and run all
browser/provider lanes on pull requests.
Critical mutation manifests, selection logic, and changed-function resolution
run on each PR. Actual cargo-mutants execution is a merge-queue gate, avoiding
a long and low-frequency compile workload on the author feedback path without
allowing selected mutations to reach main untested.
The four provider-operation shards run as two concurrent pairs. Each shard
keeps its own pytest process and hermetic runtime, while each pair shares one
runner and one Emulate checkout/build cycle. This preserves shard isolation
without restoring the two runner allocations removed by pairing.
The fast Responses API and black-box contracts share the browser worker in
separate hermetic pytest processes, capping the E2E fan-out at four workers.
History: the slim-vs-full clippy matrix violated this — the queue linted only
--all-features while push linted a broader matrix, so feature-gated dead code
could pass the queue and turn main red post-merge.
Required checks and where they're enforced
Branch enforcement lives in the repository ruleset "Main" (Settings → Rules
→ Rulesets), not classic branch protection — the classic API reports
required_status_checks: null. Inspect the effective rules with:
gh api repos/nearai/ironclaw/rules/branches/main
The ruleset enables the merge queue and requires these check contexts (stable roll-up job names, never individual matrix jobs):
| Check context (job name) | Workflow | Status |
|---|---|---|
Code Style (fmt + clippy) |
code_style.yml |
required |
Tests (Reborn) |
reborn-tests.yml |
required |
Reborn E2E |
reborn-e2e.yml |
candidate — require once queue cost is confirmed |
Platform & Compat |
platform-and-compat.yml |
candidate — require once queue cost is confirmed |
The 2026-07-30 queue-cost audit found only one retained merge_group sample
for each candidate: Reborn E2E took 594 seconds and failed; Platform & Compat
took 364 seconds and passed. Pull-request history was healthier (Reborn E2E
p50/p95 877/1124 seconds; Platform & Compat 415/492 seconds), but one real queue
sample—especially a failing E2E sample—is not enough evidence to alter the
repository ruleset. Both checks therefore remain candidates. Refresh the
workflow-run sample before promotion; a workflow being present on
merge_group is not itself proof that it is safe to require.
Rules for a roll-up job that is (or may become) required:
- Trigger on
merge_groupand report on every run (if: always()), so the queue never waits on a check that will never arrive. - Tolerate
skippedonly for jobs that are event- or scope-gated by design; anything that ran must have succeeded. - Assert expected coverage where feasible — the Code Style roll-up fails if a merge-queue/push run's clippy matrix is missing any required feature lane, so a "green but slim" regression cannot come back silently.
Code Style deliberately consolidates formatting, dependency policy, static
guards, panic checks, and composition-budget checks into one
fast-checks job. These checks complete in seconds to a few minutes and do not
benefit from separate runners. Clippy and WebUI checks remain separate because
they are expensive independent gates. The CLI Rust smoke remains a separate
merged-state lane; its unique Python harness contract runs in fast-checks on
pull requests.
Reborn release and manual compile preflight
ironclaw-release.yml is the tag-only cargo-dist publisher for the shipping Reborn
ironclaw package and binary. Matching ironclaw-v* tags build the seven
release targets, produce archives and checksums plus shell, PowerShell, and MSI
installers, and create the tag's GitHub Release. After that Release exists, the
workflow publishes the regular nearaidev/ironclaw Docker image with version,
latest, and source-SHA tags. cargo-dist derives the Release title and body
from the release metadata and CHANGELOG.md.
cargo-dist 0.31 generates workflow-wide contents: write and does not expose a
setting for built-in job permissions. The checked-in workflow is therefore
intentionally hardened beyond the generated template: repository access
defaults to contents: read, only host receives contents: write, and local
and global build jobs do not receive GH_TOKEN. allow-dirty = ["ci"] in the
workspace dist config prevents a later dist generate from silently restoring
the broader permission. When updating cargo-dist or its CI configuration,
remove that allow-dirty entry temporarily, regenerate the workflow, reapply the
permission boundary, and verify it with:
cargo test -p ironclaw --test smoke release_ci_ -- --nocapture
rg -n "permissions:|GH_TOKEN" .github/workflows/ironclaw-release.yml
reborn-release-compile.yml remains an independent compile-and-smoke preflight
that runs only through workflow_dispatch. It uploads temporary evidence
artifacts but does not publish a Release, and it is not called by the tag or
pull-request workflows.
| Rust target | GitHub runner |
|---|---|
x86_64-unknown-linux-gnu |
ubuntu-22.04 |
x86_64-unknown-linux-musl |
ubuntu-22.04 |
aarch64-unknown-linux-gnu |
ubuntu-24.04-arm |
aarch64-unknown-linux-musl |
ubuntu-24.04-arm |
x86_64-apple-darwin |
macos-15-intel |
aarch64-apple-darwin |
macos-15 |
x86_64-pc-windows-msvc |
windows-2022 |
The cargo-dist release and manual preflight both build the ironclaw package
and binary without backend feature flags; database backends compile
unconditionally. The tag publisher extracts each target's completed cargo-dist
archive and runs the shared release smoke before the artifact can enter the
upload set. The manual preflight runs the same smoke against its exact
dist-profile binary before uploading compile evidence. The smoke uses an
isolated home to verify CLI identity/help, the supported profile contract,
production-derived bundled-extension discovery through a real local runtime
assembly (including first-party, MCP-server, and WASM-tool runtime kinds), the
non-empty libSQL database created after its migrations complete, and the
migration-dry-run profile selection. Its catalog denominator is the shipping
binary itself, so adding or removing a bundled package does not require a second
hand-maintained CI list. The musl entries also use readelf to reject a program
interpreter or dynamic-library dependency, which prevents an installed musl
loader on the build runner from hiding a non-portable artifact.
The scheduled Postgres capacity lane complements that portable gate by building
the same canonical binary with --profile dist, starting serve, applying the
Postgres-backed runtime migrations, and driving its authenticated API against a
mock provider. Weekly live provider jobs build the bundled WASM extensions and
exercise the Anthropic and OpenAI-compatible provider paths. The portable
archive smoke itself does not invoke every WASM/MCP/script runtime lane or
execute the generated shell/PowerShell/MSI installers; those remain separately
owned evidence and a green portable smoke must not be read as proof of them.
Deep tier (nightly)
codebase-graph-refresh.yml runs at 02:30 UTC, regenerates the committed
.codebase-memory/graph.db.zst bootstrap snapshot from the default branch with
a checksum-pinned codebase-memory-mcp, and opens or updates a normal review
PR through the repository GitHub App. It never pushes generated state directly
to main; required checks and human approval remain in the path.
nightly-deep-ci.yml (04:00 UTC) reuses platform-and-compat.yml,
reborn-tests.yml, and reborn-e2e.yml via workflow_call at full scope.
reborn-e2e.yml owns the deterministic Reborn surface coverage used by pull
requests, the merge queue candidate check, and main. The standalone
reborn-playwright.yml schedule owns the broader generated four-shard browser
matrix. ws12-suite-shards.toml records the source run, duration weights,
provider-world affinities, and owned waivers; its generator refuses missing
files, affinity splits, stale entries, and retry-enabled deterministic shards.
It is post-merge nightly coverage, not a required merge check. Failed nightly
shards upload server logs, Playwright traces, screenshots, and videos, and the
nightly watchdog owns alerting for that workflow.
The same deep reuse raises all existing property-test generators from 256 to
2,048 random cases, runs the bounded mutation frontier, and replays the complete
hermetic journey/provider-fault inventory. ironclaw-stress.yml adds a
15-minute libSQL user-session soak alongside its libSQL ramps and
shipping-profile Postgres API capacity lane. live-canary.yml keeps the
three-hour Reborn WebUI cadence. Workflow-contract sabotage tests fail if any
of these schedules, guards, merge/main triggers, or release gates disappear.
The Reborn E2E job also publishes product-surface-coverage-<sha>. Its JSON and
Markdown files join the shipped capability denominator with typed contract,
journey, and fault registries. The generator fails on unclassified or
unevidenced tested capabilities and lists owned gaps, waivers, and live-only
rows separately. Reporting imports the existing registries; it does not own a
duplicate CI capability or journey list. Provider journeys carry typed
scheduled-live bindings to the exact live-canary.yml job, case id, and
results.json artifact. The matrix reports those cells as scheduled, not
covered: a recorded trace or declared cron is never presented as a passing
live result.
The legacy v1 suite (test.yml) is deliberately not invoked — see the
freeze note in nightly-deep-ci.yml. Two hard-won gotchas are encoded in
the configuration:
github.event_namein a reusable workflow is the caller's event — it is neverworkflow_call. Conditions written asgithub.event_name == 'workflow_call'silently skip when invoked from nightly (this hid the Windows/bench/docker deep coverage). Called workflows use thedeepmarker input instead: it defaults to true and only materializes underworkflow_call.- A called workflow that references
secrets.*needs those secrets passed at the call site, either through an explicit mapping (preferred for a narrowly privileged publish job) orsecrets: inheritwhen it truly needs the caller's full secret set. Otherwise the entire caller run dies at trigger time as astartup_failurewith zero jobs — including any in-run alert job. Nightly Deep CI had zero successful runs from its creation (2026-05-06) through 2026-07-08 — 65 of its 74 retained runs are startup_failures — precisely because this failure mode is invisible from inside the run.nightly-watchdog.yml(08:00 UTC) exists for exactly that: it checks each nightly's latest scheduled run from outside and posts the failure to Slack even when the run itself never started.
Nightly alerting
One path only: nightly-watchdog.yml (08:00 UTC) checks the latest scheduled
run of each nightly — Codebase Graph Refresh, Nightly Deep CI, Reborn
Playwright, IronClaw Stress. A
run that is missing, stale (>26h: the cron didn't fire),
or concluded anything but success posts a failure line (workflow, conclusion,
failed job names, run link) to the Slack channel behind
secrets.SLACK_WEBHOOK_URL — the same webhook the live-canary report uses —
and turns that watchdog matrix job red, so the watchdog's own run history is
the failure record. Successes post nothing, and there is no GitHub-issue
trail: the former in-run alert jobs and nightly-alert-issue.sh were removed
in favor of this single external check, because an in-run alert dies with its
own run on a startup_failure and can never see a cron that didn't fire.
Main branch and merge-queue alerting
main-ci-slack-alerts.yml watches completed workflow_run events for the
current push to main and merge_group workflows: Code Style, Tests
(Reborn), Reborn E2E, Platform & Compat, Replay Snapshot Gate, Code Coverage,
nearai-bench dispatcher tests, and Release-plz. Any watched run that concludes
failure, timed_out, action_required, or startup_failure posts a Slack
message with the workflow, conclusion, failed job and step names, available
failure annotations, commit, actor, and run link. Merge-queue alerts also
resolve the PR number from GitHub's gh-readonly-queue/main/pr-<number>-...
ref and include the PR title, author, and link.
Main-branch alerts go to secrets.MAIN_CI_SLACK_WEBHOOK_URLS; the value may be
a single webhook URL or multiple URLs separated by newlines or commas.
Merge-queue alerts go to secrets.SLACK_WEBHOOK_URL, the existing live-canary
channel. This keeps post-merge CI alerts in their dedicated channels while
making queue bounces visible alongside live-canary failures.
When adding a new workflow that runs on push to main, add its workflow
name: to the watched list in main-ci-slack-alerts.yml.
Code Coverage uses same-ref concurrency with cancellation. When merges land
faster than coverage completes, only the newest cumulative main commit keeps
running; superseded post-merge coverage runs do not consume runners needed by
pull requests.
Reborn-only release policy
For #6160, ironclaw-release.yml uses cargo-dist to publish only the canonical Reborn
ironclaw package. The active tag DAG consists of cargo-dist planning, the
seven target builds, universal installer generation, and GitHub Release
hosting, followed by the regular Reborn Docker image. Legacy v1 artifacts,
independently published WASM extensions, ironclaw-worker, and the old
registry-checksum path remain outside this DAG. The generated announce job
remains cargo-dist's final release step and does not restore those retired
products.
The docker-image job runs only after host creates the GitHub Release. If
Docker publishing fails, the existing GitHub Release and its artifacts remain
available while the overall workflow reports failure for retry/repair. Release
builds publish only nearaidev/ironclaw; they do not dispatch
nearai/ironclaw-dind because the caller explicitly sets trigger_dind: false.
The reusable docker.yml keeps its independent manual and hourly staging entry
points, including their existing optional DIND dispatch. The manual
reborn-release-compile.yml preflight remains independent from publishing.
Known accepted gaps (deliberate, revisit as needed)
- Windows clippy (
code_style.ymlclippy-windows) runs on push only; Windows build (platform-and-compat.ymlwindows-build) runs on push and in the nightly deep reuse. Windows-only breakage is accepted as post-merge; the Linux full feature matrix catches the dominant class (feature-gated cfg errors). - Benchmark compilation (
cargo bench --no-run) runs on push and nightly only, and the clippy lanes do not pass--benches. Bench targets exist only incrates/substrates/ironclaw_safetytoday. - Replay Snapshot Gate runs on push + via the nightly legacy suite; it covers the retiring v1 engine.
- The legacy v1 suites are deliberately invoked nowhere — v1 (
src/) is frozen pending removal.test.yml(the only place the rootironclawpackage's tests run) is no longer called by nightly, and the formernightly-e2e.ymlscheduler for the v1 browser suite (e2e.ymlfull mode) is deleted — it had zero successful runs in retained history. Untilsrc/is deleted, a v1 bug fix that must land should temporarily restore thedeterministic-deep-testscall innightly-deep-ci.yml(and/or dispatche2e.ymlmanually). Deletetest.ymlande2e.ymltogether withsrc/. - Broad full-path extension↔provider mutation coverage remains legacy-only:
test_reborn_emulate_full_path.pystill boots the legacy binary. The Reborn E2E job now replays every harvested live-QA model trace against Emulate's supported provider operations and runs a Reborn-native Drive read path throughironclaw serve; equivalent standalone mutation paths for every provider are still follow-up work. - Scope classifiers (
scripts/ci/classify-test-scope.shand per-workflowchangesjobs) are curated allowlists. Adding a new crate or test directory requires updating them, or the queue's scoped checks silently narrow. Keepreborn-e2e.yml'schangesregex in sync with itspaths:filters. - Code Coverage, IronClaw Stress, live canaries, Docker/release pipelines are informational or post-merge; they are not merge-gating.