Skip to content

promote: a new lecture that shares a single-consumer asset with a switched lecture can never be promoted #16

Description

@quantecon-services

Follow-up from the review of #14.

What happens

After promote --switch SERIES, a switched lecture can never be re-promoted (by design: the pool copy is the source of truth). Its single-consumer assets stay at _static/<slug>/.... If a lecture from another series is later promoted and references the same asset (same mirror path, same bytes), the destination step decides the asset now has two consumers and belongs in _static/_shared/..., finds the old location is still used by a lecture outside the batch, and refuses with:

[other] FAILED: asset _static/lecture_specific/mccall/fig.png is recorded at _static/mccall/fig.png but now maps to _static/_shared/mccall/fig.png — re-promote its other consumer(s) (mccall) in the same batch

The remedy it names is impossible. Adding mccall to the batch gives:

[mccall] FAILED: lectures/mccall.md is canonical in the pool (switched from intermediate on 2026-10-08) — promotion would overwrite the pool copy

So the new lecture cannot enter the pool while it shares that asset. Switching the new lecture's series does not help either, since the switch only touches ledger entries and the lecture has none yet.

Reproduction (with the tools/tests harness)

repo = make_repo(["intro", "intermediate"])
fig = b"figure bytes"
repo.mirror_file("intermediate", "_static/lecture_specific/mccall/fig.png", fig)
repo.lecture("intermediate", "mccall", lecture_text("McCall", refs=["_static/lecture_specific/mccall/fig.png"]))
assert repo.promote("mccall").returncode == 0
assert repo.promote("--switch", "intermediate").returncode == 0
repo.mirror_file("intro", "_static/lecture_specific/mccall/fig.png", fig)
repo.lecture("intro", "other", lecture_text("Other", refs=["_static/lecture_specific/mccall/fig.png"]))
assert repo.promote("other").returncode == 1          # the error above
assert repo.promote("other", "mccall").returncode == 1  # the suggested remedy also fails

When it bites

Only mid-transition: one series switched, another still fed from its mirror, and a new lecture in the second reusing a figure the first already owns. Assets that already live in _static/_shared/ are unaffected (the destination does not change, and #14 already keeps a switched asset's record when another lecture comes to share it).

Suggested fix

Treat a switched asset's location as frozen. In the destination pass, when the old pool path of (src_rel, digest) is referenced by a lecture whose ledger entry is canonical: pool, keep dest_map[(src_rel, digest)] = old instead of recomputing it, so the new lecture is rewritten to the existing path and shares the file in place. The switched lecture's pool text is untouched, the ledger gains the new lecture as a user of the asset, and the asset keeps its canonical: pool record. A test alongside test_an_asset_shared_with_a_guarded_lecture_stays_guarded would cover it.

Activity

  1. mmcky commented on Oct 8, 2026

    @mmcky
    Contributor

    --switch has been taken out of #14: the review found it incomplete, and it lands, fixed, through a separate work item before the first cutover. Its reviewed implementation is kept on the promote/switch branch. This defect cannot occur on main until the switch returns, so it moves with it: the switch work item lists it among the things the rework must handle.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions