feat(upstream): detect frontend-base landings from the default branch, not legacy-mfe - #211
feat(upstream): detect frontend-base landings from the default branch, not legacy-mfe#211blarghmatey wants to merge 4 commits into
Conversation
…, not legacy-mfe The recorded trigger for the Willow cutover was "openedx/frontend-app-X has a legacy-mfe branch". Polling upstream on 2026-09-01 shows that signal is wrong in both directions: - False positive. Upstream cuts the branch, then merges. frontend-app-catalog's branch was cut 2026-08-27T19:48Z and frontend-template-application's on 2026-08-17, both ahead of the merge. A poll in that window reports a landing that has not happened. - False negative. The branch is only cut when a supported release still ships the micro-frontend. frontend-app-notifications landed 2026-08-27 with openedx.org/release: null and no branch at all. Read the default branch's package.json instead: after the merge it describes an npm module library (exports set, build script no longer webpack/fedx-scripts) rather than a webpack app. legacy-mfe becomes a second-order fact, answering "which branch does a legacy build move to" rather than "did it land" -- and its absence on a landed repo is the load-bearing case, because then there is no branch to repoint at and the build has to be retired. upstream_watch.yaml records which OL deployments build each repo from its default branch. ol-infrastructure version_matrix.py stays the source of truth for what is built; this only records the exposure, verified against 92ad0bdbd. Running it surfaces one already-broken exposure: frontend-app-instructor-dashboard is a frontend-base module library on main with no legacy-mfe branch, and version_matrix.py still declares an `instructor` MFE build against main for mitxonline, mitx, and mitx-staging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KwW2f9msAkAuR45QQGRj9
…on a change A landing arrives with no change in this repo and no failing PR, so nothing in CI notices until a release-day mitxonline build fails. Upstream has been leaving about a day between cutting `legacy-mfe` and merging, and that window is the only point at which the version_matrix.py pin can be made without a break either side of it, so the poll has to be daily to be worth running. Notifies on a digest of (repo, stage) rather than on every run, so an issue nobody has acted on yet does not collect a daily comment repeating itself. An npm version bump or a reworded advice line does not re-notify; a stage transition does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KwW2f9msAkAuR45QQGRj9
It is `tsc --project tsconfig.build.json` plus a copy of SCSS and assets into dist/, checked on authn, catalog, notifications and instructor-dashboard. `openedx build:module` was a guess, and core/mfe.py already records that it does not exist in @openedx/frontend-base. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KwW2f9msAkAuR45QQGRj9
… bundle
The three mitxonline/mitx/mitx-staging `instructor` jobs do not fail against
the already-landed frontend-app-instructor-dashboard. They succeed and ship a
useless artifact.
build_legacy ends at `container.directory("/app/mfe/dist")`. A landed repo's
`npm run build` is `tsc` plus an SCSS copy, so dist/ exists and holds
index.js -- non-empty, exports cleanly, pipeline green, and the deployment
serves a directory no browser can load. Nothing downstream inspects the
artifact's shape, so the build has to refuse it.
index.html is the discriminator. frontend-build's prod config templates
public/index.html through HtmlWebpackPlugin (config/webpack.prod.config.js
line 205), so every legacy MFE emits one; all seven mitxonline builds from
master have public/index.html. A frontend-base App Repository never does,
because its build does not touch public/ -- instructor-dashboard has
public/index.html on main and still emits only dist/index.js.
The message names the cause and the next command, because where this fires is
a Concourse log nobody is reading closely.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014KwW2f9msAkAuR45QQGRj9
There was a problem hiding this comment.
Pull request overview
Adds upstream frontend-base conversion detection and automated daily alerting.
Changes:
- Classifies MFE repositories using default-branch package metadata.
- Adds a CLI status command and deployment watch list.
- Adds tests and a scheduled issue-notification workflow.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/lehrer/core/upstream.py |
Implements landing classification. |
src/lehrer/cli/upstream.py |
Adds upstream polling and reporting CLI. |
src/lehrer/cli/__init__.py |
Registers the upstream command. |
deployments/mit-ol/upstream_watch.yaml |
Defines watched repositories and exposures. |
.github/workflows/frontend-base-landing.yml |
Adds daily polling and issue updates. |
tests/cli/test_upstream.py |
Tests classification, advice, and configuration loading. |
Suppressed comments (7)
deployments/mit-ol/upstream_watch.yaml:22
- The referenced
version_matrix.pyalso tracksauthoringatrelease="master"for mitx and mitx-staging (lines 582–585 and 662–665). These deployments should be included so the alert reports the full exposure.
exposed_deployments: [mitxonline]
deployments/mit-ol/upstream_watch.yaml:24
- The referenced
version_matrix.pyalso trackscommunicationsatrelease="master"for mitx and mitx-staging (lines 577–580 and 657–660). The current list causes status and advice to omit both exposed deployments.
exposed_deployments: [mitxonline]
deployments/mit-ol/upstream_watch.yaml:26
- The referenced
version_matrix.pyalso tracksdiscussionsatrelease="master"for mitx and mitx-staging (lines 587–590 and 667–670). Include those deployments so a branch cut or landing reports every affected build.
exposed_deployments: [mitxonline]
deployments/mit-ol/upstream_watch.yaml:28
- The referenced
version_matrix.pyalso tracksgradebookatrelease="master"for mitx and mitx-staging (lines 604–606 and 684–686). The watch entry currently omits those exposed builds.
exposed_deployments: [mitxonline]
deployments/mit-ol/upstream_watch.yaml:30
- The referenced
version_matrix.pyalso trackslearningatrelease="master"for mitx and mitx-staging (lines 620–623 and 695–698). Add them so the alert does not understate the landing's impact.
exposed_deployments: [mitxonline]
deployments/mit-ol/upstream_watch.yaml:32
- The referenced
version_matrix.pyalso tracksora-gradingatrelease="master"for mitx and mitx-staging (lines 629–632 and 709–712). Both deployments are exposed and should appear in this entry.
exposed_deployments: [mitxonline]
deployments/mit-ol/upstream_watch.yaml:45
- This is not built only from Verawood: the referenced matrix tracks
learner-dashboardfrommasterfor mitx and mitx-staging (lines 615–618 and 700–703). Sincemastermaps to the upstream default branch, the landed conversion already exposes both deployments, but this entry reports none and prevents--fail-on-landingfrom flagging them.
# Built only at release="verawood" today, so no deployment is exposed, but the
# Site Projects will consume the module library and mitx/xpro move to Willow
# eventually. Watched, not urgent.
- repo: openedx/frontend-app-learner-dashboard
exposed_deployments: []
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # Verified against version_matrix.py @ 92ad0bdbd on 2026-09-01. | ||
| repos: | ||
| - repo: openedx/frontend-app-admin-console | ||
| exposed_deployments: [mitxonline] |
| also records the OL deployments whose legacy MFE build tracks each | ||
| repository's default branch. | ||
| """ | ||
| require("gh") |
| entries = upstream.load_watch_list(_paths.repo_root() / "deployments" / "mit-ol") | ||
| assert entries | ||
| for entry in entries: | ||
| assert entry["repo"].startswith("openedx/frontend-app-") | ||
| assert isinstance(entry["exposed_deployments"], list) |
|
Updated the body: the open question about Concourse is answered. Those three That makes the silence a lehrer bug, not just an ol-infrastructure config drift, so I added Drop that commit if you would rather it went in on its own; the rest of the PR stands without it. |
What
Adds
lehrer upstream frontend-base-status, which reports whether each upstream MFE repo OL depends on has landed its frontend-base conversion, plus a daily GitHub Actions job that opens an issue when the answer changes.Why
Upstream is merging each learner-facing MFE's frontend-base conversion into its repository's default branch (openedx/frontend-base#243, OEP-65 ADR 0004). That merge turns the default branch into an npm module library, so every OL legacy MFE build tracking it stops producing a servable bundle. mitxonline builds seven MFEs from
master. The break arrives with no change in this repo and no failing PR, on upstream's schedule rather than ours.The detection rule changed
Our recorded trigger was "
gh api repos/openedx/<repo>/branches/legacy-mfereturns 200". Polling upstream on 2026-09-01 shows that is wrong in both directions:frontend-app-catalog's branch was cut2026-08-27T19:48:18Z,frontend-app-authn's2026-08-28T19:12:31Z,frontend-app-learner-dashboard's2026-08-28T20:31:36Z,frontend-template-application's2026-08-17T12:50:58Z. A poll inside that window reports a landing that has not happened.openedx.org/releasetonull"if none does".frontend-app-notificationslanded 2026-08-27 withopenedx.org/release: nulland no branch at all.The check reads the default branch's
package.jsoninstead. After the merge it describes an npm module library:exportsis set, andscripts.buildno longer names webpack or fedx-scripts. Converted repos usemake build, whose target istsc --project tsconfig.build.jsonplus a copy of SCSS and assets intodist/(checked on authn, catalog, notifications and instructor-dashboard). Both conditions are required — a micro-frontend can declareexportsfor its own consumers, and a repo can wrap webpack in a Makefile.legacy-mfeis still read, but as a second-order fact answering "which branch does a legacy build move to", not "did it land". Its absence on a landed repo is the load-bearing case: there is nothing to repoint at, so the build has to be retired rather than pinned.What it finds today
Wave 1 of frontend-base#243 (template-application, notifications, catalog, learner-dashboard, authn) is complete — every sub-issue closed, none open — and broke nothing: none of it is built by an OL deployment from its default branch.
It does surface one live exposure.
frontend-app-instructor-dashboardis a module library onmain(version: 0.0.0-dev,scripts.build: make build,exports: {".": "./dist/index.js"},files: ["/dist"],openedx.org/release: null) with nolegacy-mfebranch, while ol-infrastructureversion_matrix.py(@92ad0bdbd) still declares aninstructorMFE build againstmainat lines 535/609/689 for mitxonline, mitx and mitx-staging.Those three jobs do not fail. They succeed and ship a useless artifact. Removing the rows is a separate ol-infrastructure change, but the silence is a lehrer bug, so this PR also fixes that.
The silent-failure fix
build_legacyends atcontainer.directory("/app/mfe/dist"). A landed repo'snpm run buildistscplus an SCSS copy, sodist/exists and holdsindex.js— non-empty, exports cleanly, pipeline green, and the deployment serves a directory no browser can load. Nothing downstream inspects the artifact's shape, so the build now refuses it.index.htmlis the discriminator:public/index.htmlthroughHtmlWebpackPlugin(config/webpack.prod.config.js:205), so every legacy MFE emits one. All seven repos mitxonline builds frommasterhavepublic/index.html.public/. instructor-dashboard haspublic/index.htmlonmainand still emits onlydist/index.js.The failure message names the cause and the next command, because where it fires is a Concourse log nobody is reading closely:
This is the half that matters when nobody has run the detection command: the daily job tells you a landing happened, the guard stops the pipeline shipping garbage regardless. Happy to split it into its own PR if you would rather review it separately — it is one commit (
7cf4cea).Layout
src/lehrer/core/upstream.py— classification only, no I/O and no knowledge of OL deployments (thelehrer-core-boundaryhook enforces the latter).src/lehrer/cli/upstream.py—gh/npmreads, watch-list loading, operator-facing advice.deployments/mit-ol/upstream_watch.yaml— which OL deployments build each repo from its default branch. ol-infrastructureversion_matrix.pystays the source of truth for what is built; this only records the exposure, verified against92ad0bdbd..github/workflows/frontend-base-landing.yml— daily poll. Notifies on a digest of (repo, stage), so an unactioned issue does not collect a daily comment repeating itself.Testing
tests/cli/test_upstream.py, 14 cases built from the shapes actually observed upstream, including both the false-positive and false-negative cases above, plus 3 intests/core/test_mfe.pyrunning the guard's generated script against a real webpack-shaped and a real module-library-shapeddist/. Full suite 292 passed; mypy and pre-commit clean. The command and the workflow's shell logic were both run against live upstream.🤖 Generated with Claude Code
https://claude.ai/code/session_014KwW2f9msAkAuR45QQGRj9