fix(ci): keep Kysely guardrails blocking on pull requests
Some checks are pending
ClawSweeper Dispatch / dispatch (push) Waiting to run
CodeQL / Security High (actions) (push) Waiting to run
CodeQL / Security High (channel-runtime-boundary) (push) Waiting to run
CodeQL / Security High (core-auth-secrets) (push) Waiting to run
CodeQL / Security High (mcp-process-tool-boundary) (push) Waiting to run
CodeQL / Security High (network-ssrf-boundary) (push) Waiting to run
CodeQL / Security High (plugin-trust-boundary) (push) Waiting to run
CodeQL / Security High (process-exec-boundary) (push) Waiting to run
Docs Sync Publish Repo / sync-publish-repo (push) Waiting to run
Docs / docs (push) Waiting to run
Node Runtime Conformance / TypeScript contracts (push) Blocked by required conditions
Node Runtime Conformance / scope (push) Waiting to run
Node Runtime Conformance / Rust workspace (push) Blocked by required conditions
Plugin Init Scaffold Validation / scope (push) Waiting to run
Plugin Init Scaffold Validation / Validate provider scaffold (push) Blocked by required conditions
Plugin NPM Release / preview_plugins_npm (push) Waiting to run
Plugin NPM Release / Validate release publish approval (push) Blocked by required conditions
Plugin NPM Release / preview_plugin_pack (push) Blocked by required conditions
Plugin NPM Release / Preflight plugin npm package () (push) Blocked by required conditions
Plugin NPM Release / Seal prepared plugin npm release (push) Blocked by required conditions
Plugin NPM Release / Trusted publisher OIDC exchange (push) Blocked by required conditions
Plugin NPM Release / approve_plugins_npm_release (push) Blocked by required conditions
Plugin NPM Release / Publish plugin npm package () (push) Blocked by required conditions
Plugin NPM Release / verify_plugins_npm (push) Blocked by required conditions
Vitest Cache Warm / dependencies (push) Waiting to run
Vitest Cache Warm / warm (push) Waiting to run
Workflow Sanity / actionlint (push) Waiting to run
Workflow Sanity / generated-doc-baselines (push) Waiting to run

Two raw SQLite boundary violations reached main after architecture checks moved out of PR CI. Select lint:kysely for TypeScript changes under src/, extensions/, and packages/ in the existing check-guards job, including fork PRs. Preserve conservative full-scope and older-owner fallback.

Keep the other architecture checks in their existing hourly and release owners. Add no jobs, runners, queue dependencies, permissions, or repository variables. Extend the scope, manifest, and executable workflow tests and document the restored boundary.

Validation: full test-types, all four tooling shards, owning unit configs, four real preflight checkout shapes, changed-file lint and P2 review. The unchanged base reproduces three unused exports and the Voice Call test expecting retired setup migrations; these are recorded separately from the passing CI-owner proof.
This commit is contained in:
Peter Steinberger 2026-10-05 12:21:39 -07:00
parent b2750b2346
commit 38784a3aa5
No known key found for this signature in database
9 changed files with 94 additions and 26 deletions

View file

@ -183,6 +183,7 @@ jobs:
run_checks_node_core_dist: ${{ steps.manifest.outputs.run_checks_node_core_dist }}
run_check: ${{ steps.manifest.outputs.run_check }}
run_pr_madge_import_cycles: ${{ steps.manifest.outputs.run_pr_madge_import_cycles }}
run_pr_kysely_guardrails: ${{ steps.manifest.outputs.run_pr_kysely_guardrails }}
startup_corpus_node_revision: ${{ steps.manifest.outputs.startup_corpus_node_revision }}
startup_corpus_test_files_json: ${{ steps.manifest.outputs.startup_corpus_test_files_json }}
changed_core_test_paths_json: ${{ steps.manifest.outputs.changed_core_test_paths_json }}
@ -3672,6 +3673,7 @@ jobs:
HISTORICAL_TARGET: *historical_target
FORMAT_CHECK: ${{ needs.preflight.outputs.run_format_check }}
RUN_PR_MADGE_IMPORT_CYCLES: ${{ needs.preflight.outputs.run_pr_madge_import_cycles }}
RUN_PR_KYSELY_GUARDRAILS: ${{ needs.preflight.outputs.run_pr_kysely_guardrails }}
RELEASE_GATE: &release_gate ${{ inputs.release_gate && (needs.preflight.outputs.node_runner_backend != 'runson' && needs.preflight.outputs.ci_qualification != 'true') && 'true' || 'false' }}
RUN_CONTROL_UI_I18N: ${{ needs.preflight.outputs.run_control_ui_i18n }}
RUN_UI_TESTS: ${{ needs.preflight.outputs.run_ui_tests }}
@ -3767,6 +3769,9 @@ jobs:
if [[ "${RUN_PR_MADGE_IMPORT_CYCLES:-false}" == "true" ]]; then
GOGC="${GOGC:-30}" GOMEMLIMIT="${GOMEMLIMIT:-3GiB}" pnpm check:madge-import-cycles
fi
if [[ "${RUN_PR_KYSELY_GUARDRAILS:-false}" == "true" ]]; then
pnpm lint:kysely
fi
;;
npm-lock)
if [[ "$SKIP_NPM_LOCK" == "true" ]]; then
@ -4300,6 +4305,7 @@ jobs:
env:
TASK: ${{ matrix.group }}
RUN_PR_MADGE_IMPORT_CYCLES: ${{ needs.preflight.outputs.run_pr_madge_import_cycles }}
RUN_PR_KYSELY_GUARDRAILS: ${{ needs.preflight.outputs.run_pr_kysely_guardrails }}
OPENCLAW_LOCAL_CHECK: "0"
FROZEN_TARGET: *frozen_target
HISTORICAL_TARGET: *historical_target

View file

@ -16,7 +16,9 @@ expensive jobs in `ci.yml`:
- The published-npm-driver × candidate update cell, including on update-owner PRs.
- Dependency/dead-export scanning (Knip) and the full runtime topology/architecture job.
Runtime import-cycle checks remain in the existing PR guard; TypeScript changes
also select Madge there. See [scope selection](/ci/scope-and-routing/selection).
also select Madge there. TypeScript changes under `src/`, `extensions/`, or
`packages/` also select Kysely guardrails in that same job; generated Kysely
types remain deferred. See [scope selection](/ci/scope-and-routing/selection).
- Android screenshot capture.
The first four groups retain their existing hourly main-tier and full release

View file

@ -26,7 +26,7 @@ Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests
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 checks remain blocking in the existing guard job.
expected absence. Import-cycle and Kysely guardrail checks remain blocking in the existing guard job.
| Proof | Ordinary PR | Hourly main tier | Full release validation |
| ----------------------------------- | ------------------------------------- | ---------------------------- | ----------------------------- |
@ -37,6 +37,7 @@ expected absence. Import-cycle checks remain blocking in the existing guard job.
| 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) | Existing guard for TypeScript changes | Existing checks | Existing checks |
| Kysely guardrails | Existing 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 |
@ -51,6 +52,18 @@ checker already runs in the PR guard; adding Madge there restores type-cycle
coverage without restoring the larger architecture row. Hourly and manual/release
commands remain unchanged.
Kysely guardrails also run in that guard job when TypeScript under `src/`,
`extensions/`, or `packages/` changes. Two raw-SQL boundary violations reached
main after the deferral: `495c243be11a`
([#165546](https://github.com/openclaw/openclaw/pull/165546), repaired by
[#165637](https://github.com/openclaw/openclaw/pull/165637)) and `fe43e10f4952`
([#165678](https://github.com/openclaw/openclaw/pull/165678)).
[Hourly job 111895593533](https://github.com/openclaw/openclaw/actions/runs/37349110851/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. No additional runner is started.
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
@ -157,7 +170,7 @@ The iOS, macOS, and both shared OpenClawKit Periphery scans use Xcode 27 on GitH
- **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-set` with 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-plan` job 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, and `openclaw/ci-gate` requires 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. The existing required `check-guards` job retains `check:import-cycles` and also runs `check:madge-import-cycles` when TypeScript files change, including type declarations, TSX, MTS, and CTS. 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 that same guard row, with no extra job, runner, checkout, or compiler-plan wait. Scoped non-TypeScript changes do not add Madge. Knip and the remaining runtime topology checks (deprecated APIs, wrapper shadowing, JSDoc, Kysely, 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.
- **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-plan` job 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, and `openclaw/ci-gate` requires 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. The existing required `check-guards` job retains `check:import-cycles` and also runs `check:madge-import-cycles` when TypeScript files change, including type declarations, TSX, MTS, and CTS. 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 that same guard row, with no extra job, runner, checkout, or compiler-plan wait. Scoped non-TypeScript changes do not add Madge. The same check-family owner selects `lint:kysely` for TypeScript under `src/`, `extensions/`, or `packages/`, 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_FULL` enables this aggressive policy; `true`, `1` or `full` restores 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.

View file

@ -1396,9 +1396,11 @@ const manifest = {
run_checks_node_core_dist: runNodeCoreDist,
run_check: runCheck,
// The existing guards row already runs the runtime-value cycle check.
// Older scope owners retain Madge rather than silently dropping the type graph.
// Older scope owners retain these guards rather than silently dropping coverage.
run_pr_madge_import_cycles:
runCheck && ordinaryPullRequest && (proposedCheckScope?.madgeImportCycles ?? true),
run_pr_kysely_guardrails:
runCheck && ordinaryPullRequest && (proposedCheckScope?.kyselyGuardrails ?? true),
narrow_check_paths_json: runCheckPlan ? JSON.stringify(changedPaths) : "",
run_check_plan: runCheckPlan,
check_plan_input_json: runCheckPlan

View file

@ -25,6 +25,7 @@ type CiCheckFamilyScope = {
additionalGroups: string[];
baselineRatchets: boolean;
madgeImportCycles: boolean;
kyselyGuardrails: boolean;
lint: boolean;
types: boolean;
};
@ -61,6 +62,7 @@ export function resolveCiCheckFamilyScope(changedPaths: readonly string[]): CiCh
additionalGroups: [...ADDITIONAL_GROUPS],
baselineRatchets: true,
madgeImportCycles: true,
kyselyGuardrails: true,
lint: true,
types: true,
};
@ -123,6 +125,9 @@ export function resolveCiCheckFamilyScope(changedPaths: readonly string[]): CiCh
),
baselineRatchets: code || runtimeSource || inventoryDocs,
madgeImportCycles: matches(/\.(?:[cm]?ts|tsx)$/u),
kyselyGuardrails: paths.some(
(path) => SOURCE_PATH_RE.test(path) && /\.(?:[cm]?ts|tsx)$/u.test(path),
),
// The changed-lint row also owns formatting for YAML, JSON5, and other data.
lint: true,
types,

View file

@ -8,6 +8,7 @@ type ScopeCase = {
const scoped: ScopeCase["expected"] = {
baselineRatchets: true,
madgeImportCycles: true,
kyselyGuardrails: true,
lint: true,
types: true,
};
@ -24,6 +25,7 @@ const cases: ScopeCase[] = [
},
...[
"src/shared/runtime.ts",
"src/shared/view.tsx",
"packages/media-core/src/runtime.mts",
"packages/media-core/src/runtime.cts",
"extensions/telegram/src/runtime.ts",
@ -95,12 +97,23 @@ const cases: ScopeCase[] = [
{ paths: ["src/config/catalog.yaml"], expected: { lint: true, types: false } },
{
paths: ["src/config/catalog.json"],
expected: { lint: true, types: true, madgeImportCycles: false },
expected: { lint: true, types: true, madgeImportCycles: false, kyselyGuardrails: false },
},
{
paths: ["src/shared/runtime.js"],
expected: { madgeImportCycles: false, kyselyGuardrails: false },
},
{ paths: ["src/shared/runtime.js"], expected: { madgeImportCycles: false } },
{
paths: ["ui/src/runtime.tsx"],
expected: { madgeImportCycles: true, checkTasks: ["guards", "dependencies"] },
expected: {
madgeImportCycles: true,
kyselyGuardrails: false,
checkTasks: ["guards", "dependencies"],
},
},
{
paths: ["ui/src/runtime.tsx", "src/config/catalog.json"],
expected: { madgeImportCycles: true, kyselyGuardrails: false },
},
{ paths: ["test/openclaw-launcher.e2e.test.ts"], expected: { fastTasks: ["bun-launcher"] } },
];
@ -114,6 +127,7 @@ describe("narrow PR check families", () => {
additionalGroups: [],
baselineRatchets: false,
madgeImportCycles: false,
kyselyGuardrails: false,
lint: true,
types: false,
});

View file

@ -13,11 +13,14 @@ import {
} from "./ci-workflow.test-support.js";
it.each(["openclaw/openclaw", "contributor/openclaw"])(
"keeps TypeScript cycle checks in the existing PR guard for %s",
"keeps TypeScript cycle and Kysely checks in the existing PR guard for %s",
(headRepository) => {
for (const [changedPath, selected] of [
["src/skills/runtime/refresh.ts", true],
["src/shared/runtime.js", false],
for (const [changedPath, cycles, kysely] of [
["src/skills/runtime/refresh.ts", true, true],
["extensions/telegram/src/runtime.ts", true, true],
["packages/media-core/src/runtime.mts", true, true],
["ui/src/runtime.tsx", true, false],
["src/shared/runtime.js", false, false],
] as const) {
const result = runCiManifestFixture({
bundledPlanner: true,
@ -28,7 +31,8 @@ it.each(["openclaw/openclaw", "contributor/openclaw"])(
scopeEnv: { OPENCLAW_CI_HEAD_REPOSITORY: headRepository },
});
expect(result.status, result.output).toBe(0);
expect(result.outputs.run_pr_madge_import_cycles).toBe(String(selected));
expect(result.outputs.run_pr_madge_import_cycles).toBe(String(cycles));
expect(result.outputs.run_pr_kysely_guardrails).toBe(String(kysely));
const rows = ["check_matrix", "check_additional_matrix"].flatMap(
(key) =>
JSON.parse(expectDefined(result.outputs[key], key)).include as Array<{
@ -39,7 +43,7 @@ it.each(["openclaw/openclaw", "contributor/openclaw"])(
);
expect(rows.filter((row) => (row.task ?? row.group) === "guards")).toHaveLength(1);
expect(rows.some((row) => row.group === "runtime-topology-architecture")).toBe(false);
expect(rows.some((row) => row.check_name.includes("import-cycle"))).toBe(false);
expect(rows.some((row) => /import-cycle|kysely/u.test(row.check_name))).toBe(false);
}
},
);
@ -99,6 +103,7 @@ it.each([
});
expect(result.status, result.output).toBe(0);
expect(result.outputs.run_pr_madge_import_cycles).toBe(String(deferred));
expect(result.outputs.run_pr_kysely_guardrails).toBe(String(deferred));
for (const flag of [
"run_ui_real_gateway",
"run_checks_windows",

View file

@ -5137,6 +5137,7 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
TASK: task,
RUN_BUNDLED_TESTS: String(eventName !== "pull_request"),
RUN_PR_MADGE_IMPORT_CYCLES: String(eventName === "pull_request"),
RUN_PR_KYSELY_GUARDRAILS: String(eventName === "pull_request"),
GITHUB_EVENT_NAME: eventName,
CHECKOUT_KIND: "linux-node",
CHECKOUT_BASE_SHA: String(checkoutBase),

View file

@ -501,6 +501,7 @@ function runCheckShardFixture(options: {
task?: "guards" | "npm-lock" | "prod-types" | "test-types";
earlyGuards?: boolean;
madgeImportCycles?: boolean;
kyselyGuardrails?: boolean;
failPackageScript?: string;
checkoutBase?: string;
types?: {
@ -632,6 +633,7 @@ appendFileSync(process.env.TYPE_CALLS, [process.env.TYPE_ROW, process.env.OPENCL
compatibility_target: String(options.frozenTarget),
run_format_check: "false",
run_pr_madge_import_cycles: String(options.madgeImportCycles ?? false),
run_pr_kysely_guardrails: String(options.kyselyGuardrails ?? false),
changed_core_test_paths_json: options.types?.changedPathsJson ?? "",
narrow_check_paths_json:
options.types?.narrowPathsJson ?? (options.earlyGuards ? '["src/shared/runtime.ts"]' : ""),
@ -707,6 +709,9 @@ appendFileSync(process.env.TYPE_CALLS, [process.env.TYPE_ROW, process.env.OPENCL
RUN_PR_MADGE_IMPORT_CYCLES: String(
resolveValue(row.step.env?.RUN_PR_MADGE_IMPORT_CYCLES) ?? "",
),
RUN_PR_KYSELY_GUARDRAILS: String(
resolveValue(row.step.env?.RUN_PR_KYSELY_GUARDRAILS) ?? "",
),
...(typeCheck || options.earlyGuards
? {
OPENCLAW_LOCAL_CHECK: undefined,
@ -6668,22 +6673,25 @@ describe("ci workflow guards", () => {
},
);
it.each([
{ earlyGuards: false, selected: false, failure: "" },
{ earlyGuards: true, selected: false, failure: "" },
{ earlyGuards: false, selected: true, failure: "" },
{ earlyGuards: true, selected: true, failure: "" },
{ earlyGuards: false, selected: true, failure: "check:import-cycles" },
{ earlyGuards: true, selected: true, failure: "check:import-cycles" },
{ earlyGuards: false, selected: true, failure: "check:madge-import-cycles" },
{ earlyGuards: true, selected: true, failure: "check:madge-import-cycles" },
])(
"keeps cycle checks blocking in the existing guard row ($earlyGuards, $selected, $failure)",
({ earlyGuards, selected, failure }) => {
it.each(
[false, true].flatMap((earlyGuards) =>
[
{ selected: false, kysely: false, failure: "" },
{ selected: true, kysely: false, failure: "" },
{ selected: true, kysely: true, failure: "" },
{ selected: true, kysely: true, failure: "check:import-cycles" },
{ selected: true, kysely: true, failure: "check:madge-import-cycles" },
{ selected: true, kysely: true, failure: "lint:kysely" },
].map(({ selected, kysely, failure }) => ({ selected, kysely, failure, earlyGuards })),
),
)(
"keeps cycle and Kysely checks blocking in the existing guard row ($earlyGuards, $selected, $kysely, $failure)",
({ earlyGuards, selected, kysely, failure }) => {
const result = runCheckShardFixture({
frozenTarget: false,
earlyGuards,
madgeImportCycles: selected,
kyselyGuardrails: kysely,
failPackageScript: failure,
scripts: [
"check:doctor-deprecation-registry",
@ -6698,7 +6706,19 @@ describe("ci workflow guards", () => {
? ["check:import-cycles", "check:madge-import-cycles"]
: ["check:import-cycles"],
);
expect(result.calls).not.toContain("check:architecture");
expect(result.calls.includes("lint:kysely")).toBe(
kysely && (failure === "" || failure === "lint:kysely"),
);
for (const deferred of [
"check:architecture",
"check:deprecated-api-usage",
"check:wrapper-shadowing",
"check:deprecated-jsdoc",
"db:kysely:check",
"check:database-first-legacy-stores",
]) {
expect(result.calls).not.toContain(deferred);
}
},
);