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
12 changes: 11 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ jobs:
- name: Install validation tools
run: sudo apt-get update && sudo apt-get install -y bats shellcheck
- name: Validate consumer
run: ./tests/validate.sh
run: |
python3 -m venv "$RUNNER_TEMP/spdx"
"$RUNNER_TEMP/spdx/bin/pip" install -r tests/requirements-artifacts.txt
export SPDX_VALIDATOR_PYTHON="$RUNNER_TEMP/spdx/bin/python"
./tests/validate.sh

bash-42:
name: Minimum runtime (Bash 4.2.53)
Expand Down Expand Up @@ -64,6 +68,9 @@ jobs:
- name: Validate consumer
run: |
export PATH="$(brew --prefix)/bin:$PATH"
python3 -m venv "$RUNNER_TEMP/spdx"
"$RUNNER_TEMP/spdx/bin/pip" install -r tests/requirements-artifacts.txt
export SPDX_VALIDATOR_PYTHON="$RUNNER_TEMP/spdx/bin/python"
./tests/validate.sh

artifact:
Expand All @@ -88,4 +95,7 @@ jobs:
if [[ "$RUNNER_OS" == "macOS" ]]; then
export PATH="$(brew --prefix)/bin:$PATH"
fi
python3 -m venv "$RUNNER_TEMP/spdx"
"$RUNNER_TEMP/spdx/bin/pip" install -r tests/requirements-artifacts.txt
export SPDX_VALIDATOR_PYTHON="$RUNNER_TEMP/spdx/bin/python"
./tests/release-artifact.sh
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and versions are tracked in the repo-root `VERSION` file.

### Fixed

- Emit independently validated SPDX 2.3 file and package evidence with SHA1 and SHA256.

- Reserve bundle destinations exclusively to prevent concurrent publication nesting.
- Honor user configuration and scenario precedence without implicit CLI overrides.
- Normalize release timestamps in UTC and test cross-timezone reproducibility.
Expand Down
15 changes: 15 additions & 0 deletions docs/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ release, and a framework pin update does not publish Beacon by itself.

## Standalone Artifact Gate

Artifact tests require the independent pinned SPDX validator. Install it once in
a virtual environment and point the tests at that interpreter:

```bash
python3 -m venv /tmp/beacon-spdx-validator
/tmp/beacon-spdx-validator/bin/pip install -r tests/requirements-artifacts.txt
export SPDX_VALIDATOR_PYTHON=/tmp/beacon-spdx-validator/bin/python
```

The test gate requires zero semantic validation messages from spdx-tools 0.8.5.
The generator uses only Python's standard library and remains offline. SHA1 is
included for SPDX 2.3 interoperability; SHA256 remains the integrity checksum.
Package verification codes and CONTAINS relationships partition application
files and embedded framework files without claiming per-file license analysis.

From the clean release commit, build the four assets without publishing them:

```bash
Expand Down
72 changes: 72 additions & 0 deletions scripts/artifact-evidence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Deterministic Beacon SPDX evidence (standard library only)."""

import hashlib
import json
from pathlib import Path
import sys


def metadata(path):
values = {}
for line in path.read_text().splitlines():
if "=" in line:
key, value = line.split("=", 1)
if key in values:
raise ValueError("duplicate metadata key")
values[key] = value
return values


def sbom(root, version, commit):
lock = metadata(root / "base-bash-libs.lock")
beacon = "SPDXRef-Package-Beacon"
framework = "SPDXRef-Package-BaseBashLibs"
files, relationships = [], [
{"spdxElementId": "SPDXRef-DOCUMENT", "relationshipType": "DESCRIBES", "relatedSpdxElement": beacon},
{"spdxElementId": beacon, "relationshipType": "DEPENDS_ON", "relatedSpdxElement": framework},
]
hashes = {beacon: [], framework: []}
for index, path in enumerate(sorted(p for p in root.rglob("*") if p.is_file()), 1):
relative = path.relative_to(root).as_posix()
content = path.read_bytes()
sha1 = hashlib.sha1(content).hexdigest()
owner = framework if relative.startswith("vendor/base-bash-libs/") else beacon
hashes[owner].append(sha1)
file_id = f"SPDXRef-File-{index}"
files.append({
"SPDXID": file_id, "fileName": "./" + relative,
"checksums": [
{"algorithm": "SHA1", "checksumValue": sha1},
{"algorithm": "SHA256", "checksumValue": hashlib.sha256(content).hexdigest()},
],
"licenseConcluded": "NOASSERTION",
})
relationships.append({"spdxElementId": owner, "relationshipType": "CONTAINS", "relatedSpdxElement": file_id})
packages = []
for identifier, name, release, source in (
(beacon, "beacon", version, commit),
(framework, "base-bash-libs", lock["version"], lock["commit"]),
):
repo = "base-bash-libs-demo" if identifier == beacon else name
packages.append({
"SPDXID": identifier, "name": name, "versionInfo": release,
"downloadLocation": f"https://github.com/basefoundry/{repo}/releases/tag/v{release}",
"filesAnalyzed": True,
"packageVerificationCode": {"packageVerificationCodeValue": hashlib.sha1("".join(sorted(hashes[identifier])).encode()).hexdigest()},
"licenseConcluded": "Apache-2.0", "licenseDeclared": "Apache-2.0",
"externalRefs": [{"referenceCategory": "OTHER", "referenceType": "source-commit", "referenceLocator": source}],
})
return {
"spdxVersion": "SPDX-2.3", "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT",
"name": f"beacon-v{version}-sbom",
"documentNamespace": f"https://github.com/basefoundry/base-bash-libs-demo/releases/v{version}/{commit}",
"creationInfo": {"created": "1970-01-01T00:00:00Z", "creators": ["Tool: base-bash-libs-demo/release-artifact"]},
"packages": packages, "files": files, "relationships": relationships,
}


if __name__ == "__main__":
if len(sys.argv) != 5 or sys.argv[1] != "sbom":
sys.exit("usage: artifact-evidence.py sbom ROOT VERSION COMMIT")
json.dump(sbom(Path(sys.argv[2]), sys.argv[3], sys.argv[4]), sys.stdout, indent=2)
print()
40 changes: 2 additions & 38 deletions scripts/release-artifact
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ artifact_copy_payload() {
fixtures/workspace
lib/beacon.sh
scripts/release-artifact
scripts/artifact-evidence.py
scripts/verify-vendor
vendor/base-bash-libs
vendor/evidence
Expand Down Expand Up @@ -203,44 +204,7 @@ artifact_create_archive() {
}

artifact_write_sbom() {
local output="$1" version="$2" source_commit="$3" root="$4"
local framework_version framework_commit path relative first=1 index=0

framework_version="$(artifact_metadata_value "$root/base-bash-libs.lock" version)" || return 1
framework_commit="$(artifact_metadata_value "$root/base-bash-libs.lock" commit)" || return 1
{
printf '{\n'
printf ' "spdxVersion": "SPDX-2.3",\n'
printf ' "dataLicense": "CC0-1.0",\n'
printf ' "SPDXID": "SPDXRef-DOCUMENT",\n'
printf ' "name": "beacon-v%s-sbom",\n' "$(artifact_json_escape "$version")"
printf ' "documentNamespace": "https://github.com/basefoundry/base-bash-libs-demo/releases/v%s/%s",\n' \
"$(artifact_json_escape "$version")" "$source_commit"
printf ' "creationInfo": {"created": "1970-01-01T00:00:00Z", "creators": ["Tool: base-bash-libs-demo/release-artifact"]},\n'
printf ' "packages": [\n'
printf ' {"SPDXID": "SPDXRef-Package-Beacon", "name": "beacon", "versionInfo": "%s", "downloadLocation": "https://github.com/basefoundry/base-bash-libs-demo/releases/tag/v%s", "licenseConcluded": "Apache-2.0", "licenseDeclared": "Apache-2.0"},\n' \
"$(artifact_json_escape "$version")" "$(artifact_json_escape "$version")"
printf ' {"SPDXID": "SPDXRef-Package-BaseBashLibs", "name": "base-bash-libs", "versionInfo": "%s", "downloadLocation": "https://github.com/basefoundry/base-bash-libs/releases/tag/v%s", "licenseConcluded": "Apache-2.0", "licenseDeclared": "Apache-2.0", "externalRefs": [{"referenceCategory": "OTHER", "referenceType": "source-commit", "referenceLocator": "%s"}]}\n' \
"$(artifact_json_escape "$framework_version")" "$(artifact_json_escape "$framework_version")" "$framework_commit"
printf ' ],\n'
printf ' "files": [\n'
while IFS= read -r path; do
relative="${path#"$root/"}"
((index += 1))
if ((first == 0)); then
printf ',\n'
fi
first=0
printf ' {"SPDXID": "SPDXRef-File-%s", "fileName": "%s", "checksums": [{"algorithm": "SHA256", "checksumValue": "%s"}], "licenseConcluded": "NOASSERTION"}' \
"$index" "$(artifact_json_escape "$relative")" "$(artifact_hash_file "$path")"
done < <(find "$root" -type f -print | LC_ALL=C sort)
printf '\n ],\n'
printf ' "relationships": [\n'
printf ' {"spdxElementId": "SPDXRef-DOCUMENT", "relationshipType": "DESCRIBES", "relatedSpdxElement": "SPDXRef-Package-Beacon"},\n'
printf ' {"spdxElementId": "SPDXRef-Package-Beacon", "relationshipType": "DEPENDS_ON", "relatedSpdxElement": "SPDXRef-Package-BaseBashLibs"}\n'
printf ' ]\n'
printf '}\n'
} > "$output"
python3 "$artifact_repo_root/scripts/artifact-evidence.py" sbom "$4" "$2" "$3" > "$1"
}

artifact_write_provenance() {
Expand Down
2 changes: 2 additions & 0 deletions tests/release-artifact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ TZ=Asia/Kolkata "$release_test_root/scripts/release-artifact" build \
--version "$release_test_version" --output "$release_test_second"

diff -r "$release_test_first" "$release_test_second"
"${SPDX_VALIDATOR_PYTHON:-python3}" "$release_test_root/tests/validate-spdx.py" \
"$release_test_first/beacon-v$release_test_version.spdx.json"
"$release_test_root/scripts/release-artifact" verify "$release_test_first"

if "$release_test_root/scripts/release-artifact" build \
Expand Down
1 change: 1 addition & 0 deletions tests/requirements-artifacts.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spdx-tools==0.8.5
13 changes: 13 additions & 0 deletions tests/validate-spdx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Independent SPDX 2.3 semantic validation; required, not an optional gate."""
import importlib.metadata
import sys

from spdx_tools.spdx.parser.parse_anything import parse_file
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document

if importlib.metadata.version("spdx-tools") != "0.8.5":
sys.exit("Install the pinned tests/requirements-artifacts.txt validator")
messages = validate_full_spdx_document(parse_file(sys.argv[1]))
for message in messages:
print(message, file=sys.stderr)
sys.exit(bool(messages))
3 changes: 3 additions & 0 deletions tests/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ required_files=(
examples/minimal-cli
lib/beacon.sh
scripts/release-artifact
scripts/artifact-evidence.py
tests/requirements-artifacts.txt
tests/validate-spdx.py
scripts/verify-vendor
tests/beacon.bats
tests/lifecycle.bats
Expand Down
Loading