Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8a8245a
fix(portable): bind CMake and Ninja to installed producer bytes
zoeyrose Oct 12, 2026
96eafd2
fix(portable): reject symlink hops in protected build tool paths
zoeyrose Oct 12, 2026
4079934
build(portable): add bounded installed inventory foundation
zoeyrose Oct 12, 2026
1ede7cc
fix(portable): bind pkgconf queries to producer executable receipt
zoeyrose Oct 12, 2026
84bdeb7
fix(portable): generate curl producer receipt before inventory
zoeyrose Oct 12, 2026
3529659
fix(portable): align installed build tool receipt validation
zoeyrose Oct 12, 2026
a935976
fix(portable): require installed pkgconf identity
zoeyrose Oct 12, 2026
0307d05
build(portable): bind SDK to merged Classic LLVM source
zoeyrose Oct 12, 2026
a25b5df
build(portable): bind SDK to merged Classic LLVM repair
zoeyrose Oct 12, 2026
46f3961
build(portable): bind SDK to Classic inspector repair
zoeyrose Oct 12, 2026
a0f5240
fix(sdk): require nonempty collector files by default
zoeyrose Oct 12, 2026
cfb2beb
fix(sdk): verify audio and LLVM source archive closure
zoeyrose Oct 12, 2026
e07ffb5
test(sdk): reject empty inventory notices and build tools
zoeyrose Oct 12, 2026
d18a2de
docs(sdk): clarify retained manifests and sealed tool cohort
zoeyrose Oct 12, 2026
1c1865a
fix(sdk): require complete portable source archive declarations
zoeyrose Oct 12, 2026
7b021fb
chore(ci): synchronize current dependency review policy
zoeyrose Oct 12, 2026
502c672
ci: retain scrubbed dependency review job summaries
zoeyrose Oct 12, 2026
ebe1b46
build: retarget portable consumer to merged Classic revision
zoeyrose Oct 12, 2026
cc6b6ed
fix(sdk): bind provider bytes and protect tool ownership
zoeyrose Oct 12, 2026
90cac14
fix(sdk): fence published Linux image identity
zoeyrose Oct 12, 2026
0a97144
fix(linux): install pinned gh-stack without API discovery
zoeyrose Oct 12, 2026
abcbc50
fix(build): preserve Docker 1.49 image identity checks
zoeyrose Oct 13, 2026
7a69f75
fix(ci): select Linux images for publication identity changes
zoeyrose Oct 13, 2026
0de3f50
build(portable): pin current Classic consumer revision
zoeyrose Oct 13, 2026
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
10 changes: 10 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,13 @@ jobs:

if __name__ == '__main__':
main()
- name: Retain dependency review job summary
if: ${{ always() }}
continue-on-error: true
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: dependency-review-job-summary-${{ github.event.pull_request.head.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
# Retain only the runner's masked copies used for the actual job summary.
path: ${{ runner.temp }}/_runner_file_commands/step_summary_*-scrubbed
retention-days: 7
if-no-files-found: warn
107 changes: 90 additions & 17 deletions .github/workflows/publish-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
publish:
runs-on: ubuntu-26.04
timeout-minutes: 360
env:
CANDIDATE_ONLY: ${{ inputs.candidate_only }}
steps:
- name: Check out image sources
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -55,6 +57,9 @@ jobs:

{
echo "image=${image}"
prefix=
if [[ "${CANDIDATE_ONLY}" == true ]]; then prefix=candidate-; fi
echo "publish_tag=${image}:${prefix}sha-${GITHUB_SHA}"
echo 'tags<<EOF'
echo "${tags}"
echo EOF
Expand Down Expand Up @@ -87,6 +92,9 @@ jobs:

{
echo "image=${image}"
prefix=
if [[ "${CANDIDATE_ONLY}" == true ]]; then prefix=candidate-; fi
echo "publish_tag=${image}:${prefix}sha-${GITHUB_SHA}"
echo 'tags<<EOF'
echo "${tags}"
echo EOF
Expand Down Expand Up @@ -156,10 +164,18 @@ jobs:
type=gha,scope=llvm-toolchain-23.1.3
type=gha,scope=classic-build-image-llvm-23.1.3

- name: Capture loaded classic image identity
id: classic-identity
run: python3 -I tools/verify_published_identity.py capture --target classic

- name: Qualify Classic cancellation and public TLS before publication
run: tools/smoke-classic-curl.sh atrinik-classic-curl:qualification build/curl-classic
env:
IMAGE_ID: ${{ steps.classic-identity.outputs.image_id }}
run: tools/smoke-classic-curl.sh "${IMAGE_ID}" build/curl-classic

- name: Run Classic client and server checks before publication
env:
IMAGE_ID: ${{ steps.classic-identity.outputs.image_id }}
run: |
install -d -m 1777 "${RUNNER_TEMP}/classic-ccache"
docker run --rm \
Expand All @@ -168,7 +184,7 @@ jobs:
--volume "${GITHUB_WORKSPACE}:/image-source:ro" \
--volume "${GITHUB_WORKSPACE}/build/curl-classic:/workspace" \
--volume "${RUNNER_TEMP}/classic-ccache:/cache/ccache" \
--workdir /workspace atrinik-classic-curl:qualification \
--workdir /workspace "${IMAGE_ID}" \
/image-source/tools/validate-classic-check.sh /workspace

- name: Build and smoke broad Linux validation target
Expand Down Expand Up @@ -199,8 +215,14 @@ jobs:
type=gha,scope=llvm-toolchain-23.1.3
type=gha,scope=linux-build-image-llvm-23.1.3

- name: Capture loaded broad image identity
id: broad-identity
run: python3 -I tools/verify_published_identity.py capture --target broad

- name: Qualify broad Linux cancellation and public TLS before publication
run: tools/smoke-classic-curl.sh atrinik-linux-curl:qualification build/curl-classic
env:
IMAGE_ID: ${{ steps.broad-identity.outputs.image_id }}
run: tools/smoke-classic-curl.sh "${IMAGE_ID}" build/curl-classic

# The portable target has its own ABI and real consumer; validate it before aliases move.
- name: Read portable consumer revision
Expand Down Expand Up @@ -246,7 +268,13 @@ jobs:
type=gha,scope=llvm-toolchain-23.1.3
type=gha,scope=classic-portable-build-image-llvm-23.1.3

- name: Capture loaded portable image identity
id: portable-identity
run: python3 -I tools/verify_published_identity.py capture --target portable

- name: Verify portable candidate public CA
env:
IMAGE_ID: ${{ steps.portable-identity.outputs.image_id }}
shell: bash
run: |
set -euo pipefail
Expand All @@ -256,31 +284,35 @@ jobs:
--env HTTP_PROXY= --env HTTPS_PROXY= --env ALL_PROXY= --env NO_PROXY= \
--env http_proxy= --env https_proxy= --env all_proxy= --env no_proxy= \
--volume "${GITHUB_WORKSPACE}/build/portable-classic/client/ca-bundle.crt:/workspace/client/ca-bundle.crt:ro" \
atrinik-classic-portable:release-validation \
"${IMAGE_ID}" \
python3 /opt/atrinik-portable/curl_evidence.py \
--public-ca /workspace/client/ca-bundle.crt \
| tee build/portable-reports/public-ca.json

- name: Validate real portable consumer before publication
env:
IMAGE_ID: ${{ steps.portable-identity.outputs.image_id }}
run: |
install -d build/portable-reports
docker run --rm --network none \
--user "$(id -u):$(id -g)" --env HOME=/tmp/portable-home \
--volume "${GITHUB_WORKSPACE}/build/portable-classic:/classic:ro" \
--volume "${GITHUB_WORKSPACE}/build/portable-reports:/reports" \
atrinik-classic-portable:release-validation \
"${IMAGE_ID}" \
/opt/atrinik-portable/consumer.sh /classic /reports

- name: Build and publish Linux image
id: broad-publish
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
file: linux/Dockerfile
build-args: LLVM_BUILD_JOBS=2
target: final
platforms: linux/amd64
pull: true
push: true
tags: ${{ steps.metadata.outputs.tags }}
tags: ${{ steps.metadata.outputs.publish_tag }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
Expand All @@ -293,6 +325,7 @@ jobs:
type=gha,mode=max,scope=linux-build-image-llvm-23.1.3,ignore-error=true

- name: Build and publish Classic image
id: classic-publish
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
Expand All @@ -302,7 +335,7 @@ jobs:
platforms: linux/amd64
pull: true
push: true
tags: ${{ steps.classic-metadata.outputs.tags }}
tags: ${{ steps.classic-metadata.outputs.publish_tag }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
Expand Down Expand Up @@ -348,17 +381,57 @@ jobs:
type=gha,scope=llvm-toolchain-23.1.3
type=gha,scope=classic-portable-build-image-llvm-23.1.3

- name: Promote validated portable aliases
- name: Verify all published image identities
env:
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BROAD_DIGEST: ${{ steps.broad-publish.outputs.digest }}
CLASSIC_DIGEST: ${{ steps.classic-publish.outputs.digest }}
PORTABLE_DIGEST: ${{ steps.portable-publish.outputs.digest }}
run: |
python3 -I tools/verify_published_identity.py verify --target broad --digest "${BROAD_DIGEST}"
Comment thread
zoeyrose marked this conversation as resolved.
python3 -I tools/verify_published_identity.py verify --target classic --digest "${CLASSIC_DIGEST}"
python3 -I tools/verify_published_identity.py verify --target portable --digest "${PORTABLE_DIGEST}"

# Upload is part of acceptance: an unavailable receipt blocks every alias.
- name: Retain published identity evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: linux-published-identity-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/linux-published-identity/${{ github.run_id }}-${{ github.run_attempt }}
if-no-files-found: error
retention-days: 14

- name: Promote validated Linux aliases
if: ${{ !inputs.candidate_only }}
env:
BROAD_DIGEST: ${{ steps.broad-publish.outputs.digest }}
CLASSIC_DIGEST: ${{ steps.classic-publish.outputs.digest }}
PORTABLE_DIGEST: ${{ steps.portable-publish.outputs.digest }}
run: |
[[ "${PORTABLE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]
image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/classic-portable-build"
tags=(--tag "${image}:latest" --tag "${image}:debian-12")
if [[ "${GITHUB_REF_TYPE}" == tag ]]; then
[[ "${GITHUB_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
tags+=(--tag "${image}:${GITHUB_REF_NAME#v}")
fi
docker buildx imagetools create "${tags[@]}" "${image}@${PORTABLE_DIGEST}"
printf '%s@%s\n' "${image}" "${PORTABLE_DIGEST}" >> "${GITHUB_STEP_SUMMARY}"
set -euo pipefail
images=(linux-build classic-build classic-portable-build)
platforms=(ubuntu-26.04 ubuntu-26.04 debian-12)
digests=("${BROAD_DIGEST}" "${CLASSIC_DIGEST}" "${PORTABLE_DIGEST}")
for digest in "${digests[@]}"; do [[ "${digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; done
for index in 0 1 2; do
image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/${images[index]}"
tags=(--tag "${image}:latest" --tag "${image}:${platforms[index]}")
if [[ "${GITHUB_REF_TYPE}" == tag ]]; then
[[ "${GITHUB_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
tags+=(--tag "${image}:${GITHUB_REF_NAME#v}")
fi
if ! docker buildx imagetools create "${tags[@]}" "${image}@${digests[index]}"; then
echo "Alias promotion incomplete. Rerun the failed job in this workflow run to converge all aliases." >&2
exit 1
fi
printf '%s@%s\n' "${image}" "${digests[index]}" >> "${GITHUB_STEP_SUMMARY}"
done

- name: Retain failed identity evidence
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: linux-published-identity-failure-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/linux-published-identity/${{ github.run_id }}-${{ github.run_attempt }}
if-no-files-found: error
retention-days: 14
6 changes: 6 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
case "${path}" in
.dockerignore | llvm-toolchain.json | llvm-bootstrap-packages.lock | \
tools/install_llvm_toolchain.py | tools/validate_llvm_toolchain.py | \
tools/verify_published_identity.py | tools/tests/test_verify_published_identity.py | \
tools/collect-released-sdk-metadata.py | tools/tests/test_collect_released_sdk_metadata.py | \
tools/tests/test_install_llvm_toolchain.py | tools/tests/test_workflow_contracts.py | .github/actionlint.yaml | .github/workflows/* | \
portable/* | classic-packages.lock | classic-curl-toolchain.json | \
classic-shader-toolchain.json | classic-shader-toolchain.spdx.json | \
Expand All @@ -51,6 +53,8 @@ jobs:
case "${path}" in
.dockerignore | llvm-toolchain.json | llvm-bootstrap-packages.lock | \
tools/install_llvm_toolchain.py | tools/validate_llvm_toolchain.py | \
tools/verify_published_identity.py | tools/tests/test_verify_published_identity.py | \
tools/collect-released-sdk-metadata.py | tools/tests/test_collect_released_sdk_metadata.py | \
tools/tests/test_install_llvm_toolchain.py | tools/tests/test_workflow_contracts.py | .github/actionlint.yaml | \
.github/workflows/* | linux/* | \
audio-toolchain.json | audio-toolchain.spdx.json | \
Expand All @@ -72,6 +76,8 @@ jobs:
case "${path}" in
.dockerignore | llvm-toolchain.json | llvm-bootstrap-packages.lock | \
tools/install_llvm_toolchain.py | tools/validate_llvm_toolchain.py | \
tools/verify_published_identity.py | tools/tests/test_verify_published_identity.py | \
tools/collect-released-sdk-metadata.py | tools/tests/test_collect_released_sdk_metadata.py | \
tools/tests/test_install_llvm_toolchain.py | tools/tests/test_workflow_contracts.py | .github/workflows/publish-linux.yml | \
.github/workflows/validate.yml | linux/* | \
audio-toolchain.json | audio-toolchain.spdx.json | \
Expand Down
30 changes: 29 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,17 @@
source commits or shader inputs. Schema 2 compiler evidence requires every
selected LLVM utility, including separate Clang C/C++ drivers and
archiver/ranlib, with canonical paths, versions and hashes; rehash before
consumer builds and publication. Portable curl is an LLVM rebuild of
consumer builds and publication. The separate schema-1 `installed.json.build_tools`
receipt binds fixed `/usr/bin/cmake`, `/usr/bin/ninja` and `/usr/bin/pkgconf`
paths, canonical executables, hashes, sizes and full version stdout. Verify
all three protected identities before probing any tool and fence
configure/build execution;
the mandatory `pkg_config` role also fences inventory module-version and
consumer/smoke/curl package queries through the canonical executable.
reject symlinks in these fixed Debian executable and metadata paths.
Missing or incomplete receipts require a rebuilt producer. The producer OS, libraries and
CMake modules and earlier library-production builds remain trusted; this
receipt does not attest historical execution. Portable curl is an LLVM rebuild of
security-patched Debian `7.88.1-10+deb12u15` with c-ares 1.34.6, retaining
existing features including LDAP/LDAPS, RTMP, SCP and SFTP. Its separate
`portable/curl-toolchain.json`, `portable/build-curl.py`,
Expand Down Expand Up @@ -75,6 +85,20 @@
`candidate-sha-<commit>`. Windows publishes `candidate-sha-<commit>` and
`classic-check-candidate-sha-<commit>` with cross-build and native Windows
tests intact. Neither path may move rolling, platform, or version aliases.
- The Linux publisher captures each loaded image's immutable Docker ID and raw
inspection before runtime tests. Curl/TLS, Classic client/server, portable
public-CA and offline consumer checks must execute those captured IDs. After
all runtime gates, publish only SHA candidates, then verify the actual pushed
OCI index, Linux/amd64 manifest and config against the captured RootFS and
complete execution config with `tools/verify_published_identity.py`. Only
exact source/revision publication labels, creation/history and BuildKit inline
cache metadata may differ; unknown raw config and execution fields fail closed.
Keep bounded metadata-only authenticated acquisition confined to the
same-repository hosted publisher. Retain raw inspection, OCI objects,
descriptor relationships and hashed reports as mandatory artifacts before
promoting any release aliases. Rerun the failed job in the same workflow run
to recover partial promotion; preserve historical released-SDK collector
targets and final-cohort gates.
- `llvm-toolchain.json`, `llvm-bootstrap-packages.lock`, and the shared LLVM
installer/validator own LLVM 23.1.3 Clang/LLD/compiler-rt/libunwind source,
licensing, installed metadata, and target contracts. Every affected input
Expand Down Expand Up @@ -110,6 +134,10 @@
exists; fix actionable introduced risk or record explicit maintainer-accepted
existing risk. License metadata does not certify compatibility; existing
repository license policies apply.
For programmatic access, read every runner-scrubbed report in the
`dependency-review-job-summary-<head-sha>-<run-id>-<run-attempt>` artifact after
verifying its digest and latest current-head run/attempt identity; an absent
or unreadable actual report remains unknown, not clean.
- Commits and pull-request titles use Conventional Commits. Preserve unrelated
work and finish with `git diff --check`.
- Update this `AGENTS.md` in the same change when major rework alters image
Expand Down
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,30 @@ shader-generation record under
versions and hashes for every selected LLVM utility, including independent
`compiler` (`clang`) and `cxx_compiler` (`clang++`) records plus `llvm-ar` and
`llvm-ranlib`; incomplete earlier evidence is rejected. Consumers rehash these
identities before building and publication. Shared application libraries live under `/usr/local`;
Debian packages provide the baseline transitive libraries. Compiler flags use
identities before building and publication.

`installed.json.build_tools` separately seals CMake, Ninja and pkgconf with schema 1,
fixed `/usr/bin/cmake`, `/usr/bin/ninja` and `/usr/bin/pkgconf` paths,
canonical executable paths, SHA-256 hashes, byte sizes,
executable status and complete version stdout. Its first lines agree with the
existing installed tool version strings. The consumer verifies all three protected
root-owned executables through open descriptors before any version probe and
fences their resolution, bytes and file identity around configure and build.
The mandatory `pkg_config` role also supplies the installed tool version string,
inventory module-version queries, and consumer/smoke/curl package probes through
the same admission and execution fences. Module versions and feature selections
retain their existing contract.
Every directory component must be protected, and these fixed Debian executable
and metadata paths must not contain symlinks.
SDKs without all three producer identities require a rebuild. This check trusts the
producer OS, dynamic libraries and CMake modules; it does not attest the full
transitive execution environment.
It binds the installed executables used by the later consumer; earlier library
builds remain part of trusted SDK production and are not historical execution
attestations.

Shared application libraries live under `/usr/local`; Debian packages provide
the baseline transitive libraries. Compiler flags use
`-march=x86-64 -mtune=generic`. Verification checks actual ELF dependency
providers, versioned symbols, loader relocations and CPU notes, plus device-free
image/font decoding, audio decoding and OpenSSL provider loading. The
Expand Down
Loading
Loading