Skip to content

Commit 589de9d

Browse files
authored
fix(notify): read draft assets by release ID (#120)
Resolve staged helper releases through GraphQL so rollout can read a private draft before finalization. Download only validated asset IDs, verify server digests locally, and keep finalization retries draft-aware without changing permissions or publication semantics. Preserve helper runtime, wire formats, pairing, namespace behavior, Decision 024, and upload, download, and roundtrip evidence semantics.
1 parent f3a823d commit 589de9d

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

‎.github/scripts/notify-publish-safety.rb‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,9 @@ def resume_action(existing_digest, expected_digest, mutable:)
413413
finalize_text.include?("gh api graphql") &&
414414
finalize_text.include?("databaseId") &&
415415
finalize_text.include?('releases/${release_id}') &&
416+
finalize_text.include?('releases/assets/${evidence_id}') &&
417+
finalize_text.include?('Accept: application/octet-stream') &&
418+
!finalize_text.include?('gh release download "$RELEASE_TAG"') &&
416419
finalize_text.include?("|| return 1") &&
417420
public_branch && public_exit && release_edit && public_exit < release_edit,
418421
"rollout evidence and release finalization must be read-only once public")
@@ -522,6 +525,16 @@ def resume_action(existing_digest, expected_digest, mutable:)
522525

523526
assert_policy((%w[release-binaries image-ready] - jobs.fetch("rollout-verify").fetch("needs")).empty?,
524527
"published rollout must follow complete draft staging and immutable image selection")
528+
rollout_text = flattened_step_text(jobs.fetch("rollout-verify"))
529+
assert_policy(rollout_text.include?("gh api graphql") &&
530+
rollout_text.include?("databaseId") &&
531+
rollout_text.include?('releases/${release_id}') &&
532+
rollout_text.include?('releases/assets/${asset_id}') &&
533+
rollout_text.include?('Accept: application/octet-stream') &&
534+
rollout_text.include?('test "$matches" = 1') &&
535+
rollout_text.include?('^sha256:[0-9a-f]{64}$') &&
536+
!rollout_text.include?('gh release download "$RELEASE_TAG"'),
537+
"published rollout must resolve draft assets by validated release and asset IDs")
525538
assert_policy((%w[rollout-verify image-ready] - jobs.fetch("finalize-release").fetch("needs")).empty?,
526539
"release finalization must follow the published rollback proof")
527540
assert_policy((%w[finalize-release image-ready] - jobs.fetch("verify-published").fetch("needs")).empty?,

‎.github/workflows/docker.yml‎

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,8 +1104,48 @@ jobs:
11041104
IMAGE_DIGEST: ${{ needs.image-ready.outputs.digest }}
11051105
run: |
11061106
set -euo pipefail
1107+
resolve_release() {
1108+
repository_owner=${GITHUB_REPOSITORY%%/*}
1109+
repository_name=${GITHUB_REPOSITORY#*/}
1110+
# GraphQL includes drafts, unlike the REST release-by-tag endpoint.
1111+
# Keep the query literal so shell expansion cannot alter its variables.
1112+
# shellcheck disable=SC2016
1113+
lookup=$(gh api graphql \
1114+
-F owner="$repository_owner" -F name="$repository_name" -F tag="$RELEASE_TAG" \
1115+
-f query='query($owner: String!, $name: String!, $tag: String!) {
1116+
repository(owner: $owner, name: $name) {
1117+
release(tagName: $tag) { databaseId }
1118+
}
1119+
}') || return 1
1120+
jq -e '.data.repository != null and ((.errors // []) | length == 0)' <<<"$lookup" >/dev/null || return 1
1121+
release_id=$(jq -r '.data.repository.release.databaseId // empty' <<<"$lookup") || return 1
1122+
printf '%s\n' "$release_id" | grep -Eq '^[1-9][0-9]*$' || return 1
1123+
gh api "repos/${GITHUB_REPOSITORY}/releases/${release_id}" || return 1
1124+
}
1125+
1126+
release_json=$(resolve_release)
1127+
test "$(jq -r .tag_name <<<"$release_json")" = "$RELEASE_TAG"
1128+
test "$(jq -r .prerelease <<<"$release_json")" = false
1129+
while IFS= read -r existing_name; do
1130+
jq -e --arg name "$existing_name" '.release_assets | index($name) != null' "$RELEASE_SPEC" >/dev/null
1131+
done < <(jq -r '.assets[].name' <<<"$release_json")
1132+
11071133
mkdir -p /tmp/vaultsync-notify-release
1108-
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --dir /tmp/vaultsync-notify-release
1134+
mapfile -t required_names < <(
1135+
jq -r '.release_assets[] | select(. != "ROLLOUT-EVIDENCE.txt")' "$RELEASE_SPEC"
1136+
)
1137+
for name in "${required_names[@]}"; do
1138+
matches=$(jq -r --arg name "$name" '[.assets[] | select(.name == $name)] | length' <<<"$release_json")
1139+
test "$matches" = 1
1140+
asset_id=$(jq -r --arg name "$name" '.assets[] | select(.name == $name) | .id' <<<"$release_json")
1141+
asset_digest=$(jq -r --arg name "$name" '.assets[] | select(.name == $name) | .digest' <<<"$release_json")
1142+
printf '%s\n' "$asset_id" | grep -Eq '^[1-9][0-9]*$'
1143+
printf '%s\n' "$asset_digest" | grep -Eq '^sha256:[0-9a-f]{64}$'
1144+
target="/tmp/vaultsync-notify-release/$name"
1145+
gh api --method GET -H 'Accept: application/octet-stream' \
1146+
"repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}" > "$target"
1147+
test "sha256:$(sha256sum "$target" | awk '{print $1}')" = "$asset_digest"
1148+
done
11091149
(cd /tmp/vaultsync-notify-release && sha256sum -c SHA256SUMS)
11101150
jq -e --arg sha "$RELEASE_SHA" --arg digest "$IMAGE_DIGEST" \
11111151
'.source_commit == $sha and .image_index_digest == $digest' \
@@ -1213,7 +1253,10 @@ jobs:
12131253
if [ -n "$evidence_digest" ]; then
12141254
rm -rf /tmp/existing-rollout
12151255
mkdir -p /tmp/existing-rollout
1216-
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --pattern "$evidence_name" --dir /tmp/existing-rollout
1256+
evidence_id=$(jq -r --arg name "$evidence_name" '.assets[] | select(.name == $name) | .id' <<<"$release_json")
1257+
printf '%s\n' "$evidence_id" | grep -Eq '^[1-9][0-9]*$'
1258+
gh api --method GET -H 'Accept: application/octet-stream' \
1259+
"repos/${GITHUB_REPOSITORY}/releases/assets/${evidence_id}" > "/tmp/existing-rollout/$evidence_name"
12171260
test "sha256:$(sha256sum "/tmp/existing-rollout/$evidence_name" | awk '{print $1}')" = "$evidence_digest"
12181261
grep -qx "source_commit=${RELEASE_SHA}" /tmp/existing-rollout/$evidence_name
12191262
grep -qx "release_tag=${RELEASE_TAG}" /tmp/existing-rollout/$evidence_name

0 commit comments

Comments
 (0)