Skip to content

Commit 26c4059

Browse files
committed
ci(observability-map): widen the obsmap filter to what the suite reads
The filter watched apps/webapp/app/routes only, which was narrower than the suite's actual coupling. webappSymbols.test.ts walks all of apps/webapp/app and fails when a guard, sensitive or audit symbol stops resolving, so renaming e.g. requireUserId in app/services/session.server.ts matched the webapp filter and nothing else: no job ran this suite and the break landed on main, or on the next unrelated internal-packages PR. integration.test.ts also asserts on the text of observability-map.yml, which no filter watched at all, so editing the report workflow alone ran nothing. Derived the coupling set from the code rather than from the comment. Outside its own directory the suite reads apps/webapp/app (whole tree for symbols, the route subtree for the scan), packages/plugins/src, internal-packages/rbac/src, and four workflow files. packages/plugins/src and internal-packages/rbac/src stay out: internal already matches packages/** and internal-packages/**, and unit-tests-internal.yml runs the same suite, so listing them here would run it twice. A new test pins that reasoning. Cost, over the last 400 commits on main: 31% touch routes, 52% touch apps/webapp/app, so the job fires on roughly half of PRs instead of roughly a third. It is the cheap one, a single 4x runner with no containers and no database. Reported by Devin on #4455.
1 parent 00b78cb commit 26c4059

3 files changed

Lines changed: 65 additions & 22 deletions

File tree

.github/workflows/pr_checks.yml

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,33 @@ jobs:
8282
- 'pnpm-lock.yaml'
8383
- 'pnpm-workspace.yaml'
8484
- 'turbo.json'
85-
# Routes only, and that is the whole reason this filter exists: one test in
86-
# @internal/observability-map scans the live route tree, and without this a webapp-only
87-
# PR that renames a route merges green and the break lands on main, or on the next
88-
# unrelated internal-packages PR.
85+
# The whole webapp app tree, not just its routes, and that is the whole reason this
86+
# filter exists. Two tests in @internal/observability-map read it: integration.test.ts
87+
# scans the live route tree, and webappSymbols.test.ts walks all of apps/webapp/app and
88+
# fails when a guard, sensitive or audit symbol stops resolving. Routes-only was this
89+
# filter's own bug: renaming e.g. requireUserId in app/services/session.server.ts
90+
# matched `webapp` and nothing else, so no job ran the suite and the break landed on
91+
# main, or on the next unrelated internal-packages PR.
8992
#
90-
# The package's own paths are deliberately NOT here. `internal` above already matches
91-
# `internal-packages/**`, and `unit-tests-internal.yml` runs `turbo run test --filter
92-
# "@internal/*"`, which picks up @internal/observability-map and runs the same vitest
93-
# suite including that route-tree test. Listing the package here as well ran the suite
94-
# twice on every PR touching it, which was this filter's own doing.
93+
# The cost of the wider set, measured over the last 400 commits on main: 31% touch
94+
# routes, 52% touch apps/webapp/app, so the job goes from firing on roughly a third of
95+
# PRs to roughly a half. It is the cheap one -- a single 4x runner, no containers, no
96+
# database, no prisma generate -- which is what makes that affordable.
97+
#
98+
# observability-map.yml is here because integration.test.ts asserts on its text and no
99+
# other filter watches it, so editing the report workflow alone ran nothing at all.
100+
#
101+
# Deliberately NOT here: this package's own paths, and packages/plugins/src and
102+
# internal-packages/rbac/src, the other two trees webappSymbols.test.ts reads.
103+
# `internal` above already matches `internal-packages/**` and `packages/**`, and
104+
# `unit-tests-internal.yml` runs `turbo run test --filter "@internal/*"`, which picks up
105+
# @internal/observability-map and runs the same vitest suite. Listing them here as well
106+
# ran the suite twice on every PR touching them, which was this filter's own doing.
95107
obsmap:
96-
- 'apps/webapp/app/routes/**'
108+
- 'apps/webapp/app/**'
97109
- '.github/workflows/pr_checks.yml'
98110
- '.github/workflows/unit-tests-observability-map.yml'
111+
- '.github/workflows/observability-map.yml'
99112
- 'package.json'
100113
- 'pnpm-lock.yaml'
101114
- 'pnpm-workspace.yaml'

.github/workflows/unit-tests-observability-map.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ jobs:
3636
- name: 📥 Download deps
3737
run: pnpm install --frozen-lockfile
3838

39-
# One test in this suite scans apps/webapp/app/routes, which is why the filter that gates
40-
# this workflow watches those paths as well as the package's own.
39+
# This suite reads apps/webapp/app (the route tree for the scan, the whole app tree for the
40+
# symbol check) and the report workflow's text, which is why the filter that gates this
41+
# workflow watches all of those and not only the routes folder.
4142
- name: 🧪 Run tests
4243
run: pnpm --filter @internal/observability-map run test

internal-packages/observability-map/src/integration.test.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,29 @@ import { buildReport } from "./score.js";
1414
import { SCORED_CHECK_IDS } from "./checks/index.js";
1515

1616
/**
17-
* The one deliberate coupling to `apps/webapp/app/routes` in the suite. Everything else, including
18-
* the CLI tests, runs against a fixture tree of this package's own making.
17+
* This file's deliberate coupling to `apps/webapp/app/routes`. It is not the suite's only one, and
18+
* saying it was is what let the paths filter be written for this file alone:
19+
* `webappSymbols.test.ts` walks all of `apps/webapp/app`, `packages/plugins/src` and
20+
* `internal-packages/rbac/src`, and `mutationCorpus.test.ts` scans the route tree behind an env
21+
* gate. Everything else, including the CLI tests, runs against a fixture tree of this package's own
22+
* making.
1923
*
2024
* The coupling is acceptable because nothing here names a route or a count: the scan must not
2125
* crash, the entry point count must sit inside a wide band, and parse failures must be zero. Those
2226
* survive routes being added, renamed and deleted, and they are the only things a fixture tree
2327
* cannot tell us, since a fixture only contains shapes somebody thought to write down.
2428
*
2529
* What runs this for a webapp pull request is `.github/workflows/unit-tests-observability-map.yml`,
26-
* called from `pr_checks.yml` behind an `obsmap` paths filter covering `apps/webapp/app/routes/**`,
27-
* and listed in the `all-checks` aggregate so it actually gates. A pull request touching this
28-
* PACKAGE reaches the same test by the other road: `internal` already matches
29-
* `internal-packages/**`, and `unit-tests-internal.yml` runs `turbo run test --filter "@internal/*"`
30-
* over this package too. So both directions are gated, and neither is gated twice; the `obsmap`
31-
* filter used to name the package as well, which ran this suite twice on every PR touching it.
30+
* called from `pr_checks.yml` behind an `obsmap` paths filter covering the whole of
31+
* `apps/webapp/app` plus the report workflow, and listed in the `all-checks` aggregate so it
32+
* actually gates. The filter is wider than this file's own coupling because the suite's is:
33+
* `webappSymbols.test.ts` walks all of `apps/webapp/app`, and the describes below read
34+
* `observability-map.yml`. A pull request touching this PACKAGE, or `packages/plugins/src` or
35+
* `internal-packages/rbac/src`, reaches the same test by the other road: `internal` already matches
36+
* `internal-packages/**` and `packages/**`, and `unit-tests-internal.yml` runs `turbo run test
37+
* --filter "@internal/*"` over this package too. So every direction is gated and none is gated
38+
* twice; the `obsmap` filter used to name the package as well, which ran this suite twice on every
39+
* PR touching it.
3240
*
3341
* Two shapes were tried and rejected on the way here. Widening `pr_checks.yml`'s `internal` filter
3442
* to the route paths ran all eighteen internal packages, twelve shards with postgres, clickhouse,
@@ -179,9 +187,30 @@ describe("the package's tests are wired into the gate", () => {
179187
expect(read(REUSABLE)).toContain("workflow_call");
180188
});
181189

182-
it("watches the live route tree, which is the only thing the internal filter misses", () => {
190+
// Round 5. The filter watched `apps/webapp/app/routes/**` while the suite reads more than that,
191+
// so a rename outside the routes folder matched only `webapp`, ran no job that runs this suite,
192+
// and broke the build for whoever pushed next. Asserted as the whole set the suite reads and no
193+
// other filter covers, rather than as the one path that prompted the filter, because the routes
194+
// entry looked complete right up until it wasn't.
195+
it("watches every webapp path the internal filter misses, not just the routes folder", () => {
183196
const filter = read(PR_CHECKS).split(" obsmap:")[1]!.split(" cli:")[0]!;
184-
expect(filter).toContain("'apps/webapp/app/routes/**'");
197+
// webappSymbols.test.ts walks all of apps/webapp/app, not just routes.
198+
expect(filter).toContain("'apps/webapp/app/**'");
199+
// The report workflow, whose text the two describes above assert on. No other filter names it.
200+
expect(filter).toContain("'.github/workflows/observability-map.yml'");
201+
});
202+
203+
// The other two trees webappSymbols.test.ts reads. They belong to `internal`, not here, and this
204+
// pins the reason so the obvious-looking addition has to argue with a test first.
205+
it("leaves the two non-webapp roots it reads to the internal filter", () => {
206+
const text = read(PR_CHECKS);
207+
const obsmap = text.split(" obsmap:")[1]!.split(" cli:")[0]!;
208+
expect(obsmap).not.toContain("packages/plugins");
209+
expect(obsmap).not.toContain("internal-packages/rbac");
210+
211+
const internal = text.split(" internal:")[1]!.split(" # ")[0]!;
212+
expect(internal).toContain("'packages/**'");
213+
expect(internal).toContain("'internal-packages/**'");
185214
});
186215

187216
// Round E item 6. `internal` matches `internal-packages/**` and `unit-tests-internal.yml` runs

0 commit comments

Comments
 (0)