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
67 changes: 60 additions & 7 deletions .github/cla/cla_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
SOURCE = "SNatangelo/Callimachus"
ARCHIVE = "SNatangelo/callimachus-legal"
OWNER_ID = 248719534
DEPENDABOT_ID = 49699333
DEPENDABOT_LOGIN = "dependabot[bot]"
DEPENDABOT_EXEMPTION_REASON = "verified_same_source_repository_dependabot_pr_opener"
VERSION = "1.1"
AGREEMENT_PATH = ".github/cla/agreements/CLA-v1.1.md"
AGREEMENT_SHA256 = "f89491e1cf64c52a5119adabe9928c665af0392c23af76258ce4446f003d9634"
Expand Down Expand Up @@ -127,9 +130,49 @@ def valid_signature(record, user_id):
and bool(record.get("accepted_at")))


def contributor_ids(api: API, pr_number: int, opener):
"""Include the submitter and every GitHub-linked author/coauthor, not just committer."""
people = {opener["id"]: opener["login"]}
def trusted_dependabot_exemption(pr):
"""Return an auditable exemption only for the exact same-repository bot PR."""
if not isinstance(pr, dict):
return None
opener = pr.get("user") or {}
head_repo = ((pr.get("head") or {}).get("repo") or {})
base_repo = ((pr.get("base") or {}).get("repo") or {})
head_name = head_repo.get("full_name")
base_name = base_repo.get("full_name")
if (
opener.get("id") != DEPENDABOT_ID
or opener.get("login") != DEPENDABOT_LOGIN
or opener.get("type") != "Bot"
or not isinstance(head_name, str)
or head_name.lower() != SOURCE.lower()
or head_repo.get("fork") is not False
or not isinstance(base_name, str)
or base_name.lower() != SOURCE.lower()
):
return None
return {
"github_user_id": DEPENDABOT_ID,
"github_username": DEPENDABOT_LOGIN,
"identity_type": "Bot",
"reason": DEPENDABOT_EXEMPTION_REASON,
"head_repository": head_name,
"base_repository": base_name,
}


def contributor_ids(api: API, pr_number: int, opener, pr_context=None):
"""Include the submitter and every GitHub-linked author/coauthor.

A bot opener is excluded only when the full GitHub PR context independently
verifies the exact Dependabot bot and same-source-repository head. All commit
authors are still enumerated, and unlinked authors remain a hard failure.
"""
automation_exemption = trusted_dependabot_exemption(pr_context)
if pr_context is not None:
context_opener = pr_context.get("user") or {}
if any(opener.get(key) != context_opener.get(key) for key in ("id", "login", "type")):
raise Failure("Pull request opener context mismatch; manual review required")
people = {} if automation_exemption else {opener["id"]: opener["login"]}
query = """query($number:Int!, $after:String) {
repository(owner:"SNatangelo", name:"Callimachus") {
pullRequest(number:$number) { commits(first:100, after:$after) {
Expand All @@ -154,7 +197,13 @@ def contributor_ids(api: API, pr_number: int, opener):
user = actor.get("user")
if not user or not isinstance(user.get("databaseId"), int):
raise Failure("An author/coauthor is not linked to GitHub; resolve attribution before merge")
people[user["databaseId"]] = user["login"]
uid = user["databaseId"]
login = user.get("login")
if automation_exemption and uid == DEPENDABOT_ID:
if login != DEPENDABOT_LOGIN:
raise Failure("Dependabot author identity differs from the verified PR opener")
continue
people[uid] = login
if not connection["pageInfo"]["hasNextPage"]:
# The original maintainer already owns his own contributions. No bot wildcard exemptions.
people.pop(OWNER_ID, None)
Expand Down Expand Up @@ -202,7 +251,8 @@ def status(state, description):
"schema": 1, "version": VERSION, "sha256": AGREEMENT_SHA256,
"source_repository": SOURCE, "source_path": AGREEMENT_PATH,
"archive_path": archive_path, "signature_text": SIGN}))
people = contributor_ids(public, number, pr["user"])
automation_exemption = trusted_dependabot_exemption(pr)
people = contributor_ids(public, number, pr["user"], pr_context=pr)
comments = list(public.pages(f"/repos/{SOURCE}/issues/{number}/comments"))
prompts = [c for c in comments if MARKER in (c.get("body") or "")
and (c.get("user") or {}).get("login") == "github-actions[bot]"
Expand Down Expand Up @@ -265,10 +315,13 @@ def status(state, description):
status("failure", "CLA acceptance missing; follow the bot comment")
return
# This record binds the successful check to the exact PR head and version.
put_once(private, f"checks/v{VERSION}/pr-{number}/{sha}.json", json_bytes({
check_record = {
"schema": 1, "agreement_sha256": AGREEMENT_SHA256,
"repository": SOURCE, "pull_request": number, "head_sha": sha,
"github_user_ids": sorted(people), "maintainer_exemption_id": OWNER_ID}))
"github_user_ids": sorted(people), "maintainer_exemption_id": OWNER_ID}
if automation_exemption:
check_record["automation_exemptions"] = [automation_exemption]
put_once(private, f"checks/v{VERSION}/pr-{number}/{sha}.json", json_bytes(check_record))
status("success", "CLA acceptance verified; versioned evidence preserved privately")
except Exception:
status("failure", "CLA check/archive unavailable; inspect workflow configuration")
Expand Down
4 changes: 2 additions & 2 deletions packaging/release-constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ rapidocr==3.9.2
onnxruntime==1.23.2; sys_platform == "darwin" and platform_machine == "x86_64"
onnxruntime==1.29.0; sys_platform != "darwin" or platform_machine != "x86_64"
bm25s==0.3.10
PyMuPDF==1.28.0
PyMuPDF==1.28.2
pdfminer.six==20260107
cryptography==50.0.1
Brotli==1.2.1
Pillow==13.0.0
certifi==2026.10.19
certifi==2026.10.23
pip==26.2.2
setuptools==84.1.0
wheel==0.46.3
Expand Down
20 changes: 10 additions & 10 deletions packaging/release-sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@
{
"name": "PyMuPDF",
"constraint": "PyMuPDF",
"package_version": "1.28.0",
"version": "1.28.0",
"filename": "pymupdf-1.28.0.tar.gz",
"url": "https://files.pythonhosted.org/packages/8e/e9/6d6c5d6c0a3551bffd47681a6240caf941727f195b45593cf20ab36f018f/pymupdf-1.28.0.tar.gz",
"sha256": "e53f3567403a92da15caa9e7ae0164327fff48817e9f40175367fb9de524258d"
"package_version": "1.28.2",
"version": "1.28.2",
"filename": "pymupdf-1.28.2.tar.gz",
"url": "https://files.pythonhosted.org/packages/a3/fb/b6761fa2d5266f2cdb24c3b91f4023070ab7848381417678e7a289a1d52a/pymupdf-1.28.2.tar.gz",
"sha256": "5e0be7908a715aa20333caddd73f1d6f01e4cd0c26e869fa2dd0b7f344da2249"
},
{
"name": "MuPDF",
"constraint": "PyMuPDF",
"package_version": "1.28.0",
"version": "1.28.0",
"filename": "mupdf-1.28.0-source.tar.gz",
"url": "https://github.com/ArtifexSoftware/mupdf-downloads/releases/download/1.28.0/mupdf-1.28.0-source.tar.gz",
"sha256": "21c7f064903154f1c3a7458bee81f130fc36f9b5147ea13328f9980e02d2dea2"
"package_version": "1.28.2",
"version": "1.28.2",
"filename": "mupdf-1.28.2-source.tar.gz",
"url": "https://github.com/ArtifexSoftware/mupdf-downloads/releases/download/1.28.2/mupdf-1.28.2-source.tar.gz",
"sha256": "44075a84e329db55b9bef5f342a70fd26d69e48ad1d33cb89d9664581c641156"
},
{
"name": "OpenCV Python",
Expand Down
8 changes: 4 additions & 4 deletions packaging/store/native-recipes/bootstrap-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ cffi==2.1.1
cmake==4.4.3
colorama==0.4.6
coloredlogs==15.0.1
delvewheel==1.13.1
delvewheel==1.13.2
distro==1.9.0
flatbuffers==25.12.19
hatch-vcs==0.5.0
hatchling==1.32.4
humanfriendly==10.0
iniconfig==2.3.0
iniconfig==2.3.1
libclang==18.1.1
maturin==1.15.0
meson==1.12.1
meson-python==0.22.1
mpmath==1.3.0
ninja==1.13.2
numpy==2.5.3
numpy==2.5.4
packaging==26.3
pathspec==1.1.1
pefile==2024.8.26
Expand All @@ -41,7 +41,7 @@ setuptools==84.1.0
setuptools-scm==10.3.4
swig==4.4.1
sympy==1.14.0
tomlkit==0.15.1
tomlkit==0.15.2
trove-classifiers==2026.9.21.13
vcs-versioning==2.5.0
wheel==0.46.3
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
altgraph==0.17.5
bm25s==0.3.10
certifi==2026.10.19
certifi==2026.10.23
colorama==0.4.6
colorlog==6.12.0
flatbuffers==25.12.19
idna==3.20
iniconfig==2.3.0
iniconfig==2.3.1
jaraco.classes==3.4.0
jaraco.context==6.1.2
jaraco.functools==4.6.0
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# TLS trust bundle for urllib HTTPS requests on environments whose Python trust
# store does not include every issuer used by OA repositories.
certifi>=2026.10.19
certifi>=2026.10.23

# Decode HTTP Brotli responses before any HTML/PDF parser sees the body. Raw
# Wayback snapshots may preserve the archived Content-Encoding even though the
Expand All @@ -18,7 +18,7 @@ Brotli>=1.2.1
# pymupdf is the preferred backend (fastest, best layout/column handling); the code
# falls back to pdfminer.six and then pdftotext when it is absent. Either one satisfies
# the startup PDF-backend check — install at least one.
pymupdf>=1.24
pymupdf>=1.28.2
pdfminer.six>=20221105

# ── Optional: OCR for scanned PDFs without a text layer ───────────────────────
Expand Down
2 changes: 1 addition & 1 deletion tests/test_bootstrap_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ def test_staged_recipe_reads_active_constraints_from_project_root():

active = recipe.validated_active_constraints(expected)
assert active == {
"certifi": "2026.10.19",
"certifi": "2026.10.23",
"pip": "26.2.2",
"setuptools": "84.1.0",
"wheel": "0.46.3",
Expand Down
Loading
Loading