diff --git a/PROVENANCE.md b/PROVENANCE.md index fb7437d..bf8fab2 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -37,3 +37,30 @@ of coverage, and this source-reuse route does not permit GPL/AGPL dependencies or bundles. The checked-in Classic distribution remains GPL-2.0-or-later; MIT permission applies only to the exact selected destination material recorded by the review. + +`provenance/identity-reference.synthetic.json` demonstrates the canonical +privacy-preserving identity reference workflow for issue #84. It is +reviewer-signed synthetic evidence only: it grants no permission for real +material and copies neither the coordinator registry nor identity aliases. +`tools/check-foundations.sh` always validates the local record shape. With an +explicit coordinator checkout it also performs bounded offline verification: + +```sh +ATRINIK_COORDINATOR=/path/to/atrinik tools/check-foundations.sh +``` + +Before coordinator PR #381 merges, audit its pushed branch without treating +the result as approval: + +```sh +ATRINIK_COORDINATOR=/path/to/atrinik \ +ATRINIK_COORDINATOR_TRUSTED_REF=origin/feat/privacy-preserving-provenance-registry \ +tools/check-provenance-identity-reference.sh +``` + +The record's `evidence_reference.url` is the immutable online permalink. +This permanently synthetic demonstration remains a non-authorizing audit of +the exact pushed feature commit. Production evidence must instead pin a +revision already reachable from `origin/main` (or a future implemented and +verified signed release); squash-merging PR #381 does not promote its internal +commits to production authority. diff --git a/provenance/identity-reference.synthetic.json b/provenance/identity-reference.synthetic.json new file mode 100644 index 0000000..987f3b6 --- /dev/null +++ b/provenance/identity-reference.synthetic.json @@ -0,0 +1,28 @@ +{ + "schema_version": 1, + "synthetic": true, + "source": { + "repository": "atrinik/synthetic-history", + "path": "engine/beta.c", + "revision": "2222222222222222222222222222222222222222" + }, + "destination": { + "repository": "atrinik/server", + "path": "internal/kernel/kernel.go" + }, + "transformation": "Synthetic port used only to verify the reference contract.", + "scope_binding": "psb-22222222222222222222222222222222", + "scope_approval": { + "key_id": "synthetic-reviewer-2026", + "signature": "-----BEGIN SSH SIGNATURE-----\nU1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBruVWBv4FEuaHvyX1z13qRO9BU\nTXED0DGAjTaWDx1NAAAAAVYXRyaW5pay1wcm92ZW5hbmNlLXYxAAAAAAAAAAZzaGE1MTIA\nAABTAAAAC3NzaC1lZDI1NTE5AAAAQIIntFyZwxlBb8JrL5UGvsL55gDCSPrsD63OUhQdDz\n1u0Aiv3EtOOR5D79Ort0aOsVmoADkKb3IVmxYRM7vt3wI=\n-----END SSH SIGNATURE-----" + }, + "evidence_reference": { + "repository": "atrinik/atrinik", + "revision": "51aa7ac9d5ae9c0ff0b2a24a46b5d3e97739bbe0", + "record_id": "pir-c-22222222222222222222222222222222", + "registry_sha256": "0a04fcadccb37e64f6c1209bc0b7e8d5762639ce4c5521c3bba028d9eba037bd", + "schema_sha256": "9c726627c8679b4b437db2b54b2480fe0c45eef788de7cd76de1dec5c3ce3409", + "reviewers_sha256": "b7a44ad27cec663a9092b924a0f6a9c95bf95a2ce74cf75ba8873e7938782240", + "url": "https://github.com/atrinik/atrinik/blob/51aa7ac9d5ae9c0ff0b2a24a46b5d3e97739bbe0/governance/provenance-identities/registry.json#pir-c-22222222222222222222222222222222" + } +} diff --git a/tools/check-foundations.sh b/tools/check-foundations.sh index 4a6e112..7c8a872 100755 --- a/tools/check-foundations.sh +++ b/tools/check-foundations.sh @@ -23,6 +23,7 @@ required=(CONTRIBUTING.md PROVENANCE.md SECURITY.md THIRD_PARTY_NOTICES.md) for document in "${required[@]}"; do test -s "${document}" done +tools/test-provenance-identity-reference.sh if grep -RhE '^[[:space:]]*uses:' .github/workflows \ | grep -Ev '@[0-9a-f]{40}([[:space:]]|$)' >/dev/null; then diff --git a/tools/check-provenance-identity-reference.sh b/tools/check-provenance-identity-reference.sh new file mode 100755 index 0000000..c26475a --- /dev/null +++ b/tools/check-provenance-identity-reference.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +repository=$(git rev-parse --show-toplevel) +record=${1:-"${repository}/provenance/identity-reference.synthetic.json"} + +jq -e ' + type == "object" and + (keys | sort) == ["destination", "evidence_reference", "schema_version", "scope_approval", "scope_binding", "source", "synthetic", "transformation"] and + .schema_version == 1 and .synthetic == true and + (.source | type == "object" and (keys | sort) == ["path", "repository", "revision"] and + .repository == "atrinik/synthetic-history" and .path == "engine/beta.c" and + (.revision | test("^[0-9a-f]{40}$"))) and + (.destination | type == "object" and (keys | sort) == ["path", "repository"] and + .repository == "atrinik/server" and .path == "internal/kernel/kernel.go") and + (.transformation | type == "string" and length > 0) and + (.scope_approval | type == "object" and (keys | sort) == ["key_id", "signature"] and + .key_id == "synthetic-reviewer-2026" and + (.signature | test("^-----BEGIN SSH SIGNATURE-----\\n[A-Za-z0-9+/=\\n]+\\n-----END SSH SIGNATURE-----$"))) and + (.evidence_reference | type == "object" and + (keys | sort) == ["record_id", "registry_sha256", "repository", "reviewers_sha256", "revision", "schema_sha256", "url"] and + .repository == "atrinik/atrinik" and + .record_id == "pir-c-22222222222222222222222222222222" and + ([.registry_sha256, .reviewers_sha256, .schema_sha256] | all(test("^[0-9a-f]{64}$"))) and + (.revision | test("^[0-9a-f]{40}$")) and + (. as $e | $e.url == ("https://github.com/atrinik/atrinik/blob/" + $e.revision + + "/governance/provenance-identities/registry.json#" + $e.record_id))) and + (.scope_binding | test("^psb-[0-9a-f]{32}$")) and + true +' "${record}" >/dev/null + +if [[ -n "${ATRINIK_COORDINATOR:-}" ]]; then + trusted_ref=${ATRINIK_COORDINATOR_TRUSTED_REF:-origin/main} + arguments=(provenance validate --reference "${record}") + if [[ "${trusted_ref}" != origin/main ]]; then + arguments+=(--non-authorizing-audit-ref "${trusted_ref}") + fi + "${ATRINIK_COORDINATOR}/atrinik" "${arguments[@]}" +fi diff --git a/tools/test-provenance-identity-reference.sh b/tools/test-provenance-identity-reference.sh new file mode 100755 index 0000000..7859e81 --- /dev/null +++ b/tools/test-provenance-identity-reference.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +repository=$(git rev-parse --show-toplevel) +source_record="${repository}/provenance/identity-reference.synthetic.json" +temporary=$(mktemp /tmp/atrinik-server-provenance-reference.XXXXXX) +trap 'rm -f -- "${temporary}"' EXIT + +tools/check-provenance-identity-reference.sh "${source_record}" +for mutation in \ + '.source = null' \ + '.destination.contact = "forbidden"' \ + '.evidence_reference.registry_sha256 = "invalid"' \ + '.evidence_reference.url = "https://example.invalid/movable"' \ + '.scope_approval.signature = "placeholder"'; do + jq "${mutation}" "${source_record}" >"${temporary}" + if tools/check-provenance-identity-reference.sh "${temporary}"; then + echo "malformed provenance identity reference passed: ${mutation}" >&2 + exit 1 + fi +done