Skip to content

Commit 8d54ecd

Browse files
Pigbibicursoragent
andcommitted
ci: checkout matrix consumers and enforce full pin validation
Clone all matrix consumer repos in Validate CI and fail strict checks when dependency files are missing, so org-wide pin drift is caught. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2e5e9e6 commit 8d54ecd

4 files changed

Lines changed: 131 additions & 7 deletions

File tree

.github/workflows/validate.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ jobs:
3434
run: python3 scripts/runtime_settings.py validate
3535
- name: Run unit tests
3636
run: python3 -m unittest discover -s tests -v
37+
- name: Checkout internal dependency consumer repos
38+
env:
39+
GH_TOKEN: ${{ github.token }}
40+
run: bash scripts/checkout_internal_dependency_consumers.sh --output-root ..
3741
- name: Report internal dependency matrix
38-
run: python3 scripts/check_internal_dependency_matrix.py --projects-root .. --json --strict
42+
run: python3 scripts/check_internal_dependency_matrix.py --projects-root .. --json --strict --require-consumer-files
3943
- name: Validate strategy switch web assets
4044
run: |
4145
set -euo pipefail

scripts/check_internal_dependency_matrix.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,30 @@ def build_parser() -> argparse.ArgumentParser:
129129
parser.add_argument("--projects-root", type=Path, default=DEFAULT_PROJECTS_ROOT)
130130
parser.add_argument("--json", action="store_true", help="Print machine-readable report.")
131131
parser.add_argument("--strict", action="store_true", help="Exit non-zero when drift is detected.")
132+
parser.add_argument(
133+
"--require-consumer-files",
134+
action="store_true",
135+
help="Treat missing consumer dependency files as validation failures.",
136+
)
132137
return parser
133138

134139

135140
def main(argv: list[str] | None = None) -> int:
136141
args = build_parser().parse_args(argv)
137142
report = check_matrix(matrix_pins=load_matrix(args.matrix), projects_root=args.projects_root)
143+
issues = list(report.issues)
144+
if args.require_consumer_files and report.missing_files:
145+
for item in report.missing_files:
146+
issues.append(f"missing consumer dependency file {item}")
147+
ok = not issues
138148
if args.json:
139149
print(
140150
json.dumps(
141151
{
142152
"checked_files": report.checked_files,
143153
"missing_files": report.missing_files,
144-
"issues": report.issues,
145-
"ok": report.ok,
154+
"issues": issues,
155+
"ok": ok,
146156
},
147157
ensure_ascii=False,
148158
indent=2,
@@ -154,13 +164,13 @@ def main(argv: list[str] | None = None) -> int:
154164
print("missing_files:")
155165
for item in report.missing_files:
156166
print(f"- {item}")
157-
if report.issues:
167+
if issues:
158168
print("issues:")
159-
for issue in report.issues:
169+
for issue in issues:
160170
print(f"- {issue}")
161-
if report.ok:
171+
if ok:
162172
print("internal dependency matrix is current")
163-
return 1 if args.strict and not report.ok else 0
173+
return 1 if args.strict and not ok else 0
164174

165175

166176
if __name__ == "__main__":
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
output_root=".."
5+
matrix_path="internal_dependency_matrix.json"
6+
7+
usage() {
8+
cat <<'EOF'
9+
Usage: checkout_internal_dependency_consumers.sh [--output-root PATH] [--matrix PATH]
10+
11+
Clone QuantStrategyLab consumer repositories referenced by the internal dependency matrix.
12+
EOF
13+
}
14+
15+
while [ "$#" -gt 0 ]; do
16+
case "$1" in
17+
--output-root)
18+
output_root="${2:?--output-root requires a path}"
19+
shift 2
20+
;;
21+
--matrix)
22+
matrix_path="${2:?--matrix requires a path}"
23+
shift 2
24+
;;
25+
-h|--help)
26+
usage
27+
exit 0
28+
;;
29+
*)
30+
echo "Unknown argument: $1" >&2
31+
usage >&2
32+
exit 1
33+
;;
34+
esac
35+
done
36+
37+
if ! command -v gh >/dev/null 2>&1; then
38+
echo "gh CLI is required to checkout internal dependency consumer repos." >&2
39+
exit 1
40+
fi
41+
42+
if [ -z "${GH_TOKEN:-}" ] && [ -z "${GITHUB_TOKEN:-}" ]; then
43+
echo "GH_TOKEN or GITHUB_TOKEN is required to checkout internal dependency consumer repos." >&2
44+
exit 1
45+
fi
46+
47+
export GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN}}"
48+
49+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
50+
repo_root="$(cd "${script_dir}/.." && pwd)"
51+
matrix_file="${matrix_path}"
52+
if [ ! -f "${matrix_file}" ]; then
53+
matrix_file="${repo_root}/${matrix_path}"
54+
fi
55+
if [ ! -f "${matrix_file}" ]; then
56+
echo "Matrix file not found: ${matrix_path}" >&2
57+
exit 1
58+
fi
59+
60+
mkdir -p "${output_root}"
61+
output_root="$(cd "${output_root}" && pwd)"
62+
63+
mapfile -t consumer_repos < <(
64+
python3 - "${matrix_file}" <<'PY'
65+
import json
66+
import sys
67+
from pathlib import Path
68+
69+
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
70+
repos = sorted(
71+
{
72+
item["consumer_repo"]
73+
for item in payload.get("dependencies", [])
74+
if isinstance(item, dict) and item.get("consumer_repo")
75+
}
76+
)
77+
for repo in repos:
78+
print(repo)
79+
PY
80+
)
81+
82+
for consumer_repo in "${consumer_repos[@]}"; do
83+
target_dir="${output_root}/${consumer_repo}"
84+
if [ -d "${target_dir}/.git" ]; then
85+
echo "Already checked out ${consumer_repo} at ${target_dir}"
86+
continue
87+
fi
88+
echo "Cloning QuantStrategyLab/${consumer_repo} into ${target_dir}"
89+
gh repo clone "QuantStrategyLab/${consumer_repo}" "${target_dir}" -- --depth 1 --branch main
90+
done
91+
92+
echo "Checked out ${#consumer_repos[@]} internal dependency consumer repositories under ${output_root}."

tests/test_internal_dependency_matrix.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ def test_current_matrix_matches_local_workspace(self):
8686
self.assertEqual(report.missing_files, [])
8787
self.assertEqual(report.issues, [])
8888

89+
def test_require_consumer_files_treats_missing_paths_as_issues(self):
90+
projects_root = self._make_projects_root({})
91+
expected = [
92+
check_internal_dependency_matrix.DependencyPin(
93+
consumer_repo="ExamplePlatform",
94+
path="requirements.txt",
95+
package="quant-platform-kit",
96+
source_repo="QuantPlatformKit",
97+
ref="v0.7.35",
98+
)
99+
]
100+
101+
report = check_internal_dependency_matrix.check_matrix(matrix_pins=expected, projects_root=projects_root)
102+
103+
self.assertEqual(report.checked_files, 0)
104+
self.assertEqual(report.missing_files, ["ExamplePlatform/requirements.txt"])
105+
self.assertEqual(report.issues, [])
106+
89107
def _make_projects_root(self, files: dict[str, str]) -> Path:
90108
import tempfile
91109

0 commit comments

Comments
 (0)