perf(release): add proof-gated hot transaction

This commit is contained in:
PerishCode
2026-08-18 17:40:10 +08:00
parent 67359b39d8
commit ae9fe176db
16 changed files with 662 additions and 324 deletions
@@ -7,6 +7,10 @@ inputs:
identity-digest:
description: Canonical content identity digest for the shared Closure build
required: true
handoff-enabled:
description: Upload the cross-runner contribution artifact
required: false
default: "true"
min-shell-version:
description: Frozen initial Shell compatibility declaration for this channel
required: true
@@ -37,7 +41,7 @@ runs:
RELEASE_CLOSURE_BUILD_DIGEST: ${{ inputs.identity-digest }}
RELEASE_CLOSURE_BUILD_KIND: shared
RELEASE_CLOSURE_BUILD_TOKEN: shared
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-build/closure-shared/shared-contribution.json
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-closure/shared/shared-contribution.json
RELEASE_CLOSURE_MATERIALIZE: "false"
RELEASE_VERSION: ${{ inputs.release-version }}
run: pnpm exec tools-release resolve-closure-build
@@ -58,7 +62,8 @@ runs:
--json > "$build_dir/build.json"
contribution_path="$(jq -er '.contributionPath' "$build_dir/build.json")"
blob_root="$(jq -er '.blobRoot' "$build_dir/build.json")"
cp "$contribution_path" "$build_dir/shared-contribution.json"
mkdir -p "$RUNNER_TEMP/release-closure/shared"
cp "$contribution_path" "$RUNNER_TEMP/release-closure/shared/shared-contribution.json"
cp -R "$blob_root" "$build_dir/blobs"
- name: Register immutable shared Closure build
@@ -70,7 +75,7 @@ runs:
RELEASE_CLOSURE_BUILD_DIGEST: ${{ inputs.identity-digest }}
RELEASE_CLOSURE_BUILD_KIND: shared
RELEASE_CLOSURE_BUILD_TOKEN: shared
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-build/closure-shared/shared-contribution.json
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-closure/shared/shared-contribution.json
run: pnpm exec tools-release register-closure-build
- name: Publish verified shared Closure blobs
@@ -79,7 +84,7 @@ runs:
env:
RELEASE_CHANNEL: ${{ inputs.channel }}
RELEASE_CLOSURE_BLOB_ROOT: ${{ runner.temp }}/release-build/closure-shared/blobs
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-build/closure-shared/shared-contribution.json
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-closure/shared/shared-contribution.json
RELEASE_CLOSURE_CONTRIBUTION_KIND: shared
RELEASE_CLOSURE_PUBLICATION_JSON_PATH: ${{ runner.temp }}/release-build/closure-shared/publication.json
RELEASE_CLOSURE_BUILD_DIGEST: ${{ inputs.identity-digest }}
@@ -90,10 +95,11 @@ runs:
run: pnpm exec tools-release publish-closure-contribution
- name: Upload shared Closure contract and acceptance blobs
if: ${{ inputs.handoff-enabled == 'true' }}
uses: actions/upload-artifact@v7
with:
name: open-design-${{ inputs.channel }}-closure-shared
path: |
${{ runner.temp }}/release-build/closure-shared/shared-contribution.json
${{ runner.temp }}/release-closure/shared/shared-contribution.json
if-no-files-found: error
retention-days: 1
@@ -0,0 +1,65 @@
name: Stage exact release metadata
description: Merge one Closure graph, publish immutable metadata, and stage its run-scoped acceptance feed
inputs:
exact-name:
required: true
release-version:
required: true
outputs:
acceptance_latest_url:
value: ${{ steps.acceptance_feed.outputs.latest_url }}
release_state:
value: ${{ steps.distribute.outputs.release_state }}
version_metadata_url:
value: ${{ steps.distribute.outputs.version_metadata_url }}
mac_arm64_url:
value: ${{ steps.distribute.outputs.mac_arm64_url }}
mac_x64_url:
value: ${{ steps.distribute.outputs.mac_x64_url }}
win_x64_url:
value: ${{ steps.distribute.outputs.win_x64_url }}
runs:
using: composite
steps:
- name: Merge the sole version-wide Standalone graph
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/release-closure"
pnpm exec tools-release merge-closure-distribution \
"$RUNNER_TEMP/release-closure/shared/shared-contribution.json" \
"$RUNNER_TEMP/release-closure/targets/darwin-arm64/target-contribution.json" \
"$RUNNER_TEMP/release-closure/targets/darwin-x64/target-contribution.json" \
"$RUNNER_TEMP/release-closure/targets/win32-x64/target-contribution.json" \
--output "$RUNNER_TEMP/release-closure/closure-distribution.json"
- name: Accept the N-1 Shell preflight boundary
shell: bash
run: pnpm exec tools-release verify-closure-preflight --channel "${{ inputs.exact-name }}" --manifest "$RUNNER_TEMP/release-closure/closure-distribution.json" --release-version "${{ inputs.release-version }}"
- name: Distribute staged exact metadata
id: distribute
uses: ./.github/actions/release/publish-metadata
with:
enable_mac_arm64: true
enable_mac_x64: true
enable_win_x64: true
mac_arm64_result: success
mac_x64_result: success
win_x64_result: success
- name: Stage run-scoped mutable acceptance feed
id: acceptance_feed
shell: bash
env:
RELEASE_ACCEPTANCE_FEED_PREFIX: ${{ inputs.exact-name }}/acceptance/runs/${{ github.run_id }}-${{ github.run_attempt }}
RELEASE_METADATA_PATH: ${{ runner.temp }}/release-metadata/metadata.json
RELEASE_VERSION: ${{ inputs.release-version }}
run: |
set -euo pipefail
pnpm exec tools-release stage-acceptance-feed
origin="${RELEASE_PUBLIC_ORIGIN%/}"
echo "latest_url=$origin/$RELEASE_ACCEPTANCE_FEED_PREFIX/latest/metadata.json" >> "$GITHUB_OUTPUT"
@@ -4,6 +4,10 @@ description: Project immutable Shell and Closure registry content into one exact
inputs:
artifact-id:
required: true
handoff-enabled:
description: Upload cross-runner projection artifacts
required: false
default: "true"
channel:
required: true
closure-identity-digest:
@@ -35,7 +39,7 @@ runs:
RELEASE_CLOSURE_BUILD_DIGEST: ${{ inputs.closure-identity-digest }}
RELEASE_CLOSURE_BUILD_KIND: target
RELEASE_CLOSURE_BUILD_TOKEN: target-${{ inputs.platform }}
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-projection/${{ inputs.target }}/target-contribution.json
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-closure/targets/${{ inputs.platform }}/target-contribution.json
RELEASE_CLOSURE_MATERIALIZE: "false"
RELEASE_PUBLIC_ORIGIN: ${{ inputs.public-origin }}
RELEASE_VERSION: ${{ inputs.release-version }}
@@ -75,7 +79,7 @@ runs:
RELEASE_CLOSURE_BUILD_DIGEST: ${{ inputs.closure-identity-digest }}
RELEASE_CLOSURE_BUILD_KIND: target
RELEASE_CLOSURE_BUILD_TOKEN: target-${{ inputs.platform }}
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-projection/${{ inputs.target }}/target-contribution.json
RELEASE_CLOSURE_CONTRIBUTION_JSON_PATH: ${{ runner.temp }}/release-closure/targets/${{ inputs.platform }}/target-contribution.json
RELEASE_CLOSURE_CONTRIBUTION_KIND: target
RELEASE_CLOSURE_PUBLICATION_JSON_PATH: ${{ runner.temp }}/release-projection/${{ inputs.target }}/closure-publication.json
RELEASE_CLOSURE_REMOTE_PROJECTION: "true"
@@ -126,6 +130,7 @@ runs:
}));
- name: Upload ${{ inputs.target }} publish manifest
if: ${{ inputs.handoff-enabled == 'true' }}
uses: actions/upload-artifact@v7
with:
name: open-design-${{ inputs.channel }}-${{ inputs.artifact-id }}-publish-manifest
@@ -133,13 +138,15 @@ runs:
if-no-files-found: error
- name: Upload ${{ inputs.target }} Standalone contribution
if: ${{ inputs.handoff-enabled == 'true' }}
uses: actions/upload-artifact@v7
with:
name: open-design-${{ inputs.channel }}-closure-${{ inputs.platform }}
path: ${{ runner.temp }}/release-projection/${{ inputs.target }}/target-contribution.json
path: ${{ runner.temp }}/release-closure/targets/${{ inputs.platform }}/target-contribution.json
if-no-files-found: error
- name: Upload ${{ inputs.target }} projection result
if: ${{ inputs.handoff-enabled == 'true' }}
uses: actions/upload-artifact@v7
with:
name: open-design-${{ inputs.channel }}-${{ inputs.target }}-build-result
@@ -0,0 +1,71 @@
name: Activate accepted public release
description: Project reusable credentials and atomically activate one accepted exact release
inputs:
channel:
required: true
commit:
required: true
metadata-url:
required: true
public-origin:
required: true
release-version:
required: true
reuse-json:
required: true
outputs:
activated_at:
value: ${{ steps.completed.outputs.activated_at }}
runs:
using: composite
steps:
- name: Project reusable public acceptance credentials
if: ${{ inputs.reuse-json != '{}' }}
shell: bash
env:
RELEASE_COMMIT: ${{ inputs.commit }}
RELEASE_METADATA_URL: ${{ inputs.metadata-url }}
RELEASE_PUBLIC_ACCEPTANCE_CREDENTIAL_DIR: ${{ runner.temp }}/public-acceptance
RELEASE_PUBLIC_ACCEPTANCE_REUSE_JSON: ${{ inputs.reuse-json }}
RELEASE_PUBLIC_ACCEPTANCE_WORK_DIR: ${{ runner.temp }}/public-acceptance-projection
run: pnpm exec tools-release project-public-acceptance
- name: Activate accepted immutable exact metadata with CAS
shell: bash
env:
RELEASE_ACTIVATION_WORK_DIR: ${{ runner.temp }}/release-activation
RELEASE_CHANNEL: ${{ inputs.channel }}
RELEASE_OUTPUTS_PATH: ${{ runner.temp }}/release-activation/outputs.json
RELEASE_PUBLIC_ACCEPTANCE_CREDENTIAL_DIR: ${{ runner.temp }}/public-acceptance
RELEASE_PUBLIC_ORIGIN: ${{ inputs.public-origin }}
RELEASE_VERSION: ${{ inputs.release-version }}
run: pnpm exec tools-release activate-public-release
- name: Read back activated exact public feed
shell: bash
env:
RELEASE_LATEST_METADATA_URL: ${{ inputs.public-origin }}/${{ inputs.channel }}/latest/metadata.json
RELEASE_METADATA_URL: ${{ inputs.metadata-url }}
RELEASE_PUBLIC_FEED_REPORT_PATH: ${{ runner.temp }}/release-activation/public-feed-observation.json
run: pnpm exec tools-release observe-public-feed
- name: Publish activation summary
shell: bash
run: |
{
echo "## Accepted exact activation"
echo ""
echo "- version: \`${{ inputs.release-version }}\`"
echo "- commit: \`${{ inputs.commit }}\`"
echo "- mac_arm64/mac_x64/win_x64 public artifact smoke: \`success\`"
echo "- latest CAS and public readback: \`success\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Record activation completion
id: completed
uses: actions/github-script@v8
with:
script: core.setOutput("activated_at", new Date().toISOString())
@@ -0,0 +1,88 @@
name: Execute public content acceptance
description: Re-download, install, smoke, and attest one immutable public target
inputs:
artifact-dir:
required: true
channel:
required: true
commit:
required: true
metadata-url:
required: true
mutable-metadata-url:
required: true
namespace:
required: true
os:
required: true
public-origin:
required: true
release-version:
required: true
target:
required: true
runs:
using: composite
steps:
- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: 10.33.2
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.18.0
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfile
- name: Re-download immutable public ${{ inputs.target }} artifacts
shell: bash
env:
RELEASE_COMMIT: ${{ inputs.commit }}
RELEASE_METADATA_URL: ${{ inputs.metadata-url }}
RELEASE_PUBLIC_ACCEPTANCE_BUILD_JSON_PATH: ${{ runner.temp }}/public-feedback/build.json
RELEASE_PUBLIC_ACCEPTANCE_DOWNLOAD_DIR: ${{ runner.temp }}/public-feedback/tools-pack/out/${{ inputs.os }}/namespaces/${{ inputs.namespace }}/${{ inputs.artifact-dir }}
RELEASE_PUBLIC_ACCEPTANCE_PLAN_PATH: ${{ runner.temp }}/public-feedback/plan.json
RELEASE_PUBLIC_ORIGIN: ${{ inputs.public-origin }}
RELEASE_TARGET: ${{ inputs.target }}
RELEASE_VERSION: ${{ inputs.release-version }}
run: pnpm exec tools-release prepare-public-acceptance
- name: Smoke immutable public ${{ inputs.target }} install and online Closure cold start
shell: bash
working-directory: e2e
env:
OD_PACKAGED_E2E_BUILD_JSON_PATH: ${{ runner.temp }}/public-feedback/build.json
OD_PACKAGED_E2E_MAC: ${{ inputs.os == 'mac' && '1' || '' }}
OD_PACKAGED_E2E_MAC_SMOKE_LANES: shell
OD_PACKAGED_E2E_MAC_SMOKE_PROFILE: core
OD_PACKAGED_E2E_NAMESPACE: ${{ inputs.namespace }}
OD_PACKAGED_E2E_RELEASE_CHANNEL: ${{ inputs.channel }}
OD_PACKAGED_E2E_RELEASE_VERSION: ${{ inputs.release-version }}
OD_PACKAGED_E2E_REPORT_DIR: ${{ runner.temp }}/public-feedback/report
OD_PACKAGED_E2E_SHELL_SMOKE_PROOF: public-immutable-artifacts
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/public-feedback/tools-pack
# Exercise the final reusable-Shell contract: an empty Store discovers
# mutable latest, then Standalone resolves its exact immutable sibling.
OD_STANDALONE_METADATA_URL: ${{ inputs.mutable-metadata-url }}
OD_PACKAGED_E2E_WIN: ${{ inputs.os == 'win' && '1' || '' }}
OD_PACKAGED_E2E_WIN_SMOKE_LANES: shell
OD_PACKAGED_E2E_WIN_SMOKE_PROFILE: core
OD_TOOLS_PACK_EMBEDDED_CONFIG_ONLY: "1"
run: pnpm exec tsx scripts/release-smoke.ts ${{ inputs.os }} specs/${{ inputs.os }}.spec.ts
- name: Issue public ${{ inputs.target }} acceptance credential
shell: bash
env:
RELEASE_PUBLIC_ACCEPTANCE_CREDENTIAL_PATH: ${{ runner.temp }}/public-feedback/${{ inputs.target }}.json
RELEASE_PUBLIC_ACCEPTANCE_PLAN_PATH: ${{ runner.temp }}/public-feedback/plan.json
RELEASE_PUBLIC_ACCEPTANCE_SMOKE_SUMMARY_PATH: ${{ runner.temp }}/public-feedback/report/summary.json
RELEASE_PUBLIC_ACCEPTANCE_SUITE_RESULT_PATH: ${{ runner.temp }}/public-feedback/report/suite-result.json
run: pnpm exec tools-release issue-public-acceptance
@@ -0,0 +1,127 @@
name: Plan public content acceptance
description: Resolve the sole reusable public-acceptance execution topology
inputs:
acceptance-matrix:
required: true
content-identities:
required: true
outputs:
acceptance_count:
value: ${{ steps.selection.outputs.acceptance_count }}
acceptance_matrix:
value: ${{ steps.selection.outputs.acceptance_matrix }}
mac_arm64_closure:
value: ${{ steps.identities.outputs.mac_arm64_closure }}
mac_arm64_run:
value: ${{ steps.selection.outputs.mac_arm64_run }}
mac_arm64_semantic:
value: ${{ steps.mac_arm64.outputs.hash }}
mac_arm64_shell:
value: ${{ steps.identities.outputs.mac_arm64_shell }}
mac_x64_closure:
value: ${{ steps.identities.outputs.mac_x64_closure }}
mac_x64_run:
value: ${{ steps.selection.outputs.mac_x64_run }}
mac_x64_semantic:
value: ${{ steps.mac_x64.outputs.hash }}
mac_x64_shell:
value: ${{ steps.identities.outputs.mac_x64_shell }}
reuse_json:
value: ${{ steps.selection.outputs.reuse_json }}
win_x64_closure:
value: ${{ steps.identities.outputs.win_x64_closure }}
win_x64_run:
value: ${{ steps.selection.outputs.win_x64_run }}
win_x64_semantic:
value: ${{ steps.win_x64.outputs.hash }}
win_x64_shell:
value: ${{ steps.identities.outputs.win_x64_shell }}
runs:
using: composite
steps:
- name: Resolve acceptance content identities
id: identities
uses: actions/github-script@v8
env:
CONTENT_IDENTITIES: ${{ inputs.content-identities }}
with:
script: |
const identities = JSON.parse(process.env.CONTENT_IDENTITIES || "{}");
const targets = ["mac_arm64", "mac_x64", "win_x64"];
const digest = /^sha256:[0-9a-f]{64}$/;
const enabled = targets.every((target) => digest.test(identities[target]?.closure || "") && digest.test(identities[target]?.shell || ""));
core.setOutput("enabled", String(enabled));
for (const target of targets) {
core.setOutput(`${target}_closure`, identities[target]?.closure || "");
core.setOutput(`${target}_shell`, identities[target]?.shell || "");
}
- name: Probe mac_arm64 public acceptance proof
id: mac_arm64
if: ${{ steps.identities.outputs.enabled == 'true' }}
uses: ./.github/actions/hash-skip
env:
HASH_SKIP_EXTRA_CLOSURE: ${{ steps.identities.outputs.mac_arm64_closure }}
HASH_SKIP_EXTRA_SHELL: ${{ steps.identities.outputs.mac_arm64_shell }}
with:
hash_skip: release.public-acceptance.mac
- name: Probe mac_x64 public acceptance proof
id: mac_x64
if: ${{ steps.identities.outputs.enabled == 'true' }}
uses: ./.github/actions/hash-skip
env:
HASH_SKIP_EXTRA_CLOSURE: ${{ steps.identities.outputs.mac_x64_closure }}
HASH_SKIP_EXTRA_SHELL: ${{ steps.identities.outputs.mac_x64_shell }}
with:
hash_skip: release.public-acceptance.mac
- name: Probe win_x64 public acceptance proof
id: win_x64
if: ${{ steps.identities.outputs.enabled == 'true' }}
uses: ./.github/actions/hash-skip
env:
HASH_SKIP_EXTRA_CLOSURE: ${{ steps.identities.outputs.win_x64_closure }}
HASH_SKIP_EXTRA_SHELL: ${{ steps.identities.outputs.win_x64_shell }}
with:
hash_skip: release.public-acceptance.win
- name: Select public acceptance topology
id: selection
uses: actions/github-script@v8
env:
ACCEPTANCE_MATRIX: ${{ inputs.acceptance-matrix }}
CONTENT_IDENTITIES: ${{ inputs.content-identities }}
CONTENT_IDENTITIES_ENABLED: ${{ steps.identities.outputs.enabled }}
MAC_ARM64_HASH: ${{ steps.mac_arm64.outputs.hash }}
MAC_ARM64_REUSE: ${{ steps.mac_arm64.outputs.skip }}
MAC_X64_HASH: ${{ steps.mac_x64.outputs.hash }}
MAC_X64_REUSE: ${{ steps.mac_x64.outputs.skip }}
WIN_X64_HASH: ${{ steps.win_x64.outputs.hash }}
WIN_X64_REUSE: ${{ steps.win_x64.outputs.skip }}
with:
script: |
const matrix = JSON.parse(process.env.ACCEPTANCE_MATRIX);
const identities = JSON.parse(process.env.CONTENT_IDENTITIES || "{}");
const enabled = process.env.CONTENT_IDENTITIES_ENABLED === "true";
const proof = {
mac_arm64: { hash: process.env.MAC_ARM64_HASH, reuse: process.env.MAC_ARM64_REUSE === "true" },
mac_x64: { hash: process.env.MAC_X64_HASH, reuse: process.env.MAC_X64_REUSE === "true" },
win_x64: { hash: process.env.WIN_X64_HASH, reuse: process.env.WIN_X64_REUSE === "true" },
};
const include = matrix.include.filter(({ target }) => !enabled || !proof[target].reuse).map((entry) => ({
...entry,
closure_identity_digest: identities[entry.target]?.closure || "",
semantic_digest: enabled ? proof[entry.target].hash : "",
shell_identity_digest: identities[entry.target]?.shell || "",
}));
const reuse = Object.fromEntries(matrix.include.filter(({ target }) => enabled && proof[target].reuse).map(({ target }) => [target, proof[target].hash]));
core.setOutput("acceptance_count", String(include.length));
core.setOutput("acceptance_matrix", JSON.stringify({ include }));
core.setOutput("reuse_json", JSON.stringify(reuse));
for (const target of ["mac_arm64", "mac_x64", "win_x64"]) {
core.setOutput(`${target}_run`, String(include.some((entry) => entry.target === target) && enabled));
}
@@ -4,7 +4,7 @@
"shell"
],
"hashPaths": [
".github/workflows/distribution-exact-accept.yml",
".github/actions/release/public-acceptance/execute/action.yml",
"e2e/lib/desktop",
"e2e/lib/vitest",
"e2e/scripts/release-smoke.ts",
@@ -4,7 +4,7 @@
"shell"
],
"hashPaths": [
".github/workflows/distribution-exact-accept.yml",
".github/actions/release/public-acceptance/execute/action.yml",
"e2e/lib/desktop",
"e2e/lib/vitest",
"e2e/scripts/release-smoke.ts",
+43 -37
View File
@@ -24,6 +24,7 @@ function releaseChannelDisplayLabel(channel: string): string {
}
export type ReleaseNotificationInput = {
activationCompletedAt: string;
actor: string;
branch: string;
channel: string;
@@ -239,6 +240,29 @@ function actorMarkdown(actor: string): string {
: "未知";
}
function commitMarkdown(input: ReleaseNotificationInput): string {
const shortCommit = input.commit.slice(0, 7);
if (shortCommit.length === 0) return "";
return input.repository.length > 0
? `[${shortCommit}](https://github.com/${input.repository}/commit/${input.commit})`
: shortCommit;
}
function executionTimingMarkdown(input: ReleaseNotificationInput, execution: ReleaseRunContext | null): string {
if (execution == null) return "";
const activationMs = Date.parse(input.activationCompletedAt);
const hasActivation = Number.isFinite(activationMs)
&& activationMs >= execution.runStartedAtMs
&& activationMs <= execution.observedAtMs;
const releaseMs = hasActivation ? activationMs - execution.runStartedAtMs : execution.durationMs;
const notificationMs = hasActivation ? execution.observedAtMs - activationMs : 0;
return [
`发布 ${duration(releaseMs)}`,
hasActivation ? `通知 ${duration(notificationMs)}` : "",
execution.queueDurationMs > 0 ? `排队 ${duration(execution.queueDurationMs)}` : "",
].filter(Boolean).join(" · ");
}
function triggerLabel(eventName: string): string {
return {
push: "Push",
@@ -325,61 +349,43 @@ export function buildReleaseFeishuCard(
published: warning ? "不可变发布完成(有告警)" : "不可变发布完成 · 未晋升",
validation: "验证完成",
}[state];
const shortCommit = input.commit.slice(0, 7);
const fields: FeishuElement[] = [];
const rerunActor = input.triggeringActor.length > 0 && input.triggeringActor !== input.actor
? ` · 重跑 ${actorMarkdown(input.triggeringActor)}`
: "";
const source = [escapeMarkdown(input.branch), commitMarkdown(input)].filter(Boolean).join(" · ");
fields.push({
is_short: true,
text: { tag: "lark_md", content: `**触发者**\n${actorMarkdown(input.actor)}${rerunActor}` },
is_short: false,
text: {
tag: "lark_md",
content: `**触发** · ${actorMarkdown(input.actor)}${rerunActor} · ${escapeMarkdown(triggerLabel(input.eventName))}`
+ (source.length > 0 ? ` · ${source}` : ""),
},
});
fields.push({
is_short: true,
text: { tag: "lark_md", content: `**触发方式**\n${escapeMarkdown(triggerLabel(input.eventName))}` },
});
fields.push({
is_short: true,
text: { tag: "lark_md", content: `**发布模式**\n${releaseModeLabel(input.releaseMode)}` },
is_short: false,
text: { tag: "lark_md", content: `**模式** · ${releaseModeLabel(input.releaseMode)}` },
});
if (input.runNumber.length > 0) {
const attempt = Number.parseInt(input.runAttempt, 10);
const attemptLabel = Number.isSafeInteger(attempt) && attempt > 1 ? ` · 第 ${attempt} 次执行` : "";
const runLabel = `${input.workflowName || "Workflow"} #${input.runNumber}${attemptLabel}`;
fields.push({
is_short: true,
is_short: false,
text: {
tag: "lark_md",
content: input.runUrl.length > 0
? `**执行**\n[${escapeMarkdown(runLabel)}](${input.runUrl})`
: `**执行**\n${escapeMarkdown(runLabel)}`,
content: `**执行** · ${input.runUrl.length > 0
? `[${escapeMarkdown(runLabel)}](${input.runUrl})`
: escapeMarkdown(runLabel)}`
+ (details.execution?.pullRequest != null
? ` · [PR #${details.execution.pullRequest.number}](${details.execution.pullRequest.url})`
: "")
+ (details.execution != null
? ` · ${executionTimingMarkdown(input, details.execution)}`
: ""),
},
});
}
if (input.branch.length > 0) fields.push({
is_short: true,
text: { tag: "lark_md", content: `**分支**\n${escapeMarkdown(input.branch)}` },
});
if (shortCommit.length > 0) fields.push({
is_short: true,
text: {
tag: "lark_md",
content: input.repository.length > 0
? `**提交**\n[${shortCommit}](https://github.com/${input.repository}/commit/${input.commit})`
: `**提交**\n${shortCommit}`,
},
});
if (details.execution != null && details.execution.durationMs > 0) fields.push({
is_short: true,
text: { tag: "lark_md", content: `**耗时**\n${duration(details.execution.durationMs)}` },
});
if (details.execution?.pullRequest != null) fields.push({
is_short: true,
text: {
tag: "lark_md",
content: `**关联 PR**\n[#${details.execution.pullRequest.number}](${details.execution.pullRequest.url})`,
},
});
const elements: FeishuElement[] = fields.length > 0
? [{ tag: "div", fields }, { tag: "hr" }]
: [];
+14 -1
View File
@@ -19,10 +19,13 @@ export type ReleaseRunFailure = {
export type ReleaseRunContext = {
durationMs: number;
observedAtMs: number;
pullRequest: {
number: number;
url: string;
} | null;
queueDurationMs: number;
runStartedAtMs: number;
};
type GitHubCompareCommit = {
@@ -110,10 +113,14 @@ export async function loadReleaseRunContext(input: {
pull_requests?: unknown;
run_started_at?: unknown;
};
const createdAtMs = Date.parse(text(payload.created_at));
const startedAt = text(payload.run_started_at) || text(payload.created_at);
const startedAtMs = Date.parse(startedAt);
const now = input.now ?? Date.now();
const durationMs = Number.isFinite(startedAtMs) ? Math.max(0, now - startedAtMs) : 0;
const queueDurationMs = Number.isFinite(createdAtMs) && Number.isFinite(startedAtMs)
? Math.max(0, startedAtMs - createdAtMs)
: 0;
const pullRequests = Array.isArray(payload.pull_requests)
? payload.pull_requests.flatMap((entry) => {
if (entry == null || typeof entry !== "object") return [];
@@ -128,7 +135,13 @@ export async function loadReleaseRunContext(input: {
url: `https://github.com/${input.repository}/pull/${uniquePullRequests[0]}`,
}
: null;
return { durationMs, pullRequest };
return {
durationMs,
observedAtMs: now,
pullRequest,
queueDurationMs,
runStartedAtMs: Number.isFinite(startedAtMs) ? startedAtMs : 0,
};
}
function failedStep(value: unknown): string {
+1
View File
@@ -28,6 +28,7 @@ if (bot == null) {
throw new Error(`unsupported release notification channel: ${channel}`);
}
const input: ReleaseNotificationInput = {
activationCompletedAt: optionalEnv("RELEASE_ACTIVATION_COMPLETED_AT"),
actor: optionalEnv("RELEASE_ACTOR"),
branch: optionalEnv("RELEASE_BRANCH"),
channel,
+47 -179
View File
@@ -65,6 +65,9 @@ on:
release_version:
required: true
type: string
outputs:
activation_completed_at:
value: ${{ jobs.activate.outputs.activation_completed_at }}
permissions:
actions: write
@@ -75,109 +78,32 @@ jobs:
name: Plan public content acceptance
runs-on: ubuntu-latest
outputs:
acceptance_count: ${{ steps.selection.outputs.acceptance_count }}
acceptance_matrix: ${{ steps.selection.outputs.acceptance_matrix }}
mac_arm64_closure: ${{ steps.identities.outputs.mac_arm64_closure }}
mac_arm64_run: ${{ steps.selection.outputs.mac_arm64_run }}
mac_arm64_semantic: ${{ steps.mac_arm64.outputs.hash }}
mac_arm64_shell: ${{ steps.identities.outputs.mac_arm64_shell }}
mac_x64_closure: ${{ steps.identities.outputs.mac_x64_closure }}
mac_x64_run: ${{ steps.selection.outputs.mac_x64_run }}
mac_x64_semantic: ${{ steps.mac_x64.outputs.hash }}
mac_x64_shell: ${{ steps.identities.outputs.mac_x64_shell }}
reuse_json: ${{ steps.selection.outputs.reuse_json }}
win_x64_closure: ${{ steps.identities.outputs.win_x64_closure }}
win_x64_run: ${{ steps.selection.outputs.win_x64_run }}
win_x64_semantic: ${{ steps.win_x64.outputs.hash }}
win_x64_shell: ${{ steps.identities.outputs.win_x64_shell }}
acceptance_count: ${{ steps.acceptance.outputs.acceptance_count }}
acceptance_matrix: ${{ steps.acceptance.outputs.acceptance_matrix }}
mac_arm64_closure: ${{ steps.acceptance.outputs.mac_arm64_closure }}
mac_arm64_run: ${{ steps.acceptance.outputs.mac_arm64_run }}
mac_arm64_semantic: ${{ steps.acceptance.outputs.mac_arm64_semantic }}
mac_arm64_shell: ${{ steps.acceptance.outputs.mac_arm64_shell }}
mac_x64_closure: ${{ steps.acceptance.outputs.mac_x64_closure }}
mac_x64_run: ${{ steps.acceptance.outputs.mac_x64_run }}
mac_x64_semantic: ${{ steps.acceptance.outputs.mac_x64_semantic }}
mac_x64_shell: ${{ steps.acceptance.outputs.mac_x64_shell }}
reuse_json: ${{ steps.acceptance.outputs.reuse_json }}
win_x64_closure: ${{ steps.acceptance.outputs.win_x64_closure }}
win_x64_run: ${{ steps.acceptance.outputs.win_x64_run }}
win_x64_semantic: ${{ steps.acceptance.outputs.win_x64_semantic }}
win_x64_shell: ${{ steps.acceptance.outputs.win_x64_shell }}
steps:
- name: Checkout accepted commit
uses: actions/checkout@v6.0.2
with:
ref: ${{ inputs.commit }}
- name: Resolve acceptance content identities
id: identities
uses: actions/github-script@v8
env:
CONTENT_IDENTITIES: ${{ inputs.content_identities }}
- name: Resolve public acceptance topology
id: acceptance
uses: ./.github/actions/release/public-acceptance/plan
with:
script: |
const identities = JSON.parse(process.env.CONTENT_IDENTITIES || "{}");
const targets = ["mac_arm64", "mac_x64", "win_x64"];
const digest = /^sha256:[0-9a-f]{64}$/;
const enabled = targets.every((target) => digest.test(identities[target]?.closure || "") && digest.test(identities[target]?.shell || ""));
core.setOutput("enabled", String(enabled));
for (const target of targets) {
core.setOutput(`${target}_closure`, identities[target]?.closure || "");
core.setOutput(`${target}_shell`, identities[target]?.shell || "");
}
- name: Probe mac_arm64 public acceptance proof
id: mac_arm64
if: ${{ steps.identities.outputs.enabled == 'true' }}
uses: ./.github/actions/hash-skip
env:
HASH_SKIP_EXTRA_CLOSURE: ${{ steps.identities.outputs.mac_arm64_closure }}
HASH_SKIP_EXTRA_SHELL: ${{ steps.identities.outputs.mac_arm64_shell }}
with:
hash_skip: release.public-acceptance.mac
- name: Probe mac_x64 public acceptance proof
id: mac_x64
if: ${{ steps.identities.outputs.enabled == 'true' }}
uses: ./.github/actions/hash-skip
env:
HASH_SKIP_EXTRA_CLOSURE: ${{ steps.identities.outputs.mac_x64_closure }}
HASH_SKIP_EXTRA_SHELL: ${{ steps.identities.outputs.mac_x64_shell }}
with:
hash_skip: release.public-acceptance.mac
- name: Probe win_x64 public acceptance proof
id: win_x64
if: ${{ steps.identities.outputs.enabled == 'true' }}
uses: ./.github/actions/hash-skip
env:
HASH_SKIP_EXTRA_CLOSURE: ${{ steps.identities.outputs.win_x64_closure }}
HASH_SKIP_EXTRA_SHELL: ${{ steps.identities.outputs.win_x64_shell }}
with:
hash_skip: release.public-acceptance.win
- name: Select public acceptance topology
id: selection
uses: actions/github-script@v8
env:
ACCEPTANCE_MATRIX: ${{ inputs.acceptance_matrix }}
CONTENT_IDENTITIES: ${{ inputs.content_identities }}
CONTENT_IDENTITIES_ENABLED: ${{ steps.identities.outputs.enabled }}
MAC_ARM64_HASH: ${{ steps.mac_arm64.outputs.hash }}
MAC_ARM64_REUSE: ${{ steps.mac_arm64.outputs.skip }}
MAC_X64_HASH: ${{ steps.mac_x64.outputs.hash }}
MAC_X64_REUSE: ${{ steps.mac_x64.outputs.skip }}
WIN_X64_HASH: ${{ steps.win_x64.outputs.hash }}
WIN_X64_REUSE: ${{ steps.win_x64.outputs.skip }}
with:
script: |
const matrix = JSON.parse(process.env.ACCEPTANCE_MATRIX);
const identities = JSON.parse(process.env.CONTENT_IDENTITIES || "{}");
const enabled = process.env.CONTENT_IDENTITIES_ENABLED === "true";
const proof = {
mac_arm64: { hash: process.env.MAC_ARM64_HASH, reuse: process.env.MAC_ARM64_REUSE === "true" },
mac_x64: { hash: process.env.MAC_X64_HASH, reuse: process.env.MAC_X64_REUSE === "true" },
win_x64: { hash: process.env.WIN_X64_HASH, reuse: process.env.WIN_X64_REUSE === "true" },
};
const include = matrix.include.filter(({ target }) => !enabled || !proof[target].reuse).map((entry) => ({
...entry,
closure_identity_digest: identities[entry.target]?.closure || "",
semantic_digest: enabled ? proof[entry.target].hash : "",
shell_identity_digest: identities[entry.target]?.shell || "",
}));
const reuse = Object.fromEntries(matrix.include.filter(({ target }) => enabled && proof[target].reuse).map(({ target }) => [target, proof[target].hash]));
core.setOutput("acceptance_count", String(include.length));
core.setOutput("acceptance_matrix", JSON.stringify({ include }));
core.setOutput("reuse_json", JSON.stringify(reuse));
for (const target of ["mac_arm64", "mac_x64", "win_x64"]) {
core.setOutput(`${target}_run`, String(include.some((entry) => entry.target === target) && enabled));
}
acceptance-matrix: ${{ inputs.acceptance_matrix }}
content-identities: ${{ inputs.content_identities }}
public_acceptance:
name: Accept public ${{ matrix.target }} exact artifacts
@@ -200,54 +126,19 @@ jobs:
uses: actions/checkout@v6.0.2
with:
ref: ${{ inputs.commit }}
- name: Setup pnpm
uses: pnpm/action-setup@v5
- name: Execute immutable public ${{ matrix.target }} acceptance
uses: ./.github/actions/release/public-acceptance/execute
with:
version: 10.33.2
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.18.0
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Re-download immutable public ${{ matrix.target }} artifacts
env:
RELEASE_METADATA_URL: ${{ inputs.metadata_url }}
RELEASE_PUBLIC_ACCEPTANCE_BUILD_JSON_PATH: ${{ runner.temp }}/public-feedback/build.json
RELEASE_PUBLIC_ACCEPTANCE_DOWNLOAD_DIR: ${{ runner.temp }}/public-feedback/tools-pack/out/${{ matrix.os }}/namespaces/${{ env.RELEASE_NAMESPACE }}/${{ matrix.artifact_dir }}
RELEASE_PUBLIC_ACCEPTANCE_PLAN_PATH: ${{ runner.temp }}/public-feedback/plan.json
run: pnpm exec tools-release prepare-public-acceptance
- name: Smoke immutable public ${{ matrix.target }} install and online Closure cold start
working-directory: e2e
env:
OD_PACKAGED_E2E_BUILD_JSON_PATH: ${{ runner.temp }}/public-feedback/build.json
OD_PACKAGED_E2E_MAC: ${{ matrix.os == 'mac' && '1' || '' }}
OD_PACKAGED_E2E_MAC_SMOKE_LANES: shell
OD_PACKAGED_E2E_MAC_SMOKE_PROFILE: core
OD_PACKAGED_E2E_NAMESPACE: ${{ env.RELEASE_NAMESPACE }}
OD_PACKAGED_E2E_RELEASE_CHANNEL: ${{ inputs.exact_name }}
OD_PACKAGED_E2E_RELEASE_VERSION: ${{ inputs.release_version }}
OD_PACKAGED_E2E_REPORT_DIR: ${{ runner.temp }}/public-feedback/report
OD_PACKAGED_E2E_SHELL_SMOKE_PROOF: public-immutable-artifacts
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/public-feedback/tools-pack
# Exercise the final reusable-Shell shape: an empty Store receives a
# mutable latest discovery URL, then Standalone re-fetches the exact
# immutable sibling before preparation. No releaseVersion is injected.
OD_STANDALONE_METADATA_URL: ${{ inputs.mutable_metadata_url }}
OD_PACKAGED_E2E_WIN: ${{ matrix.os == 'win' && '1' || '' }}
OD_PACKAGED_E2E_WIN_SMOKE_LANES: shell
OD_PACKAGED_E2E_WIN_SMOKE_PROFILE: core
OD_TOOLS_PACK_EMBEDDED_CONFIG_ONLY: "1"
run: pnpm exec tsx scripts/release-smoke.ts ${{ matrix.os }} specs/${{ matrix.os }}.spec.ts
- name: Issue public ${{ matrix.target }} acceptance credential
env:
RELEASE_PUBLIC_ACCEPTANCE_CREDENTIAL_PATH: ${{ runner.temp }}/public-feedback/${{ matrix.target }}.json
RELEASE_PUBLIC_ACCEPTANCE_PLAN_PATH: ${{ runner.temp }}/public-feedback/plan.json
RELEASE_PUBLIC_ACCEPTANCE_SMOKE_SUMMARY_PATH: ${{ runner.temp }}/public-feedback/report/summary.json
RELEASE_PUBLIC_ACCEPTANCE_SUITE_RESULT_PATH: ${{ runner.temp }}/public-feedback/report/suite-result.json
run: pnpm exec tools-release issue-public-acceptance
artifact-dir: ${{ matrix.artifact_dir }}
channel: ${{ inputs.exact_name }}
commit: ${{ inputs.commit }}
metadata-url: ${{ inputs.metadata_url }}
mutable-metadata-url: ${{ inputs.mutable_metadata_url }}
namespace: ${{ env.RELEASE_NAMESPACE }}
os: ${{ matrix.os }}
public-origin: ${{ inputs.public_origin }}
release-version: ${{ inputs.release_version }}
target: ${{ matrix.target }}
- name: Upload public ${{ matrix.target }} acceptance credential
uses: actions/upload-artifact@v7
with:
@@ -270,6 +161,8 @@ jobs:
needs: [plan, public_acceptance]
if: ${{ !cancelled() && needs.plan.result == 'success' && (needs.public_acceptance.result == 'success' || needs.public_acceptance.result == 'skipped') }}
runs-on: ubuntu-latest
outputs:
activation_completed_at: ${{ steps.activation.outputs.activated_at }}
env:
OPEN_DESIGN_POSTINSTALL_LEVEL: release-prepare
RELEASE_CHANNEL: ${{ inputs.exact_name }}
@@ -359,20 +252,6 @@ jobs:
with:
hash_skip: release.public-acceptance.win
mode: complete
- name: Project reusable public acceptance credentials
if: ${{ needs.plan.outputs.reuse_json != '{}' }}
env:
RELEASE_COMMIT: ${{ inputs.commit }}
RELEASE_METADATA_URL: ${{ inputs.metadata_url }}
RELEASE_PUBLIC_ACCEPTANCE_CREDENTIAL_DIR: ${{ runner.temp }}/public-acceptance
RELEASE_PUBLIC_ACCEPTANCE_REUSE_JSON: ${{ needs.plan.outputs.reuse_json }}
RELEASE_PUBLIC_ACCEPTANCE_WORK_DIR: ${{ runner.temp }}/public-acceptance-projection
RELEASE_STORAGE_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
RELEASE_STORAGE_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
RELEASE_STORAGE_ENDPOINT: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
RELEASE_STORAGE_REGION: auto
RELEASE_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
run: pnpm exec tools-release project-public-acceptance
- name: Issue stable qualification for accepted prerelease
if: ${{ inputs.exact_name == 'prerelease' }}
env:
@@ -397,33 +276,22 @@ jobs:
jq --arg target "$target" '.releaseTargets[$target]' "$RELEASE_METADATA_PATH" > "$RELEASE_MANIFEST_DIR/$target.json"
done
pnpm exec tools-release issue-stable-qualification
- name: Activate accepted immutable exact metadata with CAS
- name: Activate accepted public release
id: activation
uses: ./.github/actions/release/public-acceptance/activate
with:
channel: ${{ inputs.exact_name }}
commit: ${{ inputs.commit }}
metadata-url: ${{ inputs.metadata_url }}
public-origin: ${{ inputs.public_origin }}
release-version: ${{ inputs.release_version }}
reuse-json: ${{ needs.plan.outputs.reuse_json }}
env:
RELEASE_ACTIVATION_WORK_DIR: ${{ runner.temp }}/release-activation
RELEASE_OUTPUTS_PATH: ${{ runner.temp }}/release-activation/outputs.json
RELEASE_PUBLIC_ACCEPTANCE_CREDENTIAL_DIR: ${{ runner.temp }}/public-acceptance
RELEASE_STORAGE_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
RELEASE_STORAGE_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
RELEASE_STORAGE_ENDPOINT: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
RELEASE_STORAGE_REGION: auto
RELEASE_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
run: pnpm exec tools-release activate-public-release
- name: Read back activated exact public feed
env:
RELEASE_LATEST_METADATA_URL: ${{ inputs.public_origin }}/${{ inputs.exact_name }}/latest/metadata.json
RELEASE_METADATA_URL: ${{ inputs.metadata_url }}
RELEASE_PUBLIC_FEED_REPORT_PATH: ${{ runner.temp }}/release-activation/public-feed-observation.json
run: pnpm exec tools-release observe-public-feed
- name: Publish activation summary
run: |
{
echo "## Accepted exact activation"
echo ""
echo "- version: \`${{ inputs.release_version }}\`"
echo "- commit: \`${{ inputs.commit }}\`"
echo "- mac_arm64/mac_x64/win_x64 public artifact smoke: \`success\`"
echo "- latest CAS and public readback: \`success\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Cleanup workflow artifacts
if: ${{ success() }}
env:
+100 -67
View File
@@ -150,6 +150,15 @@ jobs:
version_lock_key: ${{ steps.reservation.outputs.version_lock_key }}
build_matrix: ${{ steps.selection.outputs.build_matrix }}
build_count: ${{ steps.selection.outputs.build_count }}
hot_activated: ${{ steps.hot_activate.outcome == 'success' }}
hot_activation_completed_at: ${{ steps.hot_activate.outputs.activated_at }}
hot_staged: ${{ steps.hot_stage.outcome == 'success' }}
hot_acceptance_latest_url: ${{ steps.hot_stage.outputs.acceptance_latest_url }}
hot_release_state: ${{ steps.hot_stage.outputs.release_state }}
hot_version_metadata_url: ${{ steps.hot_stage.outputs.version_metadata_url }}
hot_mac_arm64_url: ${{ steps.hot_stage.outputs.mac_arm64_url }}
hot_mac_x64_url: ${{ steps.hot_stage.outputs.mac_x64_url }}
hot_win_x64_url: ${{ steps.hot_stage.outputs.win_x64_url }}
steps:
- name: Validate checkout ref shape
env:
@@ -294,6 +303,7 @@ jobs:
- name: Prepare immutable candidate identity
id: candidate
if: ${{ !inputs.promote }}
env:
RELEASE_CANDIDATE_SPEC_PATH: ${{ runner.temp }}/release-candidate/spec.json
run: |
@@ -320,6 +330,7 @@ jobs:
pnpm exec tools-release prepare-candidate
- name: Upload immutable candidate spec
if: ${{ !inputs.promote }}
uses: actions/upload-artifact@v7
with:
name: open-design-${{ inputs.exact_name }}-candidate-spec
@@ -471,6 +482,7 @@ jobs:
RELEASE_VERSION_LOCK_REQUIRED: "true"
with:
channel: ${{ inputs.exact_name }}
handoff-enabled: ${{ steps.selection.outputs.build_count != '0' || !inputs.publish || !inputs.promote }}
identity-digest: ${{ steps.execution.outputs.shared_closure_digest }}
min-shell-version: ${{ steps.exact.outputs.closure_min_shell_version }}
publish-enabled: "true"
@@ -492,6 +504,7 @@ jobs:
with:
artifact-id: mac-arm64
channel: ${{ inputs.exact_name }}
handoff-enabled: ${{ steps.selection.outputs.build_count != '0' || !inputs.publish || !inputs.promote }}
closure-identity-digest: ${{ steps.execution.outputs.mac_arm64_closure_digest }}
platform: darwin-arm64
public-origin: ${{ env.RELEASE_PUBLIC_ORIGIN }}
@@ -507,6 +520,7 @@ jobs:
with:
artifact-id: mac-x64
channel: ${{ inputs.exact_name }}
handoff-enabled: ${{ steps.selection.outputs.build_count != '0' || !inputs.publish || !inputs.promote }}
closure-identity-digest: ${{ steps.execution.outputs.mac_x64_closure_digest }}
platform: darwin-x64
public-origin: ${{ env.RELEASE_PUBLIC_ORIGIN }}
@@ -522,6 +536,7 @@ jobs:
with:
artifact-id: win-x64
channel: ${{ inputs.exact_name }}
handoff-enabled: ${{ steps.selection.outputs.build_count != '0' || !inputs.publish || !inputs.promote }}
closure-identity-digest: ${{ steps.execution.outputs.win_x64_closure_digest }}
platform: win32-x64
public-origin: ${{ env.RELEASE_PUBLIC_ORIGIN }}
@@ -530,6 +545,64 @@ jobs:
shell-plan-path: ${{ runner.temp }}/release-identity-profiles/win-x64.json
target: win_x64
- name: Stage fully reused exact content
id: hot_stage
if: ${{ steps.selection.outputs.build_count == '0' && inputs.publish && inputs.promote }}
uses: ./.github/actions/release/exact/stage
env:
BASE_VERSION: ${{ steps.exact.outputs.base_version }}
RELEASE_ACTIVATE_LATEST: "false"
RELEASE_CHANNEL: ${{ inputs.exact_name }}
RELEASE_CLOSURE_DISTRIBUTION_MANIFEST_PATH: ${{ runner.temp }}/release-closure/closure-distribution.json
RELEASE_CLOSURE_DISTRIBUTION_REQUIRED: "true"
RELEASE_INSTALLATION_VERSION_MIN_BETA: ${{ vars.RELEASE_LAUNCHER_VERSION_MIN_BETA }}
RELEASE_INSTALLATION_VERSION_MIN_STABLE: ${{ vars.RELEASE_LAUNCHER_VERSION_MIN_STABLE }}
RELEASE_INSTALLATION_VERSION_MIN_URL_BETA: ${{ vars.RELEASE_LAUNCHER_VERSION_MIN_URL_BETA }}
RELEASE_INSTALLATION_VERSION_MIN_URL_STABLE: ${{ vars.RELEASE_LAUNCHER_VERSION_MIN_URL_STABLE }}
RELEASE_LATEST_CAS_REQUIRED: "true"
RELEASE_LEGACY_INSTALLATION_MIGRATION_REQUIRED: "true"
RELEASE_MANIFEST_DIR: ${{ runner.temp }}/release-platform-manifests
RELEASE_PUBLIC_ORIGIN: ${{ env.RELEASE_PUBLIC_ORIGIN }}
RELEASE_SHELL_REQUIRED: "true"
RELEASE_STORAGE_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
RELEASE_STORAGE_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
RELEASE_STORAGE_ENDPOINT: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
RELEASE_STORAGE_REGION: auto
RELEASE_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
RELEASE_VERSION: ${{ steps.reservation.outputs.release_version || steps.exact.outputs.release_version }}
RELEASE_VERSION_LOCK_KEY: ${{ steps.reservation.outputs.version_lock_key }}
RELEASE_VERSION_LOCK_REQUIRED: "true"
STATE_SOURCE: ${{ steps.reservation.outputs.state_source || steps.exact.outputs.state_source }}
with:
exact-name: ${{ inputs.exact_name }}
release-version: ${{ steps.reservation.outputs.release_version || steps.exact.outputs.release_version }}
- name: Plan fully reused public acceptance
id: hot_acceptance
if: ${{ steps.hot_stage.outcome == 'success' }}
uses: ./.github/actions/release/public-acceptance/plan
with:
acceptance-matrix: '{"include":[{"artifact_dir":"dmg","os":"mac","runner":"macos-14","target":"mac_arm64"},{"artifact_dir":"dmg","os":"mac","runner":"macos-15-intel","target":"mac_x64"},{"artifact_dir":"builder","os":"win","runner":"windows-latest","target":"win_x64"}]}'
content-identities: ${{ format('{{"mac_arm64":{{"closure":"{0}","shell":"{1}"}},"mac_x64":{{"closure":"{2}","shell":"{3}"}},"win_x64":{{"closure":"{4}","shell":"{5}"}}}}', steps.execution.outputs.mac_arm64_closure_digest, steps.execution.outputs.mac_arm64_shell_digest, steps.execution.outputs.mac_x64_closure_digest, steps.execution.outputs.mac_x64_shell_digest, steps.execution.outputs.win_x64_closure_digest, steps.execution.outputs.win_x64_shell_digest) }}
- name: Activate fully reused exact release
id: hot_activate
if: ${{ steps.hot_acceptance.outputs.acceptance_count == '0' }}
uses: ./.github/actions/release/public-acceptance/activate
env:
RELEASE_STORAGE_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
RELEASE_STORAGE_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
RELEASE_STORAGE_ENDPOINT: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
RELEASE_STORAGE_REGION: auto
RELEASE_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
with:
channel: ${{ inputs.exact_name }}
commit: ${{ env.BUILT_SHA }}
metadata-url: ${{ steps.hot_stage.outputs.version_metadata_url }}
public-origin: ${{ env.RELEASE_PUBLIC_ORIGIN }}
release-version: ${{ steps.reservation.outputs.release_version || steps.exact.outputs.release_version }}
reuse-json: ${{ steps.hot_acceptance.outputs.reuse_json }}
build:
name: Build exact ${{ matrix.target }}
needs: metadata
@@ -766,16 +839,17 @@ jobs:
always() &&
!cancelled() &&
needs.metadata.result == 'success' &&
needs.metadata.outputs.hot_staged != 'true' &&
(needs.build.result == 'success' || needs.build.result == 'skipped')
}}
runs-on: ubuntu-latest
outputs:
acceptance_latest_url: ${{ steps.acceptance_feed.outputs.latest_url }}
release_state: ${{ steps.distribute.outputs.release_state }}
version_metadata_url: ${{ steps.distribute.outputs.version_metadata_url }}
mac_arm64_url: ${{ steps.distribute.outputs.mac_arm64_url }}
win_x64_url: ${{ steps.distribute.outputs.win_x64_url }}
mac_x64_url: ${{ steps.distribute.outputs.mac_x64_url }}
acceptance_latest_url: ${{ steps.staged.outputs.acceptance_latest_url }}
release_state: ${{ steps.staged.outputs.release_state }}
version_metadata_url: ${{ steps.staged.outputs.version_metadata_url }}
mac_arm64_url: ${{ steps.staged.outputs.mac_arm64_url }}
win_x64_url: ${{ steps.staged.outputs.win_x64_url }}
mac_x64_url: ${{ steps.staged.outputs.mac_x64_url }}
mac_arm64_smoke_result: ${{ steps.platform_results.outputs.mac_arm64_smoke_result }}
mac_x64_smoke_result: ${{ steps.platform_results.outputs.mac_x64_smoke_result }}
win_x64_smoke_result: ${{ steps.platform_results.outputs.win_x64_smoke_result }}
@@ -868,38 +942,12 @@ jobs:
core.setOutput(`${target}_smoke_result`, String(result.smokeResult || ""));
}
- name: Merge the sole version-wide Standalone graph
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/release-closure"
pnpm exec tools-release merge-closure-distribution \
"$RUNNER_TEMP/release-closure/shared/shared-contribution.json" \
"$RUNNER_TEMP/release-closure/targets/darwin-arm64/target-contribution.json" \
"$RUNNER_TEMP/release-closure/targets/darwin-x64/target-contribution.json" \
"$RUNNER_TEMP/release-closure/targets/win32-x64/target-contribution.json" \
--output "$RUNNER_TEMP/release-closure/closure-distribution.json"
- name: Accept the N-1 Shell preflight boundary
run: |
pnpm exec tools-release verify-closure-preflight \
--channel "${{ inputs.exact_name }}" \
--manifest "$RUNNER_TEMP/release-closure/closure-distribution.json" \
--release-version "${{ needs.metadata.outputs.release_version }}"
- name: Distribute staged exact metadata
id: distribute
uses: ./.github/actions/release/publish-metadata
- name: Stage exact release content
id: staged
uses: ./.github/actions/release/exact/stage
with:
enable_mac_arm64: true
enable_mac_x64: true
enable_win_x64: true
# Replayed and runner-executed targets both reach this job only after
# their manifests/contributions/results exist. An empty dynamic matrix
# reports the build job as skipped, which is execution topology rather
# than a platform outcome.
mac_arm64_result: success
mac_x64_result: success
win_x64_result: success
exact-name: ${{ inputs.exact_name }}
release-version: ${{ needs.metadata.outputs.release_version }}
env:
BASE_VERSION: ${{ needs.metadata.outputs.base_version }}
RELEASE_ACTIVATE_LATEST: "false"
@@ -925,23 +973,6 @@ jobs:
RELEASE_VERSION: ${{ needs.metadata.outputs.release_version }}
STATE_SOURCE: ${{ needs.metadata.outputs.state_source }}
- name: Stage run-scoped mutable acceptance feed
id: acceptance_feed
env:
RELEASE_ACCEPTANCE_FEED_PREFIX: ${{ inputs.exact_name }}/acceptance/runs/${{ github.run_id }}-${{ github.run_attempt }}
RELEASE_METADATA_PATH: ${{ runner.temp }}/release-metadata/metadata.json
RELEASE_PUBLIC_ORIGIN: ${{ inputs.release_public_origin != '' && inputs.release_public_origin || vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
RELEASE_STORAGE_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
RELEASE_STORAGE_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
RELEASE_STORAGE_ENDPOINT: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
RELEASE_STORAGE_REGION: auto
RELEASE_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
RELEASE_VERSION: ${{ needs.metadata.outputs.release_version }}
run: |
set -euo pipefail
pnpm exec tools-release stage-acceptance-feed
origin="${RELEASE_PUBLIC_ORIGIN%/}"
echo "latest_url=$origin/$RELEASE_ACCEPTANCE_FEED_PREFIX/latest/metadata.json" >> "$GITHUB_OUTPUT"
accept:
name: Accept and activate exact release
@@ -954,8 +985,9 @@ jobs:
inputs.publish &&
inputs.promote &&
needs.metadata.result == 'success' &&
needs.stage.result == 'success' &&
needs.stage.outputs.release_state == 'complete'
needs.metadata.outputs.hot_activated != 'true' &&
(needs.stage.result == 'success' || needs.metadata.outputs.hot_staged == 'true') &&
(needs.stage.outputs.release_state == 'complete' || needs.metadata.outputs.hot_release_state == 'complete')
}}
uses: ./.github/workflows/distribution-exact-accept.yml
secrets: inherit
@@ -963,8 +995,8 @@ jobs:
commit: ${{ needs.metadata.outputs.commit }}
content_identities: ${{ format('{{"mac_arm64":{{"closure":"{0}","shell":"{1}"}},"mac_x64":{{"closure":"{2}","shell":"{3}"}},"win_x64":{{"closure":"{4}","shell":"{5}"}}}}', needs.metadata.outputs.mac_arm64_closure_identity_digest, needs.metadata.outputs.mac_arm64_shell_identity_digest, needs.metadata.outputs.mac_x64_closure_identity_digest, needs.metadata.outputs.mac_x64_shell_identity_digest, needs.metadata.outputs.win_x64_closure_identity_digest, needs.metadata.outputs.win_x64_shell_identity_digest) }}
exact_name: ${{ inputs.exact_name }}
metadata_url: ${{ needs.stage.outputs.version_metadata_url }}
mutable_metadata_url: ${{ needs.stage.outputs.acceptance_latest_url }}
metadata_url: ${{ needs.metadata.outputs.hot_version_metadata_url || needs.stage.outputs.version_metadata_url }}
mutable_metadata_url: ${{ needs.metadata.outputs.hot_acceptance_latest_url || needs.stage.outputs.acceptance_latest_url }}
public_origin: ${{ inputs.release_public_origin != '' && inputs.release_public_origin || vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
release_version: ${{ needs.metadata.outputs.release_version }}
@@ -979,27 +1011,28 @@ jobs:
if: ${{ always() && !cancelled() }}
uses: ./.github/workflows/release-notify.yml
with:
activation_completed_at: ${{ needs.metadata.outputs.hot_activation_completed_at || needs.accept.outputs.activation_completed_at }}
actor: ${{ github.actor }}
branch: ${{ needs.metadata.outputs.branch }}
channel: ${{ inputs.exact_name }}
commit: ${{ needs.metadata.outputs.commit }}
event_name: ${{ github.event_name }}
mac_arm64_smoke: ${{ needs.stage.outputs.mac_arm64_smoke_result }}
mac_arm64_url: ${{ !inputs.publish && needs.candidate.outputs.mac_arm64_url || needs.stage.outputs.mac_arm64_url }}
mac_x64_smoke: ${{ needs.stage.outputs.mac_x64_smoke_result }}
mac_x64_url: ${{ !inputs.publish && needs.candidate.outputs.mac_x64_url || needs.stage.outputs.mac_x64_url }}
metadata_url: ${{ !inputs.publish && needs.candidate.outputs.manifest_url || needs.stage.outputs.version_metadata_url }}
mac_arm64_smoke: ${{ needs.metadata.outputs.hot_staged == 'true' && 'success' || needs.stage.outputs.mac_arm64_smoke_result }}
mac_arm64_url: ${{ !inputs.publish && needs.candidate.outputs.mac_arm64_url || needs.metadata.outputs.hot_mac_arm64_url || needs.stage.outputs.mac_arm64_url }}
mac_x64_smoke: ${{ needs.metadata.outputs.hot_staged == 'true' && 'success' || needs.stage.outputs.mac_x64_smoke_result }}
mac_x64_url: ${{ !inputs.publish && needs.candidate.outputs.mac_x64_url || needs.metadata.outputs.hot_mac_x64_url || needs.stage.outputs.mac_x64_url }}
metadata_url: ${{ !inputs.publish && needs.candidate.outputs.manifest_url || needs.metadata.outputs.hot_version_metadata_url || needs.stage.outputs.version_metadata_url }}
notification_stream: ${{ inputs.notification_stream || 'release' }}
previous_commit: ${{ needs.metadata.outputs.previous_commit }}
release_mode: ${{ inputs.publish && (inputs.promote && 'promote' || 'publish') || 'candidate' }}
release_result: ${{ needs.accept.result != 'skipped' && needs.accept.result || needs.stage.result != 'skipped' && needs.stage.result || needs.build.result }}
release_state: ${{ needs.accept.result == 'success' && 'complete' || needs.stage.outputs.release_state || (needs.build.result == 'success' && 'partial' || 'failed') }}
release_result: ${{ needs.metadata.outputs.hot_activated == 'true' && 'success' || needs.accept.result != 'skipped' && needs.accept.result || needs.stage.result != 'skipped' && needs.stage.result || needs.build.result }}
release_state: ${{ needs.metadata.outputs.hot_activated == 'true' && 'complete' || needs.accept.result == 'success' && 'complete' || needs.metadata.outputs.hot_release_state || needs.stage.outputs.release_state || (needs.build.result == 'success' && 'partial' || 'failed') }}
run_attempt: ${{ github.run_attempt }}
run_number: ${{ github.run_number }}
triggering_actor: ${{ github.triggering_actor }}
version: ${{ needs.metadata.outputs.release_version }}
win_x64_smoke: ${{ needs.stage.outputs.win_x64_smoke_result }}
win_x64_url: ${{ !inputs.publish && needs.candidate.outputs.win_x64_url || needs.stage.outputs.win_x64_url }}
win_x64_smoke: ${{ needs.metadata.outputs.hot_staged == 'true' && 'success' || needs.stage.outputs.win_x64_smoke_result }}
win_x64_url: ${{ !inputs.publish && needs.candidate.outputs.win_x64_url || needs.metadata.outputs.hot_win_x64_url || needs.stage.outputs.win_x64_url }}
workflow_name: ${{ github.workflow }}
secrets:
bot: ${{ secrets.RELEASE_FEISHU_BETA }}
+5
View File
@@ -3,6 +3,10 @@ name: release-notify
on:
workflow_call:
inputs:
activation_completed_at:
required: false
type: string
default: ""
actor:
required: false
type: string
@@ -121,6 +125,7 @@ jobs:
id: post
continue-on-error: true
env:
RELEASE_ACTIVATION_COMPLETED_AT: ${{ inputs.activation_completed_at }}
RELEASE_ACTOR: ${{ inputs.actor }}
RELEASE_BRANCH: ${{ inputs.branch }}
RELEASE_CHANNEL: ${{ inputs.channel }}
+70 -26
View File
@@ -183,6 +183,10 @@ const runnersScriptPath = join(workspaceRoot, ".github", "scripts", "runners.py"
const notifyDailyFeishuWorkflowPath = join(workspaceRoot, ".github", "workflows", "notify-daily-feishu.yml");
const notifyReleaseFeishuWorkflowPath = join(workspaceRoot, ".github", "workflows", "notify-release-feishu.yml");
const releaseNotifyWorkflowPath = join(workspaceRoot, ".github", "workflows", "release-notify.yml");
const exactStageActionPath = join(workspaceRoot, ".github", "actions", "release", "exact", "stage", "action.yml");
const publicAcceptancePlanActionPath = join(workspaceRoot, ".github", "actions", "release", "public-acceptance", "plan", "action.yml");
const publicAcceptanceActivateActionPath = join(workspaceRoot, ".github", "actions", "release", "public-acceptance", "activate", "action.yml");
const publicAcceptanceExecuteActionPath = join(workspaceRoot, ".github", "actions", "release", "public-acceptance", "execute", "action.yml");
const cutReleaseWorkflowPath = join(workspaceRoot, ".github", "workflows", "cut-release.yml");
const cutPatchReleaseWorkflowPath = join(workspaceRoot, ".github", "workflows", "cut-patch-release.yml");
const feishuCardScriptPath = join(workspaceRoot, ".github", "scripts", "release_notification.ts");
@@ -1939,6 +1943,7 @@ process.stdin.on("end", () => {
winAction,
betaMacAction,
betaWinAction,
publicAcceptanceExecution,
] = await Promise.all([
readReleaseWorkflow(releaseStableWorkflowPath, distributionStableWorkflowPath),
readFile(releaseStableScriptPath, "utf8"),
@@ -1948,6 +1953,7 @@ process.stdin.on("end", () => {
readFile(winDistributionActionPath, "utf8"),
readFile(betaMacDistributionActionPath, "utf8"),
readFile(betaWinDistributionActionPath, "utf8"),
readFile(publicAcceptanceExecuteActionPath, "utf8"),
]);
expect(releaseStableScript).toContain('mac: releaseNamespace(channel, "mac"),');
@@ -2020,7 +2026,7 @@ process.stdin.on("end", () => {
expect(releaseBetaWorkflow).toContain("steps.mac_finish.outputs.smoke_result || steps.win_finish.outputs.smoke_result");
expect(releaseBetaWorkflow).toContain("steps.mac_prepare.outputs.candidate_manifest_url || steps.win_prepare.outputs.candidate_manifest_url");
expectWindowsUpdaterSmokeContract(`${releaseBetaWorkflow}\n${betaWinAction}`, "beta");
expectWindowsUpdaterSmokeContract(`${releaseBetaWorkflow}\n${betaWinAction}\n${publicAcceptanceExecution}`, "beta");
expectWindowsUpdaterSmokeContract(`${releasePrereleaseWorkflow}\n${winAction}`, "prerelease");
expectWindowsUpdaterSmokeContract(`${releaseStableWorkflow}\n${winAction}`, "stable");
});
@@ -2132,13 +2138,15 @@ process.stdin.on("end", () => {
}
});
it("[P2] publishes release notes through one channel-neutral tools-release pipeline", async () => {
const [betaWorkflow, prereleaseWorkflow, stableWorkflow, countedDistribution, metadataDistribution] = await Promise.all([
const [betaWorkflowSource, prereleaseWorkflow, stableWorkflow, countedDistribution, metadataDistribution, exactStage] = await Promise.all([
readReleaseWorkflow(releaseBetaWorkflowPath, distributionBetaWorkflowPath),
readReleaseWorkflow(releasePrereleaseWorkflowPath, distributionPrereleaseWorkflowPath),
readReleaseWorkflow(releaseStableWorkflowPath, distributionStableWorkflowPath),
readFile(join(workspaceRoot, ".github", "actions", "release", "publish-counted", "action.yml"), "utf8"),
readFile(join(workspaceRoot, ".github", "actions", "release", "publish-metadata", "action.yml"), "utf8"),
readFile(exactStageActionPath, "utf8"),
]);
const betaWorkflow = `${betaWorkflowSource}\n${exactStage}`;
for (const workflow of [betaWorkflow, stableWorkflow, countedDistribution]) {
expect(workflow).toContain("uses: ./.github/actions/release/publish-metadata");
@@ -2422,11 +2430,15 @@ process.stdin.on("end", () => {
});
it("[P1] keeps macOS and Windows public Closure binding and cold-start evidence symmetric", async () => {
const [mac, win, distribution] = await Promise.all([
const [mac, win, distributionWorkflow, acceptancePlan, acceptanceExecution, acceptanceActivation] = await Promise.all([
readTypeScriptSourceGraph(packagedMacSpecRoot),
readTypeScriptSourceGraph(packagedWinSpecRoot),
readFile(join(workspaceRoot, ".github", "workflows", "distribution-exact-accept.yml"), "utf8"),
readFile(publicAcceptancePlanActionPath, "utf8"),
readFile(publicAcceptanceExecuteActionPath, "utf8"),
readFile(publicAcceptanceActivateActionPath, "utf8"),
]);
const distribution = `${distributionWorkflow}\n${acceptancePlan}\n${acceptanceExecution}\n${acceptanceActivation}`;
for (const [platform, spec] of [["mac", mac], ["win", win]] as const) {
expect(spec, platform).toContain("readPackagedClosureBinding");
expect(spec, platform).toContain("createPackagedColdStartObservation");
@@ -2443,7 +2455,7 @@ process.stdin.on("end", () => {
expect(distribution).toContain('"target":"mac_arm64"');
expect(distribution).toContain('"target":"mac_x64"');
expect(distribution).toContain('"target":"win_x64"');
expect(distribution).toContain("Issue public ${{ matrix.target }} acceptance credential");
expect(distribution).toContain("Issue public ${{ inputs.target }} acceptance credential");
expect(distribution).not.toContain("lifecycle receipt");
expect(distribution).not.toContain("workflow attest-public-target");
});
@@ -2950,14 +2962,16 @@ process.stdin.on("end", () => {
});
it("keeps beta on the shared payload-aware metadata surface", async () => {
const [releaseBetaWorkflow, platformPublishScript, publishMetadataScript, metadataDistribution, betaMacAction, betaWinAction] = await Promise.all([
const [releaseBetaWorkflowSource, platformPublishScript, publishMetadataScript, metadataDistribution, betaMacAction, betaWinAction, exactStage] = await Promise.all([
readReleaseWorkflow(releaseBetaWorkflowPath, distributionBetaWorkflowPath),
readFile(releaseBetaPlatformPublishScriptPath, "utf8"),
readFile(releasePublishMetadataScriptPath, "utf8"),
readFile(join(workspaceRoot, ".github", "actions", "release", "publish-metadata", "action.yml"), "utf8"),
readFile(betaMacDistributionActionPath, "utf8"),
readFile(betaWinDistributionActionPath, "utf8"),
readFile(exactStageActionPath, "utf8"),
]);
const releaseBetaWorkflow = `${releaseBetaWorkflowSource}\n${exactStage}`;
expect(betaMacAction).toContain("dmg-and-payload");
expect(betaMacAction).toContain("tools-release publish-platform");
@@ -2973,8 +2987,32 @@ process.stdin.on("end", () => {
expect(publishMetadataScript).toContain("outputs[`${target}_${artifactName}_url`] = artifact.url");
});
it("[P0] enters the single-runner exact transaction only when every reusable proof hits", async () => {
const [workflow, acceptanceWorkflow, sharedClosure, targetProjection] = await Promise.all([
readFile(releaseBetaWorkflowPath, "utf8"),
readFile(join(workspaceRoot, ".github", "workflows", "distribution-exact-accept.yml"), "utf8"),
readFile(join(workspaceRoot, ".github", "actions", "release", "closure", "shared", "action.yml"), "utf8"),
readFile(join(workspaceRoot, ".github", "actions", "release", "platform", "project", "action.yml"), "utf8"),
]);
expect(workflow).toContain("if: ${{ steps.selection.outputs.build_count == '0' && inputs.publish && inputs.promote }}");
expect(workflow).toContain("if: ${{ steps.hot_acceptance.outputs.acceptance_count == '0' }}");
expect(workflow).toContain("needs.metadata.outputs.hot_staged != 'true'");
expect(workflow).toContain("needs.metadata.outputs.hot_activated != 'true'");
expect(workflow.match(/uses: \.\/\.github\/actions\/release\/exact\/stage/gu)).toHaveLength(2);
expect(workflow).toContain("uses: ./.github/actions/release/public-acceptance/plan");
expect(workflow).toContain("uses: ./.github/actions/release/public-acceptance/activate");
expect(acceptanceWorkflow).toContain("uses: ./.github/actions/release/public-acceptance/plan");
expect(acceptanceWorkflow).toContain("uses: ./.github/actions/release/public-acceptance/activate");
expect(workflow).toContain("handoff-enabled: ${{ steps.selection.outputs.build_count != '0' || !inputs.publish || !inputs.promote }}");
expect(sharedClosure).toContain("if: ${{ inputs.handoff-enabled == 'true' }}");
expect(targetProjection.match(/if: \$\{\{ inputs\.handoff-enabled == 'true' \}\}/gu)).toHaveLength(3);
expect(workflow).toContain("Prepare immutable candidate identity\n id: candidate\n if: ${{ !inputs.promote }}");
expect(workflow).toContain("Upload immutable candidate spec\n if: ${{ !inputs.promote }}");
});
it("publishes one shared-plus-target Standalone graph in release-beta", async () => {
const [workflow, betaMacAction, betaWinAction, sharedClosureAction, targetMacAction, targetWinAction, winSpec] = await Promise.all([
const [workflow, betaMacAction, betaWinAction, sharedClosureAction, targetMacAction, targetWinAction, winSpec, exactStage, acceptanceExecution, acceptanceActivation] = await Promise.all([
readReleaseWorkflow(releaseBetaWorkflowPath, distributionBetaWorkflowPath),
readFile(betaMacDistributionActionPath, "utf8"),
readFile(betaWinDistributionActionPath, "utf8"),
@@ -2982,6 +3020,9 @@ process.stdin.on("end", () => {
readFile(closureTargetMacActionPath, "utf8"),
readFile(closureTargetWinActionPath, "utf8"),
readTypeScriptSourceGraph(packagedWinSpecRoot),
readFile(exactStageActionPath, "utf8"),
readFile(publicAcceptanceExecuteActionPath, "utf8"),
readFile(publicAcceptanceActivateActionPath, "utf8"),
]);
const sharedJob = sectionBetween(workflow, " metadata:", " build:");
const buildJob = sectionBetween(workflow, " build:", " stage:");
@@ -2991,7 +3032,10 @@ process.stdin.on("end", () => {
" public_acceptance:",
" activate:",
);
const publicAcceptanceSurface = `${publicAcceptanceJob}\n${acceptanceExecution}`;
const activationJob = workflow.slice(workflow.indexOf("\n activate:"));
const stageSurface = `${stageJob}\n${exactStage}`;
const activationSurface = `${activationJob}\n${acceptanceActivation}`;
expect(workflow).not.toContain("CLOSURE_MIN_SHELL_VERSION: ${{ inputs.exact_name == 'beta'");
expect(sharedJob).toContain("closure_min_shell_version: ${{ steps.exact.outputs.closure_min_shell_version }}");
@@ -3088,13 +3132,13 @@ process.stdin.on("end", () => {
expect(betaWinAction).toContain('RELEASE_CLOSURE_ENABLED: "false"');
expect(betaWinAction).toContain('RELEASE_SHELL_ENABLED: "true"');
expect(betaWinAction).toContain("RELEASE_SHELL_BUILD_JSON_PATH:");
expect(stageJob).toContain("Merge the sole version-wide Standalone graph");
expect(stageJob).toContain("tools-release merge-closure-distribution");
expect(stageJob).toContain("RELEASE_CLOSURE_DISTRIBUTION_MANIFEST_PATH:");
expect(stageJob).toContain('RELEASE_CLOSURE_DISTRIBUTION_REQUIRED: "true"');
expect(stageJob).not.toContain("RELEASE_CLOSURE_REQUIRED:");
expect(stageJob).toContain('RELEASE_SHELL_REQUIRED: "true"');
expect(stageJob).toContain("mac_arm64_result: success");
expect(stageSurface).toContain("Merge the sole version-wide Standalone graph");
expect(stageSurface).toContain("tools-release merge-closure-distribution");
expect(stageSurface).toContain("RELEASE_CLOSURE_DISTRIBUTION_MANIFEST_PATH:");
expect(stageSurface).toContain('RELEASE_CLOSURE_DISTRIBUTION_REQUIRED: "true"');
expect(stageSurface).not.toContain("RELEASE_CLOSURE_REQUIRED:");
expect(stageSurface).toContain('RELEASE_SHELL_REQUIRED: "true"');
expect(stageSurface).toContain("mac_arm64_result: success");
expect(stageJob).toContain("needs.build.result == 'skipped'");
expect(workflow).not.toContain("/beta/closure/");
expect(workflow).not.toContain("OD_PACKAGED_E2E_CLOSURE_BUILD_JSON_PATH:");
@@ -3107,22 +3151,22 @@ process.stdin.on("end", () => {
expect(publicAcceptanceJob).toContain("runs-on: ${{ matrix.runner }}");
expect(publicAcceptanceJob).toContain("matrix: ${{ fromJSON(needs.plan.outputs.acceptance_matrix) }}");
expect(publicAcceptanceJob).toContain("OPEN_DESIGN_POSTINSTALL_LEVEL: release-smoke");
expect(publicAcceptanceJob).toContain("tools-release prepare-public-acceptance");
expect(publicAcceptanceJob).toContain("tools-release issue-public-acceptance");
expect(publicAcceptanceJob).toContain("OD_PACKAGED_E2E_WIN_SMOKE_PROFILE: core");
expect(publicAcceptanceJob).toContain("OD_PACKAGED_E2E_WIN_SMOKE_LANES: shell");
expect(publicAcceptanceJob).toContain('OD_TOOLS_PACK_EMBEDDED_CONFIG_ONLY: "1"');
expect(publicAcceptanceJob).toContain("OD_STANDALONE_METADATA_URL: ${{ inputs.mutable_metadata_url }}");
expect(publicAcceptanceJob).not.toContain("OD_PACKAGED_CONFIG_PATH:");
expect(publicAcceptanceJob).not.toContain("OD_PACKAGED_E2E_CLOSURE_BUILD_JSON_PATH");
expect(publicAcceptanceSurface).toContain("tools-release prepare-public-acceptance");
expect(publicAcceptanceSurface).toContain("tools-release issue-public-acceptance");
expect(publicAcceptanceSurface).toContain("OD_PACKAGED_E2E_WIN_SMOKE_PROFILE: core");
expect(publicAcceptanceSurface).toContain("OD_PACKAGED_E2E_WIN_SMOKE_LANES: shell");
expect(publicAcceptanceSurface).toContain('OD_TOOLS_PACK_EMBEDDED_CONFIG_ONLY: "1"');
expect(publicAcceptanceJob).toContain("mutable-metadata-url: ${{ inputs.mutable_metadata_url }}");
expect(publicAcceptanceSurface).not.toContain("OD_PACKAGED_CONFIG_PATH:");
expect(publicAcceptanceSurface).not.toContain("OD_PACKAGED_E2E_CLOSURE_BUILD_JSON_PATH");
expect(publicAcceptanceJob).not.toContain("RELEASE_STORAGE_SECRET_ACCESS_KEY");
expect(activationJob).toContain("needs.public_acceptance.result == 'success' || needs.public_acceptance.result == 'skipped'");
expect(activationJob).not.toContain("tools-release workflow");
expect(activationJob).toContain("tools-release activate-public-release");
expect(activationJob).toContain("Activate accepted immutable exact metadata with CAS");
expect(activationJob).toContain("Read back activated exact public feed");
expect(activationJob).toContain("tools-release observe-public-feed");
expect(activationJob).not.toContain("continue-on-error");
expect(activationSurface).toContain("tools-release activate-public-release");
expect(activationSurface).toContain("Activate accepted immutable exact metadata with CAS");
expect(activationSurface).toContain("Read back activated exact public feed");
expect(activationSurface).toContain("tools-release observe-public-feed");
expect(activationSurface).not.toContain("continue-on-error");
expect(winSpec).toContain("resolveNativeAcceptanceStandaloneMetadataUrl()");
expect(winSpec).toContain("resolveNativeAcceptanceUpdateMetadataUrl()");
expect(winSpec).toContain("${process.env.RELEASE_PUBLIC_ORIGIN}/${updateScenario.channel}/latest/metadata.json");
@@ -10,6 +10,7 @@ import {
function input(overrides: Partial<ReleaseNotificationInput> = {}): ReleaseNotificationInput {
return {
activationCompletedAt: "2026-08-17T03:47:30Z",
actor: "alice",
branch: "feat/standalone-closure",
channel: "beta",
@@ -138,10 +139,13 @@ describe("release Feishu notification", () => {
coldStarts: [],
execution: expect.objectContaining({
durationMs: expect.any(Number),
observedAtMs: expect.any(Number),
pullRequest: {
number: 6956,
url: "https://github.com/nexu-io/open-design/pull/6956",
},
queueDurationMs: 0,
runStartedAtMs: expect.any(Number),
}),
failures: [],
warnings: [],
@@ -150,14 +154,14 @@ describe("release Feishu notification", () => {
expect(serialized).toContain("[0123456](https://github.com/nexu-io/open-design/commit/");
expect(serialized).not.toContain("`0123456`");
expect(serialized).not.toContain("渠道");
expect(serialized).toContain("触发者");
expect(serialized).toContain("触发");
expect(serialized).toContain("[@alice](https://github.com/alice)");
expect(serialized).toContain("手动");
expect(serialized).toContain("publish=true · 晋升 latest");
expect(serialized).toContain("release-beta #123");
expect(serialized).toContain("关联 PR");
expect(serialized).toContain("[#6956](https://github.com/nexu-io/open-design/pull/6956)");
expect(serialized).toContain("耗时");
expect(serialized).toContain("[PR #6956](https://github.com/nexu-io/open-design/pull/6956)");
expect(serialized).toContain("发布");
expect(serialized).toContain("通知");
expect(serialized).not.toContain("Closure 冷启动");
expect(serialized).not.toContain("hidden oldest change");
expect(serialized).toContain("Mac Arm");