paid screens: narrow screens derive setup instead of running the full gate (GST-1575) (#4056)

* paid screens: narrow screens derive setup; modules/loadouts.json derives through its reader (GST-1575)

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

* changelog: name the fragment after the branch (GST-1575)

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

* harness pins: test-pr-profile.ts change is non-behavioral (case selection only) (GST-1575)

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

* changelog: setup reaches its cases through touchfiles; loadouts-only edits wait for the train census (GST-1575)

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-06 06:15:12 +00:00
1 parent 45c849c289
commit 12d18a2e10
7 files changed
+98 -6

No files matched your search

@@ -1 +1 @@
{"blob":"900a5ec1eb151f99f93a07b3b8f3c4b2c047e4b1","since":6,"nonBehavioral":"scripts/test-pr-profile.ts only adds qm/** to FREE_ONLY_PR_FILES; no paid case reads qm/, so no case's selection or verdict can change (GST-1492)"}
{"blob":"812d84b94328409328020a842696e1a34b22b095","since":6,"nonBehavioral":"narrow screens derive setup and loadouts.json is derived; case selection only, no verdict can change (GST-1575)"}
+1 -1
View File
@@ -69,7 +69,7 @@ const CODE = /\.(?:[cm]?[jt]s|tsx|jsx)$/;
/** An extensionless script Bun or Node runs: its imports are code, not shell. */
const CODE_SHEBANG = /^#![^\n]*\b(?:bun|node)\b/;
const TEXT = /\.(?:[cm]?[jt]s|tsx|jsx|sh|md|tmpl|json|ya?ml|toml|txt|py)$/;
const REPO_PATH = /(?:^|[^\w.@-])((?:bin|lib|scripts|browse|design|make-pdf|hosts|extension|model-overlays|agents|capy|test\/helpers|test\/fixtures)\/[\w.@-]+(?:\/[\w.@-]+)*)/g;
const REPO_PATH = /(?:^|[^\w.@-])((?:bin|lib|scripts|browse|design|make-pdf|hosts|extension|model-overlays|agents|capy|modules|test\/helpers|test\/fixtures)\/[\w.@-]+(?:\/[\w.@-]+)*)/g;
/** `path.join(ROOT, 'lib', 'x.ts')`: a top-level directory and its entry as separate string literals. */
const JOINED_PATH = /['"`](bin|lib|scripts|browse|design|make-pdf|hosts|extension|model-overlays|agents)['"`]\s*,\s*['"`]([\w.@-]+)['"`]/g;
const BIN_NAME = /\bgstack-[a-z0-9][a-z0-9-]*[a-z0-9]\b/g;
+10 -1
View File
@@ -328,6 +328,13 @@ export const PR_FULL_GATE_FILES = [
'test/helpers/periodic-exclude-data.ts',
] as const;
/**
* Full-gate inputs a narrow screen derives like any other file. No paid run executes ./setup (evals.yml only
* builds) and scripts/pr-dependencies.ts maps it to the cases that invoke it; pull_request runs, broad screens
* and --profile full still restore the full gate for it, and the train gate's full census runs every case.
*/
export const NARROW_DERIVED_FULL_GATE_FILES = ['setup'] as const;
/** Periodic- and marathon-lane workflows: the PR gate never runs a case under them; their branch dispatch validates them. */
export const PERIODIC_ONLY_PR_FILES = ['.github/workflows/evals-periodic.yml', '.github/workflows/evals-marathon.yml'] as const;
@@ -463,7 +470,9 @@ export function selectPrProfile(options: {
? [...consumers.keys()].filter(file => derived?.tracked.has(file) && gateIds.every(id => consumers.get(file)!.e2e.has(id))) : [];
const derivedFiles = [...consumers.keys()].filter(file => !hubFiles.includes(file));
const unknownFiles = unmapped.filter(file => !derivedGlobal.includes(file) && !consumers.has(file) && !noConsumerFiles.includes(file));
const sharedInputs = files.filter(file => depends(file, PR_FULL_GATE_FILES));
const fullGateFiles = options.screenPolicy === 'narrow'
? PR_FULL_GATE_FILES.filter(file => !(NARROW_DERIVED_FULL_GATE_FILES as readonly string[]).includes(file)) : PR_FULL_GATE_FILES;
const sharedInputs = files.filter(file => depends(file, fullGateFiles));
const fallback = unknownFiles.length > 0 || sharedInputs.length > 0;
const dependentsMode = !fallback && derivedFiles.length > 0;
const derivedE2E = derivedFiles.flatMap(file => [...consumers.get(file)!.e2e]);