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
43 changes: 43 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,48 @@ jobs:
- name: Zip coverage report
run: zip -r release-coverage.zip ./release-coverage

- name: Generate reproducible-build manifest
# Publishes the expected sha256 of each SHIPPED assembly (the deterministic
# artifact) so a third party can rebuild from the tag and verify byte-for-byte
# (#225). Hashes the .dll inside the .nupkg, not the .nupkg itself — the zip
# envelope carries non-deterministic timestamps; the assembly does not. See
# docs/REPRODUCIBLE-BUILD.md for the verification procedure.
shell: bash
run: |
set -euo pipefail
# Pick the first non-symbols .nupkg via a glob loop (SC2010: avoid ls | grep).
pkg=""
for candidate in ./nuget-packages/*.nupkg; do
case "$candidate" in
*.symbols.nupkg) continue ;;
esac
pkg="$candidate"
break
done
if [ -z "$pkg" ]; then
echo "No non-symbols .nupkg found in ./nuget-packages" >&2
exit 1
fi
version=$(basename "$pkg" .nupkg | sed 's/^Wolfgang\.Etl\.Abstractions\.//')
workdir=$(mktemp -d)
unzip -q "$pkg" 'lib/*/*.dll' -d "$workdir"
{
printf '{\n "package": "Wolfgang.Etl.Abstractions",\n "version": "%s",\n' "$version"
printf ' "algorithm": "sha256",\n "assemblies": {\n'
first=1
while IFS= read -r dll; do
rel=${dll#"$workdir"/}
hash=$(sha256sum "$dll" | cut -d' ' -f1)
[ $first -eq 1 ] || printf ',\n'
printf ' "%s": "%s"' "$rel" "$hash"
first=0
done < <(find "$workdir/lib" -name '*.dll' | sort)
printf '\n }\n}\n'
} > reproducible-build-manifest.json
echo "::group::reproducible-build-manifest.json"
cat reproducible-build-manifest.json
echo "::endgroup::"

- name: Attach artifacts to release
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
Expand All @@ -791,5 +833,6 @@ jobs:
./nuget-packages/*.nupkg
./nuget-packages/*.snupkg
./nuget-packages/*.bom.json
reproducible-build-manifest.json
release-coverage.zip

12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,22 @@ This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) f
- **API Documentation:** https://Chris-Wolfgang.github.io/ETL-Abstractions/
- **Formatting Guide:** [README-FORMATTING.md](docs/README-FORMATTING.md)
- **Architecture Decisions:** [docs/adr/](docs/adr/index.md) — the *why* behind non-obvious design choices
- **Verify the build:** [REPRODUCIBLE-BUILD.md](docs/REPRODUCIBLE-BUILD.md) — rebuild from source and confirm the shipped assembly
- **Contributing Guide:** [CONTRIBUTING.md](CONTRIBUTING.md)

---

## 🔒 Verify the build

This library is built **deterministically**, so you don't have to trust our CI — you
can rebuild it yourself from any release tag and confirm the shipped assembly, byte
for byte. Each release attaches a `reproducible-build-manifest.json` with the expected
`sha256` of every target framework's assembly. See
[**docs/REPRODUCIBLE-BUILD.md**](docs/REPRODUCIBLE-BUILD.md) for the step-by-step
verification procedure and how to report a discrepancy.

---

## 🚀 Quick Start

```csharp
Expand Down
80 changes: 80 additions & 0 deletions docs/REPRODUCIBLE-BUILD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Reproducing & verifying the build

`Wolfgang.Etl.Abstractions` is built **deterministically** — `Deterministic` +
`ContinuousIntegrationBuild` + SourceLink — so the compiled assembly is a pure
function of the source at a given commit. That means **you don't have to trust our
CI**: you can rebuild the library yourself from the published tag and confirm, byte
for byte, that the assembly we shipped is the one the source produces.

Our CI already proves the build is reproducible *internally* — `.github/workflows/reproducible-build.yaml`
builds the library twice in independent directories and fails if the two
assemblies' hashes differ (#216). This page is the **consumer side**: how *you*
verify it independently (#225).

## What is verified

The **compiled assembly** (`Wolfgang.Etl.Abstractions.dll`) is the deterministic
artifact. The `.nupkg` itself is a zip and carries non-deterministic container
metadata (entry timestamps), so its outer hash is *not* expected to match — verify
the assembly inside it, not the package envelope.

Each release attaches a **`reproducible-build-manifest.json`** listing the expected
`sha256` of the shipped assembly for every target framework, e.g.:

```json
{
"package": "Wolfgang.Etl.Abstractions",
"version": "0.16.1",
"algorithm": "sha256",
"assemblies": {
"lib/net10.0/Wolfgang.Etl.Abstractions.dll": "d34db33f…",
"lib/net8.0/Wolfgang.Etl.Abstractions.dll": "…"
}
}
```

## Verify a published package

You need the same **major .NET SDK** the release was built with (see the release's
`global.json` / the SDK line in `release.yaml` — `10.0.x` for current releases).

```bash
# 1. Get the exact published assembly hash (from the package on nuget.org)
dotnet nuget locals -c all >/dev/null # optional: clean caches
unzip -p Wolfgang.Etl.Abstractions.<version>.nupkg \
lib/net10.0/Wolfgang.Etl.Abstractions.dll | sha256sum

# 2. Rebuild from source at the tag and hash your own output
git clone https://github.com/Chris-Wolfgang/ETL-Abstractions
cd ETL-Abstractions
git checkout v<version>
dotnet build src/Wolfgang.Etl.Abstractions/Wolfgang.Etl.Abstractions.csproj \
-c Release -f net10.0 -p:ContinuousIntegrationBuild=true
sha256sum src/Wolfgang.Etl.Abstractions/bin/Release/net10.0/Wolfgang.Etl.Abstractions.dll

# 3. Compare — the two hashes, and the value in reproducible-build-manifest.json,
# must all match.
```

A match means the published binary is exactly what the tagged source compiles to,
on your machine, under your control.

## If the hashes differ

First rule out environment drift — a different SDK **feature band** or OS can change
codegen. Confirm you used the SDK major/feature band named in the release and built
with `-p:ContinuousIntegrationBuild=true`.

If they still differ with matching tooling, that is a genuine reproducibility
discrepancy worth reporting: open an issue titled `reproducibility: <version> hash
mismatch` with your **SDK version** (`dotnet --version`), **OS**, the **two hashes**,
and the target framework. It is treated as a security-relevant report.

## Third-party verification attestations

Independent verifiers are encouraged to publish their result following the
[Reproducible Builds project](https://reproducible-builds.org/) conventions (a
signed statement that "version X's assembly rebuilds to hash H"), for example via
[vouchsafe.io](https://vouchsafe.io/) or an [`actions/attest-build-provenance`](https://github.com/actions/attest-build-provenance)
attestation over your own rebuild. Link such an attestation on the discrepancy/verification
issue so others can find it.
Loading