Skip to content

Release 0.85.0 - #4164

Closed
odlbot wants to merge 27 commits into
releasefrom
release-candidate
Closed

odlbot wants to merge 27 commits into
releasefrom
release-candidate

Conversation

@odlbot

@odlbot odlbot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Ahtesham Quraish

renovate[bot]

Arslan Ashraf

Chris Chudzicki

Tobias Macey

Zaman Afzal

Carey P Gumaer

Anastasia Beglova

Matt Bertrand

Shankar Ambady

dependabot[bot]

abeglova and others added 27 commits October 21, 2026 14:01
* Hide edX content files until their release date

ContentFile gains a release_date taken from the OLX start dates on the
path from the course down to each block, with static assets dated by
the earliest dated block that names them and undated if any undated
learner-visible file does. Visibility is derived, published AND
released, via ContentFile.objects.visible(); readers go through it and
a source scan keeps new readers from filtering on published alone.

An hourly sweep reindexes runs whose files released since the last
sweep, tracked by a durable watermark with an overlap for retries. A
partial index on published rows serves the sweep and the API's counts.
Ingestion reconciles dates on every archive it downloads; the
unpublish_excluded_files backfill dates rows loaded before release
dates existed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Undo the escaping layer serialized OLX adds to CDATA

Reference sources are now serialized after dropping their dated inline
blocks, and serializing escapes CDATA content one more time than the
file did. A CDATA link to notes&amp;guide.pdf came out as
notes&amp;amp;guide.pdf and no longer matched notes&guide.pdf. Unescape
the serialization once so reference text reads as it did when taken
straight from the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Trim release-date code and tests

- One _refers_to() for the "names it or declares its video" check that
  the exclusion scan and asset dating both made
- _static_references takes one confirmed flag instead of inline and
  guess, which were always passed together
- Inline _self_paced, shorten key_dates, drop the "Z" replace that
  fromisoformat handles since 3.11
- Revert the loaders.py reordering and list the stale-file query as a
  deliberate published reader instead
- Shorten docstrings and comments
- Tests: fold release_date checks into the existing load, cleanup and
  deindex tests; merge near-duplicate unpublish, visible/hidden and
  unparsable-XML dating tests; drop a test of private _build_result

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tighten release-date docstrings and comments

Fix ones that were wrong (the 0132 drop comment, a key that maps to
None, the self-paced and reconcile docstrings, the source-scan comment),
shorten the long test docstrings, and give the tutor import test's row
the key of the file it imports, so the test fails if rows are reconciled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Deindex newly unreleased content files before loading

reconcile_release_dates runs before extraction, but the reindex only runs
after a successful load, so a failed load left newly hidden files in
OpenSearch and Qdrant. Dispatch the deindex tasks right away when
reconciling hides any visible row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep static assets visible when a reachable static page links them

* Match playlist videos on visible content files

Main's warehouse playlist sync read published=True directly, which the
content-file reader guard rejects. Use visible(), as
load_videos_from_content_files does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Require a complete filename match for static asset references

* Keep no inline block elements on OlxArchive

* Keep the release sweep watermark in a cache table of its own

* Match block files on their exact name

* Skip transcripts when dating static assets

The per-date scan searched every video transcript for asset names, which
the undated scan never reads. On MITxT 6.419x (1,236 .srt files) that took
key_dates from 1.5s to 31s and added 130 MB; now 4.4s with identical
output.

* Exempt the unit loader's published query from the visibility guard

* Check unreleased files are deindexed with unpublished ones

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Match asset names with separators collapsed, not deleted

Reference matching deleted underscores and punctuation, so a link to
lecture1.pdf also counted as a link to lecture_1.pdf. That kept the
wrong file ingested, and when only a future-dated block linked
lecture_1.pdf, an undated link to lecture1.pdf released it early.

Collapse each run of separators to one underscore instead: edX writes
a space, +, @ or other punctuation as _, so those spellings still
match, but a dropped separator no longer does. Name starts are
unchanged: after any separator run except bare underscores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Let a name start at the start of a reference source

A source that begins with "_notes.pdf" kept its leading _ in the index
without a name start after it, so it no longer matched the file, which
it did before separators were collapsed. Mark both ends of each text as
a separator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say what the asset name matcher does in its docstrings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* adding fixes for ocr issues and updated test. added new settings for ocr timeout

* Keep list markers out of math scoring and on children-only items

List item markers are now stored on ContentBlock.list_marker instead of
in the content, so a "- " bullet is no longer scored as a math symbol.
The marker goes on the item's first text, which is in its kids when the
item has no content of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Don't stack retries on the OCR fallback request

A request that batch_completion reports as failed has already used
num_retries, so the one-off fallback call now makes a single attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fall back to parsed text when full-page OCR fails

- A failed full-page OCR request now uses that page's parsed blocks
  instead of a placeholder, so an LLM outage doesn't empty the file.
  Failed image regions still get the placeholder.
- Drop the extra one-at-a-time retry; batch_completion's num_retries
  already covers retrying.
- Don't count common Symbol/Wingdings private-use bullets (e.g. U+F0B7)
  as unmappable glyphs.
- Add the missing spaces between sentences in OCR_PROMPT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The search and seat-assignment announcement debounces started a real
timer on mount even when nothing had changed. If it fired during a plain
await in a test, React logged an act() warning and jest-fail-on-console
failed the test. Skip the debounce when the value is already current,
and read exported CSV blobs inside act().

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…4137)

* Add warehouse-pull sync tasks for MITx Online courses and programs

MIT Learn loads MITx Online from its catalog API every 6 hours. The data
platform's integrations__learn__mitxonline_* views now carry what that ETL
reads and derives (published and variant runs, prices, status, course
numbers, program children with their positions), at parity with Learn.

SyncMITxOnlineCoursesTask and SyncMITxOnlineProgramsTask read those views
and hand the loaders the same dicts learning_resources.etl.mitxonline
builds, following the xPRO tasks from #4054. They no-op until
ETLSourceOwnership names the warehouse for mitxonline course and program.
The array and nullable timestamp readers move from warehouse_xpro into
warehouse_guards now that two sources use them.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn

* Build MITx Online full_description from the platform's page sections

#4089 builds full_description from the Wagtail pages API's about,
what_you_learn, prerequisites and FAQs. ol-data-platform #3050 added those
sections to integrations__learn__mitxonline_courses and _programs, with
has_catalog_page for whether the pages API lists the page to Learn.

The sync tasks run parse_full_description on them, and leave the key out
when has_catalog_page is false, as the API ETL does for a page it does not
see, so the stored value stays.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn

* Read StarRocks booleans as bools in the MITx Online sync

StarRocks returns a boolean column as 0 or 1. Passed through, published and
certification were stored on the LearningResource as ints, and the loaders'
`is False` checks then skip clearing run prices for a resource without a
certificate and skip test_mode for an unpublished force-ingest course.

The tests also assert the loader configs now.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn

* Count MITx Online courses without runs against the unpublish limit

The loader unpublishes a course that arrives with no runs, but the
course guard kept every course the courses view marks published. The
runs come from a separate view, so a runs table built apart from the
courses table (e.g. a dbt build that replaced runs and then skipped
courses) could unpublish courses without tripping the 10% limit. Only
published courses with runs now count as kept.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn

* Share the course unpublish-guard rule between MITx Online and xPRO

The MITx Online course guard counts a course as kept only when it is
published and has runs, since upsert_course_or_program unpublishes a
course with no runs. xPRO built its kept set from the published flag
alone. This moves the rule into kept_course_ids in warehouse_guards and
uses it from both syncs.

xPRO's behavior does not change: its transform_course already marks a
course published only when it has a priced run, so a course without runs
was already counted. The new xPRO test covers that case so the guard
keeps holding if the transform's published rule changes.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn
* Check ETL ownership in the remaining legacy entrypoints

transcript jobs, Canvas ingestion, the OVS video webhook and the news
feeds still write after their source is flipped away from legacy.

This adds the ownership check at the top of each, before any call to the
source:

- Content files and transcripts get keys of their own, content_file and
  transcript, because they cut over separately from their source's
  metadata. The podcast transcript job is meant to keep running after
  podcasts move to the warehouse, and the edX content-file pull is a
  later cohort than the course pull.
- ingest_canvas_course needs canvas course and content_file, since one
  archive writes both.
- The OVS video webhook is the data platform's push, so it writes only
  while webhook owns ovs video_playlist and video, and returns 409
  otherwise.
- The content_files delete webhook deletes the course, so it checks the
  source's course.
- The news feeds are keyed by feed and feed type, e.g. mitpe_news/news.

when_owned moves to ownership.py so news_events can use it.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn

* Write OVS pushes inside writing_as and gate sync_canvas_courses on content files

The OVS view checked ownership as the webhook pipeline but wrote after
the block, as legacy, so a may_write backstop added to load_video later
would skip every push.

sync_canvas_courses only queues ingest_canvas_course, which needs canvas
course and content_file, so with content_file moved it listed the bucket
and queued tasks that all skip.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn

* Check content file ownership in load_content_files

The ownership module says the batch loaders check may_write as a backstop,
but load_content_files had no check, so a caller that skipped the entry
check (a shell session, a new pipeline) could still write and unpublish a
source's content files after they were cut over.

Claude-Session: https://claude.ai/code/session_01FMtn1X7p2G1xBL6BukdYAn

* Limit the content-file cutover note to the edX archive sources

OCW content files stop at the ocw/course check and Canvas needs both keys, so
only the edX archive sources keep their content files on the legacy tasks
after a course flip.

Claude-Session: https://claude.ai/code/session_01RMmtj45php9G7NJK2nztzp
* Bump smoot-design to 6.39.0 for the contact-us export

* Add contact-us launcher config and support endpoint env var
…nding chips unchanged (#4136)

* Pin the homepage trending chips' hover text to the colour they render

On smoot 6.38.0 the hover text on the homepage's trending chips is smoot's
outlined/outlinedWhite hover colour, Dark Gray 1: smoot's
`.MuiChip-clickable:hover` rule (specificity 0,3,0) outranks TrendingChip's
`&:hover` (0,2,0), which is also why its background and border overrides
need `!important`. The Dark Gray 2 that TrendingChip declared for
outlinedWhite never applied, and Explore All (outlined) declared none.

smoot's chips are moving to Dark Gray 2 to match the design system's Chips
component, but the homepage's trending pills are a separate design. Setting
Dark Gray 1 here with `!important` keeps them as they are on either smoot.

Measured on local dev against both smoot 6.38.0 and a build with the chip
change: every computed colour, border, background, padding and font on all
six chips, at rest and on hover, is identical to today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bump smoot-design to 6.39.1 for the outlined chip fix

6.39.1 is 6.39.0 plus mitodl/smoot-design#279 and nothing else: outlined and
outlinedWhite chips draw Dark Gray 2 text, and the close icon sits 4px from
the label.

ol-components declares smoot as an exact-version peer, so it moves with main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(ai): route azure/ models to Azure OpenAI with workload identity

The chat and embedding calls only reach OpenAI with OPENAI_API_KEY. The
Azure OpenAI accounts for mit-learn have key auth disabled, and the pods
already carry the workload identity env vars for an Entra token
(mitodl/ol-infrastructure#6082).

This routes a model configured with an "azure/" prefix (the summarizer
and credential LLM models, and QDRANT_DENSE_MODEL) to
AZURE_OPENAI_ENDPOINT with a bearer token from DefaultAzureCredential.
Models without the prefix take the existing path unchanged. The prefix is
stripped wherever the base model name matters (tiktoken encoding, the
hybrid search model check, the OpenSearch connector) so the Qdrant vector
name and size don't change.

Claude-Session: https://claude.ai/code/session_019VXBicfWCU41tZBiAzToby

* fix(ai): tokenize azure/ chat models like their OpenAI model

tiktoken doesn't know "azure/gpt-4o", so token_encoding fell back to
LITELLM_TOKEN_ENCODING_NAME (cl100k_base in the deployed envs) instead of
o200k_base, and summarizer input was truncated earlier than for the same
model without the prefix.

This also caps azure-identity below 2 to match the other pins.

Claude-Session: https://claude.ai/code/session_019VXBicfWCU41tZBiAzToby
…s to settle (#4148)

* fix(test): bump happy-dom to 20.14.6 so MutationObservers survive GC

happy-dom 20.3.4 holds each MutationObserver's callback only through a
WeakRef (MutationObserverListener), with no strong reference anywhere.
A garbage collection frees it and Node.reportMutation silently drops the
observer while it is still connected. ProseMirror observes the editor
DOM this way, so text typed after a GC lands in the DOM but never
reaches editor state.

That is the "PATCH never sent" failure of `ArticleEditor autosave › an
article that has never been saved is created once, then updated`: no
edit, no autosave, `Number of calls: 1`. It only shows under memory
pressure (CI runs with coverage and parallel workers), which is why it
appears on unrelated PRs. Forcing one gc() after the first save fails
the test every time on 20.3.4 and passes on 20.14.6.

Fixed upstream in capricorn86/happy-dom#2272, released in 20.11.2. The
`^20.1.0` range already admits it, so the lockfile alone kept 20.3.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(test): wait for autosaves to settle, not just to be sent

The `act(...)` failure of `ArticleEditor autosave › an article that has
never been saved is created once, then updated` comes from a save
landing just after a `waitFor` returns. The tests waited for the request
to be recorded; the response settles a moment later, re-rendering the
autosave indicator and Publish. React Testing Library turns the act
environment back on as a `waitFor` returns, so the toolbar's
MutationObserver, reacting to that re-render, sets state outside `act`
and jest-fail-on-console fails the test.

A second `waitFor` for "Saved" does not help: the gap sits between the
two waits (5 of 64 CI-like runs still failed). `waitForSaveToSettle`
checks the request, the "Saved" indicator and an enabled Publish in a
single `waitFor`, so the save has nothing left to land when it returns.
With it, 64 of 64 CI-like runs passed.

It replaces the request-sent waits in the three autosave tests that
ended on one, and the "Saving..." check that could pass before
"Saving..." had rendered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Prevents any future relock from silently downgrading certifi to a version that trusts the compromised eMudhra CAs (e.g. while the 7-day uv cooldown applies).


Claude-Session: https://claude.ai/code/session_019gXXnoXso9aKkBtVYKsMfG

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Update dependency @sentry/nextjs to v10.75.2 [SECURITY]

* yarn.lock

* update sentry/opentelemetry

* Revert withSentryConfig import to @sentry/nextjs

The @sentry/nextjs/config entrypoint loads @sentry/node, which needs
TextDecoder and breaks nextConfig.test.ts under jsdom.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Anastasia Beglova <abeglova@mit.edu>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…n empty state, and a white topic hero (#4133)

* Count what each filter control has narrowed by

A facet's menu closes over what it selected, so a control reads exactly the
same whether or not it is filtering anything: the only record was the chip row
above the results, which names the values but sits a long way from the control
that set them.

The design answers that with a count beside the name -- a 20px red disc with
the number in white, between the label and the chevron. Hidden where a facet
has narrowed nothing, rather than shown as a nought, a badge reading 0 being a
filter announcing that it is not filtering.

The menus themselves are untouched: the same rows, checkboxes and counts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Stand the tabs and the applied filters outside the search card

The card carries the search box and its facet controls and nothing
else. The resource type tabs are drawn below it on the page rather than
in it, as the pills the shared tab list draws everywhere else -- white
for the one selected, grey for the rest -- with sorting at the far end
of the same row, and the applied filters follow them as white pills on
the page's own grey.

The card itself is plain white behind a red left edge rather than a
gradient under a thick pale rule, and its search box is the design's
486px. The banner, the card, the tabs and the filters stand 24px apart,
with the results 32px below the last of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Say what the 8px above the results grid is for

The comment described the column's gap as the whole of the space
between the filters and the grid, which it is not: the panel adds 8px
to it so the results stand 32px off rather than the 24px the rows above
them are spaced by.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Gather the search, tabs and filters into one card again

The design stacks all three 16px apart behind a single white edge, so
the tabs and the applied filters are drawn in the card rather than on
the page below it, and the card loses the red edge down its left side.

The tabs are underlined headings: the selected one black over a 2px
`darkGray2` rule drawn within the row's 42px, the rest `silverGray`,
10px apart. The applied filters lose their outline and their fill --
inside a white card there is nothing for either to separate them from
-- and "Clear all filters" is a plain underlined link in `darkGray2`
rather than red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Outline the filter under the pointer, as the design hovers it

Hovering a filter draws the outlined pill around it rather than
underlining its label. Drawn as an inset shadow: a border would lay the
row out 2px wider for as long as the pointer was over it and shunt
every filter after it sideways, and the design's stroke sits inside the
pill's 32px in any case.

The labels and "Clear all filters" are `silverGrayDark`, which is what
the design renders them at -- the Chips and Link components declare
`darkGray2`, but every instance on the frame overrides it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Redden "Clear all filters" under the pointer

Hovering it turns it `red` and leaves the underline standing, rather
than dropping the underline and keeping it grey. That is the red the
filter counts are drawn in, and the only other thing on the card that
is not grey.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Say what came back empty, and offer a way out of it

A search its filters emptied showed a bare line of text above nothing.
It now draws the design's card: a book glyph in a grey disc, what the
search found none of, and a button to drop the filters.

The heading names the active tab's results -- "no learning materials
match these filters" -- because the other tab may well have some, which
"no results" does not distinguish. The button appears only when there
is something to clear, read from the same list the chip row draws from
so the two cannot disagree. The other layouts keep the line of text:
they have a facet sidebar beside them that already offers a way out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Stand the topic hero on plain white

The design no longer runs a photograph across the top of it, so the
topic page passes nothing behind the words and the section's own white
is the ground. Its spacings tighten with it: 24px under the trail where
there was 32, and 40px above the Featured row where there was 64.

Both are the topic page's alone. The department design has not been
revised to match and is to keep its banner image, so the spacings are a
prop rather than the one answer, and the photograph and its wash stay
whole -- exported, so they go on compiling until a department passes
them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Answer the pointer on the facet dropdowns

The facet controls had nothing to show for being hovered, where the
tabs beside them darken. They now give the same answer an unselected tab
gives: the label to `darkGray2`, with nothing drawn behind it and no
change of size.

The chevron is named alongside the label because it does not inherit --
Select colours its own icon, so on the label alone the words darkened
and the arrow beside them sat unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* new changes

* Redraw the filter row and the tabs as the design now has them

The applied filters move under a rule inside the search card, led by a
"Filtered by" label and drawn as small grey tablets -- 24px, 4px radius,
12px Medium -- in place of the 32px pills, with "Clear all" in red
ending the row. The card's foot tightens to 16px to suit the shorter
row.

The tabs move back out onto the page below the card, on a hairline that
runs the row's full width for the selected tab's heavier rule to sit on,
and their counts lose the brackets. Sort loses its chrome and is a label
and a chevron: the hairline it had is the shared select's own, drawn on
the input base rather than on a notched outline, and the icon is sized
and inset from a selector of equal weight that lands later -- so both
are named from here and the icon's doubled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Redden the cross rather than the tablet on hover

The hover variant keeps the tablet's grey and its label and reddens the
cross alone, which is the half of it that does anything. The resting
cross is `silverGrayDark` rather than the label's near-black, so it is
the quieter half until it is pointed at.

Replaces a hover invented when the design had no hover state to read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix the mobile view

* Clear out what the redesign left untrue

Comments that described the arrangement before the tabs and the applied
filters moved: the bar is no longer drawn into a card the results header
sits in, the card no longer shares anything with the tabs, and the
select's border is no longer holding a box against a gradient the card
has not had for two revisions.

`Card` in the filter bar is renamed `Row`, which is all it is -- the
name was the only reason that paragraph had to explain itself.

In the tests, the doc comment on the applied filters had the counts
block inserted beneath it and was left heading the wrong tests; it moves
down to the block it describes, and now says the row sits under the
search box rather than above the results.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Say the facet's count in its name, not only on the disc

`aria-label` on a combobox replaces everything drawn inside it, so the
disc was never read as part of the control's name -- and naming the disc
instead does nothing either, since it is a span with no role, which
`aria-label` cannot name. A screen reader heard "Format" whether it was
narrowing two things or none.

The name now carries the count and the disc is hidden, so the two cannot
say different things. The count tests were asserting the drawn text and
so had nothing to say about this; one of them now asserts the name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Ahtesham Quraish <ahtesham.quraish@arbisoft.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@odlbot
odlbot requested a review from a team as a code owner October 26, 2026 10:24
@github-actions

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@agent-optibot agent-optibot Bot 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.

Summary

The release adds visibility-aware content lifecycle management, richer course-unit extraction, warehouse synchronization, and Azure OpenAI support across learning resources. It also delivers a redesigned topic search experience, feature-gated Contact Us support, and improved OCR, profiling, and ETL safeguards.

Key Changes

  • Content release dates now govern API exposure, indexing, embeddings, vector removal, and scheduled reindexing.
  • ETL pipelines build ordered course units, reconcile ownership, and synchronize MITx Online courses and programs safely.
  • Azure-prefixed language and embedding models use Azure OpenAI workload identity and provider-specific connection settings.
  • Unit resource profiles generate cached, validated, evidence-backed claims from truncated course sources.
  • Topic channels receive a plain-white hero and redesigned search filters, tabs, sorting, and empty states.
  • Contact Us support becomes available site-wide behind feature flags and configured support endpoints.
All Relevant File Changes (click to expand)
  • README.md: Documents the Learn AI support-agent endpoint.
  • RELEASE.rst: Adds Version 0.85.0 release notes covering channel, ETL, security, and content changes.
  • docker-compose.opensearch.base.yml: Updates the OpenSearch Docker image to 3.3.2.
  • docs/how-to/etl-source-cutover.md: Documents ownership cutovers for content, transcripts, OVS, Canvas, news, and warehouse tasks.
  • env/frontend.env: Adds the production Learn AI support-agent endpoint.
  • env/frontend.local.example.env: Adds the commented local Learn AI support-agent endpoint.
  • frontends/main/package.json: Bumps smoot-design, Sentry packages, and happy-dom.
  • frontends/main/src/app-pages/ChannelPage/ChannelPage.test.tsx: Updates topic hero tests for the plain-white redesign.
  • frontends/main/src/app-pages/ChannelPage/ChannelRedesignTemplate.tsx: Removes topic photography and adds redesigned spacing, typography, and banner exports.
  • frontends/main/src/app-pages/ChannelPage/ChannelSearch.test.tsx: Tests facet counts, tab labels, and topic empty states.
  • frontends/main/src/app-pages/ChannelPage/ChannelSearch.tsx: Removes obsolete filter-bar placement commentary.
  • frontends/main/src/app-pages/ChannelPage/TopicFeaturedCarousel.tsx: Adjusts topic carousel spacing.
  • frontends/main/src/app-pages/ChannelPage/TopicSearchFilterBar.tsx: Adds selected-count badges, accessible facet names, and revised filter layout.
  • frontends/main/src/app-pages/ContractAdminPage/AssignSeatsSection.tsx: Prevents redundant live-region debounce timers.
  • frontends/main/src/app-pages/ContractAdminPage/ContractAdminPage.test.tsx: Wraps CSV Blob reads in act.
  • frontends/main/src/app-pages/ContractAdminPage/ContractAdminPage.tsx: Prevents redundant search debounce timers.
  • frontends/main/src/app-pages/DashboardPage/DataConsentDialog.tsx: Removes the catch-all data-sharing clause from consent text.
  • frontends/main/src/app-pages/HomePage/HomePage.test.tsx: Tests excluding Sloan and Professional Education from featured requests.
  • frontends/main/src/app-pages/HomePage/carousels.ts: Excludes Sloan and Professional Education from homepage featured offerors.
  • frontends/main/src/app-pages/HonorCodePage/HonorCodePage.tsx: Adds Misrepresentation to Honor Code standards.
  • frontends/main/src/app/(site)/layout.tsx: Mounts the feature-gated Contact Us launcher.
  • frontends/main/src/app/(site)/page.tsx: Reuses homepage featured-resource parameters during server prefetching.
  • frontends/main/src/common/feature_flags.ts: Adds the ContactUs feature flag.
  • frontends/main/src/page-components/ContactUsLauncher/ContactUsLauncherGate.test.tsx: Tests Contact Us flags, endpoint, authentication, and settings behavior.
  • frontends/main/src/page-components/ContactUsLauncher/ContactUsLauncherGate.tsx: Renders Contact Us when flags and endpoint configuration permit it.
  • frontends/main/src/page-components/ContactUsLauncher/contactUsConfig.test.ts: Tests support endpoint settings and optional user email.
  • frontends/main/src/page-components/ContactUsLauncher/contactUsConfig.ts: Builds Contact Us chat settings from endpoint and authenticated email.
  • frontends/main/src/page-components/HeroSearch/HeroSearch.tsx: Adjusts trending-chip hover colors.
  • frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx: Removes mobile carousel top margin.
  • frontends/main/src/page-components/SearchDisplay/NoResults.tsx: Adds contextual empty-results card with filter clearing.
  • frontends/main/src/page-components/SearchDisplay/ResourceTypeGroupTabs.tsx: Supports unbracketed counts and updated topic tab wording.
  • frontends/main/src/page-components/SearchDisplay/SearchDisplay.tsx: Redesigns topic search cards, tabs, sorting, filters, spacing, and empty states.
  • frontends/main/src/page-components/SearchDisplay/SelectedFilters.tsx: Redesigns applied-filter rows, chips, labels, and clear-all control.
  • frontends/main/src/page-components/SearchDisplay/UniversalAIBanner.tsx: Adjusts mobile banner-link padding.
  • frontends/main/src/page-components/SearchDisplay/tabConfigs.ts: Adds empty-state nouns and renames the topic tab.
  • frontends/main/src/page-components/TiptapEditor/contentTypes/article/ArticleEditor.happydom.test.tsx: Waits for autosave status and Publish enablement to settle.
  • frontends/main/validateEnv.js: Validates the Learn AI support-agent endpoint.
  • frontends/ol-components/package.json: Updates the smoot-design peer dependency.
  • learning_resources/admin.py: Improves ContentFile administration, filtering, searching, and deferred loading.
  • learning_resources/content_summarizer.py: Routes Azure models through Azure OpenAI workload identity.
  • learning_resources/content_summarizer_test.py: Tests Azure model routing and unchanged OpenAI validation.
  • learning_resources/converters/opendataloader_llm_converter.py: Improves PDF OCR fallback, caching, list flattening, glyph detection, and cleanup.
  • learning_resources/converters/opendataloader_llm_converter_test.py: Tests OCR failures, lists, glyph detection, caching, and image optimization.
  • learning_resources/credentials.py: Routes Azure credential-metadata models through Azure OpenAI.
  • learning_resources/credentials_test.py: Tests Azure and OpenAI credential-metadata model routing.
  • learning_resources/etl/edx_shared.py: Adds course-unit loading, archive reuse, overwrite support, and release-date reconciliation.
  • learning_resources/etl/edx_shared_test.py: Expands archive ingestion tests for units, dates, failures, and overwrites.
  • learning_resources/etl/loaders.py: Persists course units and safeguards content ownership while matching visible videos.
  • learning_resources/etl/loaders_test.py: Tests content ownership, visibility, release dates, and unit loading.
  • learning_resources/etl/olx.py: Builds OLX units, propagates dates, links assets, and tightens filename matching.
  • learning_resources/etl/olx_test.py: Tests OLX dates, units, assets, and missing-file handling.
  • learning_resources/etl/ownership.py: Adds content-file and transcript ownership keys with a shared ownership wrapper.
  • learning_resources/etl/pipelines.py: Applies shared ownership checks and separately gates OCW content ingestion.
  • learning_resources/etl/pipelines_test.py: Tests independently owned OCW content-file ingestion.
  • learning_resources/etl/utils.py: Adds archive context management, date reconciliation, unit helpers, and reuse.
  • learning_resources/etl/utils_test.py: Tests archive helpers, dates, extraction failures, and content reconciliation.
  • learning_resources/etl/warehouse_guards.py: Adds nullable timestamps, warehouse arrays, and kept-course calculations.
  • learning_resources/etl/warehouse_media.py: Matches playlists against visible content files.
  • learning_resources/etl/warehouse_mitxonline.py: Adds MITx Online warehouse transforms and full-sync loaders.
  • learning_resources/etl/warehouse_mitxonline_test.py: Tests MITx Online transformations, ownership, loading, and safety guards.
  • learning_resources/etl/warehouse_xpro.py: Reuses warehouse parsing helpers and counts runless courses as unpublished.
  • learning_resources/etl/warehouse_xpro_test.py: Tests mass-unpublish protection for courses missing warehouse runs.
  • learning_resources/factories.py: Adds Unit and UnitContentFile factories.
  • learning_resources/management/commands/backpopulate_mit_edx_files.py: Adds the --overwrite-units option.
  • learning_resources/management/commands/backpopulate_mitxonline_files.py: Adds the --overwrite-units option.
  • learning_resources/management/commands/backpopulate_oll_files.py: Adds the --overwrite-units option.
  • learning_resources/management/commands/backpopulate_xpro_files.py: Adds the --overwrite-units option.
  • learning_resources/management/commands/unpublish_excluded_files.py: Reports and dates unreleased files while unpublishing exclusions.
  • learning_resources/migrations/0131_unit_unitcontentfile.py: Creates Unit and UnitContentFile models and constraints.
  • learning_resources/migrations/0132_contentfile_release_date.py: Adds ContentFile release_date.
  • learning_resources/migrations/0133_contentfile_release_date_unreleased_idx.py: Adds a concurrent partial release-date index.
  • learning_resources/migrations/0134_resource_profile_configuration_claim_extraction.py: Creates profile models and seeds the unit prompt.
  • learning_resources/migrations_test.py: Removes legacy ContentFile deduplication migration tests.
  • learning_resources/models.py: Adds content visibility, release dates, units, memberships, profiles, and claim extractions.
  • learning_resources/models_test.py: Tests content visibility and reader filtering.
  • learning_resources/profiles/init.py: Adds the profiles package description.
  • learning_resources/profiles/admin.py: Registers profile configuration and claim extraction administration.
  • learning_resources/profiles/prompts.py: Adds the combined unit-claims prompt builder.
  • learning_resources/profiles/schemas.py: Defines strict evidence-backed unit extraction schemas.
  • learning_resources/profiles/sources.py: Renders ordered unit sources and truncates oversized content.
  • learning_resources/profiles/sources_test.py: Tests source truncation and unit source rendering.
  • learning_resources/profiles/unit_claims.py: Adds cached, validated, evidence-verified unit claim generation.
  • learning_resources/profiles/unit_claims_test.py: Tests claim generation, caching, validation, truncation, and failures.
  • learning_resources/profiles/verification.py: Verifies model evidence passages against source text.
  • learning_resources/profiles/verification_test.py: Tests exact, fuzzy, unknown, and nested evidence verification.
  • learning_resources/tasks.py: Adds release sweeps, ownership gates, overwrite propagation, and MITx Online tasks.
  • learning_resources/tasks_test.py: Tests release sweeps, ownership gates, catalog syncs, and overwrite propagation.
  • learning_resources/utils.py: Strips Azure prefixes for tokenizer model lookup.
  • learning_resources/utils_test.py: Tests Azure-prefixed tokenizer handling.
  • learning_resources/views.py: Serves only visible content files through the API.
  • learning_resources/views_test.py: Tests unpublished and unreleased content filtering.
  • learning_resources_search/api.py: Accepts Azure-prefixed dense models for hybrid search.
  • learning_resources_search/api_test.py: Tests hybrid search with Azure-prefixed embeddings.
  • learning_resources_search/connection.py: Strips Azure prefixes for OpenSearch connectors.
  • learning_resources_search/connection_test.py: Tests Azure-prefixed connector model normalization.
  • learning_resources_search/indexing_api.py: Indexes visible files and deindexes hidden files.
  • learning_resources_search/indexing_api_test.py: Tests hidden-file indexing and release-date preservation.
  • learning_resources_search/plugins.py: Updates indexing comments to distinguish hidden and visible files.
  • learning_resources_search/tasks.py: Deindexes hidden content files instead of only unpublished files.
  • learning_resources_search/tasks_test.py: Tests deindexing unreleased content files.
  • learning_resources_search/utils.py: Limits OpenSearch content-file selection to visible files.
  • main/azure_openai.py: Adds Azure OpenAI Entra-token helpers and LiteLLM routing parameters.
  • main/azure_openai_test.py: Tests Azure model detection, prefix stripping, and connection settings.
  • main/settings.py: Bumps version and adds release sweep, Azure OpenAI, and profile timeout settings.
  • main/settings_celery.py: Schedules release sweeps and daily MITx Online warehouse synchronization.
  • main/settings_course_etl.py: Requires LaTeX OCR formatting and configures request timeouts and retries.
  • main/settings_test.py: Verifies MITx Online warehouse synchronization schedule entries.
  • news_events/etl/pipelines.py: Gates news and events pipelines through ETL ownership checks.
  • news_events/etl/pipelines_test.py: Tests feed execution under legacy ownership configuration.
  • pyproject.toml: Adds Azure Identity and Pydantic dependencies and constrains certifi.
  • uv.lock: Locks Azure authentication, security-fixed certifi, and package upgrades.
  • vector_search/encoders/litellm.py: Supports Azure-prefixed embeddings with provider parameters and preserved caching.
  • vector_search/encoders/litellm_test.py: Tests Azure parameters, hedging, cache keys, and vector naming.
  • vector_search/tasks.py: Embedding and removal tasks now process visible and hidden content files.
  • vector_search/tasks_test.py: Verifies unreleased exclusion from embeddings and hidden-content removal.
  • vector_search/utils.py: Selects visible content files for Qdrant indexing.
  • vector_search/utils_test.py: Updates Qdrant selection tests for unreleased visibility.
  • webhooks/views.py: Gates OVS writes and course deletion by ETL ownership.
  • webhooks/views_test.py: Tests ownership-gated OVS operations and protected course deletion.

💬 Feedback & Commands

Share feedback on my comments with 👍 👎 — I'll stop raising issues you push back on. Think I missed something? Adjust my sensitivity →

5 things I can do →
  • #optibot review — I'll re-review your latest changes (incremental)
  • #optibot full review — I'll re-review the entire PR from scratch
  • #optibot fix — I'll apply my suggested fixes to your code
  • #optibot compliance — I'll run a compliance check on your changes
  • #optibot release notes — I'll write up release notes for your production release

💡 Tip: I can audit every dependency change before it reaches main — one compromised package can take down your entire system. Try Supply Chain Agent.

@agent-optibot

agent-optibot Bot commented Oct 26, 2026

Copy link
Copy Markdown

Optibot: trial review limit reached

Your organization has used all 300 code reviews included in your Optibot trial, so this pull request was not reviewed. Upgrade to resume automatic reviews: https://agents.getoptimal.ai/dashboard/billing

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants