Compare commits

...
Author SHA1 Message Date
修雨 eedc41ac54 Merge branch 'main' into revert-730-codex/fix-release-version-verifier 2026-07-21 18:52:05 +08:00
修雨 29dceec5ce fix(release): check commit statuses in Code Admission gates (#732)
The "AI Behavior" context is reported as a commit status (via
github.rest.repos.createCommitStatus) rather than a check run, but the
Code Admission gates only queried check runs via
github.rest.checks.listForRef. This caused every release to fail with
"missing: AI Behavior" since the context was never found.

Add a commit-status query after the check-run loop in both the preflight
and sealed-commit Code Admission gates. Statuses are merged only for
required contexts not already covered by a check run, preserving the
existing check-run precedence.
2026-07-21 18:48:03 +08:00
SCzheng b78a0dee47 Revert "fix(release): validate packaged version at runtime" 2026-07-21 19:46:32 +09:00
修雨 807191396e Merge pull request #730 from DingTalk-Real-AI/codex/fix-release-version-verifier
fix(release): validate packaged version at runtime
2026-07-21 18:12:40 +08:00
4 changed files with 50 additions and 92 deletions
+44 -11
View File
@@ -358,6 +358,25 @@ jobs:
latestByName.set(run.name, run);
}
}
// Also check commit statuses — some contexts (e.g. "AI Behavior")
// are reported as commit statuses rather than check runs.
const statuses = await github.paginate(
github.rest.repos.listCommitStatusesForRef,
{ owner, repo, ref: sha, per_page: 100 },
);
const seenStatusContexts = new Set();
for (const status of statuses) {
if (!requiredContexts.includes(status.context) || seenStatusContexts.has(status.context)) {
continue;
}
seenStatusContexts.add(status.context);
if (!latestByName.has(status.context)) {
latestByName.set(status.context, {
conclusion: status.state,
status: "completed",
});
}
}
const missing = requiredContexts.filter((name) => !latestByName.has(name));
const nonSuccess = requiredContexts.flatMap((name) => {
const run = latestByName.get(name);
@@ -753,6 +772,25 @@ jobs:
latestByName.set(run.name, run);
}
}
// Also check commit statuses — some contexts (e.g. "AI Behavior")
// are reported as commit statuses rather than check runs.
const statuses = await github.paginate(
github.rest.repos.listCommitStatusesForRef,
{ owner, repo, ref: sha, per_page: 100 },
);
const seenStatusContexts = new Set();
for (const status of statuses) {
if (!requiredContexts.includes(status.context) || seenStatusContexts.has(status.context)) {
continue;
}
seenStatusContexts.add(status.context);
if (!latestByName.has(status.context)) {
latestByName.set(status.context, {
conclusion: status.state,
status: "completed",
});
}
}
const missing = requiredContexts.filter((name) => !latestByName.has(name));
const nonSuccess = requiredContexts.flatMap((name) => {
const run = latestByName.get(name);
@@ -1047,9 +1085,7 @@ jobs:
rm -f "$RUNNER_TEMP/dws-developer-id-password"
- name: Verify final release artifacts
run: |
DWS_PACKAGE_DIST_DIR="$GITHUB_WORKSPACE/dist" \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh" "$RELEASE_VERSION"
run: ./scripts/release/verify-release-artifacts.sh "$RELEASE_VERSION"
- name: Verify npm package before sealing GitHub Release
run: ./scripts/release/verify-package-managers.sh --npm-only --expected-version "$RELEASE_VERSION"
@@ -1174,9 +1210,7 @@ jobs:
path: dist
- name: Verify finalized release artifacts before publication
run: |
DWS_PACKAGE_DIST_DIR="$GITHUB_WORKSPACE/dist" \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh" "$RELEASE_VERSION"
run: ./scripts/release/verify-release-artifacts.sh "$RELEASE_VERSION"
- name: Verify npm package before sealing GitHub Release
run: ./scripts/release/verify-package-managers.sh --npm-only --expected-version "$RELEASE_VERSION"
@@ -1272,7 +1306,7 @@ jobs:
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/download-github-release-assets.sh" \
"$RELEASE_VERSION" "$published_dir"
DWS_PACKAGE_DIST_DIR="$published_dir" \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh" "$RELEASE_VERSION"
./scripts/release/verify-release-artifacts.sh "$RELEASE_VERSION"
for asset in "$published_dir"/dws-* "$published_dir"/checksums.txt; do
if [ "$IS_RECOVERY" = "true" ] && ! cmp -s "$asset" "dist/$(basename "$asset")"; then
echo "Public recovery asset differs from this run's sealed artifact: $(basename "$asset")" >&2
@@ -1378,7 +1412,7 @@ jobs:
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/download-github-release-assets.sh" \
"$RELEASE_VERSION" "$sealed_upload"
DWS_PACKAGE_DIST_DIR="$sealed_upload" \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh" "$RELEASE_VERSION"
./scripts/release/verify-release-artifacts.sh "$RELEASE_VERSION"
for remote_asset in "$sealed_upload"/dws-* "$sealed_upload"/checksums.txt; do
local_asset="dist/$(basename "$remote_asset")"
cmp -s "$local_asset" "$remote_asset" || {
@@ -1448,7 +1482,7 @@ jobs:
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/download-github-release-assets.sh" \
"$RELEASE_VERSION" "$immutable_dir"
DWS_PACKAGE_DIST_DIR="$immutable_dir" \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh" "$RELEASE_VERSION"
./scripts/release/verify-release-artifacts.sh "$RELEASE_VERSION"
for immutable_asset in "$immutable_dir"/dws-* "$immutable_dir"/checksums.txt; do
sealed_asset="dist/$(basename "$immutable_asset")"
cmp -s "$sealed_asset" "$immutable_asset" || {
@@ -1570,8 +1604,7 @@ jobs:
--pattern 'dws-*' \
--pattern 'checksums.txt' \
--clobber
DWS_PACKAGE_DIST_DIR="$GITHUB_WORKSPACE/dist" \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh" "$RELEASE_VERSION"
./scripts/release/verify-release-artifacts.sh "$RELEASE_VERSION"
DWS_PACKAGE_SOURCE_ROOT="$GITHUB_WORKSPACE" \
DWS_PACKAGE_DIST_DIR="$GITHUB_WORKSPACE/dist" \
./scripts/release/stage-npm-package.sh "$RELEASE_VERSION"
+4 -42
View File
@@ -72,7 +72,7 @@ else
exit 1
fi
verify_binary_archive() {
verify_binary_version() {
asset="$1"
extract_dir="$tmp/extract-${asset}"
mkdir -p "$extract_dir"
@@ -94,52 +94,14 @@ verify_binary_archive() {
printf '%s does not contain the expected dws binary\n' "$asset" >&2
return 1
}
LC_ALL=C grep -aFq "v$SEMVER" "$binary" || {
printf '%s binary does not contain expected version marker v%s\n' \
"$asset" "$SEMVER" >&2
strings "$binary" | grep -Fqx "v$SEMVER" || {
printf '%s binary does not embed expected version v%s\n' "$asset" "$SEMVER" >&2
return 1
}
# Execute one native artifact and validate the CLI's public version contract.
# `strings` output has no symbol boundaries, so requiring the injected version
# to appear on an exact line can reject a correct Go binary when adjacent
# printable bytes are coalesced into the same output line.
if [ "$asset" = dws-linux-amd64.tar.gz ]; then
command -v python3 >/dev/null 2>&1 || {
printf 'python3 is required to validate the release binary version\n' >&2
return 1
}
version_json="$extract_dir/version.json"
isolated_home="$extract_dir/home"
mkdir -p "$isolated_home"
if ! env -i HOME="$isolated_home" PATH=/usr/bin:/bin LANG=C.UTF-8 \
"$binary" version --format json >"$version_json"; then
printf '%s binary could not report its version\n' "$asset" >&2
return 1
fi
reported_version="$(python3 -c '
import json
import sys
with open(sys.argv[1], encoding="utf-8") as handle:
payload = json.load(handle)
version = payload.get("version")
if not isinstance(version, str) or not version:
raise SystemExit(1)
print(version)
' "$version_json")" || {
printf '%s binary returned an invalid version payload\n' "$asset" >&2
return 1
}
[ "$reported_version" = "v$SEMVER" ] || {
printf '%s binary reports version %s, expected v%s\n' \
"$asset" "$reported_version" "$SEMVER" >&2
return 1
}
fi
}
for asset in $EXPECTED_PLATFORM_ASSETS; do
verify_binary_archive "$asset"
verify_binary_version "$asset"
done
printf 'Release artifacts verified for v%s.\n' "$SEMVER"
-17
View File
@@ -1948,23 +1948,6 @@ printf '%s\n' "$*" >> "$SLEEP_CALL_LOG"
}
}
func TestReleaseWorkflowUsesTrustedArtifactVerifierForRecovery(t *testing.T) {
t.Parallel()
workflow := readReleaseWorkflow(t)
trusted := `"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh"`
if got := strings.Count(workflow, trusted); got != 6 {
t.Fatalf("trusted artifact verifier call count = %d, want 6", got)
}
workspaceDist := `DWS_PACKAGE_DIST_DIR="$GITHUB_WORKSPACE/dist" \` + "\n" +
` "$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-release-artifacts.sh"`
if got := strings.Count(workflow, workspaceDist); got != 3 {
t.Fatalf("workspace dist-bound trusted verifier call count = %d, want 3", got)
}
if strings.Contains(workflow, "./scripts/release/verify-release-artifacts.sh") {
t.Fatal("release workflow still executes the sealed tag's artifact verifier")
}
}
func TestReleaseStaysDraftUntilFinalizedAssetDigestsMatch(t *testing.T) {
t.Parallel()
+2 -22
View File
@@ -30,18 +30,7 @@ func writeVersionedReleaseArchive(t *testing.T, dist, asset, version string) {
if strings.HasSuffix(asset, ".zip") {
binary = "dws.exe"
}
content := []byte("fake release binary\n" + version + "\n")
if asset == "dws-linux-amd64.tar.gz" {
content = []byte(fmt.Sprintf(`#!/bin/sh
set -eu
if [ "$#" -ne 3 ] || [ "$1" != version ] || [ "$2" != --format ] || [ "$3" != json ]; then
printf 'unexpected version command\n' >&2
exit 2
fi
printf '{"version":"%s"}\n'
`, version))
}
mustWriteFile(t, filepath.Join(stage, binary), content, 0o755)
mustWriteFile(t, filepath.Join(stage, binary), []byte("fake release binary\n"+version+"\n"), 0o755)
if strings.HasSuffix(asset, ".zip") {
mustRun(t, stage, "zip", "-q", filepath.Join(dist, asset), binary)
return
@@ -1959,18 +1948,9 @@ func TestReleaseArtifactVerificationRequiresEveryChecksum(t *testing.T) {
writeReleaseChecksums(t, dist, true)
cmd = exec.Command("sh", r.verify, "v1.2.3")
cmd.Env = append(os.Environ(), "DWS_PACKAGE_DIST_DIR="+dist)
if output, err := cmd.CombinedOutput(); err == nil || !strings.Contains(string(output), "dws-windows-arm64.zip binary does not contain expected version marker v1.2.3") {
if output, err := cmd.CombinedOutput(); err == nil || !strings.Contains(string(output), "dws-windows-arm64.zip binary") {
t.Fatalf("mixed-version archive was not rejected: err=%v\noutput:\n%s", err, output)
}
writeVersionedReleaseArchive(t, dist, "dws-windows-arm64.zip", "v1.2.3")
writeVersionedReleaseArchive(t, dist, "dws-linux-amd64.tar.gz", "v1.2.3-beta.1")
writeReleaseChecksums(t, dist, true)
cmd = exec.Command("sh", r.verify, "v1.2.3")
cmd.Env = append(os.Environ(), "DWS_PACKAGE_DIST_DIR="+dist)
if output, err := cmd.CombinedOutput(); err == nil || !strings.Contains(string(output), "dws-linux-amd64.tar.gz binary reports version v1.2.3-beta.1, expected v1.2.3") {
t.Fatalf("native prefixed version was not rejected: err=%v\noutput:\n%s", err, output)
}
}
func TestReleaseCommandValidatesThenPushesAnnotatedTag(t *testing.T) {