Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 28 additions & 1 deletion .github/workflows/kmp-contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,50 @@ jobs:
with:
path: marketplace

- name: Exercise release-ref regression fixtures
working-directory: marketplace
run: python3 scripts/ci/test-kmp-release-ref.py

- name: Resolve immutable KMP source
id: source
working-directory: marketplace
shell: bash
run: echo "ref=$(python3 scripts/ci/kmp-contract.py --print-source-ref)" >> "${GITHUB_OUTPUT}"
run: |
ref="$(python3 scripts/ci/kmp-contract.py --print-source-ref --allow-unpublished-tag)"
if git ls-remote --exit-code --tags https://github.com/underpass-ai/kmp.git \
"refs/tags/${ref}" >/dev/null 2>&1; then
echo "checkout_ref=${ref}" >> "${GITHUB_OUTPUT}"
echo "published=true" >> "${GITHUB_OUTPUT}"
else
echo "checkout_ref=main" >> "${GITHUB_OUTPUT}"
echo "published=false" >> "${GITHUB_OUTPUT}"
fi
echo "ref=${ref}" >> "${GITHUB_OUTPUT}"

# This is the operation Claude Code's git-subdir installer performs.
# Keeping it literal prevents a commit SHA (fetchable by actions/checkout
# but invalid for git clone --branch) from passing the marketplace gate.
- name: Clone the KMP source exactly as Claude Code does
if: steps.source.outputs.published == 'true'
run: >-
git clone --depth 1
--branch "${{ steps.source.outputs.ref }}"
https://github.com/underpass-ai/kmp.git
kmp-source

# Before publication there is deliberately no cloneable release tag.
# KMP main is the only commit that release.sh may tag, so reviewing that
# exact tree keeps the public catalog on the previous installable release.
- name: Clone the reviewed unpublished KMP source
if: steps.source.outputs.published == 'false'
run: >-
git clone --depth 1
--branch "${{ steps.source.outputs.checkout_ref }}"
https://github.com/underpass-ai/kmp.git
kmp-source

- name: Verify catalogs, copy and exact plugin tree
run: >-
python3 marketplace/scripts/ci/kmp-contract.py
--allow-unpublished-tag
--source-root kmp-source/plugins/kmp
65 changes: 54 additions & 11 deletions scripts/ci/kmp-contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,17 @@ def mirrored_version() -> str:
return versions.pop()


def annotated_tag_commit(ref: str) -> str:
def release_commit(
ref: str,
repository: str = KMP_REMOTE,
allow_unpublished_tag: bool = False,
) -> tuple[str, bool]:
result = subprocess.run(
[
"git",
"ls-remote",
"--tags",
KMP_REMOTE,
repository,
f"refs/tags/{ref}",
f"refs/tags/{ref}^{{}}",
],
Expand All @@ -98,14 +102,30 @@ def annotated_tag_commit(ref: str) -> str:
refs[fields[1]] = fields[0]
tag_ref = f"refs/tags/{ref}"
if tag_ref not in refs:
raise SystemExit(f"KMP source tag {ref} does not exist")
if not allow_unpublished_tag:
raise SystemExit(f"KMP source tag {ref} does not exist")
main = subprocess.run(
["git", "ls-remote", repository, "refs/heads/main"],
check=False,
capture_output=True,
text=True,
)
fields = main.stdout.split()
commit = fields[0] if main.returncode == 0 and fields else ""
if not COMMIT_SHA.fullmatch(commit):
detail = main.stderr.strip() or "main ref was not found"
raise SystemExit(f"could not resolve unpublished KMP source {repository}: {detail}")
return commit, False
peeled = refs.get(f"{tag_ref}^{{}}")
if peeled is None:
raise SystemExit(f"KMP source tag {ref} must be annotated")
return peeled
return peeled, True


def claude_source() -> tuple[dict[str, object], str, str]:
def claude_source(
allow_unpublished_tag: bool = False,
repository: str = KMP_REMOTE,
) -> tuple[dict[str, object], str, str, bool]:
entry = kmp_entry(CLAUDE_LISTING)
source = entry.get("source")
if not isinstance(source, dict):
Expand All @@ -118,7 +138,8 @@ def claude_source() -> tuple[dict[str, object], str, str]:
if ref != expected_ref:
raise SystemExit(f"Claude kmp source must pin clonable immutable release tag {expected_ref}")
verify_description("Claude marketplace entry", entry.get("description"))
return entry, ref, annotated_tag_commit(ref)
commit, published = release_commit(ref, repository, allow_unpublished_tag)
return entry, ref, commit, published


def verify_description(label: str, value: object) -> None:
Expand Down Expand Up @@ -159,8 +180,14 @@ def verify_tree(source_root: pathlib.Path) -> None:
)


def verify_contract(source_root: pathlib.Path) -> None:
_, ref, expected_commit = claude_source()
def verify_contract(
source_root: pathlib.Path,
allow_unpublished_tag: bool = False,
repository: str = KMP_REMOTE,
) -> None:
_, ref, expected_commit, published = claude_source(
allow_unpublished_tag, repository
)
codex = kmp_entry(CODEX_LISTING)
if codex.get("source") != EXPECTED_CODEX_SOURCE:
raise SystemExit("Codex kmp entry no longer resolves the reviewed plugins/kmp snapshot")
Expand Down Expand Up @@ -211,21 +238,37 @@ def verify_contract(source_root: pathlib.Path) -> None:
if RETIRED_COUNT.search(readme):
raise SystemExit("marketplace README still contains retired whole-surface copy")

print(f"KMP marketplace contract passed: {versions.pop()}, 13 tools, source {ref}")
source_state = "published annotated tag" if published else "unpublished tag bound to KMP main"
print(
f"KMP marketplace contract passed: {versions.pop()}, 13 tools, "
f"source {ref} ({source_state})"
)


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--source-root", type=pathlib.Path)
parser.add_argument("--print-source-ref", action="store_true")
parser.add_argument("--allow-unpublished-tag", action="store_true")
parser.add_argument(
"--kmp-repository",
default=KMP_REMOTE,
help="override the KMP Git remote for contract tests",
)
args = parser.parse_args()
_, ref, _ = claude_source()
_, ref, _, _ = claude_source(
args.allow_unpublished_tag, args.kmp_repository
)
if args.print_source_ref:
print(ref)
return
if args.source_root is None:
parser.error("--source-root is required unless --print-source-ref is used")
verify_contract(args.source_root)
verify_contract(
args.source_root,
args.allow_unpublished_tag,
args.kmp_repository,
)


if __name__ == "__main__":
Expand Down
87 changes: 87 additions & 0 deletions scripts/ci/test-kmp-release-ref.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Regression fixtures for the pre-tag and annotated-tag marketplace contract."""

from __future__ import annotations

import importlib.util
import pathlib
import subprocess
import tempfile


SCRIPT = pathlib.Path(__file__).with_name("kmp-contract.py")
SPEC = importlib.util.spec_from_file_location("kmp_contract", SCRIPT)
if SPEC is None or SPEC.loader is None:
raise SystemExit(f"could not load {SCRIPT}")
CONTRACT = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(CONTRACT)


def run(*args: object, cwd: pathlib.Path | None = None) -> None:
subprocess.run(
[str(argument) for argument in args],
cwd=cwd,
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)


with tempfile.TemporaryDirectory(prefix="kmp-marketplace-ref-") as raw_fixture:
fixture = pathlib.Path(raw_fixture)
remote = fixture / "remote.git"
work = fixture / "work"
run("git", "init", "--bare", remote)
run("git", "init", "--initial-branch=main", work)
run("git", "config", "user.name", "KMP contract", cwd=work)
run("git", "config", "user.email", "kmp-contract@example.invalid", cwd=work)
(work / "README.md").write_text("release-ref fixture\n", encoding="utf-8")
run("git", "add", "README.md", cwd=work)
run("git", "commit", "-m", "fixture: candidate", cwd=work)
commit = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=work,
check=True,
capture_output=True,
text=True,
).stdout.strip()
run("git", "remote", "add", "origin", remote.as_uri(), cwd=work)
run("git", "push", "origin", "main", cwd=work)

try:
CONTRACT.release_commit("v0.5.1", remote.as_uri())
except SystemExit as error:
if "does not exist" not in str(error):
raise
else:
raise SystemExit("strict marketplace contract accepted an unpublished tag")

observed, published = CONTRACT.release_commit(
"v0.5.1", remote.as_uri(), allow_unpublished_tag=True
)
if observed != commit or published:
raise SystemExit("pre-tag marketplace contract did not bind remote main")

run("git", "tag", "v0.5.1", cwd=work)
run("git", "push", "origin", "refs/tags/v0.5.1", cwd=work)
try:
CONTRACT.release_commit(
"v0.5.1", remote.as_uri(), allow_unpublished_tag=True
)
except SystemExit as error:
if "must be annotated" not in str(error):
raise
else:
raise SystemExit("marketplace contract accepted a lightweight release tag")

run("git", "push", "origin", ":refs/tags/v0.5.1", cwd=work)
run("git", "tag", "-d", "v0.5.1", cwd=work)
run("git", "tag", "-a", "v0.5.1", "-m", "Release v0.5.1", cwd=work)
run("git", "push", "origin", "refs/tags/v0.5.1", cwd=work)
observed, published = CONTRACT.release_commit(
"v0.5.1", remote.as_uri(), allow_unpublished_tag=True
)
if observed != commit or not published:
raise SystemExit("annotated release tag did not peel to the reviewed commit")

print("KMP release-ref regressions passed: unpublished main, lightweight refusal, annotated tag")