Repository navigation
fix(update): walk the include graph so a transitive @branch cache can't hide behind a green check (0yc) - #317
Merged
Merged
Conversation
…'t hide behind a green check (0yc) Both `amplifier update` and `amplifier bundle update` enumerated update targets from REGISTERED sources only. A bundle reaching the active closure solely through another bundle's `includes:` was in neither the registry-root list nor app-bundle settings, so its cache was never checked -- and both commands printed "All sources up to date" over it. Foundation's loader never refreshes a cached `@<branch>` source on its own (sources/git.py:697-709: a cache hit returns verbatim, no TTL, no fetch), so a source the update path does not enumerate is a source that never updates. Adds a cycle-safe include walk that reuses the loader's own resolution (`_parse_include` / `_resolve_include_source` / `_load_from_path`), reads only caches that already exist -- resolving would download and make a deleted cache look healthy -- and refreshes each transitive `git+...@branch` with the same `GitSourceHandler.update()` a direct refresh bottoms out in. Pinned `@sha` / `@tag` includes are reported as pinned, never moved. Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Defect
amplifier updateandamplifier bundle updateboth printed a green "All sources up to date" while a cache in the active closure sat behind its@mainupstream.Measured on this host 2026-09-06:
~/.amplifier/cache/amplifier-d1dda27a16518560(git_url https://github.com/microsoft/amplifier,ref main) was pinned at c03a88b while upstreammainwas 28588b9 — 28588b9 was not even a fetched object there.microsoft/amplifieris not a registered source; it enters the closure only through amplifier-foundation'sbundle.md:16:Root cause (found first, as required)
Two independent facts combine into a permanent silent staleness.
1. Enumeration is registration-shaped, not closure-shaped.
amplifier_app_cli/commands/update.py:364_check_all_bundle_status()enumerates exactly two things:discovery.list_cached_root_bundles()andAppSettings().get_app_bundles().list_cached_root_bundles()keeps only registry entries withis_root: true—amplifier_app_cli/lib/bundle_loader/discovery.py:679-685sorts every non-root entry intonested_bundles, anddiscovery.py:496-497discards that half. A#subdirectory=include registers withis_root: false(amplifier_foundation/registry.py:520-545), so a repo whose only registry presence is such an entry is checked by nobody. On the day of the report,amplifier-expert-behaviorwas registered (is_root: false,root_name: "amplifier") with noamplifierroot entry — that entry only appeared later, at 08:43, after the cache had already been refreshed by other means at 07:48.amplifier bundle updatefails the same way from the other end:check_bundle_status()→_collect_source_uris()(amplifier_foundation/updates/__init__.py:138-140) deliberately skips includes on the comment "Included bundles are now registered as first-class bundles and will be checked independently." That premise does not hold for an include with no root entry.2. The loader never refreshes a cached
@<branch>include — no TTL, ever.amplifier_foundation/sources/git.py:697-709:GitSourceHandler.resolve()returns the existing cache verbatim whenever it passes_verify_clone_integrity(). There is no fetch, no age check, no revalidation. Refresh happens only viaGitSourceHandler.update()(git.py:877-899), which only the update commands call.So the answer to "does the loader ever refresh on session start?" is never — which is what makes a missed enumeration permanent rather than merely late. That decides the fix location: the update side, here. The foundation side is filed separately as recipes-72y (naming
updates/__init__.py:138-140and the exact fix), because fixingcheck_bundle_status()upstream would make every host correct, not just this CLI.Change
New
amplifier_app_cli/utils/include_graph.py— a cycle-safe BFS overincludes::BundleRegistry._parse_include,_resolve_include_source(handles@namespace:path,git+,#subdirectory=identically to a real session),_load_from_pathto parse bundle files,GitSourceHandler._get_cache_path/get_status/update, and foundation's ownSourceStatus.is_pinned._check_all_bundle_statuswas written to avoid.Wired into both commands:
update.py:_check_all_bundle_status+ _check_transitive_bundle_status()update.pyBundles table↳ <name> (via <parent>), grouped under the registered root, cached vs remote SHAupdate.pyexecutorGitSourceHandler.update()bundle.py:_bundle_update_async/_bundle_update_all_asynccheck_bundle_status()only+ _augment_with_transitive()/_refresh_transitive()Pinned
@sha/@v1.2.3includes renderpinnedin the Remote column and are never refreshed (get_status()already reportshas_update=Falsefor them). Because transitive rows fold intobundle_results,has_bundle_updatespicks them up automatically — "All sources up to date" can no longer print over a stale transitive cache.Tests
tests/test_transitive_include_updates.py— 12 tests, real local git repos overgit+file://so cache-key derivation,git ls-remotecomparison and re-clone all run for real:@shareported pinned,has_update=False, cache unmoved@v1.0.0tag likewise7–8. table renders
↳ … (via parent)with both SHAs; pinned row showspinned--check-onlydoes not print "All sources up to date" over a stale transitive row_check_all_bundle_status()end-to-end — verified to FAIL with the walk disabled:AssertionError: the include walk is not wired into the enumeration … Got: ['b']Gates
uv run pytest -q→ 1877 passed, 1 skipped, 13 deselected, 1 xfaileduv run ruff checkon all four touched files → All checks passedgit+file://fixtures areskipif'd (drive-letter URIs take a different resolution path); the behaviour under test is platform-independent and covered on POSIX runners. The render/executor tests run everywhere.Live proof (this host, isolated
HOME+AMPLIFIER_HOMEcopy)Isolated because refreshing against the real
~/.amplifierwould destroy the reproduction. The copy carries the realregistry.json,settings.yamland full 403 MB cache;~/.amplifier/cache/amplifier-d1dda27a16518560was restored to c03a88b with matching.amplifier_cache_meta.json, and the (later-added)amplifierroot entry removed — i.e. exactly the registry state at the time of the report.Before — installed CLI (
amplifier update --check-only):microsoft/amplifierappears nowhere in the table. Cache:c03a88b.After — this branch (
amplifier update --check-only):Apply —
yes | amplifier update:{"cached_at": "2026-09-06T10:32:40.613082", "ref": "main", "commit": "28588b93886dd4b134f131294ca98e944d71cbfd", "git_url": "https://github.com/microsoft/amplifier"}The other command —
amplifier bundle update foundation:Installed CLI, cache at
c03a88b:All sources are up to date.This branch:
The measured symptom, fixed, on both commands.
Filed alongside
check_bundle_status()drops includes on a false premise (updates/__init__.py:138-140), with the exact fix.Path.home()/.amplifier/registry.json(discovery.py:668,:537,paths.py:47), ignoringAMPLIFIER_HOME. Latent split-brain, surfaced while building the isolated-home proof.Work item: recipes-0yc
Generated with Amplifier
Co-Authored-By: Amplifier 240397093+microsoft-amplifier@users.noreply.github.com