Skip to content

feat(upstream): detect frontend-base landings from the default branch, not legacy-mfe - #211

Open
blarghmatey wants to merge 4 commits into
mainfrom
feat/upstream-frontend-base-landing-check
Open

feat(upstream): detect frontend-base landings from the default branch, not legacy-mfe#211
blarghmatey wants to merge 4 commits into
mainfrom
feat/upstream-frontend-base-landing-check

Conversation

@blarghmatey

@blarghmatey blarghmatey commented Sep 1, 2026

Copy link
Copy Markdown
Member

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-mfe returns 200". Polling upstream on 2026-09-01 shows that is wrong in both directions:

  • False positive. ADR 0004 cuts the branch and then merges. frontend-app-catalog's branch was cut 2026-08-27T19:48:18Z, frontend-app-authn's 2026-08-28T19:12:31Z, frontend-app-learner-dashboard's 2026-08-28T20:31:36Z, frontend-template-application's 2026-08-17T12:50:58Z. A poll inside 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; the ADR 0004 checklist says set openedx.org/release to null "if none does". frontend-app-notifications landed 2026-08-27 with openedx.org/release: null and no branch at all.

The check reads the default branch's package.json instead. After the merge it describes an npm module library: exports is set, and scripts.build no longer names webpack or fedx-scripts. Converted repos use make build, whose target is tsc --project tsconfig.build.json plus a copy of SCSS and assets into dist/ (checked on authn, catalog, notifications and instructor-dashboard). Both conditions are required — a micro-frontend can declare exports for its own consumers, and a repo can wrap webpack in a Makefile.

legacy-mfe is 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

openedx/frontend-app-admin-console         legacy      no action — default branch still builds the micro-frontend
openedx/frontend-app-authoring             legacy      no action — default branch still builds the micro-frontend
openedx/frontend-app-communications        legacy      no action — default branch still builds the micro-frontend
openedx/frontend-app-discussions           legacy      no action — default branch still builds the micro-frontend
openedx/frontend-app-gradebook             legacy      no action — default branch still builds the micro-frontend
openedx/frontend-app-learning              legacy      no action — default branch still builds the micro-frontend
openedx/frontend-app-ora-grading           legacy      no action — default branch still builds the micro-frontend
openedx/frontend-app-instructor-dashboard  LANDED      landed with no legacy-mfe branch — the micro-frontend is gone; drop the legacy build and consume the npm module instead (builds from the default branch: mitxonline, mitx, mitx-staging)
openedx/frontend-app-learner-dashboard     LANDED      landed — pin to legacy-mfe or absorb into the Site Project (no OL deployment builds this from its default branch)

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-dashboard is a module library on main (version: 0.0.0-dev, scripts.build: make build, exports: {".": "./dist/index.js"}, files: ["/dist"], openedx.org/release: null) with no legacy-mfe branch, while ol-infrastructure version_matrix.py (@ 92ad0bdbd) still declares an instructor MFE build against main at 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_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 now refuses it.

index.html is the discriminator:

  • frontend-build's prod config templates public/index.html through HtmlWebpackPlugin (config/webpack.prod.config.js:205), so every legacy MFE emits one. All seven repos mitxonline builds from master have public/index.html.
  • A frontend-base App Repository never does — its build does not touch public/. instructor-dashboard has public/index.html on main and still emits only dist/index.js.

The failure message names the cause and the next command, because where it fires is a Concourse log nobody is reading closely:

no index.html in /app/mfe/dist: this built an npm module library, not a
servable MFE bundle. The upstream default branch has most likely landed its
frontend-base conversion. Run 'lehrer upstream frontend-base-status', then
repoint this build at the legacy-mfe branch or retire it.

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 (the lehrer-core-boundary hook enforces the latter).
  • src/lehrer/cli/upstream.pygh/npm reads, watch-list loading, operator-facing advice.
  • deployments/mit-ol/upstream_watch.yaml — 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.
  • .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 in tests/core/test_mfe.py running the guard's generated script against a real webpack-shaped and a real module-library-shaped dist/. 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

blarghmatey and others added 3 commits September 1, 2026 11:59
…, 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
Copilot AI balanced review requested due to automatic review settings September 1, 2026 16:12
… 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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py also tracks authoring at release="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.py also tracks communications at release="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.py also tracks discussions at release="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.py also tracks gradebook at release="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.py also tracks learning at release="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.py also tracks ora-grading at release="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-dashboard from master for mitx and mitx-staging (lines 615–618 and 700–703). Since master maps to the upstream default branch, the landed conversion already exposes both deployments, but this entry reports none and prevents --fail-on-landing from 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")
Comment on lines +152 to +156
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)
@blarghmatey

Copy link
Copy Markdown
Member Author

Updated the body: the open question about Concourse is answered. Those three instructor jobs succeed and ship a useless artifact rather than failing, so the pipeline has never signalled this.

That makes the silence a lehrer bug, not just an ol-infrastructure config drift, so I added 7cf4cea: build_legacy now asserts dist/index.html exists before returning the directory. Evidence that the assertion is safe for every legacy MFE is in the commit message and the code comment — frontend-build templates it via HtmlWebpackPlugin, and all seven of mitxonline's master-tracked repos have public/index.html.

Drop that commit if you would rather it went in on its own; the rest of the PR stands without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants