From e3fef0b6d4a3232b4a3f29bb7f75e4b5c6402cd5 Mon Sep 17 00:00:00 2001 From: chichuan Date: Thu, 13 Aug 2026 13:34:43 +0800 Subject: [PATCH 1/5] ci: fast-path release seal fragment archival --- .github/workflows/ci.yml | 100 ++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eace8f78..53ed6bdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,7 @@ jobs: pull-requests: read outputs: changelog_only: ${{ steps.classify.outputs.changelog_only }} + release_seal_only: ${{ steps.classify.outputs.release_seal_only }} changelog_changed: ${{ steps.classify.outputs.changelog_changed }} docs_only: ${{ steps.classify.outputs.docs_only }} full_suite: ${{ steps.classify.outputs.full_suite }} @@ -39,6 +40,7 @@ jobs: with: script: | let changelogOnly = false; + let releaseSealOnly = false; let changelogChanged = false; let docsOnly = false; let fullSuite = context.eventName === 'push'; @@ -162,6 +164,43 @@ jobs: filename === 'go.mod' || filename === 'go.sum' ); + const isExactReleaseSeal = (candidates) => { + const changelog = candidates.filter( + ({ filename, status, previous_filename }) => + filename === 'CHANGELOG.md' && + status === 'modified' && + !previous_filename + ); + if (changelog.length !== 1 || candidates.length < 2) { + return false; + } + let version = ''; + return candidates.every((file) => { + if (file.filename === 'CHANGELOG.md') { + return file.status === 'modified' && !file.previous_filename; + } + if ( + file.status !== 'renamed' || + typeof file.filename !== 'string' || + typeof file.previous_filename !== 'string' || + file.additions !== 0 || + file.deletions !== 0 + ) { + return false; + } + const target = file.filename.match( + /^\.changes\/released\/([0-9]+\.[0-9]+\.[0-9]+(?:-beta\.[1-9][0-9]*)?)\/([a-z0-9][a-z0-9._-]*\.md)$/ + ); + if (!target || file.previous_filename !== `.changes/${target[2]}`) { + return false; + } + if (version && version !== target[1]) { + return false; + } + version = target[1]; + return true; + }); + }; const classifyFiles = (complete) => { const paths = files.flatMap(({ filename, previous_filename }) => [filename, previous_filename].filter( @@ -248,19 +287,26 @@ jobs: ); } - changelogOnly = + const exactChangelogDiff = files.length === 1 && files[0].filename === 'CHANGELOG.md' && files[0].status === 'modified' && !files[0].previous_filename; + releaseSealOnly = isExactReleaseSeal(files); + changelogOnly = exactChangelogDiff || releaseSealOnly; changelogChanged = files.some( ({ filename, previous_filename }) => filename === 'CHANGELOG.md' || previous_filename === 'CHANGELOG.md' ); classifyFiles(true); + if (releaseSealOnly) { + fullSuite = false; + } fastPathTrust = changelogOnly - ? 'exact pull-request revision and synthetic merge policy' + ? releaseSealOnly + ? 'exact release-seal fragment archival and synthetic merge policy' + : 'exact CHANGELOG-only revision and synthetic merge policy' : docsOnly ? 'documentation-only focused admission' : fullSuite @@ -307,8 +353,9 @@ jobs: files[0].filename === 'CHANGELOG.md' && files[0].status === 'modified' && !files[0].previous_filename; + const exactReleaseSealDiff = isExactReleaseSeal(files); - if (linearFromValidatedTip && exactChangelogDiff) { + if (linearFromValidatedTip && (exactChangelogDiff || exactReleaseSealDiff)) { const requiredContexts = [ 'Lint', 'Test', @@ -359,9 +406,15 @@ jobs: if (missing.length === 0 && nonSuccess.length === 0) { changelogOnly = true; + releaseSealOnly = exactReleaseSealDiff; changelogChanged = true; + if (releaseSealOnly) { + fullSuite = false; + } fastPathTrust = - `exact CHANGELOG-only successor of validated ${expectedBefore}`; + releaseSealOnly + ? `exact release-seal successor of validated ${expectedBefore}` + : `exact CHANGELOG-only successor of validated ${expectedBefore}`; } else { fastPathTrust = 'predecessor Code Admission is not fully successful; ' + @@ -376,6 +429,7 @@ jobs: } core.setOutput('changelog_only', String(changelogOnly)); + core.setOutput('release_seal_only', String(releaseSealOnly)); core.setOutput('changelog_changed', String(changelogChanged)); core.setOutput('docs_only', String(docsOnly)); core.setOutput('full_suite', String(fullSuite)); @@ -387,7 +441,8 @@ jobs: await core.summary .addHeading('Code Admission scope') .addRaw(`- Event: \`${context.eventName}\`\n`) - .addRaw(`- Exact modified CHANGELOG only: \`${changelogOnly}\`\n`) + .addRaw(`- Metadata-only fast path: \`${changelogOnly}\`\n`) + .addRaw(`- Release-seal fragments only: \`${releaseSealOnly}\`\n`) .addRaw(`- CHANGELOG touched: \`${changelogChanged}\`\n`) .addRaw(`- Documentation-only: \`${docsOnly}\`\n`) .addRaw(`- Full suite: \`${fullSuite}\`\n`) @@ -402,7 +457,14 @@ jobs: - name: Record CHANGELOG-only fast path if: steps.classify.outputs.changelog_only == 'true' - run: echo "Lint is satisfied by the trusted CHANGELOG-only Policy path." >> "$GITHUB_STEP_SUMMARY" + env: + RELEASE_SEAL_ONLY: ${{ steps.classify.outputs.release_seal_only }} + run: | + if [ "$RELEASE_SEAL_ONLY" = true ]; then + echo "Lint is satisfied by the trusted release-seal fragment Policy path." >> "$GITHUB_STEP_SUMMARY" + else + echo "Lint is satisfied by the trusted CHANGELOG-only Policy path." >> "$GITHUB_STEP_SUMMARY" + fi - name: Record documentation-only fast path if: steps.classify.outputs.changelog_only != 'true' && steps.classify.outputs.docs_only == 'true' @@ -1282,7 +1344,7 @@ jobs: fi mode=--content-only - if [ "$CHANGELOG_ONLY" = true ]; then + if [ "$CHANGELOG_ONLY" = true ] && [ "$RELEASE_SEAL_ONLY" != true ]; then mode=--fast-path fi ./scripts/policy/check-changelog-pr.sh \ @@ -1294,25 +1356,41 @@ jobs: PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} run: ./scripts/policy/check-release-fragments.sh "$PR_BASE_SHA" HEAD - - name: Validate trusted main CHANGELOG-only push + - name: Validate trusted main metadata-only push if: github.event_name == 'push' && needs.lint.outputs.changelog_only == 'true' env: PUSH_BEFORE_SHA: ${{ github.event.before }} PUSH_AFTER_SHA: ${{ github.event.after }} + RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }} run: | set -eu test "$(git rev-parse HEAD)" = "$PUSH_AFTER_SHA" || { echo "checked-out push revision does not match event after SHA" >&2 exit 1 } + mode=--fast-path + if [ "$RELEASE_SEAL_ONLY" = true ]; then + mode=--content-only + fi ./scripts/policy/check-changelog-pr.sh \ - --fast-path "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA" + "$mode" "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA" + if [ "$RELEASE_SEAL_ONLY" = true ]; then + ./scripts/policy/check-release-fragments.sh \ + "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA" + fi - name: Record CHANGELOG-only fast path if: needs.lint.outputs.changelog_only == 'true' + env: + RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }} run: | - echo "Only the trusted base-equivalent CHANGELOG validator ran; executable sources are unchanged." \ - >> "$GITHUB_STEP_SUMMARY" + if [ "$RELEASE_SEAL_ONLY" = true ]; then + echo "Only the trusted release-seal and fragment validators ran; executable sources are unchanged." \ + >> "$GITHUB_STEP_SUMMARY" + else + echo "Only the trusted base-equivalent CHANGELOG validator ran; executable sources are unchanged." \ + >> "$GITHUB_STEP_SUMMARY" + fi - name: Validate scoped policy if: ${{ needs.lint.outputs.changelog_only != 'true' && (needs.lint.outputs.docs_only == 'true' || (needs.lint.outputs.full_suite != 'true' && needs.lint.outputs.interface_sensitive != 'true')) }} From e58b85ea452b285a819930dc6ccc8545021a87eb Mon Sep 17 00:00:00 2001 From: chichuan Date: Thu, 13 Aug 2026 13:44:23 +0800 Subject: [PATCH 2/5] test: cover release seal CI fast path --- test/scripts/changelog_pr_gate_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/scripts/changelog_pr_gate_test.go b/test/scripts/changelog_pr_gate_test.go index bf05855b..dcf398e7 100644 --- a/test/scripts/changelog_pr_gate_test.go +++ b/test/scripts/changelog_pr_gate_test.go @@ -1025,6 +1025,12 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) { "files[0].filename === 'CHANGELOG.md'", "files[0].status === 'modified'", "!files[0].previous_filename", + "const isExactReleaseSeal", + "file.status !== 'renamed'", + "file.additions !== 0", + "file.deletions !== 0", + "releaseSealOnly = isExactReleaseSeal(files)", + "release_seal_only: ${{ steps.classify.outputs.release_seal_only }}", "pre-classification", "post-classification", "before.changed_files !== files.length", @@ -1048,7 +1054,8 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) { "mode=--fast-path", `"$mode" "$PR_BASE_SHA" HEAD`, `test "$(git rev-parse HEAD)" = "$PUSH_AFTER_SHA"`, - `--fast-path "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA"`, + `"$mode" "$PUSH_BEFORE_SHA" "$PUSH_AFTER_SHA"`, + `./scripts/policy/check-release-fragments.sh \`, "needs.lint.outputs.platform_sensitive == 'true'", `COVERAGE_TARGET: "100"`, `COVERAGE_ENFORCE_OVERALL: "false"`, From b525497da8987c7988478a7caab841e10a5704d7 Mon Sep 17 00:00:00 2001 From: chichuan Date: Thu, 13 Aug 2026 14:31:50 +0800 Subject: [PATCH 3/5] fix: pass release seal classification to policy --- .github/workflows/ci.yml | 1 + test/scripts/changelog_pr_gate_test.go | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53ed6bdd..2b3a3a4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1318,6 +1318,7 @@ jobs: env: CLASSIFIED_CHANGELOG_CHANGED: ${{ needs.lint.outputs.changelog_changed }} CHANGELOG_ONLY: ${{ needs.lint.outputs.changelog_only }} + RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }} PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | set -eu diff --git a/test/scripts/changelog_pr_gate_test.go b/test/scripts/changelog_pr_gate_test.go index dcf398e7..ec327062 100644 --- a/test/scripts/changelog_pr_gate_test.go +++ b/test/scripts/changelog_pr_gate_test.go @@ -1019,6 +1019,15 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) { } admission := readWorkflow(".github/workflows/ci.yml") + policyStart := strings.Index(admission, "\n policy:\n") + policyEnd := strings.Index(admission, "\n interface-integrity:\n") + if policyStart < 0 || policyEnd <= policyStart { + t.Fatal("Code Admission workflow missing Policy job boundaries") + } + policyJob := admission[policyStart:policyEnd] + if strings.Count(policyJob, "RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }}") != 2 { + t.Error("Policy job must pass release-seal classification to both PR and main metadata validators") + } for _, want := range []string{ "name: CI", "files.length === 1", From a26b16b30ee023d41cba2c4487974ab5925e4f23 Mon Sep 17 00:00:00 2001 From: chichuan Date: Thu, 13 Aug 2026 14:58:44 +0800 Subject: [PATCH 4/5] test: scope release seal env assertions --- test/scripts/changelog_pr_gate_test.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/scripts/changelog_pr_gate_test.go b/test/scripts/changelog_pr_gate_test.go index ec327062..a132f23b 100644 --- a/test/scripts/changelog_pr_gate_test.go +++ b/test/scripts/changelog_pr_gate_test.go @@ -1025,9 +1025,20 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) { t.Fatal("Code Admission workflow missing Policy job boundaries") } policyJob := admission[policyStart:policyEnd] - if strings.Count(policyJob, "RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }}") != 2 { - t.Error("Policy job must pass release-seal classification to both PR and main metadata validators") + requirePolicyEnv := func(step, nextStep string) { + t.Helper() + start := strings.Index(policyJob, " - name: "+step+"\n") + end := strings.Index(policyJob[start+1:], " - name: "+nextStep+"\n") + if start < 0 || end < 0 { + t.Fatalf("Policy job missing %q step boundary", step) + } + block := policyJob[start : start+1+end] + if !strings.Contains(block, "RELEASE_SEAL_ONLY: ${{ needs.lint.outputs.release_seal_only }}") { + t.Errorf("Policy %q step must receive release-seal classification", step) + } } + requirePolicyEnv("Validate changed CHANGELOG content", "Validate release fragment lifecycle") + requirePolicyEnv("Validate trusted main metadata-only push", "Record CHANGELOG-only fast path") for _, want := range []string{ "name: CI", "files.length === 1", From 9ea527a7c43908a1179992d48cc02473f13f096c Mon Sep 17 00:00:00 2001 From: chichuan Date: Thu, 13 Aug 2026 15:25:11 +0800 Subject: [PATCH 5/5] ci: reject truncated release seal file lists --- .github/workflows/ci.yml | 6 ++++-- test/scripts/changelog_pr_gate_test.go | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b3a3a4f..b2c76446 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -341,7 +341,8 @@ jobs: per_page: 100, }); files = Array.isArray(comparison.files) ? comparison.files : []; - classifyFiles(files.length < 300); + const pushFilesComplete = files.length < 300; + classifyFiles(pushFilesComplete); const linearFromValidatedTip = comparison.status === 'ahead' && comparison.merge_base_commit?.sha === expectedBefore && @@ -353,7 +354,8 @@ jobs: files[0].filename === 'CHANGELOG.md' && files[0].status === 'modified' && !files[0].previous_filename; - const exactReleaseSealDiff = isExactReleaseSeal(files); + const exactReleaseSealDiff = + pushFilesComplete && isExactReleaseSeal(files); if (linearFromValidatedTip && (exactChangelogDiff || exactReleaseSealDiff)) { const requiredContexts = [ diff --git a/test/scripts/changelog_pr_gate_test.go b/test/scripts/changelog_pr_gate_test.go index a132f23b..3dca2ca0 100644 --- a/test/scripts/changelog_pr_gate_test.go +++ b/test/scripts/changelog_pr_gate_test.go @@ -1039,6 +1039,15 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) { } requirePolicyEnv("Validate changed CHANGELOG content", "Validate release fragment lifecycle") requirePolicyEnv("Validate trusted main metadata-only push", "Record CHANGELOG-only fast path") + pushStart := strings.Index(admission, "} else if (context.eventName === 'push') {") + pushEnd := strings.Index(admission, "\n core.setOutput('changelog_only'") + if pushStart < 0 || pushEnd <= pushStart { + t.Fatal("Code Admission workflow missing push classification boundaries") + } + pushClassification := admission[pushStart:pushEnd] + if strings.Contains(pushClassification, "const exactReleaseSealDiff = isExactReleaseSeal(files)") { + t.Error("push release-seal fast path must not trust an unguarded compare file list") + } for _, want := range []string{ "name: CI", "files.length === 1", @@ -1087,7 +1096,9 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) { `package_output="$(./scripts/ci/test-packages.sh list`, "docs_only: ${{ steps.classify.outputs.docs_only }}", "full_suite: ${{ steps.classify.outputs.full_suite }}", - "classifyFiles(files.length < 300)", + "const pushFilesComplete = files.length < 300", + "classifyFiles(pushFilesComplete)", + "pushFilesComplete && isExactReleaseSeal(files)", "platformSensitive =", "files.some(isNativeGoChange)", "paths.some(isEditionSensitive)",