Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c8ff9acd | ||
|
|
706535b41e | ||
|
|
9e88116a2d | ||
|
|
05a306148a |
@@ -2481,13 +2481,16 @@ jobs:
|
||||
value="$(sed -n "s/^$key=//p" "$allocation")"
|
||||
printf '%s=%s\n' "$key" "$value" >> "$GITHUB_OUTPUT"
|
||||
done
|
||||
refs_fingerprint="$(
|
||||
git for-each-ref --format='%(refname)=%(objectname)' \
|
||||
refs/tags/v refs/tags/withdrawn/v \
|
||||
| LC_ALL=C sort \
|
||||
| sha256sum \
|
||||
| awk '{print $1}'
|
||||
)"
|
||||
refs_unsorted="$RUNNER_TEMP/release-refs.unsorted"
|
||||
refs_manifest="$RUNNER_TEMP/release-refs"
|
||||
git for-each-ref --format='%(refname)=%(objectname)' \
|
||||
'refs/tags/v*' 'refs/tags/withdrawn/v*' > "$refs_unsorted"
|
||||
LC_ALL=C sort "$refs_unsorted" > "$refs_manifest"
|
||||
test -s "$refs_manifest" || {
|
||||
echo "release ref manifest is empty after fetching allocated tags" >&2
|
||||
exit 1
|
||||
}
|
||||
refs_fingerprint="$(sha256sum "$refs_manifest" | awk '{print $1}')"
|
||||
echo "release_commit=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "refs_fingerprint=$refs_fingerprint" >> "$GITHUB_OUTPUT"
|
||||
case "$OSS_MIRROR" in enabled|deferred) ;; *) exit 1 ;; esac
|
||||
|
||||
@@ -6,6 +6,28 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.53] - 2026-07-21
|
||||
|
||||
This release promotes the sealed `v1.0.53-beta.6` 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.
|
||||
|
||||
### Added
|
||||
|
||||
- **Enterprise and office command coverage** — adds enterprise creation, employee invitation, and account provisioning commands; 366 declarative service shortcuts; Sheet import commands; and Aitable workflow create/update support with reviewed Schema contracts.
|
||||
- **Multiple accounts in one DingTalk organization** — profiles can distinguish accounts by organization and user, select them explicitly, and log out one account or an entire organization without overwriting another account's credentials.
|
||||
- **Expanded personal IM event subscriptions** (#651) — adds read-receipt, recall, and reaction events for one-to-one and group chats, plus specified-sender subscriptions by staff ID or OpenDingTalk ID.
|
||||
- **Official multi-platform Homebrew channel** — ships separate stable and keg-only beta Formulae for macOS and Linux across amd64 and arm64, with isolated update PRs.
|
||||
|
||||
### Changed
|
||||
|
||||
- **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.
|
||||
|
||||
### 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, and cloud planning fingerprints the actual allocated release refs.
|
||||
|
||||
## [1.0.53-beta.6] - 2026-07-21
|
||||
|
||||
This beta validates guarded local release compatibility and tag-bound OSS deferral so an unprovisioned mirror cannot block the primary release channels.
|
||||
@@ -17,6 +39,7 @@ This beta validates guarded local release compatibility and tag-bound OSS deferr
|
||||
### Fixed
|
||||
|
||||
- **Guarded local release compatibility** — The tag-push Release workflow now accepts the `Channel`-only annotated tags created by the guarded local release entry while continuing to reject any partial cloud-only seal metadata.
|
||||
- **Cloud release tag allocation fingerprint** — Release planning now fingerprints the actual `v*` and `withdrawn/v*` refs fetched from GitHub, matching the seal job's API view instead of hashing an empty non-wildcard ref prefix and rejecting every publish before tag creation.
|
||||
|
||||
## [1.0.53-beta.5] - 2026-07-21
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -836,7 +837,8 @@ func TestReleaseWorkflowPlansAndSealsCurrentMainInTheCloud(t *testing.T) {
|
||||
"persist-credentials: false",
|
||||
`refs/remotes/origin/main)" = "$GITHUB_SHA`,
|
||||
"next-release-version.sh",
|
||||
"refs/tags/withdrawn/v",
|
||||
`'refs/tags/v*' 'refs/tags/withdrawn/v*'`,
|
||||
"release ref manifest is empty after fetching allocated tags",
|
||||
"refs_fingerprint",
|
||||
"Validate the candidate release contract before sealing",
|
||||
"release-contract.sh",
|
||||
@@ -850,6 +852,9 @@ func TestReleaseWorkflowPlansAndSealsCurrentMainInTheCloud(t *testing.T) {
|
||||
if strings.Contains(plan, "contents: write") {
|
||||
t.Error("cloud release planning must remain read-only")
|
||||
}
|
||||
if strings.Contains(plan, "refs/tags/v refs/tags/withdrawn/v") {
|
||||
t.Error("cloud release planning must use wildcard ref patterns that match the seal API prefixes")
|
||||
}
|
||||
|
||||
for _, required := range []string{
|
||||
"name: Seal cloud release tag",
|
||||
@@ -884,6 +889,66 @@ func TestReleaseWorkflowPlansAndSealsCurrentMainInTheCloud(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseFingerprintRefPatternsMatchAllAllocatedTags(t *testing.T) {
|
||||
t.Parallel()
|
||||
repo := t.TempDir()
|
||||
mustRun(t, repo, "git", "init", "-b", "main")
|
||||
mustRun(t, repo, "git", "config", "user.name", "Release Fingerprint Test")
|
||||
mustRun(t, repo, "git", "config", "user.email", "release-fingerprint@example.com")
|
||||
mustWriteFile(t, filepath.Join(repo, "tracked"), []byte("fixture\n"), 0o644)
|
||||
mustRun(t, repo, "git", "add", "tracked")
|
||||
mustRun(t, repo, "git", "commit", "-m", "fixture")
|
||||
|
||||
allocatedTags := []string{
|
||||
"v1.0.52",
|
||||
"v1.0.53-beta.5",
|
||||
"withdrawn/v1.0.51",
|
||||
}
|
||||
for _, tag := range allocatedTags {
|
||||
mustRun(t, repo, "git", "tag", "-a", tag, "-m", "Release "+tag)
|
||||
}
|
||||
mustRun(t, repo, "git", "tag", "-a", "release/v1.0.52", "-m", "unrelated namespace")
|
||||
legacy := exec.Command(
|
||||
"git", "for-each-ref", "--format=%(refname)=%(objectname)",
|
||||
"refs/tags/v", "refs/tags/withdrawn/v",
|
||||
)
|
||||
legacy.Dir = repo
|
||||
legacyOutput, err := legacy.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("legacy git for-each-ref error = %v\noutput:\n%s", err, legacyOutput)
|
||||
}
|
||||
if strings.TrimSpace(string(legacyOutput)) != "" {
|
||||
t.Fatalf("legacy component patterns unexpectedly matched flat release refs:\n%s", legacyOutput)
|
||||
}
|
||||
|
||||
cmd := exec.Command(
|
||||
"git", "for-each-ref", "--format=%(refname)=%(objectname)",
|
||||
"refs/tags/v*", "refs/tags/withdrawn/v*",
|
||||
)
|
||||
cmd.Dir = repo
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("git for-each-ref error = %v\noutput:\n%s", err, output)
|
||||
}
|
||||
got := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
sort.Strings(got)
|
||||
|
||||
want := make([]string, 0, len(allocatedTags))
|
||||
for _, tag := range allocatedTags {
|
||||
object := strings.TrimSpace(mustOutput(t, repo, "git", "rev-parse", "refs/tags/"+tag))
|
||||
want = append(want, "refs/tags/"+tag+"="+object)
|
||||
}
|
||||
sort.Strings(want)
|
||||
if strings.Join(got, "\n") != strings.Join(want, "\n") {
|
||||
t.Fatalf("release ref set differs from the seal API set\ngot:\n%s\nwant:\n%s", strings.Join(got, "\n"), strings.Join(want, "\n"))
|
||||
}
|
||||
workflowDigest := sha256.Sum256([]byte(strings.Join(got, "\n") + "\n"))
|
||||
sealDigest := sha256.Sum256([]byte(strings.Join(want, "\n") + "\n"))
|
||||
if workflowDigest != sealDigest {
|
||||
t.Fatalf("release ref fingerprint differs from seal fingerprint: workflow=%x seal=%x", workflowDigest, sealDigest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseWorkflowAcceptsGuardedLocalTagMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
workflow := readReleaseWorkflow(t)
|
||||
|
||||
Reference in New Issue
Block a user