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
2 changes: 1 addition & 1 deletion .gds/repository.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ git:
verification:
commands:
lint: ["actionlint"]
test: ["python3 -I -B scripts/validate_all.py --tier core"]
test: ["scripts/validate_module.sh"]
required: ["lint", "test"]

agent:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ The project follows Semantic Versioning.
ephemeral Incus containers, destroy-after-use lifecycle, and explicit class
labels. Amsterdam is documented as a bastion/application host rather than an
Actions execution target.
- Made the GDS module verification lane hermetic with checksum-pinned `uv`,
Python 3.13.14, hash-locked dependencies and the repository package launcher,
so a clean consumer checkout can verify the exact pin without ambient Python
packages.

## [0.1.1] - 2026-08-16

Expand Down
8 changes: 6 additions & 2 deletions catalog/python-execution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schema_version": 1,
"python": {
"major_minor": "3.13",
"subject_count": 61,
"subject_count": 62,
"launcher": "scripts/check_python_execution_contract.py",
"launcher_prefix": [
".venv/bin/python",
Expand Down Expand Up @@ -96,7 +96,6 @@
}
},
"invocation_document_exemptions": {
".gds/repository.yaml": "Canonical GDS repository metadata records a verification command for orchestration; it is not an executable contributor instruction or workflow.",
"catalog/scorecard-evidence.yml": "Historical evidence record of a command as it was actually run. Rewriting it to the current launcher form would falsify the receipt."
},
"source_classes": {
Expand Down Expand Up @@ -298,6 +297,7 @@
"check_harden_runner_contract.py",
"check_maintenance_report_contract.py",
"check_merge_group.py",
"check_module_verification.py",
"check_monorepo_routing.py",
"check_permissions.py",
"check_pinned_actions.py",
Expand Down Expand Up @@ -422,6 +422,9 @@
"check_merge_group.py": [
"_workflow_yaml"
],
"check_module_verification.py": [
"_strict_yaml"
],
"check_monorepo_routing.py": [
"_workflow_yaml",
"check_python_execution_contract"
Expand Down Expand Up @@ -551,6 +554,7 @@
"check_harden_runner_contract",
"check_maintenance_report_contract",
"check_merge_group",
"check_module_verification",
"check_monorepo_routing",
"check_permissions",
"check_pinned_actions",
Expand Down
37 changes: 37 additions & 0 deletions scripts/check_module_verification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""Keep GDS module verification hermetic and executable from a clean clone."""
from __future__ import annotations

from pathlib import Path

from ci_workflows_tools._strict_yaml import strict_load

REPO_ROOT = Path(__file__).resolve().parent.parent


def check() -> list[str]:
problems: list[str] = []
anchor = strict_load(REPO_ROOT / ".gds" / "repository.yaml")
commands = ((anchor.get("verification") or {}).get("commands") or {})
if commands.get("test") != ["scripts/validate_module.sh"]:
problems.append("module test lane must call only scripts/validate_module.sh")
wrapper = REPO_ROOT / "scripts" / "validate_module.sh"
if not wrapper.is_file() or wrapper.is_symlink():
return problems + ["hermetic module verification wrapper is missing or unsafe"]
text = wrapper.read_text(encoding="utf-8")
required = (
"UV_VERSION=0.11.30",
'PYTHON_ENV="$ROOT/.venv"',
"PYTHON_ENV_OWNED=0",
"python find 3.13.14",
"-m venv --copies",
"--require-hashes -r requirements-ci.txt",
"check_python_execution_contract.py",
"--launch validate_all.py -- --tier core",
)
for marker in required:
if marker not in text:
problems.append(f"module verification wrapper omits {marker!r}")
if "python3 -I -B scripts/validate_all.py" in text:
problems.append("module verification bypasses the repository package launcher")
return problems
2 changes: 2 additions & 0 deletions scripts/validate_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
check_gate_contract,
check_harden_runner_contract,
check_merge_group,
check_module_verification,
check_monorepo_routing,
check_permissions,
check_pinned_actions,
Expand Down Expand Up @@ -137,6 +138,7 @@
("evidence-orchestration", compile_evidence_plan.check),
("side-effect-fixture", check_side_effect_fixture_contract.check),
("merge-group", check_merge_group.check),
("module-verification", check_module_verification.check),
("rulesets", check_rulesets.check),
("catalog", validate_catalog.check),
("profiles", validate_profiles.check),
Expand Down
52 changes: 52 additions & 0 deletions scripts/validate_module.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
UV_VERSION=0.11.30
UV_SHA256_X64=04bc7d180d6138bf6dc08387acf507a823f397a98fea55da36b0ccc7fbce3b68
UV_SHA256_ARM64=8c11d90f5f66d232930cf8ae3a085c39877690d409e10878234802b028b20e2a
UV_HOME=""
PYTHON_ENV="$ROOT/.venv"
PYTHON_ENV_OWNED=0

cleanup() {
[ "$PYTHON_ENV_OWNED" -eq 1 ] && rm -rf -- "$PYTHON_ENV"
[ -n "$UV_HOME" ] && rm -rf -- "$UV_HOME"
return 0
}
trap cleanup EXIT INT TERM

uv_binary=$(command -v uv 2>/dev/null || true)
if [ -z "$uv_binary" ] || [ "$("$uv_binary" --version 2>/dev/null | awk '{print $2}')" != "$UV_VERSION" ]; then
[ "$(uname -s)" = Linux ] || {
printf 'uv %s is required on this platform\n' "$UV_VERSION" >&2
exit 1
}
case "$(uname -m)" in
x86_64 | amd64) arch=x86_64; digest=$UV_SHA256_X64 ;;
aarch64 | arm64) arch=aarch64; digest=$UV_SHA256_ARM64 ;;
*) printf 'no pinned uv artifact for %s\n' "$(uname -m)" >&2; exit 1 ;;
esac
UV_HOME=$(mktemp -d "${TMPDIR:-/tmp}/ci-workflows-uv.XXXXXX")
curl -fsSL \
"https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${arch}-unknown-linux-gnu.tar.gz" \
-o "$UV_HOME/uv.tar.gz"
printf '%s %s\n' "$digest" "$UV_HOME/uv.tar.gz" | sha256sum --check --status
tar -xzf "$UV_HOME/uv.tar.gz" --strip-components=1 -C "$UV_HOME"
uv_binary=$UV_HOME/uv
fi

cd "$ROOT"
if [ ! -e "$PYTHON_ENV" ]; then
python_binary=$("$uv_binary" python find 3.13.14)
"$python_binary" -I -B -m venv --copies "$PYTHON_ENV"
PYTHON_ENV_OWNED=1
fi
[ -d "$PYTHON_ENV" ] && [ ! -L "$PYTHON_ENV" ] || {
printf '%s must be a real directory\n' "$PYTHON_ENV" >&2
exit 1
}
"$uv_binary" pip install --python "$PYTHON_ENV/bin/python" \
--require-hashes -r requirements-ci.txt
"$PYTHON_ENV/bin/python" -I -B scripts/check_python_execution_contract.py \
--launch validate_all.py -- --tier core
Loading