diff --git a/complete/2026/07/minimum-library-version-adoption.md b/complete/2026/07/minimum-library-version-adoption.md new file mode 100644 index 00000000..22458d69 --- /dev/null +++ b/complete/2026/07/minimum-library-version-adoption.md @@ -0,0 +1,75 @@ +# minimum-library-version-adoption + +- shipped: 2026-07-17 (autolens_workspace `21702119`; siblings adopted the same key) +- follows: PyAutoConf#119 (merged 2026-07-08) — `check_version` prefers `version.minimum_library_version` over the legacy records +- repos: + - autofit_workspace, autogalaxy_workspace, autolens_workspace + - HowToFit, HowToGalaxy, HowToLens + - euclid_strong_lens_modeling_pipeline + +## Summary + +Every one of the seven repos in scope carries +`version.minimum_library_version: 2026.7.9.1` in `config/general.yaml`, with the +bump-deliberately rule documented inline as a comment on the key itself: + +> The compatibility FLOOR: the oldest library release whose API this workspace's +> scripts require. Preferred over workspace_version (autonerves/workspace.py). +> Bump DELIBERATELY — only when a script starts needing new API — never per +> release. Must always name an INSTALLABLE (non-yanked) release. + +## Verified 2026-08-08, all three legs of the prompt's scope + +1. **The key is adopted in all 7** — confirmed by reading each repo's + `config/general.yaml` on `main`, not inferred from one. +2. **The dead `workspace_version` key is gone.** The only surviving matches are + the explanatory comment above and `workspace_version_check`, which is a + *different* and still-live key (the documented bypass for `main`-branch + clones, where mismatches are expected because `main` moves faster than + releases). +3. **`version.txt` is gone** — `HTTP 404` on `main` for autolens_workspace, + autofit_workspace and HowToLens. + +## Bookkeeping note + +Reconstructed 2026-08-08. This prompt was sitting in +`draft/feature/workspaces/` — the backlog folder — with `Priority: high`, while +the work had been fully delivered for three weeks. `draft/` is graded by no +check at all, which is how it stayed there; that gap is what +`lifecycle.py issues --drafts` now partially addresses, though only for the +minority of drafts that cite an issue (this one cited none). + +## Original prompt + +# Adopt version.minimum_library_version in workspace configs + +Type: feature +Target: workspaces +Difficulty: small +Autonomy: supervised +Priority: high +Status: formalised + +## Context + +Follow-up to PyAutoConf#119 (merged 2026-07-08): `check_version` now enforces +a compatibility floor, preferring `version.minimum_library_version` in +`config/general.yaml` over the legacy `workspace_version`/`version.txt` +records (which releases no longer write since PyAutoBuild#121). + +## Scope + +- Add `version.minimum_library_version` to `config/general.yaml` in + autofit_workspace, autogalaxy_workspace, autolens_workspace, HowToFit, + HowToGalaxy, HowToLens, euclid_strong_lens_modeling_pipeline — set to the + oldest release whose API the workspace's scripts actually require (at + adoption time: the first real release after 2026-07-08, since workspace + mains depend on post-2026.7.6.649 API). +- Remove the now-dead `workspace_version` key and `version.txt` once the new + key is in place (they are only read as fallbacks). +- Document the bump-deliberately rule in each workspace README/AGENTS: the + floor moves only when scripts start needing new API, never per release. +- Coordinate claims: autofit_workspace (ep-examples-tests) and + autolens_workspace (kxs-core) are claimed as of filing — serialise or wait. +- Best sequenced AFTER the first real release (Q1 on PyAutoBuild#118), so the + floor value is an installable version. diff --git a/complete/index.md b/complete/index.md index f08fc0cc..5afcb39f 100644 --- a/complete/index.md +++ b/complete/index.md @@ -6,7 +6,7 @@ Token-light navigation over the finished-work records (schema: only then grep a dated bucket. Curators: edit the band between the CURATED markers; everything below GENERATED is rebuilt. -946 records across 7 buckets. +947 records across 7 buckets. ## Highlights @@ -334,6 +334,7 @@ _(curate hard-won records here — survives regeneration.)_ - [memory-structure-cleanup](2026/07/memory-structure-cleanup.md) — wiki/ + bibliography restructure, structure lint, template sync — SHIPPED - [merge-adjacent-docstrings](2026/07/merge-adjacent-docstrings.md) — Phase 3 of the adjacent-docstrings arc (PyAutoHands#196 → PyAutoBrain#162 → here). Merged 80 adjacent-docstrin… - [mind-guard-cd-fix](2026/07/mind-guard-cd-fix.md) — mind_commit_guard v1.2 — honour a leading `cd` away from Mind. v1.1 keyed is-Mind-commit off the ambient cwd t… +- [minimum-library-version-adoption](2026/07/minimum-library-version-adoption.md) - [morning-status-release-rehearsal](2026/07/morning-status-release-rehearsal.md) — morning Slack digest + release rehearsal — RESOLVED - [multi-galaxy-features-group-parity](2026/07/multi-galaxy-features-group-parity.md) — Arc close-out: all four phases of the multi_galaxy features group-parity campaign shipped across ten PRs — pha… - [multi-galaxy-features-parity-phase-1](2026/07/multi-galaxy-features-parity-phase-1.md) diff --git a/draft/feature/autolens/restore_multiple_sources_lensing_of_lens.md b/draft/feature/autolens/restore_multiple_sources_lensing_of_lens.md deleted file mode 100644 index eb547c9a..00000000 --- a/draft/feature/autolens/restore_multiple_sources_lensing_of_lens.md +++ /dev/null @@ -1,39 +0,0 @@ -Once https://github.com/PyAutoLabs/PyAutoLens/issues/480 is fixed (PointSolver -magnification filter must use `plane_redshift`, not the tracer's last plane), -revisit `@autolens_workspace/scripts/point_source/features/multiple_sources/`. - -First, lets fix https://github.com/PyAutoLabs/PyAutoLens/issues/480, I want -you to first validate that the cause of the issue described there is -acitually right. I'm a bit unsure I totally buy it, so do some epxlpicit -tests which more directly remove the mass profile but also edit the -magnification_threshold setting. I'm happy to be convinced, but need a bit -more confirmation. - -When that workspace example was first written (autolens_workspace issue #97), -the upstream PointSolver bug made it impossible to simulate or fit a configuration -where the intermediate source itself acts as a deflector for the further source. -To unblock the multi/factor-graph tutorial, the example was simplified so the -only deflector is the foreground lens — both source galaxies are point-only at -different redshifts, no source-plane mass profile. - -This task restores the original "double Einstein cross" intent now that the -PointSolver bug is fixed: - -1. Update `simulator.py` so source_0 (z=1.0) regains its `Isothermal` mass profile - at (0.02, 0.03) with `einstein_radius=0.2` and a small ellipticity. Source_0 - should now genuinely lens source_1 in addition to the foreground lens. -2. Verify the simulator still runs end-to-end with a single tracer - `[lens, source_0_with_mass, source_1]` and that `solver.solve(plane_redshift=1.0)` - returns >=4 image-plane positions for source_0. -3. Update `modeling.py` so the model includes source_0's mass: - - `source_0 = af.Model(al.Galaxy, redshift=1.0, mass=al.mp.Isothermal, point_0=al.ps.Point)` - The lens model dimensionality goes from N=9 to N=14. -4. Decide whether the `AnalysisFactor` for source_0's dataset should fit using - the full multi-plane model or a sub-model excluding source_1. With #480 fixed, - the full multi-plane model should fit cleanly and is preferred — both factors - share `lens` and `source_0.mass` priors, the factor graph sums log-likelihoods. -5. Re-run end-to-end with `PYAUTO_TEST_MODE=2` (no `PYAUTO_SMALL_DATASETS`) to - confirm the simulator and modeling both work, then regenerate notebooks. -6. Update the script docstrings and the folder/feature README to mention the - lensing-of-lens richness again, and remove any "simplified to work around - PyAutoLens #480" comments. diff --git a/draft/feature/workspaces/minimum_library_version_adoption.md b/draft/feature/workspaces/minimum_library_version_adoption.md deleted file mode 100644 index cdcf4ed9..00000000 --- a/draft/feature/workspaces/minimum_library_version_adoption.md +++ /dev/null @@ -1,32 +0,0 @@ -# Adopt version.minimum_library_version in workspace configs - -Type: feature -Target: workspaces -Difficulty: small -Autonomy: supervised -Priority: high -Status: formalised - -## Context - -Follow-up to PyAutoConf#119 (merged 2026-07-08): `check_version` now enforces -a compatibility floor, preferring `version.minimum_library_version` in -`config/general.yaml` over the legacy `workspace_version`/`version.txt` -records (which releases no longer write since PyAutoBuild#121). - -## Scope - -- Add `version.minimum_library_version` to `config/general.yaml` in - autofit_workspace, autogalaxy_workspace, autolens_workspace, HowToFit, - HowToGalaxy, HowToLens, euclid_strong_lens_modeling_pipeline — set to the - oldest release whose API the workspace's scripts actually require (at - adoption time: the first real release after 2026-07-08, since workspace - mains depend on post-2026.7.6.649 API). -- Remove the now-dead `workspace_version` key and `version.txt` once the new - key is in place (they are only read as fallbacks). -- Document the bump-deliberately rule in each workspace README/AGENTS: the - floor moves only when scripts start needing new API, never per release. -- Coordinate claims: autofit_workspace (ep-examples-tests) and - autolens_workspace (kxs-core) are claimed as of filing — serialise or wait. -- Best sequenced AFTER the first real release (Q1 on PyAutoBuild#118), so the - floor value is an installable version. diff --git a/scripts/lifecycle.py b/scripts/lifecycle.py index d023daad..2cee32e5 100644 --- a/scripts/lifecycle.py +++ b/scripts/lifecycle.py @@ -318,6 +318,44 @@ def issue_problems(root: Path, fetch=None) -> "list[str]": return problems +def draft_issue_refs(root: Path) -> "list[tuple[str, str]]": + """(draft_path, issue_url) for draft prompts citing a GitHub issue. + + Only a handful do — drafts are pre-issue by definition — but `draft/` is + backlog no check grades, and it carries shipped work too (the 2026-08-08 + sweep found `minimum_library_version_adoption` fully delivered across all + seven repos while still sitting in draft/).""" + draft = root / "draft" + if not draft.is_dir(): + return [] + refs = [] + for f in sorted(draft.rglob("*.md")): + m = ISSUE_URL_RE.search(f.read_text(errors="replace")) + if m: + refs.append((str(f.relative_to(root)), m.group(0))) + return refs + + +def draft_issue_notes(root: Path, fetch=None) -> "list[str]": + """ADVISORY notes on drafts whose cited issue is closed. + + Deliberately weaker than `issue_problems`, and deliberately not drift. A + registry entry's `issue:` is its OWN tracking issue, so closed means done. A + draft usually cites an issue as CONTEXT — "Once #480 is fixed…", "Follow-up + to #57" — so closed can mean the draft is newly UNBLOCKED rather than + finished. Both readings are worth a human look; neither is a gate.""" + refs = draft_issue_refs(root) + if not refs: + return [] + fetch = fetch or _gh_issue_states + states = fetch([url for _, url in refs]) + return [ + f"{path}: cited issue is closed — shipped, or newly unblocked? {url}" + for path, url in refs + if states.get(url) == "closed" + ] + + def cmd_issues(args) -> int: """Cross-check every registry entry's tracking issue against GitHub.""" try: @@ -331,13 +369,28 @@ def cmd_issues(args) -> int: file=sys.stderr, ) return 2 - if not problems: + notes = [] + if getattr(args, "drafts", False): + try: + notes = draft_issue_notes(ROOT) + except GhUnavailable: + pass # unreachable: issue_problems above would already have raised + + if problems: + print("lifecycle issues: DRIFT") + for line in problems: + print(f" - {line}") + else: print(f"lifecycle issues: OK ({len(registry_issue_refs(ROOT))} tracking issue(s) open)") - return 0 - print("lifecycle issues: DRIFT") - for p in problems: - print(f" - {p}") - return 1 + + # Advisory only — never affects the exit code. A draft citing a closed issue + # may be shipped OR newly unblocked; that is a judgement, not drift. + if notes: + print(f"\nadvisory — {len(notes)} draft(s) citing a closed issue:") + for line in notes: + print(f" ? {line}") + + return 1 if problems else 0 def orphan_prompts(root: Path) -> "list[Path]": @@ -750,6 +803,8 @@ def main() -> int: iss = sub.add_parser( "issues", help="cross-check registry tracking issues against GitHub (needs gh)" ) + iss.add_argument("--drafts", action="store_true", + help="also flag draft/ prompts citing a closed issue (advisory)") iss.set_defaults(func=cmd_issues) o = sub.add_parser("orphans", help="report active/ prompts no registry claims") diff --git a/tests/test_lifecycle_check.py b/tests/test_lifecycle_check.py index fa8af738..4e4d9511 100644 --- a/tests/test_lifecycle_check.py +++ b/tests/test_lifecycle_check.py @@ -301,6 +301,34 @@ def test_merged_pr_links_are_not_treated_as_tracking_refs(tmp_path): assert [r[2] for r in refs] == [GHOST_ISSUE] +def test_draft_citing_a_closed_issue_is_advisory(tmp_path): + """draft/ is backlog no check grades, and it carries shipped work too. A + closed cited issue is worth a look — but NOT drift, because a draft usually + cites an issue as context ("Once #480 is fixed…"), so closed can mean newly + unblocked rather than finished.""" + d = tmp_path / "draft" / "feature" / "flywheel" + d.mkdir(parents=True) + (d / "sprocket_calibration.md").write_text(f"Once {GHOST_ISSUE} is fixed, do X.\n") + notes = lifecycle.draft_issue_notes(tmp_path, fetch=_states({GHOST_ISSUE: "closed"})) + assert len(notes) == 1 + assert "shipped, or newly unblocked?" in notes[0] + + +def test_draft_with_an_open_issue_is_silent(tmp_path): + d = tmp_path / "draft" / "feature" / "flywheel" + d.mkdir(parents=True) + (d / "sprocket_calibration.md").write_text(f"Blocked on {GHOST_ISSUE}.\n") + assert lifecycle.draft_issue_notes(tmp_path, fetch=_states({GHOST_ISSUE: "open"})) == [] + + +def test_drafts_are_not_mixed_into_registry_drift(tmp_path): + """The advisory must never leak into `issue_problems`, which is the gate.""" + d = tmp_path / "draft" / "feature" / "flywheel" + d.mkdir(parents=True) + (d / "sprocket_calibration.md").write_text(f"Once {GHOST_ISSUE} is fixed.\n") + assert lifecycle.issue_problems(tmp_path, fetch=_states({GHOST_ISSUE: "closed"})) == [] + + def test_missing_gh_propagates_rather_than_reporting_all_clear(tmp_path): """"gh is not installed" must never be mistaken for "no findings" — a check that silently could not run is worse than one that fails loudly."""