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
14 changes: 7 additions & 7 deletions .gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
schema_version: 1

bundle:
version: "0.9.3-dev"
version: "0.9.4-dev"
release_sequence: 0
channel: "development"
source_tree_digest: "sha256:dbbd338cb723846e6f140f16fd2e76594fea9377bf792c03fbae60a54e88ee01"
digest: "sha256:d8b7f50891831664b429e19e5df040fcc7e1fd6d1b544cf8c8ebf3147cc2afef"
source_tree_digest: "sha256:85d792bf33db30ad34ecd6ad0bf7bf0bdf84c41778432e87b45a89e200dbe18b"
digest: "sha256:7d089792b096f905b6b9568f62fef25b568f5e37b1ed8a17543380bf8e9d28e3"

projection:
input_digest: "sha256:a56667c2156bdae3807b57a647c5f92c07223bf37b0edefbb4a50eb0038bd3e1"
output_digest: "sha256:5a94f145b67e94ed66d769850bed7a5e2a9ea29439d5aebcd400fab18eb49ec1"
input_digest: "sha256:7eac6db12d8a42a5003f12d2e22b63e17f20aff3b49764e000b94a9a782702f2"
output_digest: "sha256:2174c5325ae3eb87af133acd34eab9b63dedb780c795e562a134fd4608c55d68"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:26f4268e1b6df625045487c49b80342e94c5c6a81f2d8dc966f1a3a05021ac37"
digest: "sha256:37ece9020fd9eeed4ee90582600c5e50cb3a7e32ee773719d1ccfad2d9cd1b6d"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:e12974ce96d1fccf477eaab11d2def7a22240df5d0a4e5bfb78c5c2b70e8fc7d"
digest: "sha256:1269d657b675dc22f6e72ddd885a11d14519d8039d97a8d86627f296333b2390"
4 changes: 2 additions & 2 deletions .gds/compiled-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"schema_version": 1,
"compiled_policy": {
"repository_id": "repo_01M0EZ7TB3KNXNSP78Z8M64WXG",
"bundle_version": "0.9.3-dev",
"digest": "sha256:497a28d43aad2a561c0144598da07c453bd1d48de59f02a5321c88f03f27e291"
"bundle_version": "0.9.4-dev",
"digest": "sha256:1b0f8c63bd653f2ff4f2478acd77ed18785a1591d801d51171df93aa7a280092"
},
"sources": [
{
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/gds-ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED FILE - DO NOT EDIT DIRECTLY
# generator: gds
# bundle: 0.9.3-dev
# source-tree-digest: sha256:dbbd338cb723846e6f140f16fd2e76594fea9377bf792c03fbae60a54e88ee01
# input-digest: sha256:a56667c2156bdae3807b57a647c5f92c07223bf37b0edefbb4a50eb0038bd3e1
# bundle: 0.9.4-dev
# source-tree-digest: sha256:85d792bf33db30ad34ecd6ad0bf7bf0bdf84c41778432e87b45a89e200dbe18b
# input-digest: sha256:7eac6db12d8a42a5003f12d2e22b63e17f20aff3b49764e000b94a9a782702f2
# output-digest: sha256:15b87303cd0692fd8e5d88281771dded9fdc2fc027d600681155cb4e71f42386
# edit-source:
# - .gds/repository.yaml
Expand Down
45 changes: 29 additions & 16 deletions .github/workflows/release-bundle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,6 @@ jobs:
go-version: "1.27.1"
cache: true

- name: Run release gates
shell: bash
env:
GDS_TEST_PYTHON: ${{ runner.temp }}/gds-release-python/bin/python
run: |
python3 -m venv "${GDS_TEST_PYTHON%/bin/python}"
export PATH="${GDS_TEST_PYTHON%/python}:$PATH"
"$GDS_TEST_PYTHON" -m pip install --quiet --require-hashes -r requirements/test.txt
scripts/validate_release.sh

- name: Materialize bounded signed harness evidence input
id: harness-evidence
shell: bash
Expand All @@ -99,19 +89,42 @@ jobs:
echo "arguments=" >> "$GITHUB_OUTPUT"
exit 0
fi
[ -n "$HARNESS_EVIDENCE_BUNDLE_BASE64" ]
[ -n "$HARNESS_EVIDENCE_TRUST_POLICY_BASE64" ]
[ -n "$HARNESS_EVIDENCE_TRUST_POLICY_DIGEST" ]
if [ -z "$HARNESS_EVIDENCE_BUNDLE_BASE64" ] || [ -z "$HARNESS_EVIDENCE_TRUST_POLICY_BASE64" ]; then
echo "Signed harness evidence archive and trust policy must be supplied together" >&2
exit 1
fi
expected_digest="${HARNESS_EVIDENCE_TRUST_POLICY_DIGEST#sha256:}"
[[ "$expected_digest" =~ ^[0-9a-f]{64}$ ]] || {
echo "HARNESS_EVIDENCE_TRUST_POLICY_DIGEST must be an independent SHA-256 pin (64 lowercase hex digits, optionally prefixed with sha256:)" >&2
exit 1
}
install -d -m 0700 "$EVIDENCE_INPUT_ROOT"
printf '%s' "$HARNESS_EVIDENCE_BUNDLE_BASE64" | base64 --decode > "$EVIDENCE_INPUT_ROOT/evidence.tar.gz"
printf '%s' "$HARNESS_EVIDENCE_TRUST_POLICY_BASE64" | base64 --decode > "$EVIDENCE_INPUT_ROOT/trust.json"
test "sha256:$(sha256sum "$EVIDENCE_INPUT_ROOT/trust.json" | cut -d' ' -f1)" = "$HARNESS_EVIDENCE_TRUST_POLICY_DIGEST"
go run ./core/cmd/gds-release-builder \
actual_digest="$(sha256sum "$EVIDENCE_INPUT_ROOT/trust.json" | cut -d' ' -f1)"
if [ "$actual_digest" != "$expected_digest" ]; then
echo "Harness evidence trust policy does not match the independent repository pin" >&2
exit 1
fi
if ! go run ./core/cmd/gds-release-builder \
--extract-harness-evidence-archive "$EVIDENCE_INPUT_ROOT/evidence.tar.gz" \
--extract-harness-evidence-destination "$EVIDENCE_INPUT_ROOT/records" \
> "$RUNNER_TEMP/gds-harness-evidence-materialization.json"
> "$RUNNER_TEMP/gds-harness-evidence-materialization.json"; then
cat "$RUNNER_TEMP/gds-harness-evidence-materialization.json" >&2
exit 1
fi
echo "arguments=--harness-evidence-directory $EVIDENCE_INPUT_ROOT/records --harness-evidence-trust-policy $EVIDENCE_INPUT_ROOT/trust.json" >> "$GITHUB_OUTPUT"

- name: Run release gates
shell: bash
env:
GDS_TEST_PYTHON: ${{ runner.temp }}/gds-release-python/bin/python
run: |
python3 -m venv "${GDS_TEST_PYTHON%/bin/python}"
export PATH="${GDS_TEST_PYTHON%/python}:$PATH"
"$GDS_TEST_PYTHON" -m pip install --quiet --require-hashes -r requirements/test.txt
scripts/validate_release.sh

- name: Build and independently verify release unit
env:
RELEASE_OUTPUT_ROOT: ${{ runner.temp }}/gds-release-output
Expand Down
2 changes: 1 addition & 1 deletion core/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
// Version is the development default; a release build overrides it with the
// exact tag via -X. It carries the -dev suffix so an unstamped binary can
// never claim to be a released one.
var Version = "0.9.3-dev"
var Version = "0.9.4-dev"

type options struct {
json bool
Expand Down
2 changes: 1 addition & 1 deletion core/cmd/gds-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (

// version is the development default; the release builder stamps the exact
// tag via -X main.version. The -dev suffix keeps an unstamped binary honest.
var version = "0.9.3-dev"
var version = "0.9.4-dev"

func main() {
ctx, stop := signal.NotifyContext(
Expand Down
2 changes: 1 addition & 1 deletion core/compiler/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// policy-owner checkout. It tracks the current release line with a -dev
// suffix so a development bundle is dated honestly; the development channel
// field, not this string, is what classifies the bundle.
const DevelopmentBundleVersion = "0.9.3-dev"
const DevelopmentBundleVersion = "0.9.4-dev"

type PolicySource struct {
SchemaVersion int `json:"schema_version"`
Expand Down
6 changes: 6 additions & 0 deletions docs/contracts/bundle-release-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ The independent local `bundle-trust.yaml` binds:
- the exact GitHub CLI version and extracted executable digest for every
supported release target (`darwin`/`linux` × `amd64`/`arm64`).

The signed harness-evidence input is decoded and checked before the expensive
release gates. Its independent repository pin accepts 64 lowercase SHA-256 hex
digits with an optional `sha256:` prefix; missing, malformed, or mismatched pins
fail before building. A supplied policy never authorizes its own digest.
The release builder separately verifies the signed records and their freshness.

The trusted-root digest is obtained and reviewed out of band. A trusted root
delivered beside an attestation is not self-authenticating. Producer CI and the
consumer both compare it with the independent local pin before using it.
Expand Down
4 changes: 3 additions & 1 deletion scripts/validate_ci_tier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ case "$TIER" in
scripts/validate_go_core.sh
run_python_contracts
run_python_tests
scripts/validate_assurance.sh
# validate_go_core.sh already ran the complete race suite. Keep the
# integrated assurance scenario without executing that same suite twice.
scripts/validate_assurance.sh --skip-tests
;;
release)
scripts/validate_release.sh
Expand Down
4 changes: 2 additions & 2 deletions tests/golden/projections/control-plane/.claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!--
GENERATED FILE - DO NOT EDIT DIRECTLY
generator: gds
bundle: 0.9.3-dev
bundle: 0.9.4-dev
source-tree-digest: sha256:0000000000000000000000000000000000000000000000000000000000000001
input-digest: sha256:2166c46344bebb92028b5e9c20165d60316a3481b322fd53601c12d36732ce14
input-digest: sha256:e9687bc6603c6252585736cc1745c9096de32cf97fca37ac0f1d8b8859135ca4
output-digest: sha256:88cb57297d8d713287872a8afaca8d42f7146ecf7a091e4996e65eee8f962665
edit-source:
- .gds/repository.yaml
Expand Down
16 changes: 8 additions & 8 deletions tests/golden/projections/control-plane/.gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
schema_version: 1

bundle:
version: "0.9.3-dev"
version: "0.9.4-dev"
release_sequence: 0
channel: "development"
source_tree_digest: "sha256:0000000000000000000000000000000000000000000000000000000000000001"
digest: "sha256:e456e1a242a6267b9832cc2f45e7ae89d422ba45773d2e5478b5e0a5390dd156"
digest: "sha256:10ac7ebfa7ab684a862bc94b56fe7b5088b1c3e8331f0ada387567d7270a60a7"

projection:
input_digest: "sha256:2166c46344bebb92028b5e9c20165d60316a3481b322fd53601c12d36732ce14"
output_digest: "sha256:587fa4e8aa5b2c401247809cccd4387bc8cae55a90d1efcfd9a2c896681bc56a"
input_digest: "sha256:e9687bc6603c6252585736cc1745c9096de32cf97fca37ac0f1d8b8859135ca4"
output_digest: "sha256:563472d67819dcafcb39d465411ec47d6f9ad690210ba4cbaa066a9f2f3486d1"
files:
- path: ".claude/CLAUDE.md"
digest: "sha256:e523156c25acfe28213e755fee8323e27f6be06cd5f954d31b6084074fbc5529"
digest: "sha256:b113bc39249e78ec4ca3c4256aa450b81e6f8a578f1e8e40db55968d888512e1"
- path: ".gds/compiled-policy.json"
digest: "sha256:6aed44f82d7b00a8f59aabc760779ce1b2f97e2c65f69196c350f77c4ffc2702"
digest: "sha256:0cbdf7a659e32c65f3c41a630fc1541e84c94f259cf86200992e1cc711da80c7"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:5343f224f08fe265182397ce541c2f418b9b89765c79a32c69c424ffe74edb77"
digest: "sha256:1c94eab39ab7aaa209b5ac40a303afa7082169ae02d52caeaca60570d0cac6fd"
- path: "AGENTS.md"
digest: "sha256:b05202663aa8b91045731e625c635096324df6ee19ad7608efd9acc812e0e771"
digest: "sha256:7193b538c7196ee6fd5dde68ae25fd8153ecba4260471cf5b9e1e0aa542ee645"
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"schema_version": 1,
"compiled_policy": {
"repository_id": "repo_01M0EZ7TB3KNXNSP78Z8M64WXG",
"bundle_version": "0.9.3-dev",
"digest": "sha256:51a598c3755839fd82af1600ab810ab6d7b64d377c07e6c7487303ff5464ec6e"
"bundle_version": "0.9.4-dev",
"digest": "sha256:6fe9ed4fb7e146109ccf97d26fb924bac6dd5817b2618568feef8981ba7ec09b"
},
"sources": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED FILE - DO NOT EDIT DIRECTLY
# generator: gds
# bundle: 0.9.3-dev
# bundle: 0.9.4-dev
# source-tree-digest: sha256:0000000000000000000000000000000000000000000000000000000000000001
# input-digest: sha256:2166c46344bebb92028b5e9c20165d60316a3481b322fd53601c12d36732ce14
# input-digest: sha256:e9687bc6603c6252585736cc1745c9096de32cf97fca37ac0f1d8b8859135ca4
# output-digest: sha256:15b87303cd0692fd8e5d88281771dded9fdc2fc027d600681155cb4e71f42386
# edit-source:
# - .gds/repository.yaml
Expand Down
8 changes: 4 additions & 4 deletions tests/golden/projections/control-plane/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!--
GENERATED FILE - DO NOT EDIT DIRECTLY
generator: gds
bundle: 0.9.3-dev
bundle: 0.9.4-dev
source-tree-digest: sha256:0000000000000000000000000000000000000000000000000000000000000001
input-digest: sha256:2166c46344bebb92028b5e9c20165d60316a3481b322fd53601c12d36732ce14
output-digest: sha256:93d273d51063e15e4dd0ea53b03804a9b7e0005928550bda402b1e1b7714201f
input-digest: sha256:e9687bc6603c6252585736cc1745c9096de32cf97fca37ac0f1d8b8859135ca4
output-digest: sha256:819456f92e6f6b6e6c49fdf53a1f73988508e6a9dbc705daa7329203749b63aa
edit-source:
- .gds/repository.yaml
- policies/base/repository-default.yaml
Expand Down Expand Up @@ -67,6 +67,6 @@ GDS is the public engine for a multi-owner GitHub estate. It loads an external e

## Facts

- Repository `repo_01M0EZ7TB3KNXNSP78Z8M64WXG`, roles `control-plane`, bundle `0.9.3-dev`.
- Repository `repo_01M0EZ7TB3KNXNSP78Z8M64WXG`, roles `control-plane`, bundle `0.9.4-dev`.
- Canonical inputs: `.gds/repository.yaml`; compiled result: `.gds/compiled-policy.json`.
- Visibility `public`, data `public`.
100 changes: 100 additions & 0 deletions tests/test_release_evidence_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Execute the hosted input step against the native bounded archive reader."""
from pathlib import Path
import base64
import hashlib
import io
import json
import os
import subprocess
import tarfile

import pytest
import yaml

ROOT = Path(__file__).resolve().parents[1]


def evidence_step() -> str:
doc = yaml.safe_load((ROOT / ".github/workflows/release-bundle.yml").read_text())
steps = doc["jobs"]["build"]["steps"]
names = [step.get("name") for step in steps]
name = "Materialize bounded signed harness evidence input"
assert names.index(name) < names.index("Run release gates")
return steps[names.index(name)]["run"]


@pytest.fixture(scope="module")
def native_builder(tmp_path_factory: pytest.TempPathFactory) -> Path:
binary = tmp_path_factory.mktemp("release-evidence-builder") / "gds-release-builder"
subprocess.run(["go", "build", "-trimpath", "-o", str(binary), "./core/cmd/gds-release-builder"],
cwd=ROOT, check=True, capture_output=True, text=True)
return binary


def run_input(tmp_path: Path, native_builder: Path, pin: str, *, channel="stable",
archive=True, policy=True, malformed_archive=False):
records = ["manifest", "antigravity", "claude-code", "codex", "cursor", "grok-build", "opencode", "pi"]
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w:gz", format=tarfile.USTAR_FORMAT) as tar:
for name in records:
info = tarfile.TarInfo(name + ".json")
info.size, info.mode = 3, 0o600
tar.addfile(info, io.BytesIO(b"{}\n"))
raw_archive = b"invalid archive" if malformed_archive else stream.getvalue()
trust = b'{"schema_version":1}\n'
output = tmp_path / "outputs"
step = evidence_step()
command = "go run ./core/cmd/gds-release-builder"
assert step.count(command) == 1
step = step.replace(command, '"$TEST_RELEASE_BUILDER"')
result = subprocess.run(["bash", "-c", step], cwd=ROOT, capture_output=True, text=True,
env={**os.environ, "TEST_RELEASE_BUILDER": str(native_builder),
"RUNNER_TEMP": str(tmp_path), "EVIDENCE_INPUT_ROOT": str(tmp_path / "input"),
"GITHUB_OUTPUT": str(output), "CHANNEL": channel,
"HARNESS_EVIDENCE_BUNDLE_BASE64": base64.b64encode(raw_archive).decode() if archive else "",
"HARNESS_EVIDENCE_TRUST_POLICY_BASE64": base64.b64encode(trust).decode() if policy else "",
"HARNESS_EVIDENCE_TRUST_POLICY_DIGEST": pin}, timeout=30)
return result, output


@pytest.mark.parametrize("prefix", ["", "sha256:"])
def test_both_exact_sha256_notations_reach_native_materialization(tmp_path: Path, native_builder: Path, prefix: str):
digest = hashlib.sha256(b'{"schema_version":1}\n').hexdigest()
result, output = run_input(tmp_path, native_builder, prefix + digest)
assert result.returncode == 0, result.stderr
assert "--harness-evidence-directory" in output.read_text()
assert len(list((tmp_path / "input/records").glob("*.json"))) == 8


@pytest.mark.parametrize("pin,message", [
("", "must be an independent SHA-256 pin"),
("sha512:" + "a" * 64, "must be an independent SHA-256 pin"),
("sha256:not-a-digest", "must be an independent SHA-256 pin"),
("sha256:" + "0" * 64, "does not match the independent repository pin"),
])
def test_missing_malformed_and_mismatched_independent_pins_fail(tmp_path: Path, native_builder: Path, pin: str, message: str):
result, output = run_input(tmp_path, native_builder, pin)
assert result.returncode != 0 and message in result.stderr
assert not output.exists()
assert not (tmp_path / "input/records").exists()


@pytest.mark.parametrize("archive,policy", [(True, False), (False, True), (False, False)])
def test_stable_requires_both_signed_inputs(tmp_path: Path, native_builder: Path, archive: bool, policy: bool):
result, output = run_input(tmp_path, native_builder, "", archive=archive, policy=policy)
assert result.returncode != 0
assert not output.exists()


def test_canary_can_explicitly_omit_both_inputs(tmp_path: Path, native_builder: Path):
result, output = run_input(tmp_path, native_builder, "", channel="canary", archive=False, policy=False)
assert result.returncode == 0, result.stderr
assert output.read_text() == "arguments=\n"


def test_native_archive_refusal_is_visible_in_the_step_log(tmp_path: Path, native_builder: Path):
digest = hashlib.sha256(b'{"schema_version":1}\n').hexdigest()
result, output = run_input(tmp_path, native_builder, digest, malformed_archive=True)
assert result.returncode != 0
assert json.loads(result.stderr)["code"] == "GDS_HARNESS_EVIDENCE_ARCHIVE_INVALID"
assert not output.exists()
Loading