fix(deps): git-semver update idempotency in build_update_plan#2165
fix(deps): git-semver update idempotency in build_update_plan#2165srobroek wants to merge 3 commits into
Conversation
`apm update` reported a spurious UPDATE on every run for git-source semver dependencies already at their locked tag, and never converged: the lockfile was rewritten with the identical SHA each time (only timestamps changed). Root cause: the git-semver resolver rewrites `dep.reference` to the concrete tag and computes its SHA, but stashes the result in `ctx.git_semver_resolutions` (and the download result) without attaching it back to `dep.resolved_reference`. At plan time `resolved_reference` is None, so `build_update_plan` compares the locked commit against `None` and classifies every git-semver dep as `update`. Fix mirrors the registry idempotency fix in microsoft#1908, applied to the git-source path: when a dep has no freshly-resolved commit, the locked entry pins an immutable tag (`resolved_tag` set), and the ref is unchanged, borrow the locked commit so the comparison sees `unchanged`. Scoped to tags -- a branch tip can advance under a stable ref name, so branch deps (no `resolved_tag`) still surface real updates. Adds two regression tests: cached git-semver tag stays `unchanged`; branch-tip advance still shows `update`.
There was a problem hiding this comment.
Pull request overview
Fixes apm update idempotency for git-source semver dependencies that are already locked to an immutable tag, preventing repeated “update” results when nothing has changed.
Changes:
- Adjusts
build_update_planto treat cached git-semver tag deps as unchanged by borrowing the locked commit when the resolved ref is unchanged and the lockfile pins aresolved_tag. - Adds regression tests covering (1) the cached git-semver tag unchanged case and (2) ensuring branch-tip advances still report updates.
- Adds an Unreleased changelog entry describing the fix.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/apm_cli/install/plan.py |
Adds a locked-commit fallback for cached git-semver tag deps to prevent spurious updates. |
tests/unit/install/test_plan.py |
Adds unit tests to reproduce the bug and guard against masking branch updates. |
CHANGELOG.md |
Documents the fix under Unreleased. |
| - `apm update` no longer reports a spurious update on every run for git-source | ||
| semver dependencies already at their locked tag. The git-semver resolver | ||
| rewrites the dep reference to the concrete tag but does not attach the | ||
| resolved SHA to `resolved_reference`, so the update plan compared the locked | ||
| commit against `None` and never converged. `build_update_plan` now borrows the | ||
| locked commit for cached, immutable-tag deps whose ref is unchanged, mirroring | ||
| the registry fix in #1908 (branch deps are unaffected -- their tips can still | ||
| advance under a stable ref name). (closes microsoft/apm#2163) |
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python architect | 0 | 0 | 0 | Schema failure -- see full findings. |
| CLI logging expert | 0 | 0 | 0 | No CLI output, logging, or diagnostic surface touched; upstream data fix removes spurious UPDATE. Ship. |
| DevX UX expert | 0 | 0 | 0 | Internal plan-logic fix eliminates a spurious non-converging UPDATE; no CLI surface changes. Ship. |
| Supply-chain security expert | 0 | 0 | 1 | Trust model is sound; only a comment overstates tag immutability. |
| OSS growth hacker | 0 | 1 | 0 | CHANGELOG entry buries the user-facing fix under resolver internals. |
| Doc writer | 0 | 1 | 2 | CHANGELOG entry is accurate but should focus on user-visible behavior. |
| Test coverage expert | 0 | 0 | 0 | Schema failure -- see full findings. |
| Performance expert | 0 | 0 | 0 | O(1) guard inside existing O(n) loop; no I/O, network, or algorithmic regression. Ship. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 2 follow-ups
- [OSS growth hacker] Rewrite CHANGELOG entry for user-facing clarity: lead with symptom and corrected outcome, drop internal symbols. -- Release notes are a conversion surface; maintainer-centric jargon reduces shareability and user confidence.
- [Supply-chain security expert] Rephrase the comment at
src/apm_cli/install/plan.py:225to reference lockfile-pinned SHA trust rather than claiming tags are immutable. -- Accurate comments prevent future contributors from making incorrect assumptions about git tag mutability.
Recommendation
Merge after tightening the CHANGELOG entry to a user-facing symptom/outcome statement and rephrasing the tag-immutability comment. Both are small in-PR edits. No blocking findings from any validated panelist; the fix is correct, scoped, and tested.
Full per-persona findings
Python architect
No findings. Panelist response remained schema-invalid after two retries; the schema failure was surfaced to CEO arbitration.
CLI logging expert
No findings.
DevX UX expert
No findings.
Supply-chain security expert
- [nit] Comment calls git tags "immutable" but git tags are force-pushable at
src/apm_cli/install/plan.py:225
The code is sound because the locked commit SHA is the trust anchor; the comment overstates tag immutability.
Suggested: Replace "immutable tag" with "tag-pinned lockfile entry" and identify the locked SHA as integrity anchor.
OSS growth hacker
- [recommended] CHANGELOG entry is maintainer-centric; rewrite for user-facing clarity at
CHANGELOG.md:10
The entry leads with resolver mechanics and internal symbols instead of the symptom and corrected outcome.
Suggested: State thatapm updatenow converges for git-source semver deps already at their locked tag.
Auth expert -- inactive
PR touches only CHANGELOG.md, src/apm_cli/install/plan.py, and tests/unit/install/test_plan.py -- no auth, token, credential, or host-resolution surface.
Doc writer
- [recommended] CHANGELOG entry exposes internal Python symbols that belong in the commit message, not user-facing release notes at
CHANGELOG.md
The entry namesresolved_reference,build_update_plan, andNonerather than just user-visible behavior.
Suggested: Use a compact symptom/outcome statement and say branch dependencies remain unaffected. - [nit] Cross-reference to fix(registry): registry deps as first-class citizens in display/UX surfaces #1908 is unexplained jargon for changelog readers at
CHANGELOG.md
CHANGELOG entries should be self-contained.
Suggested: Drop fix(registry): registry deps as first-class citizens in display/UX surfaces #1908. - [nit] Double-hyphen separator is less readable than a sentence break at
CHANGELOG.md
Use a direct sentence for readability.
Test coverage expert
No findings. Panelist response remained schema-invalid after two retries; the schema failure and its unvalidated integration concern were surfaced to CEO arbitration.
Performance expert
No findings.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
Focus the release note on user-visible convergence and describe the locked SHA as the integrity anchor rather than treating git tags as immutable. Addresses panel follow-ups from OSS growth, docs, and supply-chain review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python architect | 0 | 0 | 0 | Schema failure -- see full findings. |
| CLI logging expert | 0 | 0 | 0 | Schema failure -- see full findings. |
| DevX UX expert | 0 | 0 | 0 | Pure internal logic fix removes the non-converging update loop; no UX concerns. |
| Supply-chain security expert | 0 | 0 | 0 | Schema failure -- see full findings. |
| OSS growth hacker | 0 | 0 | 1 | CHANGELOG credits the author and explains user-visible convergence; ship. |
| Auth expert | 0 | 0 | 0 | Schema failure -- see full findings. |
| Doc writer | 0 | 0 | 0 | CHANGELOG and existing update/lockfile docs remain consistent. |
| Test coverage expert | 0 | 0 | 0 | Schema failure -- see full findings. |
| Performance expert | 0 | 0 | 2 | PR guard is O(1) inside existing O(n) work with no I/O; ship. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Recommendation
Merge as-is. The fix is narrowly scoped, mutation-proven, CI-green, and unopposed by every panelist that returned a valid response. No follow-ups to track.
Folded in this run
- (panel) Rewrote the CHANGELOG entry around user-visible convergence and preserved external-author credit -- resolved in
6d7d0eb142d753bc4e81aa75f7cd91e0f73d0dda. - (panel) Rephrased code and test comments to identify the locked SHA as the integrity anchor without treating git tags as immutable -- resolved in
6d7d0eb142d753bc4e81aa75f7cd91e0f73d0dda.
Copilot signals reviewed
- Two fetch rounds completed; Copilot produced no inline review comments to classify.
Regression-trap evidence (mutation-break gate)
TestBuildUpdatePlan::test_git_semver_tag_dep_unchanged_when_cached_and_ref_matches-- deleted the locked-commit fallback; test FAILED as expected; guard restored.TestBuildUpdatePlan::test_git_branch_dep_tip_advance_still_shows_update-- removed the fresh-SHA and tag-only guard conditions; test FAILED as expected; guard restored.
Lint contract
uv run --extra dev ruff check src/ tests/ and uv run --extra dev ruff format --check src/ tests/ completed with no diagnostics. YAML I/O, file length, relative_to, pylint R0801, auth-signal, and architecture-boundary guards also passed.
CI
All 13 checks on 7e32f66dbea525d7c45185435d40e1a3eefe6931 passed, including both Linux test shards, coverage combine, Lint, CodeQL, binary smoke, spec conformance, NOTICE drift, Merge Gate, APM Self-Check, and CLA. CI fix iterations: 0. Primary run: https://github.com/microsoft/apm/actions/runs/29197385197
Mergeability status
Captured from gh pr view 2165 --repo microsoft/apm --json number,headRefOid,mergeable,mergeStateStatus,statusCheckRollup after the final push.
| PR | head SHA | CEO stance | iters | folds | defers | Copilot rounds | CI | mergeable | mergeStateStatus | notes |
|---|---|---|---|---|---|---|---|---|---|---|
| #2165 | 7e32f66 |
ship_now | 1 | 2 | 0 | 2 | green | MERGEABLE | BLOCKED | pending required review |
Convergence
1 outer iteration; 2 Copilot rounds. Final panel stance: ship_now.
Ready for maintainer review.
Full per-persona findings
Python architect
No validated findings. The response remained outside the required JSON-only envelope after two retries.
CLI logging expert
No validated findings. The response remained on an obsolete schema or outside the JSON-only envelope after two retries.
DevX UX expert
No findings.
Supply-chain security expert
No validated findings. The response remained outside the required JSON-only envelope after two retries.
OSS growth hacker
- [nit] CHANGELOG entry could use a more immediately quotable user hook at
CHANGELOG.md:12
The entry is accurate; the suggested wording is optional polish. Doc-writer and DevX both found the current user-facing rewrite clear.
Auth expert
No validated findings. The conditional reviewer repeatedly returned an obsolete schema; the touched files are auth-neutral.
Doc writer
No findings.
Test coverage expert
No validated findings. The response remained outside the required JSON-only envelope after two retries. Mutation-break evidence and green CI independently verify the changed behavior.
Performance expert
- [nit] Out-of-scope main change: update recheck guard is ordered for fast exit at
src/apm_cli/deps/apm_resolver.py:972
This file change is already in current main, not PR fix(deps): git-semver update idempotency in build_update_plan #2165. No action for this PR. - [nit] Out-of-scope main change: local dependency mapping remains linear at
src/apm_cli/commands/deps/cli.py:287
This file change is already in current main, not PR fix(deps): git-semver update idempotency in build_update_plan #2165. No action for this PR.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
Summary
apm update(andapm update -g) reports a spurious UPDATE on every run for git-source semver dependencies that are already at their locked tag, and never converges. Re-running produces the same "N updated" line indefinitely; the lockfile is rewritten with the identical commit SHA each time (onlygenerated_at/resolved_attimestamps change), and no deployed files change on disk.This is the git-source counterpart of the registry idempotency bug fixed for registry deps in #1908 — that fix did not cover git-semver deps.
Root cause
On
apm update, the git-semver resolver (_maybe_resolve_git_semverininstall/phases/resolve.py) rewritesdep.referenceto the concrete tag and computes its SHA (GitSemverResolution.resolved_sha). But the SHA is stashed inctx.git_semver_resolutionsand the download result — it is never attached back todep.resolved_reference.build_update_plan(called frominstall/pipeline.pyright after resolve, before download) readsdep.resolved_reference, which isNoneat that point. So_extract_new_ref_and_commitreturns the correct concrete tag for the ref butNonefor the commit. The classifier then compares:even though
old_ref == new_ref(same concrete tag). Verified by instrumentingbuild_update_planduring a live update:Fix
Mirror #1908's registry approach on the git-source path. In
build_update_plan, when a dep has no freshly-resolved commit, the locked entry pins an immutable tag (resolved_tagis set), and the ref is unchanged, borrow the locked commit so the comparison correctly yieldsunchanged.Scoped deliberately to tags: a branch tip can advance under a stable ref name, so branch deps (no
resolved_tag) must still surface real updates. The existing registry fallback immediately above is left untouched.Tests
test_git_semver_tag_dep_unchanged_when_cached_and_ref_matches— the reproduction; fails before the fix, passes after.test_git_branch_dep_tip_advance_still_shows_update— guards that a real branch-tip advance is not masked.tests/unit/install/andtests/unit/deps/suites green post-rebase onto currentmain.End-to-end verification
Against a real git-semver dep (
srobroek/agentic-packages/packages/eli5#>=2.0.0 <3.0.0), built from this branch:apm update→Updated N APM dependencies(lockfile diff = timestamps only).All dependencies already at their latest matching refs.— converged.Closes #2163