Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
031cceb
Add ExecPlan for discovery-driven bump lockfile regeneration
leynos Jul 7, 2026
cb4e462
Restore a passing typecheck gate under ty 0.0.8
leynos Jul 7, 2026
6d134d7
Discover tracked nested lockfiles during bump regeneration
leynos Jul 7, 2026
98c43ee
Document bump lockfile discovery and reword the stale pre-flight hint
leynos Jul 7, 2026
51df64b
Complete the regenerate-lockfiles ExecPlan
leynos Jul 7, 2026
dc59669
Pin ty 0.0.56 in the Makefile and stop installing it in CI
leynos Jul 7, 2026
acc0e65
Index the execution plan in the documentation contents
leynos Jul 8, 2026
c6062fa
Address lockfile regeneration review feedback
Jul 13, 2026
c84e345
Preserve lockfile discovery behind repository port
Jul 13, 2026
504a182
Align lockfile ExecPlan with repository port
Jul 19, 2026
e103a95
Make index failure returns explicit
Jul 19, 2026
86fdfb8
Format replayed lockfile tests
Jul 27, 2026
725082e
Verify discovered lockfile update calls
Jul 27, 2026
25ba391
Split lockfile tests by subject
Jul 28, 2026
55a6479
Clarify lockfile ownership documentation
Jul 28, 2026
6497463
Split lockfile regeneration by responsibility
leynos Jul 29, 2026
e9c788d
Document scoped lockfile test stubs
leynos Jul 29, 2026
9fd9293
Clarify lockfile helper documentation
leynos Jul 29, 2026
2bc06e5
Narrow lockfile runner exception handling
leynos Jul 29, 2026
4201ad5
Document lockfile path public APIs
leynos Jul 29, 2026
9917aa0
Clarify compatibility shim table caption
leynos Jul 29, 2026
8b706fc
Publish lockfile manifest resolver
leynos Jul 30, 2026
e10a83d
Add shim inventory triage marker
leynos Jul 31, 2026
8f6e059
Document lockfile API usage
leynos Jul 31, 2026
6a73a46
Document lockfile helper boundaries
leynos Jul 31, 2026
99a09c0
Remove review triage metadata
leynos Jul 31, 2026
19b3479
Document non-Git lockfile fallback
leynos Jul 31, 2026
92a574b
Instrument lockfile regeneration
leynos Jul 31, 2026
4b0b785
Deduplicate regeneration metric assertions
leynos Jul 31, 2026
fd3e38c
Clarify lockfile failure metrics
leynos Jul 31, 2026
926e04f
Strengthen lockfile metric diagnostics
leynos Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ jobs:
run: |
for tool in mbake; do uv tool install ${tool}; done
# ty is not installed here: `make typecheck` runs it via
# `uv tool run ty@$(TY_VERSION)`. TY_VERSION in the Makefile is the
# sole ty version declaration; update it there.
# `uv tool run --from ty==$(TY_VERSION) ty`. TY_VERSION in the
# Makefile is the sole ty version declaration; update it there.
# Pin ruff to the version the Makefile invokes (RUFF_VERSION) so the
# linter behaves identically across the uv tool, the synced
# virtualenv, and developer PATH; bump both sites together.
uv tool install ruff==0.15.12
uv tool install ruff==0.15.21
npm install -g markdownlint-cli2

- name: Validate Makefile
Expand Down
13 changes: 7 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ UV ?= $(shell command -v uv 2>/dev/null || printf '%s/.local/bin/uv' "$$HOME")
# .github/workflows/ci.yml. Bump all three sites together: a version mismatch
# causes version-skew lint failures because rule sets differ between Ruff
# releases.
RUFF_VERSION ?= 0.15.12
RUFF_VERSION ?= 0.15.21
RUFF ?= $(UV) tool run --from ruff==$(RUFF_VERSION) ruff
TYPOS_VERSION ?= 1.48.0
# Pin ty. `make typecheck` invokes it via `uv tool run ty@$(TY_VERSION)`, so
# TY_VERSION is the sole ty version declaration (CI runs `make typecheck` and
# installs no separate ty). Bump it here to move local and CI checks together.
TY_VERSION ?= 0.0.32
TY ?= $(UV) tool run ty@$(TY_VERSION)
# Pin ty so `make` and CI invoke the same typechecker release. ty is
# pre-1.0 and diagnostics shift between releases, so an unpinned install
# breaks the typecheck gate without any code change. Bump deliberately and
# fix new diagnostics in the same commit.
TY_VERSION ?= 0.0.56
TY ?= $(UV) tool run --from ty==$(TY_VERSION) ty
UV_ENV = UV_CACHE_DIR=.uv-cache UV_TOOL_DIR=.uv-tools
TOOLS = $(MDFORMAT_ALL) $(MDLINT) $(NIXIE) $(UV)
PY_SOURCES := $(sort $(shell find lading scripts -type f -name '*.py' -print))
Expand Down
9 changes: 9 additions & 0 deletions docs/contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ looking for project guidance, then follow the link that matches the task.
- [Roadmap](roadmap.md) - phased delivery plan and tracked implementation
tasks.

## Execution plans

Execution plans are living delivery documents under `execplans/`, one per
branch, recording the plan, progress, decisions, and retrospective for a change.

- [Regenerate discovered nested lockfiles][execplan-regenerate-lockfiles] -
completed plan for discovery-driven lockfile regeneration in the bump command.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
## Decision records

- [ADR-003: Use three-tier Python linting][adr-003] - accepted linting policy
Expand All @@ -38,3 +46,4 @@ looking for project guidance, then follow the link that matches the task.

[adr-003]: adr/003-three-tier-python-linting.md
[adr-004]: adr/004-in-process-metrics-backend.md
[execplan-regenerate-lockfiles]: execplans/regenerate-lockfiles.md
81 changes: 60 additions & 21 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,19 @@ hygiene, and design-size limits.

The relevant Makefile variables are:

- `RUFF_VERSION` — pinned Ruff version; defaults to `0.15.12`. Keep it in sync
- `RUFF_VERSION` — pinned Ruff version; defaults to `0.15.21`. Keep it in sync
with the `ruff==` dev dependency in `pyproject.toml` and the
`uv tool install ruff==` step in `.github/workflows/ci.yml`, bumping all
three together to avoid version-skew lint failures.
- `RUFF` — the pinned Ruff command
(`uv tool run --from ruff==$(RUFF_VERSION) ruff`) that the `fmt`,
`check-fmt`, and `lint` targets invoke.
- `TY_VERSION` — pinned ty version used by `make typecheck`; defaults to
`0.0.56`. ty is pre-1.0 and diagnostics shift between releases, so bump it
deliberately and fix any new diagnostics in the same commit. CI does not
install ty separately; it runs whatever `TY_VERSION` pins.
- `TY` — the pinned ty command (`uv tool run --from ty==$(TY_VERSION) ty`)
that the `typecheck` target invokes.
- `PYLINT_PYTHON` — Python executable used by `uv tool run`; defaults to `pypy`.
- `PYLINT_TARGETS` — directories passed to Pylint; defaults to
`lading scripts tests`.
Expand Down Expand Up @@ -203,10 +209,28 @@ resolve from the crate directory, and leaves absolute URI targets unchanged.
Markdown links in fenced code blocks, indented code blocks, and inline code
spans are preserved verbatim.

`lading.commands.bump_lockfiles` owns Cargo lockfile discovery and regeneration
after manifest changes. It always includes the workspace root `Cargo.toml`,
validates configured nested manifests before invoking Cargo, and de-duplicates
resolved manifest paths.
### Lockfile regeneration modules

`lading.commands.bump_lockfiles` is the public compatibility façade. It owns
the bump-side `LockfileRepository` port and `CargoLockfileRepository` adapter
and re-exports the public lockfile helpers from three cohesive modules:

- `lading.commands.bump_lockfile_manifests` owns
`merge_discovered_manifests`. It unions configured `bump.lockfile_manifests`
entries with manifests implied by git-tracked `Cargo.lock` files (reusing
`lading.commands.lockfile.discover_tracked_lockfiles`); configured entries
keep their order and discovered entries follow in sorted order.
- `lading.commands.bump_lockfile_paths` owns `LockfileRegenerationError`,
`resolve_manifest_paths`, and `resolve_lockfile_paths`.
`resolve_manifest_paths` validates that configured entries stay inside the
workspace and are named `Cargo.toml`, inserts the workspace-root manifest,
and de-duplicates resolved paths. `resolve_lockfile_paths` projects that
execution-ordered manifest tuple to the corresponding `Cargo.lock` paths
without invoking Cargo.
- `lading.commands.bump_lockfile_regeneration` owns `regenerate_lockfiles` and
its Cargo-execution helpers. `regenerate_lockfiles` uses the same validated,
root-first manifest order, attempts every Cargo update, returns successful
lockfile paths in execution order, and aggregates multi-manifest failures.

For screen readers: the following flowchart traces `regenerate_lockfiles`. It
resolves the manifest list, then initializes empty `lockfiles` and `failures`
Expand Down Expand Up @@ -258,11 +282,17 @@ the original cargo error rather than wrapped in the aggregate message.
`lockfile_repository` field, a `bump_lockfiles.LockfileRepository` port
introduced by issue #82: the bump domain never holds a raw command runner. When
the field is `None`, bump uses `bump_lockfiles.CargoLockfileRepository`, the
cargo-backed adapter bound to the default subprocess runner; the CLI binds the
adapter to its selected runner. Tests inject a repository (or bind the adapter
to a recording runner) so lockfile commands can be observed without invoking
real Cargo processes. The port's scope is bump-side lockfile projection and
regeneration; publish-side discovery and validation go through the sibling
cargo- and git-backed adapter bound to the default subprocess runner; the CLI
binds the adapter to its selected runner. The adapter merges configured
manifests with manifests discovered from tracked lockfiles before either
projecting dry-run paths or regenerating live lockfiles. In a non-Git
workspace, discovery emits a warning and the adapter falls back to the
configured manifests. Dry-run projection suppresses successful-discovery
metrics and informational logging while retaining that warning and all
discovery errors. Tests inject a repository (or bind the adapter to a recording
runner) so lockfile commands can be observed without invoking real processes.
The port's scope is bump-side lockfile projection and regeneration;
publish-side discovery and validation go through the sibling
`lockfile.LockfileInspectionRepository` port (see the Lockfile helpers section
below), so neither the bump nor the publish lockfile domain holds a raw
`CommandRunner` (issue #82).
Expand Down Expand Up @@ -369,11 +399,15 @@ Private helpers `_handle_git_ls_files_failure` and `_lockfiles_with_manifests`
perform the error-handling and path-filtering passes respectively.

Lockfile regeneration after `lading bump` is owned by
`lading.commands.bump_lockfiles.regenerate_lockfiles`, which runs
`cargo update --workspace` per configured manifest. The two cargo strategies
differ deliberately: bump refreshes existing pinned versions in place after
manifest rewrites, while publish only probes freshness read-only via
`cargo metadata --locked` and never regenerates.
`lading.commands.bump_lockfiles.CargoLockfileRepository`. The adapter uses
`bump_lockfiles.merge_discovered_manifests` to union the configured
`bump.lockfile_manifests` entries with manifests implied by
`discover_tracked_lockfiles`, then delegates to `regenerate_lockfiles`, which
runs `cargo update --workspace` per merged manifest. In a non-Git workspace,
discovery emits a warning and the merge uses only the configured entries. The
two cargo strategies differ deliberately: bump refreshes existing pinned
versions in place after manifest rewrites, while publish only probes freshness
read-only via `cargo metadata --locked` and never regenerates.

`validate_lockfile_freshness(manifest_path, runner)` runs
`cargo metadata --locked --manifest-path ... --format-version=1`. It returns a
Expand Down Expand Up @@ -623,6 +657,9 @@ canonical replacement callers and tests now use directly:
| Private `_append_section` / `_format_plan` | `publish_plan.py` | renamed to public `append_section` / `format_plan` |
| `split_command` / `_split_command` wrapper | `publish_execution.py` | `lading.runtime.subprocess_runner.split_command` |

_Table 1: Compatibility shims removed by the issue `#163` sweep and their
canonical replacements._

#### Retained boundaries

Not every module-level indirection is a compatibility shim. The following
Expand Down Expand Up @@ -927,12 +964,14 @@ than bucketed.

Defined metrics:

| Metric | Labels | Incremented when |
| -------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `publish.index_lookup_downgrade` | `subcommand`, `missing_crate` | `_handle_index_missing_version` downgrades a crates.io index-lookup failure to a warning (in-plan, override enabled). |
| `lockfile.discovered` | (none) | Incremented by the number of tracked lockfiles each `discover_tracked_lockfiles` call returns. |
| `lockfile.validate` | `outcome` | One increment per `validate_lockfile_freshness` call; `outcome` is `fresh`, `stale`, or `failed`. |
| `lockfile.validate.duration` | (none) | Duration observation around each `cargo metadata --locked` probe. |
| Metric | Labels | Incremented when |
| -------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `publish.index_lookup_downgrade` | `subcommand`, `missing_crate` | `_handle_index_missing_version` downgrades a crates.io index-lookup failure to a warning (in-plan, override enabled). |
| `lockfile.discovered` | (none) | Incremented by tracked-lockfile count when discovery observability is enabled; dry-run bump projection suppresses it. |
| `lockfile.regenerate` | `outcome`, `cause` | Incremented per successful or failed lockfile regeneration; `cause` is `none`, `validation`, `command_spawn`, `runner_value`, or `cargo_exit`. |
| `lockfile.regenerate.duration` | (none) | Total duration observation around each lockfile-regeneration run. |
| `lockfile.validate` | `outcome` | One increment per `validate_lockfile_freshness` call; `outcome` is `fresh`, `stale`, or `failed`. |
| `lockfile.validate.duration` | (none) | Duration observation around each `cargo metadata --locked` probe. |

Duration metrics aggregate a count and total seconds per label set via
`observe_duration` / `duration_stats` and appear in the exit summary with
Expand Down
Loading
Loading