183 lines
6.8 KiB
YAML
183 lines
6.8 KiB
YAML
name: Release Candidate Pipeline
|
|
|
|
# ═══════════════════════════════════════════════════════
|
|
# Release Candidate Pipeline — Active Memory
|
|
# ═══════════════════════════════════════════════════════
|
|
#
|
|
# Flow:
|
|
# Step 1) Run Tests
|
|
# Step 2) Build RC Payload
|
|
# Step 3) RC Checklist (Gate 1)
|
|
# Step 4) Baseline Compare (Gate 2)
|
|
# Step 5) Release Gate (Final Decision)
|
|
# Step 6) Generate RC Report
|
|
# Step 7) Upload Artifact
|
|
# Step 8) Post-Release (only if OPEN)
|
|
#
|
|
# Exit Codes:
|
|
# Checklist: PASS/WARN → 0, FAIL → 1
|
|
# Baseline: PASS/WARN → 0, FAIL → 1
|
|
# Gate: OPEN → 0, CLOSED → 1
|
|
# Report: success → 0, invalid → 1
|
|
#
|
|
# Docs: docs/README.md
|
|
# ═══════════════════════════════════════════════════════
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
readiness_status:
|
|
description: 'Readiness 状态 (pass/warn/fail)'
|
|
required: false
|
|
default: 'pass'
|
|
rollout_action:
|
|
description: '阶段晋级建议 (promote/freeze/rollback/activate_kill_switch)'
|
|
required: false
|
|
default: 'promote'
|
|
rollout_stage:
|
|
description: '当前灰度阶段 (0-100)'
|
|
required: false
|
|
default: '100'
|
|
rollback_recommendation:
|
|
description: '回滚建议 (keep_enabled/disable_replacement/activate_kill_switch)'
|
|
required: false
|
|
default: 'keep_enabled'
|
|
circuit_state:
|
|
description: '断路器状态 (closed/open/half_open)'
|
|
required: false
|
|
default: 'closed'
|
|
strict_mode:
|
|
description: 'Strict 模式'
|
|
required: false
|
|
default: 'false'
|
|
type: choice
|
|
options:
|
|
- 'true'
|
|
- 'false'
|
|
require_manual_approval:
|
|
description: '要求手动审批 (PR-17)'
|
|
required: false
|
|
default: 'false'
|
|
type: choice
|
|
options:
|
|
- 'true'
|
|
- 'false'
|
|
|
|
jobs:
|
|
rc-pipeline:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
|
|
# ─── Step 1: Run Tests ───
|
|
- name: Run unit tests
|
|
id: tests
|
|
run: |
|
|
if node --test test/baseline/test-*.test.mjs 2>&1 | tee test-output.log; then
|
|
echo "tests_passed=true" >> $GITHUB_OUTPUT
|
|
echo "tests_failed=0" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "tests_passed=false" >> $GITHUB_OUTPUT
|
|
echo "tests_failed=1" >> $GITHUB_OUTPUT
|
|
fi
|
|
continue-on-error: true
|
|
|
|
# ─── Step 2: Build RC Payload ───
|
|
- name: Compose RC payload
|
|
run: |
|
|
cat > test-summary.json << 'EOF'
|
|
{ "total": 0, "failed": ${{ steps.tests.outputs.tests_failed || 0 }}, "regressions": 0 }
|
|
EOF
|
|
cat > readiness.json << 'EOF'
|
|
{ "status": "${{ inputs.readiness_status || 'pass' }}", "safeToReplace": true }
|
|
EOF
|
|
cat > stage.json << 'EOF'
|
|
{ "action": "${{ inputs.rollout_action || 'promote' }}", "currentStage": ${{ inputs.rollout_stage || 100 }} }
|
|
EOF
|
|
cat > rollback.json << 'EOF'
|
|
{ "recommendation": "${{ inputs.rollback_recommendation || 'keep_enabled' }}" }
|
|
EOF
|
|
cat > circuit.json << 'EOF'
|
|
{ "state": "${{ inputs.circuit_state || 'closed' }}" }
|
|
EOF
|
|
|
|
# ─── Step 3: RC Checklist (Gate 1) ───
|
|
- name: RC Checklist
|
|
id: checklist
|
|
run: |
|
|
STRICT_FLAG=""
|
|
if [ "${{ inputs.strict_mode }}" = "true" ]; then STRICT_FLAG="--strict"; fi
|
|
node scripts/active-memory-release-candidate-check.mjs \
|
|
--ci \
|
|
--readiness readiness.json \
|
|
--stage stage.json \
|
|
--rollback rollback.json \
|
|
--circuit circuit.json \
|
|
--tests test-summary.json \
|
|
$STRICT_FLAG > checklist-result.json
|
|
|
|
# ─── Step 4: Baseline Compare (Gate 2) ───
|
|
- name: Baseline Compare
|
|
id: baseline
|
|
run: |
|
|
echo '{
|
|
"baseline": {"blocCount":10,"cycleCount":3,"candidateCount":391,"activeCandidateCount":220,"archivedCandidateCount":171},
|
|
"candidate": {"blocCount":10,"cycleCount":3,"candidateCount":391,"activeCandidateCount":220,"archivedCandidateCount":171}
|
|
}' | node scripts/active-memory-release-candidate-baseline-compare.mjs > baseline-result.json
|
|
|
|
# ─── Step 5: Release Gate (Final Decision) ───
|
|
- name: Release Gate
|
|
id: gate
|
|
run: |
|
|
MANUAL_FLAG=""
|
|
if [ "${{ inputs.require_manual_approval }}" = "true" ]; then MANUAL_FLAG="--require-manual-approval"; fi
|
|
|
|
node -e "
|
|
const checklist = require('./checklist-result.json');
|
|
const baseline = require('./baseline-result.json');
|
|
process.stdout.write(JSON.stringify({ checklist, baselineCompare: baseline }));
|
|
" | node scripts/active-memory-release-gate.mjs $MANUAL_FLAG > gate-result.json
|
|
|
|
STATUS=$(node -e "console.log(require('./gate-result.json').decision)")
|
|
echo "Release Gate: $STATUS"
|
|
|
|
# ─── Step 6: Generate RC Report ───
|
|
- name: Generate RC Report
|
|
if: always()
|
|
run: |
|
|
node -e "
|
|
const checklist = require('./checklist-result.json');
|
|
const baseline = require('./baseline-result.json');
|
|
const gate = require('./gate-result.json');
|
|
const { generateReleaseCandidateReport } = require('./src/active-memory/generate-release-candidate-report.mjs');
|
|
process.stdout.write(generateReleaseCandidateReport(checklist, baseline, gate));
|
|
" > rc-report.md 2>/dev/null || \
|
|
node -e "
|
|
const checklist = require('./checklist-result.json');
|
|
const { generateReleaseCandidateReport } = require('./src/active-memory/generate-release-candidate-report.mjs');
|
|
process.stdout.write(generateReleaseCandidateReport(checklist));
|
|
" > rc-report.md
|
|
|
|
echo "📄 RC Report generated:"
|
|
cat rc-report.md
|
|
|
|
- name: Upload RC Report
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: rc-report
|
|
path: rc-report.md
|
|
|
|
# ─── Step 7: Post-Gate (only if OPEN) ───
|
|
- name: Post-Release
|
|
if: success()
|
|
run: |
|
|
echo "✅ Release Gate: OPEN"
|
|
echo " 后续可接入:deploy、publish、notify 等步骤。"
|