You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
promote: a new lecture that shares a single-consumer asset with a switched lecture can never be promoted #16
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"]))
assertrepo.promote("mccall").returncode==0assertrepo.promote("--switch", "intermediate").returncode==0repo.mirror_file("intro", "_static/lecture_specific/mccall/fig.png", fig)
repo.lecture("intro", "other", lecture_text("Other", refs=["_static/lecture_specific/mccall/fig.png"]))
assertrepo.promote("other").returncode==1# the error aboveassertrepo.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.
--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.
Activity
mmcky commentedon Oct 8, 2026
--switchhas 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 thepromote/switchbranch. This defect cannot occur onmainuntil the switch returns, so it moves with it: the switch work item lists it among the things the rework must handle.