fix(program): a measure's rate is its own rows, and the Maui e2e suite runs the configuration the pilot deploys - #543
Merged
Conversation
added 4 commits
September 8, 2026 17:23
…e runs the configuration the pilot deploys The programs overview served the whole run's official evidence under each measure's name. `aggregateOfficialRun` paged `listOutcomes(runId)` unfiltered, under a comment claiming a run evaluates one measure with one engine — true of a MEASURE-scoped run, false of the ALL_PROGRAMS run the pilot actually runs, which holds a row per (subject, measure) pair. On the sandbox CMS122's card carried CMS125's initial population, CMS125's 59.2% and `ecqmId: 125FHIR`. The MeasureReport and reconciliation routes were unaffected (both refuse a multi-measure run with 422); the overview was the one unguarded caller. `listOutcomes` now takes an optional `measureId` and narrows in SQL, before the page window, in both stores — the app-side alternative would page 120,000 rows once per measure on a six-measure nightly. `runProducedOfficialEvidence` is scoped the same way, so a run that mixes an official and an authored measure (ADR-072 admits one) no longer answers for both from whichever row sorted first. The Maui Playwright job ran with no VSAC credential, so cms2/cms130/cms165/cms137 booted `official-pending` and the suite's green was about a stack nobody deploys. It now vendors the six terminology sidecars with the credential and sets WORKWELL_OFFICIAL_MEASURES to the deployed value. Because a missing sidecar degrades rather than crashes, the backend's own `runnable=` boot line is asserted — without it the job runs two measures and reports green, which is what it had been doing since 2026-09-06. The job is dispatch-only and this is a public repo, so the key is scoped to a single step that runs before the frontend and e2e dependency trees exist, and the completed expansions are not cached: an Actions cache is restorable by a fork pull request. Eleven failing specs, none a backend regression: three needed the routed stack, two asserted the case fan-out ADR-078 removed (now bounds, with the out-of-population gap named), three signed in as the quality lead for pages that are engineering-gated to ADMIN — two of those had been asserting against an AccessDenied panel — one contradicted the §6.3 patient-column contract, and one matched a PCP option inside a closed select. 43 tests to 26 with six measures covered instead of two; four workers and one shared sign-in per role replace ~40 logins. The roster's Status/PCP/Age/Sex/Page-size selects had no accessible name (Chrome computes none from the wrapping label) and now carry aria-labels, matching Panel/System/Segment.
…torage state cannot break the twh project
Review found the boot-line guard close to vacuous. `classifyRunnable` returns `official-pending` for
one reason — the id is absent from WORKWELL_OFFICIAL_MEASURES — and never returns it for
cms122/cms125, which reach the `authored` branch first. So a routing list that lost those two would
print `cms122:authored`, pass a negative grep, and run the pilot's flagship measures on the authored
engine while `runs.spec`'s 48 x 6 held either way. The check is now a positive exact match on
`<id>:official`, verified against the four boot-line shapes including the one the old job produced.
Terminology is not part of the classification at all, so the sidecar control is and always was the
`truncated` refusal in the vendor step; the comment and the journal said otherwise and now do not.
The maui specs declared `test.use({ storageState })` at describe scope. The twh job runs
`npx playwright test` with no `--project`, which loads BOTH projects, and Playwright resolves the
`page` fixture before the `beforeEach` that calls `test.skip` — so those specs would have thrown
ENOENT on the twh run instead of skipping. The session helpers now return `{}` off the maui profile.
Verified by listing both projects with no `.auth/` present.
Storage state is per (role, spec file) rather than per role. Contexts built from one state share a
refresh-token family, and presenting an already-rotated token revokes the family, so two parallel
workers refreshing in the same window would sign each other out with failures pointing nowhere near
the cause. Nine sign-ins instead of two, still far short of the ~40 the per-test login cost.
Three assertion floors restored, all the vacuous-guard shape: the roster search asserted only
`rows <= 2`, satisfied by a search that matched nothing; the chip/worklist bound was satisfied by any
chip sum when the worklist rendered 0; and the drill-down's `rowCount <= chip.count` passed at zero,
which is what a chip href that stopped filtering would produce — the settle-wait accepts the empty
state, so nothing else would have caught it. The out-of-population relaxation only ever justified
MISSING_DATA, so OVERDUE and DUE_SOON now require a non-empty chip to list cases.
…beside them
The first four-worker CI run came back "4 flaky, 22 passed" — green only because retries hid it. One
flake was the roster reading "0 patients" for a full 20 seconds while `runs.spec`'s manual
ALL_PROGRAMS run was in flight; the others were a roster filter, a panel filter and a case-detail
navigation, all under the same window.
Green-by-retry is how a suite starts rotting, so the ordering is now a constraint rather than a hope:
the Maui project splits into `maui` (every read-only spec, still four workers) and `maui-writes`
(`runs.spec` alone) with `dependencies: ["maui"]`, which makes Playwright finish the first before
starting the second. `case-workflow` stays in the parallel set: its mutations are case-level, and
IN_PROGRESS is inside ACTIVE_CASE_STATUSES so it does not move an Open Worklist count.
Not asserted as a product bug: the roster read model already excludes in-flight runs in SQL
(`UPPER(r2.status) IN ('COMPLETED','PARTIAL_FAILURE')`) and again in app code, so the mechanism behind
the empty read is not yet identified. Removing the overlap makes the suite honest either way; if the
empty roster shows up somewhere a run is not in flight, that is the thread to pull.
The remaining flake was mine, not contention. Splitting the jelly-bean tests left the second one calling readChips straight out of a beforeEach that waits only for a heading — which renders before the measure cards have data. locator.count() is a snapshot, so a slow load read zero chips and the caller failed on 'chips.length > 0' while the page was simply not ready. The wait now lives inside readChips, where every caller gets it, and it doubles as the assertion that a routed measure renders a card at all.
Owner
Author
|
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things the six-measure flip exposed once it was live on the sandbox: a dashboard rate that was the wrong measure's, and an e2e suite that had stopped testing the pilot's configuration.
A measure's rate was the whole run's
aggregateOfficialRunpagedlistOutcomes(runId)with no measure filter, under a comment asserting that "a run evaluates one measure with one engine". That is true of a MEASURE-scoped run and false of the ALL_PROGRAMS run the pilot actually runs, which holds one row per (subject, measure) pair. So it summed every measure the run touched and served that one number under whichever measure was asked about.On the sandbox, CMS122's card carried CMS125's initial population, CMS125's 59.2% and
ecqmId: 125FHIR. With six measures routed it gets six times wronger.The MeasureReport and reconciliation routes were unaffected — both refuse a multi-measure run with a 422 via
distinctMeasuresForRun— so the programs overview was the single unguarded consumer.listOutcomesnow takes an optionalmeasureIdand narrows in SQL, before the page window, on both stores. Filtering after the read would page 120,000 rows once per measure on a six-measure nightly.runProducedOfficialEvidenceis scoped the same way, so a run mixing an official and an authored measure (ADR-072 admits one) no longer answers for both from whichever row sorted first.Guarded by two tests chosen so they cannot pass vacuously:
The e2e suite had been green by absence since 2026-09-03
The job is
workflow_dispatch-only, so nothing ran it across seven merges. A manual dispatch returned 32 passed, 11 failed. None was a backend regression:official-pendingand the specs feat(corpus): MM-1 U2 — the Maui roster is a 20,000-patient data-first corpus, and a multi-rate measure is read as every one of its rates (ADR-073/074/075) #528 added for them waited 30s for chips that could not exist.employeeExternalIdwhere a patient deployment emitspatientExternalId) — the stack was right and the test was wrong.<option>inside a closed select, because the panel filters gave those options names containing clinic names.<label>, sogetByLabel(/^PCP$/)resolved nothing. They now carryaria-labels, matching Panel/System/Segment which already had them.The job now runs the configuration the sandbox deploys — it vendors the six terminology sidecars with the VSAC credential and sets
WORKWELL_OFFICIAL_MEASURES. A suite that boots a stack nobody deploys is not a pilot test. Confirmed by the backend's own boot line on every run since:On the credential, since this is a public repo
The key is scoped to a single step, never job-level
env, and that step runs before the frontend and e2e dependency trees are installed — so it executes with the backend tree only. The job isworkflow_dispatch-gated, which needs write access, and GitHub does not pass secrets to fork-PR workflows regardless.The completed expansions are deliberately not cached. They are VSAC value-set expansions — licensed content, gitignored for that reason — and an Actions cache is restorable by a fork pull request. Re-vendoring costs 45s.
Speed
The suite was never slow; the failures were. Of the original 8m24s, the passing tests were about 20 seconds and the rest was 11 failures burning 20-60s timeouts and retrying.
Fewer tests, more covered: six measures instead of two, and the two vacuous assertions made real. The trimming was collapsing per-page loops and a drill-down that navigated and reloaded once per chip per measure. Beyond that: four workers instead of one, and one sign-in per (role, spec file) instead of ~40.
What review caught, and what the first CI runs caught
Three rounds, all recorded because they are the interesting part:
classifyRunnablereturnsofficial-pendingfor one reason — the id missing fromWORKWELL_OFFICIAL_MEASURES— and never returns it for cms122/cms125, which reach theauthoredbranch first. A routing list that lost those two would have printedcms122:authored, passed a negative grep, and run the pilot's flagship measures on the authored engine whileruns.spec's 48x6 held either way. Now a positive exact match on<id>:official, tested against four boot-line shapes including the one the old job produced. Terminology is not part of that classification at all, so the sidecar control is and always was thetruncatedrefusal in the vendor step.npx playwright testwith no--project, loading both projects, and Playwright resolvesstorageStatebefore thebeforeEachthat skips — so the maui specs would have thrown ENOENT instead of skipping. The session helpers now return{}off the maui profile; verified by listing both projects with no.auth/present.rows <= 2(satisfied by matching nothing), a chip/worklist bound satisfied by any sum when the worklist rendered 0, and a drill-downrowCount <= chip.countthat passed at zero — which is precisely what a chip href that stopped filtering would produce, and the settle-wait accepts the empty state, so nothing else would have caught it.Then CI itself caught what review could not: the first four-worker run came back 4 flaky, 22 passed — green only via retry, including a roster reading "0 patients" for 20 seconds while
runs.spec's ALL_PROGRAMS run was in flight. The Maui project now splits intomaui(read-only, four workers) andmaui-writes(runs.specalone) withdependencies: ["maui"], making the ordering a constraint rather than a hope. The last remaining flake was mine: the split leftreadChipscounting locators before the cards had rendered.Not asserted as a product bug: the roster read model already excludes in-flight runs in SQL and again in app code, so the mechanism behind that empty read is unidentified. If an empty roster appears when no run is in flight, that is the thread to pull.
Verification
pnpm typecheckclean; backend fhir/program/export 288 pass, routes 397 pass, SQLite store contract 89 pass.postgres:16); CI covers it.Known and not addressed here
The live six-measure run is slow — about 9.7 (subject, measure) pairs per second against Neon, steady between 17% and 50% completion. Outcomes are already batched; the case pass is not, and costs 2-3 sequential round trips per pair (
findByKey, then insert/update, then the audit append). That is ~300,000 round trips for a 120,000-pair run, and the flat rate says it is a fixed per-pair charge rather than anything compounding. Batching it per chunk the wayrecordOutcomesalready batches outcomes is a follow-up PR, deliberately separate: it touches the idempotency contract (DATA_MODEL_CONTRACTS §4) and ADR-076 d2's compare-and-set, so the store-contract tests go first.