From ce3553267ddf0a69fe1ca0d8f910ba14f05905fb Mon Sep 17 00:00:00 2001 From: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:43:35 -0700 Subject: [PATCH] docs(sources): document commit-SHA pinning; test SHA-predicate agreement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #282 (merged as 1a40883), which added full-commit-SHA refs to the git source handler but shipped the feature undiscoverable: every git-source example in docs/ used @main and SHA pinning appeared nowhere. - docs/URI_FORMATS.md, docs/BUNDLE_GUIDE.md: add a pinned-commit row to the source-format tables plus a short note covering what pinning is for (reproducible installs, eval/CI), that pinned sources are skipped by `amplifier update` (get_status -> is_pinned -> has_update=False -> excluded from updateable_sources), that short SHAs are rejected by design, and the cost of a typo'd-but-valid-looking SHA (full-clone fallback before a clear checkout error; a failed pinned resolve can leave a cache entry behind that a later resolve may serve, so clear the cache entry if a pin fails). - tests/test_sources.py: the full-SHA rule is encoded twice -- _is_full_commit_sha (regex, sources/git.py) and SourceStatus.is_pinned (length+charset, sources/protocol.py) -- guarded only by a comment. Add TestShaPredicatesAgree asserting the two agree across 40-hex lower/upper/mixed, 39/41-char, non-hex, and branch-name refs, plus one test documenting the intentional v-tag divergence. Converts the load-bearing comment into a checked invariant without abstracting (two instances is below the bar). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- docs/BUNDLE_GUIDE.md | 3 +++ docs/URI_FORMATS.md | 9 +++++++++ tests/test_sources.py | 45 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/docs/BUNDLE_GUIDE.md b/docs/BUNDLE_GUIDE.md index be017015..df88b823 100644 --- a/docs/BUNDLE_GUIDE.md +++ b/docs/BUNDLE_GUIDE.md @@ -1266,10 +1266,13 @@ Bundles support multiple source formats for modules: | Local path | `./modules/my-module` | Modules within the bundle | | Relative path | `../shared-module` | Sibling directories | | Git URL | `git+https://github.com/org/repo@main` | External modules | +| Git pinned to commit | `git+https://github.com/org/repo@32d4052dad46016f91ce698646580473e4121344` | Reproducible installs (eval runs, CI) | | Git with subpath | `git+https://github.com/org/repo@main#subdirectory=modules/foo` | Module within larger repo | **Local paths are resolved relative to the bundle's location.** +**Commit SHA pinning**: A ref that is a full 40-character hex commit SHA pins the source to that exact commit. Pinned sources are automatically skipped by `amplifier update` (a pinned ref can never have updates). Short/abbreviated SHAs are rejected by design — an ambiguous ref defeats reproducibility. Note that a typo'd but valid-looking SHA fails only after a full-history clone fallback: the fast single-commit fetch fails, git falls back to a full clone, and checkout then fails with a clear error (a failed pinned resolve can leave a cache entry behind, and a subsequent resolve of the same URI may serve it — clear the cache entry if a pin fails). + --- ## Composition with includes: diff --git a/docs/URI_FORMATS.md b/docs/URI_FORMATS.md index aea97bb5..d689fe71 100644 --- a/docs/URI_FORMATS.md +++ b/docs/URI_FORMATS.md @@ -10,8 +10,17 @@ Quick reference for source URIs. For complete details, see `parse_uri()` docstri | **Local directory** | `/path/to/bundle/` (finds `bundle.md` inside) | | **Git HTTPS** | `git+https://github.com/org/repo@main` | | **Git SSH** | `git+ssh://git@github.com/org/repo@main` | +| **Git pinned to commit** | `git+https://github.com/org/repo@32d4052dad46016f91ce698646580473e4121344` | | **Subdirectory** | `git+https://github.com/org/repo@main#subdirectory=path/to/bundle` | +### Pinning to a Commit SHA + +A ref that is a full 40-character hex commit SHA pins the source to that exact commit. Use this for reproducible installs — evaluation runs, CI, anywhere "same URI, same code" must hold. + +- Pinned sources are automatically **skipped by `amplifier update`** — a pinned ref can never have updates. +- Short/abbreviated SHAs are **rejected by design**: an ambiguous ref defeats reproducibility. Use the full 40 characters. +- A typo'd but valid-looking SHA is expensive: the fast single-commit fetch fails, git falls back to a **full-history clone**, and checkout then fails with a clear error (a failed pinned resolve can leave a cache entry behind, and a subsequent resolve of the same URI may serve it — clear the cache entry if a pin fails). A wrong SHA costs a full-history download before it errors. + ## Common Examples ```python diff --git a/tests/test_sources.py b/tests/test_sources.py index 5576a5b9..148b60a2 100644 --- a/tests/test_sources.py +++ b/tests/test_sources.py @@ -12,6 +12,7 @@ from amplifier_foundation.sources.git import GitSourceHandler from amplifier_foundation.sources.git import _is_full_commit_sha from amplifier_foundation.sources.http import HttpSourceHandler +from amplifier_foundation.sources.protocol import SourceStatus from amplifier_foundation.sources.zip import ZipSourceHandler @@ -375,6 +376,50 @@ def test_rejects_branch_names(self) -> None: assert _is_full_commit_sha("v1.0.0") is False +class TestShaPredicatesAgree: + """Invariant: the full-SHA rule is encoded twice and must not drift. + + `_is_full_commit_sha` (regex, sources/git.py) and `SourceStatus.is_pinned` + (length + charset, sources/protocol.py) independently encode "40-char hex + commit SHA". A code comment claims they agree; this test checks it. + + Note: `is_pinned` additionally treats v-prefixed version tags as pinned. + That branch is deliberate divergence outside the shared SHA rule, so + tag-shaped refs are excluded from the equality set and covered separately. + """ + + @pytest.mark.parametrize( + "ref", + [ + "32d4052dad46016f91ce698646580473e4121344", # 40-hex lowercase + "32D4052DAD46016F91CE698646580473E4121344", # 40-hex uppercase + "32D4052dad46016F91ce698646580473E4121344", # 40-hex mixed case + "3" * 39, # one char too short + "3" * 41, # one char too long + "g" + "3" * 39, # 40 chars, non-hex + "main", # branch name + "feat/some-branch", # branch name with slash + ], + ) + def test_predicates_agree_on_sha_classification(self, ref: str) -> None: + status = SourceStatus( + source_uri=f"git+https://example.com/org/repo@{ref}", + is_cached=True, + cached_ref=ref, + ) + assert _is_full_commit_sha(ref) == status.is_pinned + + def test_version_tag_is_pinned_but_not_a_sha(self) -> None: + """Documents the intentional divergence: tags pin without being SHAs.""" + status = SourceStatus( + source_uri="git+https://example.com/org/repo@v1.0.0", + is_cached=True, + cached_ref="v1.0.0", + ) + assert _is_full_commit_sha("v1.0.0") is False + assert status.is_pinned is True + + class TestGitSourceHandlerShaRefs: """Tests for resolving git refs pinned to a commit SHA."""