Compare commits

..
Author SHA1 Message Date
DWS Release Bot 7a41cfdaa5 chore: update formula for v1.0.53 2026-07-21 11:46:42 +00:00
修雨 2f0f32f56f Merge pull request #739 from DingTalk-Real-AI/fix/release-artifact-raw-version-check
fix(release): verify packaged artifact versions from raw binary bytes
2026-07-21 19:25:39 +08:00
修雨 068d9ff2f5 fix(release): verify packaged artifact versions from raw binary bytes 2026-07-21 19:25:14 +08:00
修雨 418928b9a5 Merge pull request #736 from DingTalk-Real-AI/chore/changelog-v1.0.53-stable
docs(changelog): finalize v1.0.53 stable section
2026-07-21 19:00:26 +08:00
修雨 b047b2c3c9 docs(changelog): fold post-beta.7 entries into v1.0.53 stable section 2026-07-21 18:59:56 +08:00
修雨 a516e5f54a Merge pull request #735 from sczheng189/codex/fix-stable-version-verification
fix(release): verify package versions from raw binaries
2026-07-21 18:55:54 +08:00
修雨 70e4e75c66 Merge branch 'main' into codex/fix-stable-version-verification 2026-07-21 18:55:31 +08:00
修雨 1116916b24 Merge pull request #734 from DingTalk-Real-AI/revert-732-fix/release-admission-commit-statuses
Revert "fix(release): check commit statuses in Code Admission gates"
2026-07-21 18:53:57 +08:00
修雨 c0c81b4d70 Merge pull request #733 from DingTalk-Real-AI/revert-730-codex/fix-release-version-verifier
Revert "fix(release): validate packaged version at runtime"
2026-07-21 18:53:53 +08:00
修雨 eedc41ac54 Merge branch 'main' into revert-730-codex/fix-release-version-verifier 2026-07-21 18:52:05 +08:00
zhengyubai c14e24569c fix(release): verify package versions from raw binaries 2026-07-21 19:49:18 +09:00
SCzheng 8add2c00cf Revert "fix(release): check commit statuses in Code Admission gates (#732)"
This reverts commit 29dceec5ce.
2026-07-21 19:48:47 +09: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
7 changed files with 54 additions and 113 deletions
+6 -11
View File
@@ -1047,9 +1047,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 +1172,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 +1268,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 +1374,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 +1444,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 +1566,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"
+3 -5
View File
@@ -6,13 +6,9 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
## [Unreleased]
### Changed
- **Relaxed stable promotion contract** — a stable release still requires a delivered, non-withdrawn beta baseline in its commit history, but no longer requires a byte-identical tree with that beta; reviewed commits merged to `main` after the beta can now ship in the stable release. Local releases now accept any sealed commit contained in `main` history and push only the release tag, so `main` is never frozen during the beta-to-stable window.
## [1.0.53] - 2026-07-21
This release promotes the sealed `v1.0.53-beta.7` contents to stable. It adds enterprise onboarding, declarative shortcuts, Sheet/Aitable writes, multi-account profiles, and broader personal IM events, while hardening authentication and the guarded release path.
This release promotes the validated `v1.0.53-beta.7` baseline to stable. It adds enterprise onboarding, declarative shortcuts, Sheet/Aitable writes, multi-account profiles, and broader personal IM events, while hardening authentication and the guarded release path.
### Added
@@ -25,12 +21,14 @@ This release promotes the sealed `v1.0.53-beta.7` contents to stable. It adds en
- **Personal event output contract** (#651) — `event consume` now emits event-specific top-level structured fields; scripts that consumed the former transport envelope must use the flat fields or select `-f raw`, while `--debug-raw-events` retains the diagnostic envelope.
- **Guarded release lifecycle** — beta/stable publication now uses explicit promotion, immutable delivery proofs, protected recovery, and tag-bound optional OSS policy; an unprovisioned OSS mirror is sealed as `deferred` so GitHub, npm, and Homebrew are not blocked.
- **Relaxed stable promotion contract** (#729) — a stable release still requires a delivered, non-withdrawn beta baseline in its commit history, but no longer requires a byte-identical tree with that beta; reviewed commits merged to `main` after the beta can now ship in the stable release. Local releases now accept any sealed commit contained in `main` history and push only the release tag, so `main` is never frozen during the beta-to-stable window.
### Fixed
- **Authentication and credential reliability** — organization-policy denials stop before mutation or polling, long-running clients reload and refresh access tokens consistently, concurrent credential writes are atomic, and Windows portable-auth commands fail before reading or writing unsupported credential bundles.
- **Command validation and compatibility** — invalid Sheet/task targets fail locally, IM shortcuts preserve AI-tag and alias compatibility, and Aitable import uploads require and forward a positive file size.
- **Release publication reliability** — GitHub draft publication is bound to one verified release ID and exact assets, preflight uses isolated installer worktrees, guarded local tags remain compatible, cloud planning fingerprints the actual allocated release refs, and npm channel verification waits for bounded registry propagation without moving tags.
- **Package-manager version verification** (#735) — npm-vendored, Homebrew-installed, and packaged release binaries are now verified by searching their raw bytes for the injected version marker, so a correctly versioned stable binary is no longer rejected when the short version marker coalesces with adjacent printable linker metadata; incorrect or missing markers still fail closed.
## [1.0.53-beta.7] - 2026-07-21
+13 -11
View File
@@ -1,32 +1,33 @@
class DingtalkWorkspaceCli < Formula
desc "Automate DingTalk workspace tasks from the terminal"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.52"
version "1.0.53"
license "Apache-2.0"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.52/dws-darwin-arm64.tar.gz"
sha256 "4f6b4d064a76bcefac42feb5f356253fe43f9499b8cec9d2cdf202e7d3b9b60c"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.53/dws-darwin-arm64.tar.gz"
sha256 "504df1b720f0114b69b31bf57ed8e25c7342f28fc8f8a977f7e59e2e724a49e6"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.52/dws-darwin-amd64.tar.gz"
sha256 "abc87128f4b98d0a01ea99235449031971db8fa4ce94167403e3b736c4b81e9a"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.53/dws-darwin-amd64.tar.gz"
sha256 "979937a184c067db67f1e5d104a97de3bfdb6e203169e3a872455c48d819ed70"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.52/dws-linux-arm64.tar.gz"
sha256 "0d357ef0535f99f2f63b5ecbfdee9c32448be2a2c24f3096c03126b3b7570bc5"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.53/dws-linux-arm64.tar.gz"
sha256 "a171684af5d6d4b7d53f8a4e0bb2ec90ad92b5872195558504dc60c6eb7db759"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.52/dws-linux-amd64.tar.gz"
sha256 "b7dfd9a4b3489211359261747ed0cb9c8c261434bb762ad3f76df33bdbabd5cb"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.53/dws-linux-amd64.tar.gz"
sha256 "6523ca7e11d08f7e402d98dd78f85d3c9fed88b123d69fa6d6e49fad9660c08c"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.52/dws-skills.zip"
sha256 "0fa3c8dec500c1659e6480d6772ae901b2d12d24322dd5d7283f016024290c21"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.53/dws-skills.zip"
sha256 "9c5deef3fdbf8d07a054e74c96f6498ff714e574002bc115f3271a7eb5af3e90"
end
def install
@@ -52,6 +53,7 @@ class DingtalkWorkspaceCli < Formula
<<~EOS
Agent Skills are bundled in #{pkgshare}/skills/dws.
Run `dws skill setup` to install them into your Agent directories.
EOS
end
+4 -6
View File
@@ -53,8 +53,6 @@ while [ "$#" -gt 0 ]; do
esac
done
[ -z "$EXPECTED_VERSION" ] || need_cmd strings
TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-package-verify-XXXXXX")"
HOME_AGENT_PARENTS="
.claude
@@ -145,8 +143,8 @@ verify_npm() {
if [ -n "$EXPECTED_VERSION" ]; then
vendor_bin="$npm_prefix/lib/node_modules/dingtalk-workspace-cli/vendor/dws"
need_file "$vendor_bin"
strings "$vendor_bin" | grep -Fqx "v$EXPECTED_VERSION" || \
err "npm-installed binary does not embed expected version v$EXPECTED_VERSION"
LC_ALL=C grep -aFq "v$EXPECTED_VERSION" "$vendor_bin" || \
err "npm-installed binary does not contain expected version marker v$EXPECTED_VERSION"
EXPECTED_VERSION="$EXPECTED_VERSION" node -e '
const pkg = require(process.argv[1]);
if (pkg.version !== process.env.EXPECTED_VERSION) process.exit(1);
@@ -193,8 +191,8 @@ verify_brew() {
[ -x "$prefix/bin/dws" ] || err "brew install did not create $prefix/bin/dws"
"$prefix/bin/dws" --help >/dev/null
if [ -n "$EXPECTED_VERSION" ]; then
strings "$prefix/bin/dws" | grep -Fqx "v$EXPECTED_VERSION" || \
err "Homebrew-installed binary does not embed expected version v$EXPECTED_VERSION"
LC_ALL=C grep -aFq "v$EXPECTED_VERSION" "$prefix/bin/dws" || \
err "Homebrew-installed binary does not contain expected version marker v$EXPECTED_VERSION"
fi
need_file "$prefix/share/dingtalk-workspace-cli-local/skills/dws/SKILL.md"
}
+3 -41
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"
@@ -95,51 +95,13 @@ verify_binary_archive() {
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
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"
+23 -17
View File
@@ -46,6 +46,29 @@ var expectedReleaseAdmissionContexts = []string{
"Mock MCP",
}
func TestPackageManagerVersionVerificationReadsRawBinary(t *testing.T) {
t.Parallel()
scriptPath, err := filepath.Abs(filepath.Join("..", "..", "scripts", "release", "verify-package-managers.sh"))
if err != nil {
t.Fatalf("Abs(verify-package-managers.sh) error = %v", err)
}
data, err := os.ReadFile(scriptPath)
if err != nil {
t.Fatalf("ReadFile(%s) error = %v", scriptPath, err)
}
script := string(data)
for _, binary := range []string{`"$vendor_bin"`, `"$prefix/bin/dws"`} {
want := `LC_ALL=C grep -aFq "v$EXPECTED_VERSION" ` + binary
if !strings.Contains(script, want) {
t.Errorf("package-manager verifier is missing raw binary marker check %q", want)
}
}
if strings.Contains(script, `strings "$vendor_bin"`) || strings.Contains(script, `strings "$prefix/bin/dws"`) {
t.Fatal("package-manager verifier still requires the version marker to occupy a strings(1) line")
}
}
func seedDistArchive(t *testing.T, path string) {
t.Helper()
file, err := os.Create(path)
@@ -1948,23 +1971,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) {