On this page
On this page
Testing and CI
Scope selection
Changed-scope detection, native lane selection, and the per-area routing rules that decide which lanes a diff selects. Part of the CI scope and routing index.
Runner placement is separate from coverage selection. On automatic canonical hybrid first attempts, preflight may offload security, up to three selected Control UI unit rows, and only the browser-extension E2E row when the complete hosted base has at most 40 rows, keeping optional additions within 45 total. Base counts above 40 keep those rows on Blacksmith with their original tests and workers; an eligible base above 45 also emits a warning while retaining the complete base manifest. Narrow PR admission reserves the full lint/type row templates plus one hosted check-plan job. Later row reductions do not expand that admission, so optional offloads can remain on Blacksmith even when the final selected inventory would fit.
Scope and routing
Main-tier CI is hourly by default.
Main-push lane selection below describes the opt-in OPENCLAW_CI_ON_PUSH=true
path. Hourly runs select the complete main tier, including Android, without
changed-path filtering. Full manual and release validation additionally select
the release-only proofs described in the scheduled-workflow guide.
Scope logic lives in scripts/ci-changed-scope.mjs and is covered by unit tests in src/scripts/ci-changed-scope.test.ts. Ordinary manual dispatch skips changed-scope detection and makes the preflight manifest act as if every scoped area changed. The exact-head release_gate exception evaluates the fetched pull request merge tree and retains its macOS, iOS-build, and generated-native-locale decisions while still verifying native sources.
PR vs hourly vs release
Ordinary pull_request runs, including contributions from forks, use the
following validation tiers. For deferred jobs, the preflight manifest emits false
selection flags and omits check-matrix rows, so openclaw/ci-gate accepts their
expected absence. Import-cycle and Kysely guardrail checks remain blocking in the required guard rows.
| Proof | Ordinary PR | Hourly main tier | Full release validation |
|---|---|---|---|
| Real-Gateway Control UI E2E | Deferred | Existing inventory | Existing inventory |
| Windows Node tests | Deferred | Existing five-row inventory | Existing inventory |
| macOS Node and Swift jobs | Deferred | Existing tests/packages | Existing native phases |
| iOS build/simulator smoke | Deferred | Existing tests phase | Existing Release/tests phases |
| Published-driver update | Deferred, including update owners | Existing managed update cell | Existing managed update cell |
| Dependency/dead-export scan (Knip) | Deferred | Existing check | Existing check |
| Import cycles (runtime and types) | Parallel guard for TypeScript changes | Existing checks | Existing checks |
| Kysely guardrails | Parallel guard for source TypeScript | Existing check | Existing check |
| Other runtime topology/architecture | Deferred | Existing check | Existing check |
| Android screenshots | Deferred | Already excluded | Existing capture proof |
Import-cycle checks are blocking correctness guards. Four cycles reached main after
the architecture deferral (#165174,
#165274,
#165304, and
#165346).
Hourly job 111622440413
spent about 14 seconds on runtime cycles and 57 seconds on Madge. The runtime
checker already ran in the PR guard; restoring Madge also restores type-cycle
coverage without the larger architecture bundle. On ordinary PRs selecting Madge
or Kysely, check-guards-architecture runs the selected source checks beside
check-guards. Both rows start after preflight, use the same runner routing, and
must pass. This adds one checkout/setup while removing the serial dependency
between the two groups. Non-TypeScript guards and hourly/manual/release commands
retain their existing execution owners.
Kysely guardrails run in the parallel source-check row when TypeScript under src/,
extensions/, or packages/ changes. Two raw-SQL boundary violations reached
main after the deferral: 495c243be11a
(#165546, repaired by
#165637) and fe43e10f4952
(#165678).
Hourly job 111895593533
spent about 35 seconds on the guardrail. This restores lint:kysely only;
generated database type verification (db:kysely:check), deprecated-API,
wrapper-shadowing, JSDoc, and database-first checks remain in their existing
hourly/manual/release architecture row.
These jobs were frequent PR critical paths despite overlapping source tests and static checks. Moving their admission preserves the same cases, assertions, commands, worker limits, and runner choices in their retained tiers. A defect caught only by a deferred job can now be discovered after merge. Android screenshots remain release-only because hourly main already excludes screenshot capture; this change does not expand the hourly inventory.
Ordinary manual dispatch and exact-head release_gate fallbacks keep their
previous selections. The tier decision uses the actual workflow event, not the
PR planning mode used by dispatch fallbacks. Mocked/bundled checks-ui-e2e,
UI unit tests, browser-extension proof, Android unit/lint work, and independent
protocol/localization checks retain their existing PR owners. SwiftLint and
SwiftFormat run inside the moved Apple jobs and move with them; no new jobs are
introduced. Standalone native Periphery workflows and iOS screenshot jobs retain
their existing selection.
Published-driver update cell
scripts/lib/ci-published-driver-update-plan.mts excludes ordinary PRs, even
when an update owner or the cell itself changes. Current main-tier and ordinary
manual/release CI select the cell independently of changed paths; frozen
targets predating the harness omit it. Exact-head release_gate planning keeps
its existing updater, activation, handoff, canary, and startup-trace owner map,
with an unavailable diff retaining the cell. Execution also requires the selected checkout revision to equal
the caller's github.sha. Exact-head dispatch fallbacks and other target-ref
dispatches selecting a different revision skip this cell and record the reason
in the CI gate summary; they do not provide published-driver proof for that target.
The reusable workflow checks out only github.sha, with credentials disabled,
read-only contents permission, no inherited secrets, and dependency caching off. It cannot
accept a caller-selected checkout ref in a different cache scope. The selected
build-artifacts job packages its existing build with the canonical integrity
check and publishes one candidate tarball. The cell downloads that same-run
artifact by its immutable ID, verifies its SHA-256, and skips host dependency
installation and candidate compilation. Successful producer artifacts can be
reused when only the failed consumer job is rerun.
The checksum gate prints both digests and rejects mismatches before starting
the update. The download action's digest-mismatch: error input configures its
archive verification policy; that input line is not a reported mismatch.
The published driver version is resolved once from npm. Its installed prefix is cached as an archive keyed by that version, runner platform, and the pinned Docker recipe. PRs only restore this exact key and mount the seed read-only; the update runs against a private extraction. Only canonical main push, scheduled, and manual runs prepare and save missing seeds, before any candidate executes. A missing seed retains the ordinary published npm install path.
The retained main and release cell initializes synthetic state with its initial Doctor repair pass. Readiness keeps its 100 ms interval, 30-second overall limit, and one-second per-request limit. Managed update, recorded-run, restart, running-build, and readiness assertions are unchanged.
The cell reserves termination and diagnostic time within its twenty-minute budget.
Its command deadline returns a failed step with the active phase recorded,
instead of cancelling the workflow through a job-level timeout. The shared bare
Docker runner installs the latest stable npm package as the driver and supplies
a disposable OS account for managed-service ownership. A private Docker volume
holds the disposable npm installation and retained runtime on one native
filesystem, avoiding OverlayFS's forced full-runtime copies. The runner removes
the volume after its container settles, including failed updates. It performs one
managed update with two synthetic agents and isolated state, requiring a
finished update, the running candidate version, HTTP 200 from /readyz, no
canary/identity/lease warnings, and no candidate-startup or authority-check
failure in the recorded run. This catches compatibility failures hidden by
tests that use the same source for both driver and candidate. The selected job
is required by openclaw/ci-gate, reserves one GitHub-hosted runner in the
existing capacity accounting, and has a twenty-minute cell budget.
Labeler skips PR edits without title or base-branch changes. These ignored edits use isolated per-run concurrency groups so they cannot cancel running labeling or replace useful pending work. Opened, reopened, synchronize, and title/base-edit events retain the shared per-PR group and supersede older labeling runs. Issue labeling and manual backfills retain their existing non-cancelling ref group.
Affected main pushes and exact-head release_gate fallbacks retain one required ios-build (smoke) phase and its Xcode, Swift, and Watch Rust tooling. Current smoke always runs Swift lint and compiles the app and test bundles once with build-for-testing. The retained scripts/lib/ci-ios-smoke-plan.mjs owner describes the existing voice/media/typography and Access/chat lifecycle simulator groups from their source owners. Both groups own the app's real hosted startup and linked runtime sources; the voice group also audits Watch and ActivityWidget typography. Their selected test files and fixtures select their respective group. Build/test configuration and unavailable path metadata retain both groups.
When neither simulator group is selected, smoke uses a generic simulator build destination and skips simulator selection, boot, and SimSlim preparation. Selected groups keep separate test-without-building invocations against the same built products. The preflight and iOS job summaries record each group's selection and reasons. Repository variable OPENCLAW_CI_IOS_SIMULATOR_FULL=true (or 1) retains both groups for selected historical PR jobs; it cannot re-enable iOS on an ordinary PR. Main, scheduled, manual, release-gate, and compatibility planning retain full group selection. Existing historical execution contracts remain unchanged.
Smoke still omits Rust tests and the broader app/notification lifecycle and Watch simulator suites. A failed, cancelled, or unexpectedly skipped smoke still fails openclaw/ci-gate.
Hourly main-tier CI retains ios-build (tests), including Rust, voice cleanup, native Access, and every focused app/notification lifecycle test, including its Watch messaging and activation coverage. Managed attachment UI/export proof, Watch operation simulator suites, and Watch delivery UI proof belong to full-tier manual/release validation; hourly runs also exclude screenshot capture. Main-tier and smoke builds use the selected simulator's native architecture without compiler indexing; voice and lifecycle tests retain logs and xcresult bundles without Xcode's verbose diagnostic collection. Only a canceled selected scheduled ios-build on canonical main can pass the aggregate with a coalescing notice; this delegates proof to a later job rather than validating the canceled revision. Other failures, unexpected skips, and cancellations remain failures. See scheduled iOS aggregation.
Screenshot-input changes on pull requests, admitted pushes, and their exact-head release_gate fallbacks select both device shards and the evidence reducer through shouldRunIosScreenshots: iOS UI/resources/UI tests and Fastlane configuration, linked Swift sources, and capture/build tooling. Native unit-test-only Swift edits do not select screenshots.
Full-tier manual CI (workflow_dispatch with validation_tier=full, release_gate=false, and release_scope=full), including full-scope release validation, retains separate Release device and Debug/native-test phases plus the full iPhone, iPad, and Watch screenshot matrix. The test phase keeps universal simulator builds, failure diagnostics, Rust, voice cleanup, native Access, lifecycle, managed attachment UI/export, Watch operation, and Watch delivery UI tests. Every case and assertion moved out of hourly CI remains in this phase. The two device shards build their own simulator apps independently of ios-build; scenarios stay serial within each device, with Watch evidence in the iPad shard. A hosted reducer verifies the exact evidence union and provenance before publishing the canonical screenshot artifact. Provenance binds every shard to the reducer's run, target SHA, and workflow SHA, not its attempt: rerunning one failed shard reuses the other shard's evidence from its earlier successful attempt, while GitHub starts the reducer only after each shard's latest attempt succeeded. The final gate requires both build phases, both screenshot shards, and the reducer. Frozen compatibility targets retain their existing Debug-only full-manual contract and screenshot exclusion; npm-beta and npm-stable still defer native qualification. All iOS build phases and screenshot shards use Xcode 27 on GitHub-hosted xcode-27, the preview macOS 27 image. A pure iOS app change does not select macOS jobs by itself.
The iPad shard captures Watch from the Watch app compiled by its own fresh screenshot build, after all four iPad captures pass. It does not rebuild Watch or transfer simulator products between jobs. The standalone watch_screenshot lane still prepares and builds a fresh Watch app. A missing or invalid product fails capture without a rebuild fallback; completed iOS screenshots and result bundles remain available in the failure artifact.
Separate iOS and macOS Periphery workflows enforce a zero-findings dead-code policy. Each runs only when a non-draft pull request touches its native scan scope, or when manually dispatched.
The shared PR commenter reads each producer's fixed run title to distinguish report admission, explicit converted_to_draft cleanup, passive draft events, and manual runs. Reports require a live open PR at the same repository and head with draft status off; draft cleanup requires draft status on. Passive runs cannot publish or supersede reports. Newer eligible runs and attempts supersede older results, including while pending. A report-admitted run that successfully detects no scan scope can clear an existing comment; draft cleanup names draft status instead of claiming scope loss. The commenter rechecks PR state, repository, head, and draft status immediately before writing, but separate REST calls are not atomic.
Runs without recognized admission metadata are logged no-ops: they neither publish nor supersede. After rollout, a new pull-request source event is needed to produce an eligible run; rerunning an old unmarked run does not recover its original admission intent.
The iOS, macOS, and both shared OpenClawKit Periphery scans use Xcode 27 on GitHub-hosted xcode-27. The toolchain change preserves all four hosted scan jobs, their scope, workloads, timeouts, artifacts, and rerun behavior; it changes no Blacksmith placement or registration counts. Periphery remains pinned to 3.8.0, with native compatibility required across all four consumers when its toolchain changes.
- Max-lines baseline maintenance uses the existing required
checks-fast-baseline-ratchetsguard on pull requests. When that guard is selected, changing onlyconfig/max-lines-baseline.txtdoes not add unrelated tooling tests; mixed changes retain their owning tests and boundary checks. The guard still verifies the exact tested merge tree against its prepared base and rejects baseline expansion or stale entries. Changes to the ratchet implementation, CI routing, or other config files retain their existing test selection. - CI workflow edits validate the Node CI graph and workflow linting on PRs. Windows proof is deferred with the other platform jobs listed above. Workflow edits do not force Android native builds by themselves; those stay scoped to platform source changes.
- Native inputs beside Node changes retain the platform checks admitted by their event tier and the Node tests that own, directly import, or explicitly watch those inputs. Native-only inputs do not request the complete Node inventory. Independent static compiler and boundary checks retain their existing selection.
- Compact planner policy changes select the planner, inventory, routing, and policy tests owned by those files, plus affected import consumers, protected regressions, and the fixed smoke set. They do not select the complete compact core or every extension. Hourly main and Full Release Validation still exercise the full inventories through their existing owners.
- Control UI source selection retains the complete ordinary UI unit family when its area changes. Control UI E2E on PRs uses the existing PR-exempt owner policy: a fixed smoke cohort plus tests whose source owners, imported dependencies, harness inputs, or test files changed. Unmapped tests run on hourly main and in release validation; PRs still select them when their own file or an imported dependency changes. Only the core E2E harness, UI bundle configuration, explicit global app-shell owners, or unavailable changed-path information select the full Control UI E2E inventory. Shared components, styles, and feature modules follow explicit route/component owners and direct runtime imports instead; test and fixture imports retain transitive selection. Shared shell/store cycles do not make a leaf change select every route. Hourly and release validation own the full transitive composition. Owner selections share one browser row per 30 files, bounded by the existing row cap; full validation and the kill switch retain their existing row counts. The preflight job summary lists every selected file and its reasons. Set the repository variable
OPENCLAW_CI_UI_E2E_FULLtotrueor1to restore full coverage for admitted Control UI E2E jobs on PRs. Hourly main and manual/release validation select the full Control UI E2E inventory, including the mapped compositions formerly restricted to release validation. Real-Gateway proof is deferred on ordinary PRs. The browser-extension family retains its independent owner, so a browser-extension change need not select Control UI jobs. Node excludes files delegated to a selected UI job. - Documentation beside code keeps all
docs/content, including navigation JSON, generated metadata, and assets, plus README files,AGENTS.md, and skill Markdown from widening the code's Node plan. Packaged workspace templates and test fixtures retain their runtime owners. Docs-only routing still selects no Node rows; unrelated runtime Markdown outsidedocs/retains its owner. - PR dependency and helper selection combines bounded owner areas, transitive test import/re-export reachability through workspace and public SDK aliases, explicit policy watches, and protected regressions. Protected tests also follow runtime imports across owner areas. One-hop proximity alone misses observed catches. Unrelated global inputs do not request the full runtime repository; owner and protected-test policy supplies the retained coverage.
- PR extension lint reports directly changed plugin directories, the Telegram/Codex/Slack smoke cohort for shared source changes, and direct consumers of changed public SDK entries (including test imports). It shares package-selection policy with the boundary checker; transitive and ambient-type fan-out runs on hourly main and release validation. The planner records selected extensions and reasons in its summary; no selected extensions means no extension lint work. Selected stripes retain the existing full declaration preparation, native typed lint program, and diagnostics. Lint/type policy changes, directly changed loose extension sources, and unavailable public-entry inventories retain full coverage. The existing
OPENCLAW_CI_EXTENSION_LINT_FULL=true|1switch restores full PR coverage; unset keeps the aggressive default. Historical targets retain their full coverage, and non-extension checks keep their existing owners. - Changed runtime exports also select tests that mock the changed module, including consumers of shared mock factories. The existing dependency-free source scanner uses Node's native syntax stripping to compare the changed module's runtime export names with preflight's fetched base tree and resolves relative, TypeScript-path, and workspace-package specifiers through the import-graph owner. These mock consumers bypass ordinary importer depth and hub limits. Newly imported names from an already mocked module also retain tests that consume the changed caller and install that mock, covering callers that begin using an existing export. Implementation-only edits keep normal selection; missing history or unfamiliar export syntax conservatively retain mock consumers. Hourly and release inventories are unchanged. Current-source read errors fail preflight. Hand-written object fakes without a module-mock edge still rely on their ordinary owner/import coverage.
- Erased TypeScript modules retain their owner tests, affected runtime test consumers, and declared source watches. Type-only changes still select every consuming compiler graph through the separate static-check owner. Ambiguous compiler ownership, ambient/declaration files, and missing history retain complete static checks without widening runtime tests.
- Filesystem source scanners retain their declared watched inputs alongside ordinary importers. A policy watch supplements each changed source's tests and never supplies a missing ordinary owner.
- Global and hub Node inputs select their bounded owner tests, transitive import consumers, and protected regressions plus the fixed PR smoke set. Shared test bootstrap/configuration, root compiler resolution, workspace/install settings, patches, dependency hubs, and packing policy no longer request the complete runtime plan. Static checks retain full fallback where ownership is ambiguous. Missing changed paths or an unavailable bounded selector fail preflight instead of admitting every runtime family. Global package, lockfile, compiler, and shared Vitest inputs do not select every platform/browser family merely because Node ownership is broad; related protected tests remain selected. UI and wizard locale catalogs and translation metadata retain their dedicated localization and compiler checks without PR Node or browser rows; localization generators and runtime modules retain tests.
- Generated Control UI boot metadata selects its chunking and Vite tests through the shared test-target map. The existing UI performance lane still builds and checks the generated chunks; the metadata alone does not require unrelated core suites.
- Preflight diagnostics log
Node test plan changed-setwith the selected row count and any bounded owner-selection reason. Missing or unbounded PR plans fail preflight; they never become the full compact matrix. The logged changed-path manifest supplies replay inputs. Hourly main and ordinary manual/release validation retain complete inventories through their existing owners. - PR check families select static checks and guards from their own changed-path owners, independently of whether Node test targeting finds a precise plan. Lint and formatting reuse the local changed-check owner; lint configuration changes retain full lint. Semantic lint runs changed files and their transitive import consumers, including type-only imports and workspace/path aliases, through the existing shard owner. Each row receives only its assigned file list; workers do not rediscover the graph. Affected root tests retain their central compiler-config admission. Deleted, ambient, unsupported, and oversized selections retain full lint. Hourly main and Full Release Validation continue running full repository lint. Hosted profiles keep selected files in their existing stripes, while scripts, root tests, formatting, and localization checks retain their central owner. Preflight remains dependency-free and owns admission, static-check selection, and the original row/resource templates. For admitted narrow PRs, the hosted
check-planjob installs through the existing Node setup owner, then materializes selected-file lint and compiler facts. It validates compiler ownership and selects every graph that consumes a changed file, including erased type imports into UI, plugins, scripts, and tests. Its consumers require a successful plan; failure cannot silently select a full fallback, andopenclaw/ci-gaterequires the planner itself. Existing production, test, and stripe jobs retain their runner placement and compiler concurrency; only rows with selected graphs run. GitHub and hybrid keep changed core-test consumers in their canonical stripes; the central row owns the selected extensions, scripts, and root-test graphs. Markdown and UI styles do not widen a mixed TypeScript change. Deleted, ambient, configuration, and unclassified inputs retain all compiler graphs. Shared fixtures and scanner policy inputs retain conservative check families. Conflict-marker and wall-clock deprecation guards run after Node setup in the required planner job. Other guards retain their generated native inputs. SDK subpath exports, extension import boundaries, declaration compile/canary proof, protocol generation, and schema drift remain blocking. When TypeScript files change, including type declarations, TSX, MTS, and CTS, the requiredcheck-guards-architecturerow runscheck:import-cyclesandcheck:madge-import-cyclesalongside the remainingcheck-guardschecks. The check-family owner selects Madge from preflight’s changed paths; ambiguous/full scope and older owners retain it conservatively. Both same-repository and fork PRs use the two parallel guard rows with unchanged runner classes and no compiler-plan wait. Scoped non-TypeScript changes retain the original guard row without adding Madge or a second checkout. The same check-family owner selectslint:kyselyfor TypeScript undersrc/,extensions/, orpackages/, with conservative full/unknown-scope fallback; UI-only changes do not add it. Knip and the remaining runtime topology checks (deprecated APIs, wrapper shadowing, JSDoc, generated Kysely types, and database-first guards) retain hourly main and full release validation instead of ordinary PRs. Plugin and channel runtime contract tests use the exact changed-owner Node plan, including their watched SDK documentation and packaged skills. Narrow code changes run coercion checks once in the guard job. Main, ordinary manual CI, and historical targets skip this additional planner and retain their full check families. Static full fallback never widens runtime families. Bundled config metadata follows the shared schema owner map. The bundled/protocol row always verifies protocol generation; its full bundled runtime suite runs only on hourly main and ordinary manual/release validation. Specialized Bun launcher proof opts in for its direct launcher and module-generation owners. Startup, plugin, and channel tests retain their canonical configs within the changed-owner plan. - Browser integration on PRs uses the mocked Control UI file selection above and retains the browser-extension family when its existing owners change. Real-Gateway files are excluded even when directly changed or selected as import consumers; they cannot return through changed-Node fallback. Unrelated families remain omitted unless protected or affected tests select a precise subset. Named release-only real-Gateway compositions retain their existing opt-in policy. Existing row caps, worker limits, serial/parallel ownership, and runner routes stay unchanged; small Control UI selections omit empty rows. Empty real-Gateway phase groups are removed; a desktop-only carrier keeps its required desktop proof without invoking a test phase with no files. Hourly main and Full Release Validation retain the full Control UI E2E set; historical manual targets without the selector retain their existing full target-owned path.
- Extension package boundary selection uses the PR's own merge-base diff, independently of restored receipt age. The job reuses the extension-lint planner’s bounded base-fetch action. A depth-one PR merge is compared with its verified raw first-parent tree, without requiring a complete ancestry walk. It checks directly touched plugin packages. Core/SDK declaration and shared compiler/dependency changes add a fixed smoke set: Telegram, Codex and Slack, chosen to cover 119 distinct directly consumed public SDK entries. Changes to a public SDK entry file also select packages that directly import that entry, including type-only imports. Transitive declaration consumers and main-only drift are intentionally left to the complete hourly main and manual/release checks. Selected packages retain full compiler diagnostics and normal receipt validation. Declaration preparation follows their packaged entry points with the native compiler, emitting only consumed SDK roots and required plugin declaration producers, including transitive inputs and ambient declarations. Hourly and manual/release checks still prepare the complete declaration set. The negative boundary canary always runs. The job summary lists selected and skipped packages with reasons. Unset repository variable
OPENCLAW_CI_EXTENSION_BOUNDARY_FULLenables this aggressive policy;true,1orfullrestores complete PR checks. An unavailable comparison or import inventory also retains full scope. - Shared SDK declarations keep the existing boundary check as a standalone producer on supported hybrid PRs targeting the canonical repository that already select both the boundary row and the check planner. The job count and check name stay unchanged. That producer validates a full native SDK and publishes its declaration tree and checked receipt; extension lint waits for that artifact and validates it through the usual preparation owner. Other additional checks and test-type jobs retain their existing dependencies. A content-keyed main cache supplies the same SDK to other consumers, with receipt validation on every restore. Compiler directory lookups use sorted file and directory membership, so relocation can retain a valid receipt when filesystem enumeration order changes. Scheduled, manual, release, frozen, and unsupported targets retain their complete existing checks.
- Extension selection uses exact tests from the changed plugin owner, transitive test importers, protected regressions, and explicit policy watches. Global dependency, shared-runtime, SDK, and planner inputs do not append a whole-plugin fallback. The existing Plugin Prerelease workflow owns the complete extension runtime inventory hourly and in Full Release Validation; normal CI does not append a second partial inventory.
- PR builds select
build-artifactsfor a dist-dependent row, an affected build/package owner test, or an individually selected built-process proof. Pipeline ownership comes from the existing changed-target map for build, declaration, package-tarball, and dist-artifact tests; generic runtime changes do not request the full artifact job. Source boundary guards and affected channel tests keep their Node owners. An artifact build does not also select an unrelated dist boundary, channel family, or every process verifier. - PR wrapper extraction selects
pr-worktree-provision.test.tswhen the wrapper, its library, or a file inscripts/pr-lib/wrapper-components.txtchanges. This manifest-derived policy watch supplements ordinary source tests because filesystem copying is invisible to the import graph. Manifest-only changes also run provisioning, including its duplicate-inventory and eager runtime import-closure checks. - Selector integration proofs retain the complete real-checkout owner scenarios in their canonical config. Direct edits select them through the same owner mechanism. Bounded synthetic cases protect selection and budget behavior without expanding the whole checkout for each assertion.
- Tooling test changes under
test/scripts/**/*.test.tsalso selecttest-projects.test.ts, whose workflow-routing expectations depend on the test inventory. The changed tests retain their own coverage, and newly added workflow guards check routing expectations before merge. - Messaging test-root changes under
src/auto-reply/andsrc/infra/outbound/, plus core-test tsconfig inputs, selecttsgo-core-test-shards.test.ts. This filesystem-inventory guard verifies exactly-once ownership; shard size against the advisory 720-root limit is reported as a warning only and never blocks CI. It supplements the changed tests' ordinary owners; other test-root paths retain their existing selection. - Codex app-server test changes under
extensions/codex/src/app-server/**/*.test.tsalso selecttest/vitest-projects-config.test.ts, which verifies complete and unique full-suite test ownership. The changed tests retain their existing extension suite coverage. - Git-owner changes to its action, base-commit policy, projection generator, lifecycle tests and support, or named owner-adopting workflows such as Workflow Sanity, QA Profile Evidence, Mantis ref validation/installers/worktrees, Docs Sync Publish Repo, OpenClaw Performance, the Linux/macOS/npm-placeholder release admission jobs, and plugin ClawHub/npm publication select the existing
macos-nodeand Windows lanes. These run native checkout ownership proof without selecting Swift, iOS, or Android jobs; Mac app and shared-native changes retain their existing Mac lanes. - macOS Swift runner budgets are 30 minutes per worker on GitHub-hosted
xcode-27with Xcode 27, including automatic first attempts. Regular PR/main CI and PRrelease_gatedispatches runtestsfor Swift lint, schema checks, and app coverage build/tests, alongsidepackagesfor the Talk opt-out build, shared package suite, and standalone Swabble suite for current targets. Ordinary full-scope manual validation adds the independentreleaseapp build, moves lint/schema ownership to that phase, and retains health renders intests. Every selected phase must pass the existing CI gate. At most two workers run concurrently; a failed phase does not cancel the other phases' diagnostics. The preview macOS 27 image changes neither coverage nor the Swift 6.3 source-language minimum; complete native proof is required on the new toolchain. - macOS fixture support changes select the existing Mac Node gate so the shared managed-command and concurrency owner receives Darwin proof independently of Swift/app changes.
- macOS Swift build caches retain the original nanosecond timestamps and content hashes of their source inputs inside
apps/macos/.build. The restore helper replays timestamps only for byte-identical regular files with matching permissions in the current input inventory; changed, missing, linked, or invalid entries keep their checkout metadata and invalidate through SwiftPM normally. The v6 archive keys include the phase, helper, toolchain, package graph, and source identities, with same-phase, same-graph prefix reuse. Each phase starts with a cold seed instead of restoring the former combined build archive, then records metadata immediately before its own trusted save. Both phases may restore the shared SwiftPM dependency cache; its sole eligible writer istestsin regular CI orreleasein full validation. Candidate cache trust is unchanged: cache-off validation compiles every selected phase cold. Historical targets retain their target-owned build commands. - Workflow Sanity runs
actionlint,zizmorover all workflow YAML files, the composite-action interpolation guard, and the conflict-marker guard. Actionlint configuration stays with this owner and does not widen Node test selection. The PR-scopedsecurity-fastjob also runszizmorover changed workflow files so workflow security findings fail early in the main CI graph. - Docs on opt-in
mainpushes are checked by the standaloneDocsworkflow with the same ClawHub docs mirror used by CI, so mixed code+docs pushes do not also queue the CIcheck-docsshard. Pull requests and manual CI still runcheck-docsfrom CI when docs changed. - TUI PTY runs two built-CLI artifact canaries in
build-artifactson main, ordinary manual/release CI, and PRs selecting their existing test owner: a local model roundtrip and a real Gateway connection. The complete suite is defined intest/vitest/vitest.tui-pty.config.ts. Narrow pull-request plans omit the named PTY process proofs and run the source-only harness assertion test as an exact target under that same config, without requiring dist. Manual/release full plans retain thecore-runtime-tui-ptydescriptor; PRs have no broad fallback. CI consumes that descriptor only through the built-artifact selection flag, so the full suite has no executing matrix row; manual and release CI also run only the canaries. Canonicalmainpush compaction omits the full descriptor while keeping the canaries. - SQLite session lifecycle runs for its existing storage/session owner scope on PRs, exact-head PR fallbacks, and main; ordinary manual/release CI retains it. Owner selection covers the built-CLI migration, restart, compaction, cleanup, and session RPC proof only when the diff touches its direct storage/session owners or a reachable session path in the embedded runner. The
build-artifactsverifier wave runs it against the runtime already built in that job, after the isolated startup-memory measurement. It overlaps independent readers on Blacksmith and stays serial on hosted runners; ordinary manual and release dispatches always select it when the target contains the proof. - CI routing and plugin contract helper edits use the same changed-owner Node plan and static correctness gates as other PRs. Historical fast-only scope hints cannot bypass that plan or select whole contract families. Hourly main and ordinary manual/release validation retain their complete routing and contract inventories.
- Release tooling fast lane applies when a canonical pull request carries the
release-fast-lanelabel and every changed path is release tooling (.github/workflows/**,scripts/**,test/scripts/**,.agents/skills/release-*/**,docs/reference/RELEASING.md) or independently checked documentation. The manifest keepssecurity-fast,check-shard,check-docs, and the changed Node rows, relaxes the compact packing-policy proof to those rows, and skips every other lane; the preflight step summary lists the admitted and skipped lanes underRelease fast lane. A declined label (out-of-scope path, global Node input, fork, push, dispatch, docs-only) warns and keeps ordinary selection. Hourly full main CI still covers the merged result. - QA Smoke on PRs and
mainruns when the diff touches the qa-lab harness,qa/scenario data, the matrix/telegram channels the smoke profile drives, Docker packaging scripts, or the gate's orchestration. Generic runtime, UI, workspace-package, and dependency changes do not select smoke by themselves. Exact-head PR fallbacks use the same owner map. Ordinary manual CI and Full Release Validation retain the complete smoke profile; missing changed paths or an older planner without the selector retain coverage when the target supports the smoke harness. - Docker seed uses
resolveDockerSeedLanesto select exactlypublished-upgrade-survivoron every admitted canonical main run, hourly by default and independently of changed paths. It runslegacy-operator-statewithauto-authagainst an exact published predecessor. Frozen targets declaring that scenario in their inert catalog retain the same composition; historical targets without that declaration keep thebasefallback, and invalid catalogs fail. Pull requests and exact-headrelease_gatefallbacks defer the complete survivor to hourly main and Full Release Validation. They retain selected Node and static correctness gates, plus the other Docker lanes throughresolveChangedDockerSeedLanesand its owner map. No survivor case or assertion is removed. Main and selected PR Docker lanes prepare the complete runtime and public SDK types withpnpm build:ci-artifacts, then use the canonical package helper with--skip-buildand its tarball integrity check. Full declaration generation remains in the hourly protected cache warmer and ordinary manual/release package builds. Ordinary manual CI, including Full Release Validation'snormal_cichild and itsnpm-beta/npm-stablescopes, retains full package generation and all six Docker lanes. Targets must declare the Docker seed capability; older targets without the tier selector retain the main/release survivor fallback. Docs-only main pushes remain excluded at the workflow trigger. Expanded release history stays in Package Acceptance and the weekly Update Migration workflow. - Control UI performance uses the dedicated
run_control_ui_performanceoutput. Production UI files, plugin browser entries, and the UI build/performance policies select it. Other modules select it when a runtime import path from a UI source or performance script reaches them; workspace aliases use the shared import-graph owner. Type-only edges remain covered by static checks. Unrelated inputs retain hourly main and ordinary manual/release validation. Test-only files remain with their changed-test owner. Historical targets retain their existing performance-script availability contract. - Windows Node checks use the existing Windows-sensitive process/path/filesystem/service and test/planner owner map. A selected Windows owner retains the complete package-script-owned Windows inventory and all five existing measured rows, preserving their runner placement, packing, and estimates. Narrow Node target selection never clips that platform family. Ordinary PRs omit Windows proof even when a Windows owner changes; exact-head dispatch fallbacks retain the owner map. Hourly main and ordinary manual/release validation retain the complete inventory.
Owner-selected main proof gates can leave regressions outside their selected
paths undetected until manual or release validation. PR unit/boundary checks do
not replace their process proofs. Push gates compare the triggering event's
before commit with its head, covering
every commit in that push. They do not accumulate changes from earlier pushes
whose pending runs were coalesced away (cancelled). If a coalesced or otherwise
cancelled main run never executes a selected owner-path proof, that proof can
remain unexecuted until a later run selects it. Hourly main-tier CI covers its
retained lanes; release-only compositions require applicable full manual or
release validation unless a direct edit selects them. The published-upgrade survivor is
selected on every admitted canonical main run, so it does not depend on a later
owner-path match. Ordinary manual CI and Full Release Validation also select it
independently of changed paths, subject to the target's Docker seed capability.
Process proof tier
Pull requests and exact-head release_gate fallbacks admit the remaining Docker seed lanes, QA Smoke,
and built-process verification through their existing owner maps and selected
test targets. The intact published-upgrade survivor runs hourly and in full release validation.
Unrelated PRs defer the other jobs. Native-host, built Doctor, Discord
attachment, Gateway watch, SQLite lifecycle, and TUI canaries each have an
independent selection flag; choosing one never selects every process proof.
Selected built proofs reuse the one artifact build. Main and ordinary manual CI
retain their established full-proof behavior; Full Release Validation dispatches
that ordinary manual child.
The named CI_PROOF_TEST_FILES stay outside ordinary Node rows so their dedicated
artifact/workflow owners execute them. The pre-existing complete FRV and installer
process tours remain manual/release-only; this change does not move those tours
onto PRs. Their faster source/continuation guards retain changed-owner coverage.
Install Smoke predates this selection change as a nightly and manual/release workflow. Its existing 03:17 schedule and Full Release Validation child are unchanged; no new PR trigger is added. Installer-owned Node tests still opt in through the changed-source and direct-test owners. This is an existing timing exception to hourly main coverage, not a new hourly install proof.
test/scripts/ci-workflow-planning.test.ts checks proof-tier selection for PR,
main-push, ordinary manual, and PR-fallback events. The Docker scheduler guard in
test/scripts/ci-workflow-guards.test.ts
asserts that the selected docker_seed_lanes inventory reaches
pnpm test:docker:all through OPENCLAW_DOCKER_ALL_LANES, and its release-child
guard verifies that normal_ci dispatches ci.yml against the exact target.
test/scripts/ci-workflow-evidence.test.ts owns QA protocol, evidence-reader,
and maturity handoff checks. Fast CI-routing and cache warming include all three
files when their owners select them; YAML action-pinning checks retain their
original guard owner. The Node planner tests separately assert that hourly main
and Full Release Validation retain moved PR-exempt files, while the existing
named process-proof exclusions remain outside ordinary PR execution.