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
24 changes: 24 additions & 0 deletions .github/workflows/required-hmg-public-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Required HMG Public Quality

on:
pull_request:
merge_group:

permissions:
contents: read

concurrency:
group: required-hmg-public-quality-${{ github.event.pull_request.number || github.event.merge_group.head_sha }}
cancel-in-progress: true

jobs:
public-quality:
if: github.repository == 'HMG-AI/HMG-public'
name: hmg-public-quality
uses: ./.github/workflows/reusable-hmg-public-quality.yml
with:
base_sha: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
head_sha: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
head_ref: ${{ github.event.pull_request.head.ref || github.event.merge_group.head_ref }}
permissions:
contents: read
78 changes: 66 additions & 12 deletions .github/workflows/reusable-hmg-public-promotion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,55 @@ jobs:
set -Eeuo pipefail
echo "sha=$(git -C candidate rev-parse HEAD)" >> "${GITHUB_OUTPUT}"

- name: Synchronize the exact reviewed public export without credentials
shell: bash
run: |
set -Eeuo pipefail
readonly export_dir="${GITHUB_WORKSPACE}/source/export"
readonly candidate_dir="${GITHUB_WORKSPACE}/candidate"
if [[ ! -d "${export_dir}" || ! -f "${export_dir}/README.md" ]]; then
echo "::error::The exact source checkout does not contain a complete public export"
exit 2
fi

bash source/scripts/check-export-leak.sh

# Target governance and all previously reviewed provenance records are
# target-owned. They survive the source-owned exact export mirror.
rsync -a --delete \
--exclude=target/ \
--exclude=node_modules/ \
--exclude=__pycache__/ \
--exclude='*.pyc' \
--exclude=.DS_Store \
--exclude=Cachedir.tag \
--exclude=CACHEDIR.TAG \
--exclude=.git/ \
--exclude=.github/ \
--exclude=commitlint.config.mjs \
--exclude=release-provenance/ \
"${export_dir}/" \
"${candidate_dir}/"

openapi_file="${candidate_dir}/openapi/hmg-server.yaml"
if [[ -f "${openapi_file}" ]]; then
cleaned_openapi="${RUNNER_TEMP}/hmg-public-openapi.yaml"
sed -E \
-e '/disable_adaptive_recall:/d' \
-e '/adaptive cheap-path gate/d' \
-e '/disable_embedding_noise_gate:/d' \
-e '/embedding noise prototype gate/d' \
-e '/embedding_noise_gate_suppressed_vector:/d' \
-e '/disable_symbol_fusion_boost:/d' \
-e '/symbol-query weighted RRF/d' \
-e '/disable_seed_length_penalty:/d' \
-e '/length-based penalty on seed-stage/d' \
"${openapi_file}" > "${cleaned_openapi}"
mv "${cleaned_openapi}" "${openapi_file}"
fi

bash source/scripts/check-export-leak.sh --dir="${candidate_dir}"

- name: Enforce target-owned governance boundary
shell: bash
run: |
Expand Down Expand Up @@ -286,21 +335,22 @@ jobs:
echo "asset_dir=${asset_dir}" >> "${GITHUB_OUTPUT}"
echo "asset_set_sha256=${asset_set_sha256}" >> "${GITHUB_OUTPUT}"

- name: Add only minimal binary release provenance to the candidate
- name: Add signed-release metadata beside the exact public export
shell: bash
env:
ARTIFACT_NAME: ${{ steps.source-run.outputs.artifact_name }}
ASSET_SET_SHA256: ${{ steps.assets.outputs.asset_set_sha256 }}
RELEASE_TAG: ${{ inputs.release_tag }}
SOURCE_RUN_URL: ${{ steps.source-run.outputs.url }}
SOURCE_SHA: ${{ steps.source.outputs.sha }}
TARGET_BASE_SHA: ${{ steps.target-base.outputs.sha }}
run: |
set -Eeuo pipefail
readonly metadata_directory='candidate/release-provenance'
readonly metadata_relative_path="release-provenance/${RELEASE_TAG}.json"
readonly metadata_path="candidate/${metadata_relative_path}"
if [[ -L "${metadata_directory}" || -e "${metadata_path}" || -L "${metadata_path}" ]]; then
echo "::error::Release provenance path already exists or is unsafe: ${metadata_relative_path}"
if [[ -L "${metadata_directory}" || -L "${metadata_path}" ]]; then
echo "::error::Release provenance path is unsafe: ${metadata_relative_path}"
exit 2
fi
mkdir -p "${metadata_directory}"
Expand All @@ -311,27 +361,31 @@ jobs:
--arg source_repository 'HMG-AI/HMG' \
--arg source_sha "${SOURCE_SHA}" \
--arg source_workflow_run "${SOURCE_RUN_URL}" \
--arg target_base_sha "${TARGET_BASE_SHA}" \
--arg version "${RELEASE_TAG#v}" \
--argjson asset_count 17 \
'{
schema_version: "hmg-public-binary-release-provenance/v1",
schema_version: "hmg-public-binary-release-provenance/v2",
release_tag: $release_tag,
version: $version,
source_repository: $source_repository,
source_sha: $source_sha,
source_workflow_run: $source_workflow_run,
source_artifact: $artifact,
asset_count: $asset_count,
asset_set_sha256: $asset_set_sha256
asset_set_sha256: $asset_set_sha256,
target_base_sha: $target_base_sha
}' > "${metadata_path}"
candidate_status="$(
provenance_status="$(
git -C candidate status \
--porcelain=v1 \
--untracked-files=all
--untracked-files=all \
-- release-provenance
)"
if [[ "${candidate_status}" != "?? ${metadata_relative_path}" ]]; then
echo "::error::Binary promotion may add only ${metadata_relative_path}"
printf '%s\n' "${candidate_status}"
if [[ "${provenance_status}" != "?? ${metadata_relative_path}" && \
"${provenance_status}" != " M ${metadata_relative_path}" ]]; then
echo "::error::Promotion must preserve provenance history and add or replace only ${metadata_relative_path}"
printf '%s\n' "${provenance_status}"
exit 2
fi

Expand Down Expand Up @@ -873,12 +927,12 @@ jobs:

pr_title="chore(public): promote HMG ${RELEASE_TAG}"
printf -v pr_body '%s\n\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n%s' \
'Automated protected binary-only promotion from a verified HMG Actions artifact.' \
'Automated protected promotion of one exact HMG public export and its verified Actions artifact.' \
"- Source repository: \`HMG-AI/HMG\`" \
"- Source tag: \`${RELEASE_TAG}\`" \
"- Source SHA: \`${SOURCE_SHA}\`" \
"- Source public artifact build run: ${SOURCE_RUN_URL}" \
'- Repository scope: audit metadata only; no source, website, or npm synchronization' \
'- Repository scope: exact source export plus one signed release-provenance record; target governance and provenance history are preserved' \
"- Candidate tree: \`${CANDIDATE_TREE}\`" \
"- Draft asset set SHA-256: \`${ASSET_SET_SHA256}\`" \
"- Provenance key: \`${KEY_ID}\`" \
Expand Down
29 changes: 22 additions & 7 deletions .github/workflows/reusable-hmg-public-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,25 @@ jobs:
fi
fi
readonly expected_path="release-provenance/${release_tag}.json"
changed="$(git -C candidate diff --name-only "${BASE_SHA}" "${HEAD_SHA}")"
if [[ "${changed}" != "${expected_path}" || \
"$(git -C candidate diff --diff-filter=A --name-only "${BASE_SHA}" "${HEAD_SHA}")" != "${expected_path}" || \
protected_changes="$(
git -C candidate diff --name-only "${BASE_SHA}" "${HEAD_SHA}" -- \
.github \
commitlint.config.mjs
)"
provenance_changes="$(
git -C candidate diff --name-only "${BASE_SHA}" "${HEAD_SHA}" -- \
release-provenance
)"
if [[ -n "${protected_changes}" ]]; then
echo "::error::Automated promotion must preserve target-owned governance"
printf '%s\n' "${protected_changes}"
exit 2
fi
if [[ "${provenance_changes}" != "${expected_path}" || \
! -f "candidate/${expected_path}" || \
-L "candidate/${expected_path}" ]]; then
echo "::error::Automated binary promotion may add only ${expected_path}"
printf '%s\n' "${changed}"
echo "::error::Automated promotion must preserve provenance history and add or replace only ${expected_path}"
printf '%s\n' "${provenance_changes}"
exit 2
fi

Expand Down Expand Up @@ -310,6 +322,7 @@ jobs:
--arg release_tag "${release_tag}" \
--arg source_sha "${source_sha}" \
--arg source_workflow_run "${workflow_run}" \
--arg target_base_sha "${BASE_SHA}" \
--arg version "${release_tag#v}" '
keys == [
"asset_count",
Expand All @@ -320,17 +333,19 @@ jobs:
"source_repository",
"source_sha",
"source_workflow_run",
"target_base_sha",
"version"
] and
.schema_version == "hmg-public-binary-release-provenance/v1" and
.schema_version == "hmg-public-binary-release-provenance/v2" and
.release_tag == $release_tag and
.version == $version and
.source_repository == "HMG-AI/HMG" and
.source_sha == $source_sha and
.source_workflow_run == $source_workflow_run and
.source_artifact == $artifact and
.asset_count == 17 and
.asset_set_sha256 == $asset_set_sha256
.asset_set_sha256 == $asset_set_sha256 and
.target_base_sha == $target_base_sha
' "${metadata_file}" >/dev/null

set +e
Expand Down
4 changes: 2 additions & 2 deletions GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ The organization required quality workflow also runs an independent, read-only w

The HMG public promotion trust boundary is split into three fail-closed stages:

1. The source-side preparation job proves the exact SHA from a successful `Build public release artifacts (no publish)` workflow-dispatch run is reachable from protected `HMG/main`, binds its frozen version to the canonical stable release tag, and independently validates the exact 17-asset set. The candidate starts from protected `HMG-public/main` and may add only `release-provenance/v<version>.json`, a secret-free binary-release audit record; it cannot synchronize HMG source, website, npm, or other product files. Preparation then computes the aggregate asset digest and full candidate Git tree.
1. The source-side preparation job proves the exact SHA from a successful `Build public release artifacts (no publish)` workflow-dispatch run is reachable from protected `HMG/main`, binds its frozen version to the canonical stable release tag, independently validates the exact 17-asset set, and mirrors that SHA's reviewed `export/` tree into a candidate based on protected `HMG-public/main`. Target-owned `.github` policy, `commitlint.config.mjs`, and all historical `release-provenance/` records are excluded from deletion and must remain byte-for-byte unchanged; the candidate adds `release-provenance/v<version>.json`, or deterministically replaces that same record only when recovering a pre-publication failure. Provenance schema v2 binds the immutable target base SHA, so recovery never needs a cleanup pull request and cannot silently reuse a receipt from a different target tree. Preparation then computes the aggregate asset digest and signs the full source-plus-provenance candidate Git tree. An unchanged retry reuses the same deterministic branch commit and existing approval; after merge, publisher retries resume from that same approved commit and never create a second release pull request.
2. An isolated job with no repository or Actions permissions accepts only those validated scalar values, verifies the configured Ed25519 private-key fingerprint, and signs the canonical provenance statement. The target promotion commit records exactly the eight governed provenance trailers. The target-scoped write token is not available until the last job reconciles the deterministic staging transport and same-repository pull request.
3. The required target quality workflow has read-only contents permission, binds reusable inputs to the `pull_request` or `merge_group` event payload, shellchecks all target-owned release policy scripts, runs the target classifier and publisher policy fixtures, and classifies any HMG trailers across `BASE..HEAD`. The script fixtures run for every candidate, including ordinary governance pull requests, without secrets or persisted checkout credentials. A release pull request must be one promotion commit directly on its event base; a governed merge group must contain exactly one complete promotion commit and the event-head tree must equal the signed tree, so a changed base or another queued change fails closed. Partial, duplicate, or unknown HMG trailers fail. The gate also verifies the stable version increase, reviewed public-key ID, detached signature, target governance boundary, public builds, and absence of the final tag. It never receives a write credential and never reads the write-visible staging draft. After protected merge, the `hmg-public-release` environment publisher re-verifies the signed commit and exact staging bytes before creating the final tag once and publishing an immutable release.
3. The repository-specific `required-hmg-public-quality.yml` wrapper has read-only contents permission and calls `reusable-hmg-public-quality.yml` from the same immutable organization commit. A dedicated organization ruleset must target only `HMG-public` and pin that wrapper to a reviewed full SHA; the generic organization quality ruleset remains independently required and unchanged. The target gate binds reusable inputs to the `pull_request` or `merge_group` event payload, shellchecks all target-owned release policy scripts, runs the target classifier and publisher policy fixtures, and classifies any HMG trailers across `BASE..HEAD`. The script fixtures run for every candidate, including ordinary governance pull requests, without secrets or persisted checkout credentials. A release pull request must be one promotion commit directly on its event base; a governed merge group must contain exactly one complete promotion commit and the event-head tree must equal the signed tree, so a changed base or another queued change fails closed. Partial, duplicate, or unknown HMG trailers fail. The gate also verifies the reviewed public-key ID, detached signature, target governance boundary, public builds, and absence of the final tag. It never receives a write credential and never reads the write-visible staging draft. After protected merge, the `hmg-public-release` environment publisher re-verifies the signed commit and exact staging bytes before creating the final tag once and publishing an immutable release.

The canonical promotion trailers are `HMG-Source-Repository`, `HMG-Source-Tag`, `HMG-Source-SHA`, `HMG-Workflow-Run`, `HMG-Asset-Set-SHA256`, `HMG-Candidate-Tree`, `HMG-Provenance-Key-ID`, and `HMG-Provenance-Signature-Ed25519`. The final `vMAJOR.MINOR.PATCH` tag must not exist before the protected promotion merges. Final tags are never moved or deleted, release assets are never overwritten, and a staging draft is transport state rather than review evidence. An interrupted staging asset in GitHub's `starter` state may be deleted by its immutable asset ID and uploaded again without clobber; every other non-`uploaded` state fails closed.

Expand Down
Loading