From 938ce3c4b47d0dd4415a5ea1d49627ae811bd8c4 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Thu, 13 Aug 2026 16:34:10 +1000 Subject: [PATCH] AGENTS: deleting a file does not un-publish it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a fourth ordering trap. The existing third rule — "repoint, publish, then delete" — gets the ordering right but implies the delete step takes effect when it merges and publishes. It does not. `ci.yml` and `publish.yml` in lecture-python.myst both restore the `build-cache` artifact from `cache.yml` and build over it. Sphinx copies html_static_path into _build/html/_static but never prunes assets removed from source, so a deleted _static file survives in the restored tree and is deployed again. Only `cache.yml` builds from a clean checkout, and it runs weekly. Measured, not inferred: PR QuantEcon/lecture-python.myst#1035 deletes five data files, and its preview build served all five at HTTP 200 with the correct content-type and byte count, from a branch that does not contain them. A never-existed control path returned 404 on the same host, so this is not a Netlify SPA-fallback false pass. The consequence is a false completion rather than breakage: files keep resolving, which is strictly safer, but the repo, migration.yml and CATALOG.md all read "deleted" while readers still get the old bytes. Hence the rule's operative half — verify a deletion against the published URL, never against main. Scope beyond lecture-python.myst is still being established; the rule names the repo it was measured in rather than generalising ahead of the evidence. Part of QuantEcon/workspace-lectures#39. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 15f353d..6b9a645 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,7 @@ The Feb 2025 migration left files that cannot fully satisfy the rules above. The - **`builder_status: committed-frozen` — the builder is here, and deliberately will not run.** For a dataset built from a source that must not be refreshed: a frozen vintage, or a scraper we will not re-run. The artifact is kept as the record of what produced these bytes, so it is committed verbatim and not edited — editing it is what would destroy its value as provenance. Distinct from `committed`, which asserts a runnable four-stage builder, and from `unrecovered`, which says the builder is absent. - **`builder_status: unrecovered` — constructed without a recoverable builder.** A constructed dataset ships its builder, and one that omits it *silently* is the bug. Several inherited files are constructed with no recoverable extraction steps (PLAN Phase 9 tracks them). Keep `class: constructed` — reclassifying to `verbatim` to dodge the rule is misclassification — set `builder: null` and `builder_status: unrecovered`, and the gap stays visible for Phase 9 to recover. `unrecovered` is for **inherited files only**; never introduce a *new* constructed file without its builder. -### Repointing a lecture — three ordering traps +### Repointing a lecture — four ordering traps All cheap to follow and expensive to discover. `PLAN.md` carries the reasoning and the current counts. @@ -45,6 +45,8 @@ All cheap to follow and expensive to discover. `PLAN.md` carries the reasoning a - **Repoint every consumer of a dataset together.** The strict audit has no green state for a partially-repointed dataset — `pending`/`landed` fails once any consumer reads data-lectures, and `repointed`/`final` fails while any consumer still does not. Land the lecture repoints first, then flip `migration.yml`; that flip is the push that re-runs the audit, so reality and the tracker agree by the time it runs. This binds the **lecture PRs too**: merging one half of a set while the other sits open opens the same window. - **Repoint, publish, then delete — the published site lags `main`.** A lecture repo that publishes on a **tag** (`lecture-python-intro` uses `publish*`) does not refresh its site when a repoint merges, so the already-published notebooks keep the old URL. Delete the file in the same PR and that URL 404s for every reader who downloads or opens the lecture in Colab, until someone tags a publish. Rendered HTML is unaffected — figures are baked at build time — so nothing will alert you. **Split it: repoint the URLs and keep the files, publish, then delete in a follow-up PR.** Repos that publish on push to `main` (`lecture-wasm`) self-heal and need no split — and neither does deleting a copy that no lecture reads, such as one a repo committed while its lecture fetches another repo's copy by URL (a *mirror-orphan*). +- **Deleting a file does not un-publish it — the build reuses a cached `_build`.** In `lecture-python.myst`, `ci.yml` and `publish.yml` both restore the `build-cache` artifact produced by `cache.yml` and then build *over* it. Sphinx copies `html_static_path` into `_build/html/_static` but never prunes assets that have since been removed from source, so a deleted `_static` file survives in the restored tree and is deployed again. Only `cache.yml` builds from a clean checkout, and it runs **weekly** (`cron: '0 3 * * 1'`). So a deletion merged on a Tuesday keeps being served until the next Monday rebuild *and* a publish after it. **Verify a deletion against the published URL, never against `main`** — the repo, `migration.yml` and `CATALOG.md` will all say "deleted" while readers still get the bytes. Measured 2026-08-13 on [lecture-python.myst#1035](https://github.com/QuantEcon/lecture-python.myst/pull/1035): its preview served every deleted file at HTTP 200 with correct `content-type` and byte count, while a never-existed control path 404'd. To close the window deliberately, `workflow_dispatch` `cache.yml` before tagging the publish — it is a ~1h50m GPU build, which is exactly why the cache exists. + Cross-repo repoints are worked from [`QuantEcon/workspace-lectures`](https://github.com/QuantEcon/workspace-lectures) — same branch name in each repo, one PR per repo, no aggregate PR. ### Repointing a lecture — one scope rule