Let Trunk watch the Windows checks, quality gate and eval jobs on pull requests, learning only (GST-1847) (#4148)

* Let Trunk watch the Windows checks, quality gate and eval jobs on pull requests, learning only (GST-1847)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Say in the testing docs that the Trunk observer is the only secret-holding job in Free Tests only, and list its new test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Say in the testing docs which skipped jobs to leave out when reviewing Trunk's verdicts, workflow by workflow

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-08 00:50:35 +00:00
1 parent aa46032f2b
commit 892c3b13cd
11 files changed
+261 -24

No files matched your search

+31
View File
@@ -69,6 +69,37 @@ env:
EVALS_FRESH: ${{ ((github.event_name == 'workflow_dispatch' && inputs.pr_receipts == '') || contains(github.event.pull_request.labels.*.name, 'evals-fresh')) && '1' || '' }}
jobs:
# Trunk Dynamic CI, LEARNING ONLY (GST-1847; free-tests.yml has the full
# rationale). Trunk records whether it would have run or skipped build-image and eval-slices
# on same-repository pull requests; every job still runs. Trunk keys verdicts
# by workflow file, so each observed workflow carries its own observer. No job
# needs it, no condition reads it and it exports no outputs, so a mode change
# in Trunk's dashboard cannot skip anything. It checks out nothing, runs no
# pull-request code, holds no GITHUB_TOKEN scopes, never fails the run, and
# skips fork pull requests, pushes and dispatches. docs/TESTING_INTERNALS.md.
trunk-dynamic-ci:
name: Trunk Dynamic CI (learning only, evals)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
continue-on-error: true
permissions: {}
env:
TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }}
steps:
- name: Record Trunk's verdicts for build-image and eval-slices (nothing reads them)
if: env.TRUNK_API_TOKEN != ''
timeout-minutes: 3
continue-on-error: true
uses: trunk-io/dynamic-ci@5bcddbf3615c2b032c21eed1b514c91164bda4a8 # v0.1.7
env:
TRUNK_DISABLE_TELEMETRY: 'true'
with:
token: ${{ env.TRUNK_API_TOKEN }}
job-keys: build-image,eval-slices
github-token: ''
enable-annotation: 'true'
# Build Docker image with pre-baked toolchain (cached — only rebuilds on Dockerfile/lockfile change)
build-image:
# Dependabot-triggered pull_request runs get a read-only GITHUB_TOKEN, so
+31
View File
@@ -31,6 +31,37 @@ concurrency:
cancel-in-progress: true
jobs:
# Trunk Dynamic CI, LEARNING ONLY (GST-1847; free-tests.yml has the full
# rationale). Trunk records whether it would have run or skipped quality
# on same-repository pull requests; every job still runs. Trunk keys verdicts
# by workflow file, so each observed workflow carries its own observer. No job
# needs it, no condition reads it and it exports no outputs, so a mode change
# in Trunk's dashboard cannot skip anything. It checks out nothing, runs no
# pull-request code, holds no GITHUB_TOKEN scopes, never fails the run, and
# skips fork pull requests, pushes and dispatches. docs/TESTING_INTERNALS.md.
trunk-dynamic-ci:
name: Trunk Dynamic CI (learning only, quality-gate)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
continue-on-error: true
permissions: {}
env:
TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }}
steps:
- name: Record Trunk's verdicts for quality (nothing reads them)
if: env.TRUNK_API_TOKEN != ''
timeout-minutes: 3
continue-on-error: true
uses: trunk-io/dynamic-ci@5bcddbf3615c2b032c21eed1b514c91164bda4a8 # v0.1.7
env:
TRUNK_DISABLE_TELEMETRY: 'true'
with:
token: ${{ env.TRUNK_API_TOKEN }}
job-keys: quality
github-token: ''
enable-annotation: 'true'
quality:
# A ~1-minute job: GitHub's free hosted runner (public repo), not a
# billed 8-vCPU Ubicloud machine.
@@ -57,6 +57,37 @@ permissions:
contents: read
jobs:
# Trunk Dynamic CI, LEARNING ONLY (GST-1847; free-tests.yml has the full
# rationale). Trunk records whether it would have run or skipped windows-scoped-shard
# on same-repository pull requests; every job still runs. Trunk keys verdicts
# by workflow file, so each observed workflow carries its own observer. No job
# needs it, no condition reads it and it exports no outputs, so a mode change
# in Trunk's dashboard cannot skip anything. It checks out nothing, runs no
# pull-request code, holds no GITHUB_TOKEN scopes, never fails the run, and
# skips fork pull requests, pushes and dispatches. docs/TESTING_INTERNALS.md.
trunk-dynamic-ci:
name: Trunk Dynamic CI (learning only, windows-scoped-tests)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
continue-on-error: true
permissions: {}
env:
TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }}
steps:
- name: Record Trunk's verdicts for windows-scoped-shard (nothing reads them)
if: env.TRUNK_API_TOKEN != ''
timeout-minutes: 3
continue-on-error: true
uses: trunk-io/dynamic-ci@5bcddbf3615c2b032c21eed1b514c91164bda4a8 # v0.1.7
env:
TRUNK_DISABLE_TELEMETRY: 'true'
with:
token: ${{ env.TRUNK_API_TOKEN }}
job-keys: windows-scoped-shard
github-token: ''
enable-annotation: 'true'
windows-scope:
runs-on: ubuntu-24.04
timeout-minutes: 5
+31
View File
@@ -101,6 +101,37 @@ permissions:
contents: read
jobs:
# Trunk Dynamic CI, LEARNING ONLY (GST-1847; free-tests.yml has the full
# rationale). Trunk records whether it would have run or skipped windows-setup and held-install-journey
# on same-repository pull requests; every job still runs. Trunk keys verdicts
# by workflow file, so each observed workflow carries its own observer. No job
# needs it, no condition reads it and it exports no outputs, so a mode change
# in Trunk's dashboard cannot skip anything. It checks out nothing, runs no
# pull-request code, holds no GITHUB_TOKEN scopes, never fails the run, and
# skips fork pull requests, pushes and dispatches. docs/TESTING_INTERNALS.md.
trunk-dynamic-ci:
name: Trunk Dynamic CI (learning only, windows-setup-e2e)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
continue-on-error: true
permissions: {}
env:
TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }}
steps:
- name: Record Trunk's verdicts for windows-setup and held-install-journey (nothing reads them)
if: env.TRUNK_API_TOKEN != ''
timeout-minutes: 3
continue-on-error: true
uses: trunk-io/dynamic-ci@5bcddbf3615c2b032c21eed1b514c91164bda4a8 # v0.1.7
env:
TRUNK_DISABLE_TELEMETRY: 'true'
with:
token: ${{ env.TRUNK_API_TOKEN }}
job-keys: windows-setup,held-install-journey
github-token: ''
enable-annotation: 'true'
windows-scope:
# The paths filter above starts this workflow; this job decides whether
# the Windows jobs run. A workflow_dispatch run always runs them.
+31
View File
@@ -31,6 +31,37 @@ permissions:
contents: read
jobs:
# Trunk Dynamic CI, LEARNING ONLY (GST-1847; free-tests.yml has the full
# rationale). Trunk records whether it would have run or skipped windows-smoke
# on same-repository pull requests; every job still runs. Trunk keys verdicts
# by workflow file, so each observed workflow carries its own observer. No job
# needs it, no condition reads it and it exports no outputs, so a mode change
# in Trunk's dashboard cannot skip anything. It checks out nothing, runs no
# pull-request code, holds no GITHUB_TOKEN scopes, never fails the run, and
# skips fork pull requests, pushes and dispatches. docs/TESTING_INTERNALS.md.
trunk-dynamic-ci:
name: Trunk Dynamic CI (learning only, windows-smoke)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
continue-on-error: true
permissions: {}
env:
TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }}
steps:
- name: Record Trunk's verdicts for windows-smoke (nothing reads them)
if: env.TRUNK_API_TOKEN != ''
timeout-minutes: 3
continue-on-error: true
uses: trunk-io/dynamic-ci@5bcddbf3615c2b032c21eed1b514c91164bda4a8 # v0.1.7
env:
TRUNK_DISABLE_TELEMETRY: 'true'
with:
token: ${{ env.TRUNK_API_TOKEN }}
job-keys: windows-smoke
github-token: ''
enable-annotation: 'true'
windows-smoke:
runs-on: windows-latest
timeout-minutes: 15
@@ -0,0 +1 @@
- **Trunk Dynamic CI now also watches the Windows checks, the quality gate and the eval jobs on pull requests, still in learning mode, and it still can't skip anything.** Trunk answers only for jobs in the workflow that asks, so `windows-smoke.yml`, `windows-scoped-tests.yml`, `windows-setup-e2e.yml`, `quality-gate.yml` and `evals.yml` each get the same learning-only `trunk-dynamic-ci` job that `free-tests.yml` already has. Trunk records whether it would have run or skipped `windows-smoke`, `windows-scoped-shard`, `windows-setup`, `held-install-journey`, `quality`, `build-image` and `eval-slices`. No job reads the answer, so every check still runs with its own name and result. Each observer runs one pinned action, checks out no code, holds no GitHub token scopes, skips fork pull requests, skips its step when the token is missing, and never fails the run. `test/trunk-dynamic-ci-observers.test.ts` holds every observer to that shape.
+33 -12
View File
@@ -1704,9 +1704,25 @@ whether it would have run or skipped `free-suite` and
free-suite shards share one verdict. The verdicts show up in the job summary,
on the pull request's Dynamic CI tab and at app.trunk.io/dynamic-ci.
Trunk keys verdicts by workflow file, so it answers only for jobs in the
workflow that ran the action. So since GST-1847, the other costly pull-request
workflows carry the same observer job, each named "Trunk Dynamic CI (learning
only, <workflow>)":
| Workflow | Observed jobs |
|---|---|
| `free-tests.yml` | `free-suite`, `macos-named-regressions` |
| `windows-smoke.yml` | `windows-smoke` |
| `windows-scoped-tests.yml` | `windows-scoped-shard` |
| `windows-setup-e2e.yml` | `windows-setup`, `held-install-journey` |
| `quality-gate.yml` | `quality` |
| `evals.yml` | `build-image`, `eval-slices` |
Everything below holds for every observer.
It never skips anything. No job needs the observer, no condition reads its
verdicts, and it exports no outputs. Switching Trunk to enforcement in its
dashboard would still skip nothing, because nothing in this workflow reads
dashboard would still skip nothing, because nothing in its workflow reads
the verdicts. Every test runs, and every required check keeps its name and
its result. Merge evidence never reads Trunk: the `free-tests` aggregate,
the CI reuse profile, `gstack-ci-carry`, the pregate, the train gate
@@ -1722,28 +1738,33 @@ or commit disappears), the observer's own check reports failure, and
`gstack-ship-queue ready` counts every check at the head. Rerun it once;
if it fails again, delete the job. Fork pull requests, pushes and dispatches
skip the job. A missing token skips only its step, so the job still fetches
the action. It is the only job in the workflow that sees a secret
(`TRUNK_API_TOKEN`), so it checks out nothing and runs none of the pull
request's code. It holds no GITHUB_TOKEN scopes (`permissions: {}`). At the
the action. Its only secret is `TRUNK_API_TOKEN` (in free-tests.yml it is
the only job that sees any secret; evals.yml's other jobs hold their own), so
it checks out nothing and runs none of the pull request's code. It holds no GITHUB_TOKEN scopes (`permissions: {}`). At the
pinned commit (`src/config.ts`, `src/changed-files.ts`), the action makes one
network call: a request for verdicts to
`api.trunk.io/v2/dynamic-ci/generate-plan`, at most 3 attempts with a
30-second budget each. Telemetry is off. With no checkout and an empty
`github-token`, the action sends no file list, and Trunk reads the diff itself
through its GitHub App.
`test/free-tests-workflow-wiring.test.ts` and
`test/ci-reuse-workflow-wiring.test.ts` lock that shape in place.
`test/free-tests-workflow-wiring.test.ts`,
`test/ci-reuse-workflow-wiring.test.ts` and
`test/trunk-dynamic-ci-observers.test.ts` lock that shape in place.
**Review date: one week after it merges (2026-11-12 if it merges on
2026-11-05).** Compare Trunk's would-skip verdicts with what the jobs it
would have skipped actually found, and look hardest at any would-skip on a run
where that job failed. Leave out runs where `reuse` skipped the suite: Trunk
still records a verdict there, but there is no result to compare it with.
Trunk's `required-check` vote treats both jobs as not
required, because branch protection names the `free-tests` aggregate and
where that job failed. Leave out runs where the workflow itself skipped the
observed job, because Trunk still records a verdict there but there is no
result to compare it with: `reuse` skipping the Free Tests suite,
`windows-scope` skipping the scoped-test and setup Windows jobs on a pull
request that touches no Windows path (`windows-smoke` always runs), and `eval-slices` on pull-request pushes, which run evals.yml
in its screening-summary mode and plan no slices.
In free-tests.yml, Trunk's `required-check` vote treats both observed jobs as
not required, because branch protection names the `free-tests` aggregate and
not the jobs under it. But `free-tests` needs both jobs and every shard
receipt, so discount that vote when you judge the would-skip rate. Any skip
added later also has to pass the aggregate's receipt check. Turning on
receipt, so discount that vote when you judge their would-skip rate. Any skip
added there later also has to pass the aggregate's receipt check. Turning on
skipping is a separate decision for Garry, made after that review.
### Windows free lane
+5 -5
View File
@@ -147,7 +147,7 @@ const dispatch = (ref = 'main') => ({ event_name: 'workflow_dispatch', actor: 'g
event: { repository: { default_branch: 'main' } } });
const INPUTS = { evals_all: true, base_ref: '', base_sha: '', expected_sha: '', nonce: '', pr_receipts: '', cases: '', prefix_shas: '', screen_pr: '', screen_merge_sha: '', screen_policy: '' };
// rerun-plan is the free rule-16 planner (F1); its paid eval-rerun runs only when it plans a rerun.
const TODAY_PR_JOBS = ['build-image', 'eval-slices', 'plan-slices', 'readiness', 'recover-receipts', 'rerun-plan', 'slices-comment', 'slices-report'];
const TODAY_PR_JOBS = ['build-image', 'eval-slices', 'plan-slices', 'readiness', 'recover-receipts', 'rerun-plan', 'slices-comment', 'slices-report', 'trunk-dynamic-ci'];
const TODAY_DISPATCH_JOBS = ['build-image', 'eval-slices', 'plan-slices', 'rerun-plan', 'slices-report'];
const TRAIN_STEPS = ['Write the train record', 'Upload the train record', "Check the screen's PR head (Free Tests)", 'Plan the bisect (one executor per prefix)', 'Hand the bisect plan to its executors'];
const train = { record_artifact: 'gstack-train-record', full_gate_check: 'gstack/full-gate' };
@@ -188,14 +188,14 @@ describe('without a train block nothing changes (the inert proof)', () => {
const fork = pr();
(fork.event.pull_request.head.repo as { full_name: string }).full_name = 'fork/gstack';
expect(simulate(evals, { github: fork, inputs: {}, outputs: { route: { ...ROUTE, mode: 'pr' } } }).jobs).toEqual(['build-image', 'route']);
expect(simulate(evals, { github: { ...pr(), actor: 'dependabot[bot]' }, inputs: {} }).jobs).toEqual([]);
expect(simulate(evals, { github: { ...pr(), actor: 'dependabot[bot]' }, inputs: {} }).jobs).toEqual(['trunk-dynamic-ci']);
});
});
describe('under a train block (the gate edits, each only on its mode)', () => {
test('a PR push starts no paid slice and waits for nothing: route, the image and the free screening summary only', () => {
const sim = simulate(evals, { github: pr(), inputs: {}, outputs: { route: { ...ROUTE, ...train, mode: 'pr-summary' } } });
expect(sim.jobs).toEqual(['build-image', 'route', 'screen-summary']);
expect(sim.jobs).toEqual(['build-image', 'route', 'screen-summary', 'trunk-dynamic-ci']);
expect(simulate(evals, { github: pr(), inputs: {}, outputs: { route: { ...ROUTE, mode: 'pr' } } }).jobs).toContain('readiness');
const summary = evals.jobs['screen-summary']!;
expect(summary.permissions).toEqual({ contents: 'read' });
@@ -284,7 +284,7 @@ describe('under a train block (the gate edits, each only on its mode)', () => {
describe('GSTA-48 open items', () => {
test('a head the readiness job blocked replaces the stale verdict comment and plans nothing (P3-1)', () => {
const sim = simulate(evals, { github: pr(), inputs: {}, outputs: { route: { ...ROUTE, mode: 'pr' } }, results: { readiness: 'failure' } });
expect(sim.jobs).toEqual(['build-image', 'readiness', 'readiness-comment', 'route']);
expect(sim.jobs).toEqual(['build-image', 'readiness', 'readiness-comment', 'route', 'trunk-dynamic-ci']);
const comment = evals.jobs['readiness-comment']!;
expect(comment.steps.some(st => st.uses)).toBe(false);
expect(comment.steps[0]!.run).toContain('if [ -z "$COMMENT_ID" ]; then exit 0; fi');
@@ -316,7 +316,7 @@ describe('rule-16 rerun wiring (F1)', () => {
const sim = simulate(evals, { github: pr(), inputs: {}, outputs: { route: { ...ROUTE, mode: 'pr' }, 'rerun-plan': planned }, results });
expect(sim.jobs, JSON.stringify(results)).toContain('slices-report');
}
for (const [id, job] of Object.entries(evals.jobs)) expect((job as Record<string, unknown>)['continue-on-error'], id).toBeUndefined();
for (const [id, job] of Object.entries(evals.jobs).filter(([id]) => id !== 'trunk-dynamic-ci')) expect((job as Record<string, unknown>)['continue-on-error'], id).toBeUndefined();
const report = evals.jobs['slices-report']!;
const reconcile = report.steps.find(st => st.id === 'reconcile')!;
expect(reconcile.run).toContain('echo "exit=${PIPESTATUS[0]}"');
+60
View File
@@ -0,0 +1,60 @@
/**
* Trunk Dynamic CI observers (GST-1400, GST-1847; docs/TESTING_INTERNALS.md).
*
* Trunk keys its verdicts by workflow file, so every observed workflow carries
* its own learning-only `trunk-dynamic-ci` job. Each one must stay an observer:
* same-repository pull requests only, one pinned action, no checkout, no
* GITHUB_TOKEN scopes, fail-open at step and job level, no outputs, and no job
* that needs it or reads it. Then a mode change in Trunk's dashboard can never
* skip, gate or fail a check, and the token never reaches fork pull requests or
* pull-request code.
*/
import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..');
const OBSERVED: Record<string, string> = {
'free-tests.yml': 'free-suite,macos-named-regressions',
'windows-smoke.yml': 'windows-smoke',
'windows-scoped-tests.yml': 'windows-scoped-shard',
'windows-setup-e2e.yml': 'windows-setup,held-install-journey',
'quality-gate.yml': 'quality',
'evals.yml': 'build-image,eval-slices',
};
const SECRET_REF = /secrets\s*(?:\.|\[)\s*['"]?[A-Za-z0-9_-]*|toJSON\(\s*secrets\b/gi;
describe('Trunk Dynamic CI observers', () => {
// Value: protects=the learning-only contract for every observed workflow; fails_when=an observer runs on forks or non-PR events, checks out or runs code, gains token scopes, loses fail-open, exports outputs, names a job its workflow lacks, or any job needs or reads it, or the action is unpinned; seam=.github/workflows/*.yml; no_claim=what Trunk recommends
for (const [file, keys] of Object.entries(OBSERVED)) {
test(`${file}: observes ${keys} and nothing reads it`, () => {
const wf = Bun.YAML.parse(fs.readFileSync(path.join(ROOT, '.github/workflows', file), 'utf8')) as any;
expect(Object.keys(wf.on)).toContain('pull_request');
const trunk = wf.jobs['trunk-dynamic-ci'];
expect(trunk.if).toBe("github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository");
expect(trunk['runs-on']).toBe('ubuntu-24.04');
expect(trunk.permissions).toEqual({});
expect(trunk['continue-on-error']).toBe(true);
expect(trunk['timeout-minutes']).toBe(5);
expect(trunk.outputs).toBeUndefined();
expect(trunk.needs).toBeUndefined();
expect(trunk.env).toEqual({ TRUNK_API_TOKEN: '${{ secrets.TRUNK_API_TOKEN }}' });
expect(trunk.steps).toHaveLength(1);
const [step] = trunk.steps;
expect(step.uses).toBe('trunk-io/dynamic-ci@5bcddbf3615c2b032c21eed1b514c91164bda4a8');
expect(step.run).toBeUndefined();
expect(step.if).toBe("env.TRUNK_API_TOKEN != ''");
expect(step['continue-on-error']).toBe(true);
expect(step['timeout-minutes']).toBe(3);
expect(step.env).toEqual({ TRUNK_DISABLE_TELEMETRY: 'true' });
expect(step.with).toEqual({ token: '${{ env.TRUNK_API_TOKEN }}', 'job-keys': keys, 'github-token': '', 'enable-annotation': 'true' });
for (const key of keys.split(',')) expect([key, wf.jobs[key] !== undefined]).toEqual([key, true]);
for (const [name, job] of Object.entries(wf.jobs as Record<string, any>).filter(([n]) => n !== 'trunk-dynamic-ci')) {
expect([name, [job.needs ?? []].flat().includes('trunk-dynamic-ci')]).toEqual([name, false]);
expect([name, /trunk|dynamic-ci|TRUNK_API_TOKEN/i.test(JSON.stringify(job))]).toEqual([name, false]);
}
const trunkRefs = (JSON.stringify(wf.jobs).match(SECRET_REF) ?? []).filter(ref => ref === 'secrets.TRUNK_API_TOKEN');
expect(trunkRefs).toEqual(['secrets.TRUNK_API_TOKEN']);
});
}
});
+2 -2
View File
@@ -112,12 +112,12 @@ test('a record_durations dispatch times each Windows-safe file alone and uploads
expect(job.steps.at(-1).if).toBe("always() && steps.seed.outcome == 'success'");
});
test('windows-smoke is the PR gate: every pull request and main push, one windows-latest job', () => {
test('windows-smoke is the PR gate: every pull request and main push, one windows-latest job beside the Trunk observer', () => {
expect(Object.keys(smoke.on).sort()).toEqual(['pull_request', 'push', 'workflow_dispatch']);
expect(smoke.on.pull_request).toEqual({ branches: ['main'] });
expect(smoke.on.push).toEqual({ branches: ['main'] });
expect(smoke.permissions).toEqual({ contents: 'read' });
expect(Object.keys(smoke.jobs)).toEqual(['windows-smoke']);
expect(Object.keys(smoke.jobs)).toEqual(['trunk-dynamic-ci', 'windows-smoke']);
const job = smoke.jobs['windows-smoke'];
expect(job['runs-on']).toBe('windows-latest');
expect(job.if).toBeUndefined();
+5 -5
View File
@@ -130,10 +130,10 @@ function prRepo(files: string[], lines: Record<string, string> = {}) {
}
describe('windows-smoke stays on every pull request', () => {
test('no paths filter, no job condition, one job', () => {
test('no paths filter, no job condition, one test job beside the Trunk observer', () => {
expect(smoke.on.pull_request).toEqual({ branches: ['main'] });
expect(smoke.on.push).toEqual({ branches: ['main'] });
expect(Object.keys(smoke.jobs)).toEqual(['windows-smoke']);
expect(Object.keys(smoke.jobs)).toEqual(['trunk-dynamic-ci', 'windows-smoke']);
expect(smoke.jobs['windows-smoke'].if).toBeUndefined();
expect(smoke.jobs['windows-smoke'].needs).toBeUndefined();
});
@@ -159,7 +159,7 @@ describe('Windows Setup E2E runs by install path', () => {
const scope = setupE2e.jobs['windows-scope'];
expect(scope['runs-on']).toBe('ubuntu-24.04');
expect(scope.steps[0].with).toEqual({ 'fetch-depth': 2, 'persist-credentials': false });
const windowsJobs = Object.keys(setupE2e.jobs).filter(name => name !== 'windows-scope');
const windowsJobs = Object.keys(setupE2e.jobs).filter(name => name !== 'windows-scope' && name !== 'trunk-dynamic-ci');
expect(windowsJobs).toEqual(['windows-setup', 'held-install-journey']);
for (const name of windowsJobs) {
expect([name, setupE2e.jobs[name]['runs-on'], setupE2e.jobs[name].needs, setupE2e.jobs[name].if])
@@ -198,12 +198,12 @@ describe('Windows Setup E2E runs by install path', () => {
});
describe('the path-scoped Windows shard on pull requests', () => {
test('every pull request runs the cheap scope job; a dispatch names its files; read-only and secretless', () => {
test('every pull request runs the cheap scope job; a dispatch names its files; read-only and secretless outside the Trunk observer', () => {
expect(Object.keys(scoped.on).sort()).toEqual(['pull_request', 'workflow_dispatch']);
expect(scoped.on.pull_request).toEqual({ branches: ['main'] });
expect(scoped.on.workflow_dispatch.inputs.files).toMatchObject({ type: 'string', required: true });
expect(scoped.permissions).toEqual({ contents: 'read' });
expect(scopedText).not.toContain('secrets.');
expect(scopedText.match(/secrets\s*(?:\.|\[)\s*['"]?[A-Za-z0-9_-]*|toJSON\(\s*secrets\b/gi)).toEqual(['secrets.TRUNK_API_TOKEN']);
expect(scopedText).not.toContain('pull_request_target');
expect(scoped.concurrency.group).toBe('windows-scoped-tests-${{ github.event.pull_request.number || github.run_id }}');
const scope = scoped.jobs['windows-scope'];