Files
DeepSeek-Reasonix/scripts/release-workflows.test.sh
T
SivanCola c45b168a87 Build reusable release candidates for fast recovery
Problem:
Stable releases rebuild, resign, and rerun broad validation after publication failures or unrelated main-v2 changes, making recovery slow and fragile.

Root cause:
Build, signing, acceptance, and publication were coupled to one run. Candidate identity and evidence could not be verified across runs, platform scheduling introduced unrelated waits, and control-plane faults were found after expensive jobs started.

Fix:
Introduce immutable, provenance-bound release candidates; split preparation, promotion, recovery, and public verification; share CLI/npm binaries; decouple Desktop platform scheduling; reuse one Certum session; add a publication ledger; move release-control checks ahead of builds; route CI by affected surface; and document the new workflow.

Verification:
- node --test scripts/*.test.mjs npm/*.test.mjs
- bash scripts/validate-release-control-plane.test.sh
- bash scripts/validate-release-candidate-source.test.sh
- bash scripts/verify-release-push-ci.test.sh
- bash scripts/release-stable.test.sh
- bash scripts/release-workflows.test.sh
- actionlint .github/workflows/*.yml
- go run ./tools/repolint
- go vet ./...
- (cd desktop && go vet ./...)
- go test ./internal/agent ./internal/control ./tools/desktopinventory
- (cd desktop && go test ./...)
2026-09-18 17:00:33 +08:00

1729 lines
92 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root="$(git rev-parse --show-toplevel)"
node --test "$repo_root/scripts/verify-manual-desktop-producer.test.mjs"
bash "$repo_root/scripts/manual-desktop-exception.test.sh"
test_root="$(mktemp -d "${TMPDIR:-/tmp}/reasonix-release-workflow-test.XXXXXX")"
cleanup() {
case "$test_root" in
*/reasonix-release-workflow-test.*) rm -rf -- "$test_root" ;;
*) echo "refusing to clean unexpected test directory: $test_root" >&2 ;;
esac
}
trap cleanup EXIT
# Stable tags have one entrypoint and one protected environment. Reusable
# publishers must verify that only that entrypoint can claim prior approval.
# The manual exception is immutable-candidate scoped and cannot advance any
# Desktop update entry point. Keep normal signing as the default.
python3 - "$repo_root" <<'PY'
import pathlib, sys
root = pathlib.Path(sys.argv[1])
stable = (root / '.github/workflows/release-stable.yml').read_text()
desktop = (root / '.github/workflows/release-desktop.yml').read_text()
publisher = (root / 'scripts/publish-desktop-github-release.sh').read_text()
exception = (root / 'scripts/manual-desktop-exception.sh').read_text()
for workflow in (stable, desktop):
block = workflow.split(' desktop_manual_only:', 1)[1].split('\n\n', 1)[0]
assert 'default: false' in block
# One owner decides which tags may skip Authenticode, so an exception
# cannot drift between the orchestrator, the publisher, and the release.
assert 'scripts/manual-desktop-exception.sh validate' in workflow
assert '7278072720a2dc7a31cce0eec18c1eacc149c0e0' in exception
verifier = (root / 'scripts/verify-stable-release-artifacts.sh').read_text()
# Postflight reads the same allowlist and asserts the update pointers never
# serve the manual release, instead of naming one release's prior version.
assert 'manual-desktop-exception.sh" validate "$desktop_tag"' in verifier
assert '1.38.8' not in verifier and 'v1.38.7' not in verifier
assert 'inputs.allow_recovery' in stable.split('name: Restrict manual Desktop distribution', 1)[1].split('- name:', 1)[0]
assert stable.count('desktop_manual_only: ${{ inputs.desktop_manual_only || false }}') == 2
assert "HAS_CERTUM: ${{ secrets.CERTUM_USERNAME != '' && secrets.CERTUM_OTP_URI != '' && secrets.CERTUM_KEY_ID != '' && !inputs.desktop_manual_only }}" in desktop
assert desktop.index('name: Validate signing mode') < desktop.index('name: Build and package')
assert "inputs.orchestrated }}\" != \"true\"" in desktop
assert 'manual-download only' in desktop
manual_exit = desktop.index('if [ "$DESKTOP_MANUAL_ONLY" = "true" ]; then', desktop.index('name: Mirror immutable assets'))
assert manual_exit < desktop.index('validate_current_pointer()', manual_exit)
assert 'pointer_moved=false' in desktop[manual_exit:manual_exit + 350]
attach = desktop.split('name: Attach desktop manifest to matching CLI release', 1)[1].split('env:', 1)[0]
assert '!inputs.desktop_manual_only' in attach
manual_publish = publisher.split('if [ "${DESKTOP_MANUAL_ONLY:-false}" = "true" ]; then', 1)[1].split('elif', 1)[0]
assert 'manual-desktop-exception.sh" validate "$tag"' in manual_publish
assert 'args+=(--latest=false)' in manual_publish
assert 'name: Sign artifacts (minisign)' in desktop
PY
[ "$(grep -Ec '^ environment: release$' "$repo_root/.github/workflows/release-stable.yml")" = "1" ]
test ! -e "$repo_root/.github/workflows/release-stable-trigger.yml"
candidate="$repo_root/.github/workflows/release-candidate.yml"
promote="$repo_root/.github/workflows/release-promote.yml"
verify="$repo_root/.github/workflows/release-verify.yml"
for workflow in "$candidate" "$promote" "$verify"; do test -s "$workflow"; done
candidate_dispatch="$(sed -n '/workflow_dispatch:/,/^ push:/p' "$candidate")"
[ "$(grep -Ec '^ [a-z_]+:$' <<<"$candidate_dispatch")" = "1" ]
! grep -Fq 'inputs.source_sha' "$candidate"
! grep -Fq 'pull_request_target' "$candidate"
grep -Fq 'branches: [main-v2]' "$candidate"
grep -Fq -- '- release-notes/releases.json' "$candidate"
grep -Fq 'actions/attest-build-provenance@v3' "$candidate"
grep -Fq 'bash scripts/validate-release-control-plane.sh' "$candidate"
grep -Fq 'retention-days: 30' "$candidate"
grep -Fq 'retention-days: 90' "$candidate"
grep -Fq 'name: release-candidate-evidence-${{ needs.resolve.outputs.candidate_id }}' "$candidate"
grep -Fq 'RELEASE_EVIDENCE_ARTIFACT_ID: ${{ steps.evidence.outputs.artifact-id }}' "$candidate"
grep -Fq 'gh attestation verify' "$promote"
grep -Fq 'git push --atomic origin' "$promote"
[ "$(grep -Ec '^ environment: release$' "$promote")" = "1" ]
grep -Fq 'group: stable-release-publication' "$promote"
grep -Fq 'VERIFY_PUBLIC_SITE_ONLY: "true"' "$promote"
grep -Fq 'release-publication-ledger-' "$promote"
grep -Fq 'release-publication-timing-' "$promote"
grep -Fq 'release-candidate-timing-' "$candidate"
grep -Fq 'immutable-complete-newer-pointer-preserved' "$promote"
grep -Fq 'RELEASE_REVOKED_CANDIDATES' "$candidate"
grep -Fq 'PUSH_SHA: ${{ github.sha }}' "$candidate"
grep -Fq 'node scripts/resolve-release-candidate.mjs active "$candidate_id"' "$candidate"
grep -Fq 'The sealed record exists but its payload expired; preparing fresh files.' "$candidate"
grep -Fq 'RELEASE_REVOKED_CANDIDATES' "$promote"
grep -Fq 'run: bash scripts/verify-stable-release-artifacts.sh' "$verify"
grep -Fq 'RELEASE_OPERATION: recover' "$verify"
grep -Fq -- '--dump-dom' "$repo_root/scripts/verify-stable-release-artifacts.sh"
grep -Eq 'ALLOW_STABLE_RECOVERY:.*inputs\.allow_recovery' \
"$repo_root/.github/workflows/release-stable.yml"
grep -Fq 'bash scripts/validate-stable-candidate.sh "$RELEASE_VERSION" "$RELEASE_SHA"' \
"$repo_root/.github/workflows/release-stable.yml"
grep -Fq 'bash scripts/verify-release-push-ci.sh "$RELEASE_SHA"' \
"$repo_root/.github/workflows/release-stable.yml"
grep -Fq 'if: ${{ !inputs.allow_recovery }}' \
"$repo_root/.github/workflows/release-stable.yml"
test -x "$repo_root/scripts/validate-stable-candidate.sh"
test -x "$repo_root/scripts/verify-release-push-ci.sh"
test -x "$repo_root/scripts/validate-release-control-plane.sh"
test -x "$repo_root/scripts/finalize-windows-signed-candidate.sh"
for retired in release-preview.yml release-cli-trigger.yml release-desktop-trigger.yml; do
test ! -e "$repo_root/.github/workflows/$retired"
done
npm_events="$(sed -n '/^on:/,/^permissions:/p' "$repo_root/.github/workflows/release-npm.yml")"
! grep -Eq 'push:|npm-v\*-\*' <<<"$npm_events"
grep -Eq '^name: Prepare release$' "$repo_root/.github/workflows/prepare-release-notes.yml"
[ "$(grep -Fc 'git merge --no-edit origin/main-v2' "$repo_root/.github/workflows/prepare-release-notes.yml")" = "0" ]
grep -Fq 'RELEASE_NOTES_SOURCE_SHA=$source_sha' "$repo_root/.github/workflows/prepare-release-notes.yml"
grep -Fq -- '--to "$RELEASE_NOTES_SOURCE_SHA"' "$repo_root/.github/workflows/prepare-release-notes.yml"
notes_dispatch="$(sed -n '/workflow_dispatch:/,/permissions:/p' "$repo_root/.github/workflows/prepare-release-notes.yml")"
[ "$(grep -Ec '^ [a-z_]+:$' <<<"$notes_dispatch")" = "1" ]
grep -Fq 'GitHub Actions could not open the PR; the reviewed branch is preserved.' \
"$repo_root/.github/workflows/prepare-release-notes.yml"
if grep -Eq '^ push:$' "$repo_root/.github/workflows/release-stable.yml" ||
grep -Eq '^ push:$' "$repo_root/.github/workflows/release.yml" ||
grep -Eq '^ push:$' "$repo_root/.github/workflows/release-desktop.yml"; then
echo "production workflow must be dispatched on protected main-v2, not run on a tag origin" >&2
exit 1
fi
for workflow in release.yml release-npm.yml release-desktop.yml; do
grep -Eq 'github\.workflow_ref' "$repo_root/.github/workflows/$workflow"
grep -Eq 'github\.ref_protected' "$repo_root/.github/workflows/$workflow"
grep -Eq 'inputs\.approved_sha' "$repo_root/.github/workflows/$workflow"
grep -Eq 'verify-release-tag\.sh' "$repo_root/.github/workflows/$workflow"
grep -Eq 'release-\{1\}\.yml' "$repo_root/.github/workflows/$workflow"
done
for workflow in release.yml release-npm.yml; do
grep -Eq "needs\.cache-guard\.result == 'success'" "$repo_root/.github/workflows/$workflow"
done
if grep -Eq '^ cache-guard:|needs\.cache-guard' "$repo_root/.github/workflows/release-desktop.yml"; then
echo "Desktop must reuse the orchestrator cache/docs evidence instead of executing candidate guard scripts twice" >&2
exit 1
fi
grep -Eq 'options: \[stable\]' "$repo_root/.github/workflows/release.yml"
grep -A6 -E '^ channel:' "$repo_root/.github/workflows/release.yml" |
grep -Eq 'default: stable'
grep -Eq '^ environment: release$' "$repo_root/.github/workflows/release.yml"
grep -Eq 'GORELEASER_CURRENT_TAG:.*needs\.resolve\.outputs\.tag' \
"$repo_root/.github/workflows/release.yml"
grep -Eq 'bash scripts/resolve-cli-release\.sh' "$repo_root/.github/workflows/release.yml"
grep -Eq 'git merge-base --is-ancestor.*origin/main-v2' "$repo_root/.github/workflows/release.yml"
grep -Eq 'CLI Preview must tag current main-v2' "$repo_root/.github/workflows/release.yml"
grep -Fq 'ALLOW_PREVIEW_RECOVERY: ${{ inputs.allow_preview_recovery }}' "$repo_root/.github/workflows/release.yml"
grep -Eq 'Preview recovery requires the approved Preview orchestrator' "$repo_root/.github/workflows/release.yml"
grep -Eq "channel == 'stable'.*HOMEBREW_TAP_TOKEN" "$repo_root/.github/workflows/release.yml"
grep -Fq 'name: Isolate release-control checkout from product git state' \
"$repo_root/.github/workflows/release.yml"
grep -Fq "grep -qxF '/release-control/'" "$repo_root/.github/workflows/release.yml"
grep -Fq 'git check-ignore -q release-control/' "$repo_root/.github/workflows/release.yml"
release_control_isolation_line="$(
grep -n -m1 'name: Isolate release-control checkout from product git state' \
"$repo_root/.github/workflows/release.yml" | cut -d: -f1
)"
goreleaser_action_line="$(
grep -n -m1 'uses: goreleaser/goreleaser-action@' \
"$repo_root/.github/workflows/release.yml" | cut -d: -f1
)"
[ "$release_control_isolation_line" -lt "$goreleaser_action_line" ]
# A protected control-plane checkout must remain available to the workflow
# without making the immutable product checkout dirty for GoReleaser.
mkdir -p "$test_root/product-checkout/release-control"
git init -q "$test_root/product-checkout"
printf 'control plane\n' >"$test_root/product-checkout/release-control/marker"
[ "$(git -C "$test_root/product-checkout" status --porcelain --untracked-files=all)" = \
'?? release-control/marker' ]
product_git_common_dir="$(
git -C "$test_root/product-checkout" rev-parse --path-format=absolute --git-common-dir
)"
product_exclude="$product_git_common_dir/info/exclude"
printf '%s\n' '/release-control/' >>"$product_exclude"
git -C "$test_root/product-checkout" check-ignore -q release-control/
[ -z "$(git -C "$test_root/product-checkout" status --porcelain --untracked-files=all)" ]
grep -Eq "needs\.build\.result == 'success'" "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq "needs\.publish\.result == 'success'" "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'options: \[stable\]' "$repo_root/.github/workflows/release-desktop.yml"
desktop_dispatch="$(sed -n '/^ workflow_dispatch:/,/^ workflow_call:/p' "$repo_root/.github/workflows/release-desktop.yml")"
if grep -Eqi 'preview|canary' <<<"$desktop_dispatch"; then
echo "Standalone Desktop dispatch must not expose a Preview or Canary choice" >&2
exit 1
fi
grep -Eq '^ resolve:$' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'sha:.*steps\.candidate\.outputs\.sha' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'bash scripts/resolve-desktop-candidate.sh' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'name: Smoke-test packaged Electron startup' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq '^ windows-build:$' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq '^ windows-sign:$' "$repo_root/.github/workflows/release-desktop.yml"
sed -n '/^ windows-sign:/,/^ windows-runtime-acceptance:/p' \
"$repo_root/.github/workflows/release-desktop.yml" | grep -Fq 'needs: [resolve, windows-build, signing-contract]'
grep -Fq 'desktop/build/electron/${{ matrix.name }}/app' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'node desktop/packaging/smoke.mjs' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq -- '--service desktop/build/bin/reasonix-desktop.exe' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'node desktop/packaging/smoke.mjs "$RUNNER_TEMP/desktop-startup/Reasonix.app"' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'xvfb-run -a node desktop/packaging/smoke.mjs' "$repo_root/.github/workflows/release-desktop.yml"
test ! -e "$repo_root/scripts/test-webview2-approval-smoke.ps1"
# The Wails-era WebView2/WebKitGTK native smoke harnesses are retired with the
# old shell; the packaged Electron startup smoke replaces them.
test ! -e "$repo_root/scripts/test-webview2-native-smoke.ps1"
test ! -e "$repo_root/scripts/test-transcript-selection-webview2.ps1"
test ! -e "$repo_root/.github/workflows/transcript-native-smoke.yml"
grep -Fq 'name: Package Electron shell for native startup smoke' "$repo_root/.github/workflows/ci.yml"
grep -Fq 'name: Smoke-test Electron native startup' "$repo_root/.github/workflows/ci.yml"
grep -Fq 'node packaging/package.mjs windows/amd64 v0.0.0-ci canary' \
"$repo_root/.github/workflows/ci.yml"
grep -Fq -- '-X main.version=v0.0.0-ci -X main.channel=canary' "$repo_root/.github/workflows/ci.yml"
grep -Fq 'node packaging/smoke.mjs build/electron/windows-amd64/app --service build/bin/reasonix-desktop.exe' \
"$repo_root/.github/workflows/ci.yml"
grep -Fq 'node packaging/verify.mjs ../dist/Reasonix-darwin-arm64.zip' \
"$repo_root/.github/workflows/ci.yml"
grep -Fq 'node packaging/verify.mjs ../dist/Reasonix-windows-amd64.zip' \
"$repo_root/.github/workflows/ci.yml"
grep -Fq 'node packaging/signing-files.mjs build/windows/signing-payload --check' \
"$repo_root/.github/workflows/ci.yml"
if grep -Fq 'wails build' "$repo_root/.github/workflows/ci.yml"; then
echo "CI must package the Electron shell, not wails build" >&2
exit 1
fi
for retired_review_gate in \
"$repo_root/.github/workflows/cross-boundary-review.yml" \
"$repo_root/.github/workflows/cross-boundary-review-signal.yml" \
"$repo_root/.github/scripts/cross-boundary-review.cjs" \
"$repo_root/.github/scripts/cross-boundary-review.test.cjs"; do
if [ -e "$retired_review_gate" ]; then
echo "Retired cross-boundary review gate still exists: $retired_review_gate" >&2
exit 1
fi
done
! grep -Fq 'cross-boundary-review' "$repo_root/.github/workflows/ci.yml"
! grep -Fq 'independent cross-boundary review' "$repo_root/.github/pull_request_template.md"
desktop_build_line="$(grep -n -m1 'name: Build and package' "$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1)"
electron_smoke_line="$(grep -n -m1 'name: Smoke-test packaged Electron startup' "$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1)"
signing_upload_line="$(grep -n -m1 'name: Upload Windows signing inputs' "$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1)"
[ "$desktop_build_line" -lt "$electron_smoke_line" ]
[ "$electron_smoke_line" -lt "$signing_upload_line" ]
[ "$(grep -Fc 'IN_ORCHESTRATOR: ${{ inputs.orchestrator }}' "$repo_root/.github/workflows/release-desktop.yml")" = "3" ]
[ "$(grep -Fc 'name: Revalidate immutable Desktop candidate' "$repo_root/.github/workflows/release-desktop.yml")" = "2" ]
[ "$(grep -Fc 'ref: ${{ needs.resolve.outputs.sha }}' "$repo_root/.github/workflows/release-desktop.yml")" -ge 4 ]
[ "$(grep -Ec '^ path: release-control$' "$repo_root/.github/workflows/release-desktop.yml")" -ge "5" ]
grep -Fq 'name: Checkout protected release verifier' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'scripts/desktop-release-artifacts.mjs' "$repo_root/.github/workflows/release-desktop.yml"
if grep -Fq 'test-webview2-native-smoke.ps1' "$repo_root/.github/workflows/release-desktop.yml"; then
echo "Desktop release must smoke the packaged Electron shell, not the Wails/WebView2 harness" >&2
exit 1
fi
grep -Fq 'finalize-windows-signed-candidate.sh' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'verify-windows-authenticode.ps1' "$repo_root/scripts/finalize-windows-signed-candidate.sh"
[ "$(grep -Fc 'ref: ${{ github.workflow_sha }}' "$repo_root/.github/workflows/release-desktop.yml")" -ge 2 ]
[ "$(grep -Fc 'bash release-control/scripts/resolve-desktop-candidate.sh' "$repo_root/.github/workflows/release-desktop.yml")" = "2" ]
[ "$(grep -Fc 'RELEASE_TAG: ${{ inputs.approved_cli_tag }}' "$repo_root/.github/workflows/release-desktop.yml")" = "3" ]
desktop_mirror="$(sed -n '/^ mirror:/,$p' "$repo_root/.github/workflows/release-desktop.yml")"
if grep -Fq 'RELEASE_TAG: ${{ inputs.tag }}' <<<"$desktop_mirror"; then
echo "Desktop mirror must revalidate the orchestrator-approved CLI tag" >&2
exit 1
fi
desktop_publish="$(sed -n '/name: publish release/,/name: mirror to R2/p' "$repo_root/.github/workflows/release-desktop.yml")"
if grep -Eq 'bash scripts/(resolve-desktop-candidate|validate-desktop-release-manifest|publish-desktop-github-release)\.sh' <<<"$desktop_publish"; then
echo "Desktop publish job uses candidate-controlled release scripts" >&2
exit 1
fi
if grep -Eq 'bash scripts/(resolve-desktop-candidate|validate-desktop-release-manifest|verify-desktop-release-directory|decide-desktop-pointer-update)\.sh' <<<"$desktop_mirror"; then
echo "Desktop mirror job uses candidate-controlled release scripts" >&2
exit 1
fi
if grep -Fq 'ref: ${{ inputs.approved_sha || inputs.tag || github.ref }}' \
"$repo_root/.github/workflows/release-desktop.yml"; then
echo "Desktop jobs must checkout the resolved immutable candidate SHA" >&2
exit 1
fi
grep -Fq "group: release-desktop-\${{ (inputs.channel == 'preview' || inputs.channel == 'canary') && 'preview' || 'stable' }}" \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'IN_PRODUCTION_SIGNING_SMOKE:.*inputs\.production_signing_smoke' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'IN_SIGNING_PREFLIGHT:.*inputs\.signing_preflight' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq "group: release-npm-\${{ inputs.channel || 'next' }}" \
"$repo_root/.github/workflows/release-npm.yml"
npm_dispatch="$(sed -n '/^ workflow_dispatch:/,/^ workflow_call:/p' "$repo_root/.github/workflows/release-npm.yml")"
grep -Eq 'default: stable' <<<"$npm_dispatch"
if grep -Eq '^ - canary$' <<<"$npm_dispatch"; then
echo "Standalone npm dispatch must not expose public Canary publication" >&2
exit 1
fi
grep -Fq 'if: ${{ !inputs.orchestrated }}' "$repo_root/.github/workflows/release-npm.yml"
grep -Fq 'Publish or recover immutable npm packages' "$repo_root/.github/workflows/release-npm.yml"
npm_cache_guard="$(sed -n '/^ cache-guard:/,/^ npm:/p' "$repo_root/.github/workflows/release-npm.yml")"
if grep -Fq 'RECOVERY_CONTROL_SHA' <<<"$npm_cache_guard"; then
echo "npm recovery control plane must load in the publisher job after candidate checkout" >&2
exit 1
fi
npm_job="$(sed -n '/^ npm:/,$p' "$repo_root/.github/workflows/release-npm.yml")"
grep -Fq 'RECOVERY_CONTROL_SHA: ${{ github.workflow_sha }}' <<<"$npm_job"
grep -Fq 'git restore --source="$RECOVERY_CONTROL_SHA"' <<<"$npm_job"
for recovery_script in npm/publish.mjs scripts/finalize-npm-official-release.mjs; do
grep -Fq "$recovery_script" <<<"$npm_job"
done
# Orchestrated Stable recovery needs the same protected publisher repair as a
# standalone run; the immutable product checkout must not select the old helper.
npm_control_step="$(sed -n '/ - name: Load approved npm publication control plane/,/ - uses: actions\/setup-go@v7/p' "$repo_root/.github/workflows/release-npm.yml")"
[ -n "$npm_control_step" ]
if printf '%s\n' "$npm_control_step" | grep -q 'if:'; then
echo "npm publication control plane must load for orchestrated recovery too" >&2
exit 1
fi
grep -Fq 'publishPackages' "$repo_root/npm/build.mjs"
grep -Fq 'thumbprint: ${{ secrets.CERTUM_KEY_ID }}' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'SIGNPATH_RELEASE_SIGNING_ATTESTATION does not match' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq '^ signing_preflight:$' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq '^ signing_preflight_verified:$' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq '^ signing-contract:$' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq '^ attest-signing-contract:$' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq '^ production_signing_smoke:$' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq "needs\.build\.result == 'success'.*!inputs\.production_signing_smoke.*!inputs\.signing_preflight" \
"$repo_root/.github/workflows/release-desktop.yml"
! grep -Eq 'signpath/github-action-submit-signing-request|secrets.SIGNPATH_API_TOKEN|complete-signpath-request\.ps1' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'uses: ./release-control/.github/actions/setup-certum' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'sign-certum.ps1" -PayloadDirectory' "$repo_root/scripts/finalize-windows-signed-candidate.sh"
grep -Fq 'sign-certum.ps1" -FilePath' "$repo_root/scripts/finalize-windows-signed-candidate.sh"
grep -Fq -- '-ExpectedThumbprint "$CERTUM_KEY_ID"' "$repo_root/scripts/finalize-windows-signed-candidate.sh"
grep -Eq '^ signpath-preflight:$' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq 'signing_preflight: true' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq 'signing_preflight_verified: true' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq 'needs: \[authorize, signpath-preflight\]' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq 'name: Require R2 for Preview' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq "channel == 'preview'.*HAS_R2 != 'true'" "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'name: Validate generated manifest before publication' "$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'name: Validate R2 manifest before upload' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'scripts/publish-desktop-github-release.sh' "$repo_root/.github/workflows/release-desktop.yml"
if grep -Eq 'gh release create .*dist/' "$repo_root/.github/workflows/release-desktop.yml"; then
echo "Desktop release publication must recover an existing GitHub release" >&2
exit 1
fi
grep -Fq 'scripts/decide-desktop-pointer-update.sh' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'scripts/verify-desktop-release-directory.sh' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'scripts/compare-desktop-release-manifests.sh' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'go -C release-control/desktop build -o "$signature_verifier" ./cmd/sign' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'verify_signature_directory assets' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'verify_signature_directory "$existing_directory"' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq -- '--allow-authenticated-payload-differences' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'cp "$payload" "assets/$payload_relative"' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'cp "$signature" "assets/$relative"' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'verify-desktop-release-manifest-assets.sh' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'verify_signature_directory "$published_directory"' \
"$repo_root/.github/workflows/release-desktop.yml"
manifest_adoption_line="$(grep -nF 'cp "$existing_directory/latest.json" assets/latest.json' \
"$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1)"
authenticated_compare_line="$(grep -nF -- '--allow-authenticated-payload-differences assets "$existing_directory"' \
"$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1)"
if [ -z "$manifest_adoption_line" ] || [ -z "$authenticated_compare_line" ] ||
[ "$manifest_adoption_line" -ge "$authenticated_compare_line" ]; then
echo "Desktop recovery must adopt a validated existing manifest before directory comparison" >&2
exit 1
fi
grep -Fq 'download_optional "preview/latest.json"' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'download_optional "canary/latest.json"' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq '"legacy-${current_channel}" "$current_version" "$current_base"' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'legacy-preview "$current_version" "$legacy_preview_base"' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'publish_pointer canary' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'publish_pointer preview' "$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'cmp -s /tmp/reasonix-desktop-canary-latest.json /tmp/reasonix-desktop-preview-latest.json' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq 'steps.mirror_r2.outputs.pointer_moved' "$repo_root/.github/workflows/release-desktop.yml"
if grep -Fq 'aws s3 cp assets/ "s3://${R2_BUCKET}/preview/"' "$repo_root/.github/workflows/release-desktop.yml" ||
grep -Fq 'aws s3 cp assets/ "s3://${R2_BUCKET}/canary/"' "$repo_root/.github/workflows/release-desktop.yml"; then
echo "Desktop public pointers must contain only latest.json, not mutable asset copies" >&2
exit 1
fi
desktop_generated_validation_line="$(
grep -n -m1 'name: Validate generated manifest before publication' \
"$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1
)"
desktop_github_publish_line="$(
grep -n -m1 'name: Publish GitHub release' \
"$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1
)"
desktop_r2_validation_line="$(
grep -n -m1 'name: Validate R2 manifest before upload' \
"$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1
)"
desktop_r2_upload_line="$(
grep -n -m1 'name: Mirror immutable assets and advance R2 pointer' \
"$repo_root/.github/workflows/release-desktop.yml" | cut -d: -f1
)"
[ "$desktop_generated_validation_line" -lt "$desktop_github_publish_line" ]
[ "$desktop_r2_validation_line" -lt "$desktop_r2_upload_line" ]
grep -Eq '^ postflight:$' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq 'verify-stable-release-artifacts\.sh' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq 'name: Upload reviewed release notes' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq 'name: orchestrator-reviewed-release-notes' "$repo_root/.github/workflows/release-stable.yml"
for channel in cli npm desktop; do
grep -Eq '^ publish_'"$channel"':' "$repo_root/.github/workflows/release-stable.yml"
grep -Eq "inputs\.publish_$channel" "$repo_root/.github/workflows/release-stable.yml"
done
# The checked-in SignPath policy contract is the single source of truth for the
# provider allowlist and every top-level workflow that can reach signing.
go run ./cmd/signpath-contract validate
contract_fingerprint="$(go run ./cmd/signpath-contract fingerprint)"
grep -Eq '^v1:[0-9a-f]{64}$' <<<"$contract_fingerprint"
grep -Eq 'public postflight will still verify it' "$repo_root/.github/workflows/release-stable.yml"
for workflow in release.yml release-desktop.yml; do
grep -Eq 'name: Download orchestrator-reviewed release notes' "$repo_root/.github/workflows/$workflow"
grep -Eq 'name: orchestrator-reviewed-release-notes' "$repo_root/.github/workflows/$workflow"
grep -Eq 'if: \$\{\{ !inputs\.orchestrated' "$repo_root/.github/workflows/$workflow"
done
# CLI clients and the website consume separate Stable and Preview pointers. The
# publisher must validate every public archive before moving a pointer, retain an
# immutable per-tag record, and leave both public pointers untouched for RCs.
cli_release_workflow="$repo_root/.github/workflows/release.yml"
grep -Eq 'name: Publish CLI release metadata to R2' "$cli_release_workflow"
grep -Fq 'cli/releases/${TAG}/latest.json' "$cli_release_workflow"
grep -Fq 'cli/${channel}/latest.json' "$cli_release_workflow"
grep -Fq "group: release-cli-\${{ inputs.channel || 'stable' }}" "$cli_release_workflow"
grep -Fq 'scripts/decide-cli-pointer-update.sh' "$cli_release_workflow"
grep -Fq 'scripts/validate-cli-release-manifest.sh' "$cli_release_workflow"
grep -Fq 'scripts/compare-cli-release-manifests.sh' "$cli_release_workflow"
grep -Eq 'name: Decide whether CLI artifacts need publication' "$cli_release_workflow"
grep -Fq 'path: release-control' "$cli_release_workflow"
grep -Fq 'ref: ${{ github.workflow_sha }}' "$cli_release_workflow"
grep -Fq 'release-control/scripts/decide-cli-release-publication.sh' "$cli_release_workflow"
cli_goreleaser="$(sed -n '/^ goreleaser:/,$p' "$cli_release_workflow")"
if grep -Fq 'bash scripts/decide-cli-release-publication.sh' <<<"$cli_goreleaser"; then
echo "CLI recovery uses candidate-controlled publication policy" >&2
exit 1
fi
grep -Fq "steps.publication.outputs.decision == 'publish' && inputs.candidate_artifact_name == ''" "$cli_release_workflow"
grep -Fq "steps.publication.outputs.decision == 'publish' && inputs.candidate_artifact_name != ''" "$cli_release_workflow"
grep -Fq 'immutable CLI release metadata for $TAG already exists with different content' "$cli_release_workflow"
grep -Fq 'cmp -s /tmp/cli-release.json /tmp/cli-release.pointer.json' "$cli_release_workflow"
grep -Eq 'internal CLI release .*Stable and Preview pointers remain unchanged' "$cli_release_workflow"
cli_public_validation_line="$(
grep -n -m1 'if \[ -n "\$channel" \]' "$cli_release_workflow" | cut -d: -f1
)"
cli_immutable_upload_line="$(
grep -n -m1 'cli/releases/${TAG}/latest.json' "$cli_release_workflow" | cut -d: -f1
)"
[ "$cli_public_validation_line" -lt "$cli_immutable_upload_line" ]
pointer_compare="$repo_root/scripts/compare-cli-release-tags.sh"
test -x "$pointer_compare"
[ "$(bash "$pointer_compare" stable v1.2.4 v1.2.3)" = "update" ]
[ "$(bash "$pointer_compare" stable v1.2.3 v1.2.3)" = "skip" ]
[ "$(bash "$pointer_compare" stable v1.2.2 v1.2.3)" = "skip" ]
[ "$(bash "$pointer_compare" stable v100000000000000000000.0.0 v99999999999999999999.999.999)" = "update" ]
[ "$(bash "$pointer_compare" preview v1.2.3-preview.11 v1.2.3-preview.9)" = "update" ]
[ "$(bash "$pointer_compare" preview v1.2.3-preview.9 v1.2.3-preview.9)" = "skip" ]
[ "$(bash "$pointer_compare" preview v1.2.3-preview.8 v1.2.3-preview.9)" = "skip" ]
[ "$(bash "$pointer_compare" stable v1.2.3 "")" = "update" ]
if bash "$pointer_compare" stable v1.2.3 v1.2.3-preview.1 >/dev/null 2>&1; then
echo "stable comparator accepted a preview pointer" >&2
exit 1
fi
if bash "$pointer_compare" preview v1.2.3-preview.1 v1.2.3 >/dev/null 2>&1; then
echo "preview comparator accepted a stable pointer" >&2
exit 1
fi
if bash "$pointer_compare" stable v01.2.3 v1.2.2 >/dev/null 2>&1; then
echo "stable comparator accepted a non-canonical tag" >&2
exit 1
fi
cli_pointer_decider="$repo_root/scripts/decide-cli-pointer-update.sh"
test -x "$cli_pointer_decider"
cli_pointer_candidate="$test_root/cli-pointer-candidate.json"
cli_pointer_older="$test_root/cli-pointer-older.json"
cli_pointer_equal="$test_root/cli-pointer-equal.json"
cli_pointer_equal_different="$test_root/cli-pointer-equal-different.json"
cli_pointer_newer="$test_root/cli-pointer-newer.json"
printf '{"tag_name":"v1.3.0","marker":"candidate"}\n' >"$cli_pointer_candidate"
printf '{"tag_name":"v1.2.9","marker":"older"}\n' >"$cli_pointer_older"
cp "$cli_pointer_candidate" "$cli_pointer_equal"
printf '{"tag_name":"v1.3.0","marker":"different"}\n' >"$cli_pointer_equal_different"
printf '{"tag_name":"v1.3.1","marker":"newer"}\n' >"$cli_pointer_newer"
[ "$(bash "$cli_pointer_decider" stable "$cli_pointer_candidate" -)" = "update" ]
[ "$(bash "$cli_pointer_decider" stable "$cli_pointer_candidate" "$cli_pointer_older")" = "update" ]
[ "$(bash "$cli_pointer_decider" stable "$cli_pointer_candidate" "$cli_pointer_equal")" = "skip" ]
[ "$(bash "$cli_pointer_decider" stable "$cli_pointer_candidate" "$cli_pointer_equal_different")" = "update" ]
[ "$(bash "$cli_pointer_decider" stable "$cli_pointer_candidate" "$cli_pointer_newer")" = "skip" ]
if bash "$cli_pointer_decider" stable "$cli_pointer_candidate" "$test_root/missing-cli-pointer.json" >/dev/null 2>&1; then
echo "CLI pointer decider accepted an unreadable manifest path" >&2
exit 1
fi
for asset in \
reasonix-darwin-amd64.tar.gz \
reasonix-darwin-arm64.tar.gz \
reasonix-linux-amd64.tar.gz \
reasonix-linux-arm64.tar.gz \
reasonix-windows-amd64.zip \
reasonix-windows-arm64.zip \
SHA256SUMS; do
grep -Fq "\"$asset\"" "$cli_release_workflow"
done
publication_decider="$repo_root/scripts/decide-cli-release-publication.sh"
test -x "$publication_decider"
[ "$(bash "$publication_decider" stable v1.2.3 esengine/DeepSeek-Reasonix - -)" = "publish" ]
publication_checksums="$test_root/cli-publication-SHA256SUMS"
publication_release="$test_root/cli-publication-release.json"
publication_hash="0000000000000000000000000000000000000000000000000000000000000000"
publication_assets='[
"reasonix-darwin-amd64.tar.gz",
"reasonix-darwin-arm64.tar.gz",
"reasonix-linux-amd64.tar.gz",
"reasonix-linux-arm64.tar.gz",
"reasonix-windows-amd64.zip",
"reasonix-windows-arm64.zip",
"SHA256SUMS"
]'
for asset in \
reasonix-darwin-amd64.tar.gz \
reasonix-darwin-arm64.tar.gz \
reasonix-linux-amd64.tar.gz \
reasonix-linux-arm64.tar.gz \
reasonix-windows-amd64.zip \
reasonix-windows-arm64.zip; do
printf '%s %s\n' "$publication_hash" "$asset"
done >"$publication_checksums"
publication_checksum_hash="$(shasum -a 256 "$publication_checksums" | awk '{print $1}')"
jq -n \
--arg repo "esengine/DeepSeek-Reasonix" \
--arg tag "v1.2.3" \
--arg archive_hash "$publication_hash" \
--arg checksum_hash "$publication_checksum_hash" \
--argjson names "$publication_assets" '
{
tag_name: $tag,
draft: false,
prerelease: false,
html_url: ("https://github.com/" + $repo + "/releases/tag/" + $tag),
assets: [
$names[] as $name |
{
name: $name,
state: "uploaded",
size: 1,
browser_download_url:
("https://github.com/" + $repo + "/releases/download/" + $tag + "/" + $name),
digest: ("sha256:" + (if $name == "SHA256SUMS" then $checksum_hash else $archive_hash end))
}
]
}
' >"$publication_release"
[ "$(bash "$publication_decider" stable v1.2.3 esengine/DeepSeek-Reasonix \
"$publication_release" "$publication_checksums")" = "reuse" ]
publication_preview="$test_root/cli-publication-preview-release.json"
jq '.tag_name = "v1.2.3-preview.4" | .prerelease = true |
.html_url = "https://github.com/esengine/DeepSeek-Reasonix/releases/tag/v1.2.3-preview.4" |
.assets |= map(.browser_download_url |= sub("/v1.2.3/"; "/v1.2.3-preview.4/"))' \
"$publication_release" >"$publication_preview"
[ "$(bash "$publication_decider" preview v1.2.3-preview.4 esengine/DeepSeek-Reasonix \
"$publication_preview" "$publication_checksums")" = "reuse" ]
publication_rc="$test_root/cli-publication-rc-release.json"
jq '.tag_name = "v1.2.3-rc.1" | .prerelease = true |
.html_url = "https://github.com/esengine/DeepSeek-Reasonix/releases/tag/v1.2.3-rc.1" |
.assets |= map(.browser_download_url |= sub("/v1.2.3/"; "/v1.2.3-rc.1/"))' \
"$publication_release" >"$publication_rc"
[ "$(bash "$publication_decider" any v1.2.3-rc.1 esengine/DeepSeek-Reasonix \
"$publication_rc" "$publication_checksums")" = "reuse" ]
publication_partial="$test_root/cli-publication-partial-release.json"
jq '.assets |= map(select(.name != "reasonix-linux-arm64.tar.gz"))' \
"$publication_release" >"$publication_partial"
if bash "$publication_decider" stable v1.2.3 esengine/DeepSeek-Reasonix \
"$publication_partial" "$publication_checksums" >/dev/null 2>&1; then
echo "CLI publication decider accepted a partial existing release" >&2
exit 1
fi
publication_bad_checksums="$test_root/cli-publication-bad-SHA256SUMS"
sed '1s/^0/1/' "$publication_checksums" >"$publication_bad_checksums"
if bash "$publication_decider" stable v1.2.3 esengine/DeepSeek-Reasonix \
"$publication_release" "$publication_bad_checksums" >/dev/null 2>&1; then
echo "CLI publication decider accepted mismatched checksums" >&2
exit 1
fi
manifest_validator="$repo_root/scripts/validate-cli-release-manifest.sh"
manifest_comparator="$repo_root/scripts/compare-cli-release-manifests.sh"
test -x "$manifest_validator"
test -x "$manifest_comparator"
manifest_assets='[
"reasonix-darwin-amd64.tar.gz",
"reasonix-darwin-arm64.tar.gz",
"reasonix-linux-amd64.tar.gz",
"reasonix-linux-arm64.tar.gz",
"reasonix-windows-amd64.zip",
"reasonix-windows-arm64.zip",
"SHA256SUMS"
]'
manifest_repo="esengine/DeepSeek-Reasonix"
manifest_tag="v1.2.3"
manifest_file="$test_root/cli-release-manifest.json"
jq -n \
--arg repo "$manifest_repo" \
--arg tag "$manifest_tag" \
--argjson names "$manifest_assets" '
{
tag_name: $tag,
prerelease: false,
html_url: ("https://github.com/" + $repo + "/releases/tag/" + $tag),
release_notes_url: ("https://reasonix.io/changelog/" + $tag + "/"),
assets: [
$names[] as $name |
{
name: $name,
browser_download_url:
("https://github.com/" + $repo + "/releases/download/" + $tag + "/" + $name),
size: 1
}
]
}
' >"$manifest_file"
bash "$manifest_validator" stable "$manifest_tag" "$manifest_repo" "$manifest_file"
bash "$manifest_validator" any "$manifest_tag" "$manifest_repo" "$manifest_file"
legacy_manifest="$test_root/cli-release-manifest-legacy.json"
jq 'del(.release_notes_url)' "$manifest_file" >"$legacy_manifest"
bash "$manifest_validator" legacy-stable "$manifest_tag" "$manifest_repo" "$legacy_manifest"
bash "$manifest_comparator" "$manifest_file" "$legacy_manifest"
if bash "$manifest_validator" stable "$manifest_tag" "$manifest_repo" "$legacy_manifest" >/dev/null 2>&1; then
echo "strict CLI manifest validator accepted a legacy manifest" >&2
exit 1
fi
wrong_legacy_notes="$test_root/cli-release-manifest-wrong-legacy-notes.json"
jq '.release_notes_url = "https://reasonix.io/changelog/v9.9.9/"' \
"$manifest_file" >"$wrong_legacy_notes"
if bash "$manifest_validator" legacy-stable "$manifest_tag" "$manifest_repo" \
"$wrong_legacy_notes" >/dev/null 2>&1; then
echo "legacy CLI manifest validator accepted a mismatched release-notes URL" >&2
exit 1
fi
if bash "$manifest_comparator" "$manifest_file" "$wrong_legacy_notes" >/dev/null 2>&1; then
echo "CLI manifest comparator ignored a non-legacy difference" >&2
exit 1
fi
rc_manifest_tag="v1.2.4-rc.1"
rc_notes_tag="v1.2.4"
rc_manifest="$test_root/cli-release-manifest-rc.json"
jq \
--arg repo "$manifest_repo" \
--arg tag "$rc_manifest_tag" \
--arg notes_tag "$rc_notes_tag" '
.tag_name = $tag |
.prerelease = true |
.html_url = ("https://github.com/" + $repo + "/releases/tag/" + $tag) |
.release_notes_url = ("https://reasonix.io/changelog/" + $notes_tag + "/") |
.assets |= map(
.browser_download_url =
("https://github.com/" + $repo + "/releases/download/" + $tag + "/" + .name)
)
' "$manifest_file" >"$rc_manifest"
bash "$manifest_validator" any "$rc_manifest_tag" "$manifest_repo" "$rc_manifest" "$rc_notes_tag"
expect_invalid_cli_manifest() {
local description="$1"
local file="$2"
if bash "$manifest_validator" stable "$manifest_tag" "$manifest_repo" "$file" >/dev/null 2>&1; then
echo "CLI manifest validator accepted $description" >&2
exit 1
fi
}
jq '.assets[0].browser_download_url = "https://github.com/attacker/project/releases/download/v1.2.3/reasonix-darwin-amd64.tar.gz"' \
"$manifest_file" >"$test_root/wrong-repository.json"
expect_invalid_cli_manifest "an asset from another repository" "$test_root/wrong-repository.json"
jq '.assets[0].browser_download_url = "https://github.com/esengine/DeepSeek-Reasonix/releases/download/v9.9.9/reasonix-darwin-amd64.tar.gz"' \
"$manifest_file" >"$test_root/wrong-tag.json"
expect_invalid_cli_manifest "an asset from another tag" "$test_root/wrong-tag.json"
jq '.assets = .assets[:-1]' "$manifest_file" >"$test_root/missing-asset.json"
expect_invalid_cli_manifest "a missing required asset" "$test_root/missing-asset.json"
jq '.assets[0].size = 0' "$manifest_file" >"$test_root/empty-asset.json"
expect_invalid_cli_manifest "a zero-byte asset" "$test_root/empty-asset.json"
jq '.assets[0].size = 1073741825' "$manifest_file" >"$test_root/oversized-asset.json"
expect_invalid_cli_manifest "an asset above the release size limit" "$test_root/oversized-asset.json"
jq '.assets[0].size = 9007199254740992' "$manifest_file" >"$test_root/unsafe-integer-asset.json"
expect_invalid_cli_manifest "an asset size above the JavaScript safe-integer range" \
"$test_root/unsafe-integer-asset.json"
if bash "$manifest_validator" preview "$manifest_tag" "$manifest_repo" "$manifest_file" >/dev/null 2>&1; then
echo "CLI manifest validator accepted a Stable manifest as Preview" >&2
exit 1
fi
# Desktop public pointers use the same strict channel grammar, but their assets
# must also share one exact immutable directory and complete integrity metadata.
desktop_compare="$repo_root/scripts/compare-desktop-release-versions.sh"
test -x "$desktop_compare"
[ "$(bash "$desktop_compare" stable v1.2.4 v1.2.3)" = "update" ]
[ "$(bash "$desktop_compare" stable v1.2.3 v1.2.3)" = "skip" ]
[ "$(bash "$desktop_compare" stable v1.2.2 v1.2.3)" = "skip" ]
[ "$(bash "$desktop_compare" stable v100000000000000000000.0.0 v99999999999999999999.999.999)" = "update" ]
[ "$(bash "$desktop_compare" preview v1.2.3-preview.11 v1.2.3-preview.9)" = "update" ]
[ "$(bash "$desktop_compare" preview v1.2.3-preview.9 v1.2.3-preview.9)" = "skip" ]
[ "$(bash "$desktop_compare" preview v1.2.3-preview.8 v1.2.3-preview.9)" = "skip" ]
[ "$(bash "$desktop_compare" preview v1.2.3-preview.1 "")" = "update" ]
if bash "$desktop_compare" stable v1.2.3 v1.2.3-preview.1 >/dev/null 2>&1; then
echo "Desktop Stable comparator accepted a Preview pointer" >&2
exit 1
fi
if bash "$desktop_compare" preview v1.2.3-preview.1 v1.2.3 >/dev/null 2>&1; then
echo "Desktop Preview comparator accepted a Stable pointer" >&2
exit 1
fi
if bash "$desktop_compare" preview v1.2.3-preview.01 v1.2.3-preview.1 >/dev/null 2>&1; then
echo "Desktop comparator accepted a non-canonical Preview version" >&2
exit 1
fi
desktop_pointer_decider="$repo_root/scripts/decide-desktop-pointer-update.sh"
test -x "$desktop_pointer_decider"
preview_candidate="$test_root/preview-candidate.json"
preview_older="$test_root/preview-older.json"
preview_equal="$test_root/preview-equal.json"
preview_equal_different="$test_root/preview-equal-different.json"
preview_newer="$test_root/preview-newer.json"
printf '{"version":"v1.3.0-preview.42","marker":"candidate"}\n' >"$preview_candidate"
printf '{"version":"v1.3.0-preview.41","marker":"older"}\n' >"$preview_older"
cp "$preview_candidate" "$preview_equal"
printf '{"version":"v1.3.0-preview.42","marker":"different"}\n' >"$preview_equal_different"
printf '{"version":"v1.3.0-preview.43","marker":"newer"}\n' >"$preview_newer"
preview_newer_equal="$test_root/preview-newer-equal.json"
preview_newer_conflict="$test_root/preview-newer-conflict.json"
cp "$preview_newer" "$preview_newer_equal"
printf '{"version":"v1.3.0-preview.43","marker":"conflict"}\n' >"$preview_newer_conflict"
expect_pointer_source() {
local expected="$1"
shift
local decision
local action
local source
decision="$(bash "$desktop_pointer_decider" "$@")"
IFS=$'\t' read -r action source <<<"$decision"
[ "$action" = "update" ]
[ "$source" = "$expected" ]
}
expect_pointer_source "$preview_candidate" preview "$preview_candidate" - -
expect_pointer_source "$preview_candidate" preview "$preview_candidate" "$preview_older" "$preview_older"
expect_pointer_source "$preview_candidate" preview "$preview_candidate" "$preview_equal" "$preview_older"
expect_pointer_source "$preview_candidate" preview "$preview_candidate" "$preview_older" "$preview_equal"
[ "$(bash "$desktop_pointer_decider" preview "$preview_candidate" "$preview_equal" "$preview_equal")" = "skip" ]
expect_pointer_source "$preview_candidate" preview "$preview_candidate" "$preview_equal" "$preview_equal_different"
expect_pointer_source "$preview_newer" preview "$preview_candidate" "$preview_newer" "$preview_older"
[ "$(bash "$desktop_pointer_decider" preview "$preview_candidate" "$preview_newer" "$preview_newer_equal")" = "skip" ]
if bash "$desktop_pointer_decider" preview "$preview_candidate" "$preview_newer" \
"$preview_newer_conflict" >/dev/null 2>&1; then
echo "Desktop Preview pointer decider accepted conflicting equal newer aliases" >&2
exit 1
fi
if bash "$desktop_pointer_decider" preview "$preview_candidate" "$test_root/missing.json" - >/dev/null 2>&1; then
echo "Desktop Preview pointer decider accepted an unreadable manifest path" >&2
exit 1
fi
stable_candidate="$test_root/stable-candidate.json"
stable_older="$test_root/stable-older.json"
stable_equal="$test_root/stable-equal.json"
stable_equal_different="$test_root/stable-equal-different.json"
stable_newer="$test_root/stable-newer.json"
printf '{"version":"v1.3.0","marker":"candidate"}\n' >"$stable_candidate"
printf '{"version":"v1.2.9","marker":"older"}\n' >"$stable_older"
cp "$stable_candidate" "$stable_equal"
printf '{"version":"v1.3.0","marker":"different"}\n' >"$stable_equal_different"
printf '{"version":"v1.3.1","marker":"newer"}\n' >"$stable_newer"
expect_pointer_source "$stable_candidate" stable "$stable_candidate" -
expect_pointer_source "$stable_candidate" stable "$stable_candidate" "$stable_older"
[ "$(bash "$desktop_pointer_decider" stable "$stable_candidate" "$stable_equal")" = "skip" ]
expect_pointer_source "$stable_candidate" stable "$stable_candidate" "$stable_equal_different"
[ "$(bash "$desktop_pointer_decider" stable "$stable_candidate" "$stable_newer")" = "skip" ]
desktop_directory_verifier="$repo_root/scripts/verify-desktop-release-directory.sh"
test -x "$desktop_directory_verifier"
candidate_directory="$test_root/desktop-directory-candidate"
existing_directory="$test_root/desktop-directory-existing"
mkdir -p "$candidate_directory" "$existing_directory"
printf 'asset-a\n' >"$candidate_directory/a"
printf 'asset-b\n' >"$candidate_directory/b"
cp "$candidate_directory/a" "$existing_directory/a"
bash "$desktop_directory_verifier" --allow-missing "$candidate_directory" "$existing_directory"
if bash "$desktop_directory_verifier" "$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted a missing object in exact mode" >&2
exit 1
fi
cp "$candidate_directory/b" "$existing_directory/b"
bash "$desktop_directory_verifier" "$candidate_directory" "$existing_directory"
printf 'candidate-signature\n' >"$candidate_directory/a.minisig"
printf 'existing-signature\n' >"$existing_directory/a.minisig"
if bash "$desktop_directory_verifier" "$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted a byte-distinct signature without opt-in" >&2
exit 1
fi
bash "$desktop_directory_verifier" --allow-signature-differences \
"$candidate_directory" "$existing_directory"
printf 'candidate-payload\n' >"$candidate_directory/signed"
printf 'existing-payload\n' >"$existing_directory/signed"
printf 'candidate-signature\n' >"$candidate_directory/signed.minisig"
printf 'existing-signature\n' >"$existing_directory/signed.minisig"
if bash "$desktop_directory_verifier" --allow-signature-differences \
"$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted a byte-distinct payload without authenticated-payload opt-in" >&2
exit 1
fi
bash "$desktop_directory_verifier" --allow-authenticated-payload-differences \
"$candidate_directory" "$existing_directory"
printf '' >"$existing_directory/signed.minisig"
if bash "$desktop_directory_verifier" --allow-authenticated-payload-differences \
"$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted a byte-distinct payload with an empty signature" >&2
exit 1
fi
cp "$candidate_directory/signed" "$existing_directory/signed"
cp "$candidate_directory/signed.minisig" "$existing_directory/signed.minisig"
printf '' >"$existing_directory/a.minisig"
if bash "$desktop_directory_verifier" --allow-signature-differences \
"$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted an empty recovered signature" >&2
exit 1
fi
cp "$candidate_directory/a.minisig" "$existing_directory/a.minisig"
printf 'conflict\n' >"$existing_directory/a"
if bash "$desktop_directory_verifier" --allow-missing "$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted conflicting immutable content" >&2
exit 1
fi
cp "$candidate_directory/a" "$existing_directory/a"
printf 'unexpected\n' >"$existing_directory/unexpected"
if bash "$desktop_directory_verifier" --allow-missing "$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted an unexpected immutable object" >&2
exit 1
fi
recovery_candidate_directory="$test_root/desktop-directory-recovery-candidate"
recovery_existing_directory="$test_root/desktop-directory-recovery-existing"
mkdir -p "$recovery_candidate_directory" "$recovery_existing_directory"
printf 'candidate-payload\n' >"$recovery_candidate_directory/artifact"
printf 'candidate-signature\n' >"$recovery_candidate_directory/artifact.minisig"
printf 'existing-payload\n' >"$recovery_existing_directory/artifact"
printf 'existing-signature\n' >"$recovery_existing_directory/artifact.minisig"
printf '{"version":"v1.2.3","release_notes_url":"https://reasonix.io/changelog/v1.2.3/","marker":"candidate"}\n' \
>"$recovery_candidate_directory/latest.json"
printf '{"version":"v1.2.3","release_notes_url":"https://reasonix.io/changelog/v1.2.3/","marker":"existing"}\n' \
>"$recovery_existing_directory/latest.json"
if bash "$desktop_directory_verifier" --allow-missing --allow-legacy-manifest \
--allow-authenticated-payload-differences \
"$recovery_candidate_directory" "$recovery_existing_directory" >/dev/null 2>&1; then
echo "Desktop recovery accepted a conflicting manifest before validated adoption" >&2
exit 1
fi
cp "$recovery_existing_directory/latest.json" "$recovery_candidate_directory/latest.json"
bash "$desktop_directory_verifier" --allow-missing --allow-legacy-manifest \
--allow-authenticated-payload-differences \
"$recovery_candidate_directory" "$recovery_existing_directory"
legacy_candidate_directory="$test_root/desktop-directory-legacy-candidate"
legacy_existing_directory="$test_root/desktop-directory-legacy-existing"
mkdir -p "$legacy_candidate_directory" "$legacy_existing_directory"
printf 'asset\n' >"$legacy_candidate_directory/artifact"
cp "$legacy_candidate_directory/artifact" "$legacy_existing_directory/artifact"
jq -n '{
version: "v1.2.3",
release_notes_url: "https://reasonix.io/changelog/v1.2.3/",
platforms: {"darwin-arm64": {size: 42}}
}' >"$legacy_candidate_directory/latest.json"
jq 'del(.release_notes_url)' "$legacy_candidate_directory/latest.json" \
>"$legacy_existing_directory/latest.json"
bash "$desktop_directory_verifier" --allow-legacy-manifest \
"$legacy_candidate_directory" "$legacy_existing_directory"
if bash "$desktop_directory_verifier" \
"$legacy_candidate_directory" "$legacy_existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier accepted a legacy manifest without opt-in" >&2
exit 1
fi
jq '.platforms["darwin-arm64"].size = 99' "$legacy_existing_directory/latest.json" \
>"$legacy_existing_directory/latest-conflict.json"
mv "$legacy_existing_directory/latest-conflict.json" "$legacy_existing_directory/latest.json"
if bash "$desktop_directory_verifier" --allow-legacy-manifest \
"$legacy_candidate_directory" "$legacy_existing_directory" >/dev/null 2>&1; then
echo "Desktop release directory verifier ignored a non-legacy manifest difference" >&2
exit 1
fi
desktop_manifest_asset_verifier="$repo_root/scripts/verify-desktop-release-manifest-assets.sh"
test -x "$desktop_manifest_asset_verifier"
manifest_asset_directory="$test_root/desktop-manifest-assets"
mkdir -p "$manifest_asset_directory"
printf 'manifest-bound-payload\n' >"$manifest_asset_directory/payload.zip"
printf 'manifest-bound-native\n' >"$manifest_asset_directory/payload.deb"
manifest_asset_sha="$(shasum -a 256 "$manifest_asset_directory/payload.zip" | awk '{print $1}')"
manifest_asset_size="$(wc -c <"$manifest_asset_directory/payload.zip" | tr -d '[:space:]')"
manifest_native_sha="$(shasum -a 256 "$manifest_asset_directory/payload.deb" | awk '{print $1}')"
manifest_native_size="$(wc -c <"$manifest_asset_directory/payload.deb" | tr -d '[:space:]')"
jq -n \
--arg url "https://dl.reasonix.io/desktop-v1.2.3/payload.zip" \
--arg sha "$manifest_asset_sha" \
--argjson size "$manifest_asset_size" \
--arg native_url "https://dl.reasonix.io/desktop-v1.2.3/payload.deb" \
--arg native_sha "$manifest_native_sha" \
--argjson native_size "$manifest_native_size" \
'{
platforms: {test: {url: $url, sha256: $sha, size: $size}},
native_packages: {test: {url: $native_url, sha256: $native_sha, size: $native_size}},
downloads: {}
}' \
>"$manifest_asset_directory/latest.json"
bash "$desktop_manifest_asset_verifier" \
"$manifest_asset_directory/latest.json" "$manifest_asset_directory"
printf 'corrupted-payload\n' >"$manifest_asset_directory/payload.zip"
if bash "$desktop_manifest_asset_verifier" \
"$manifest_asset_directory/latest.json" "$manifest_asset_directory" >/dev/null 2>&1; then
echo "Desktop release manifest asset verifier accepted a mismatched payload" >&2
exit 1
fi
# GitHub release recovery uses the same immutable-subset rule: an interrupted
# release may fill missing assets, while conflicting or extra assets fail closed.
desktop_github_publisher="$repo_root/scripts/publish-desktop-github-release.sh"
test -x "$desktop_github_publisher"
fake_gh_bin="$test_root/fake-gh-bin"
fake_gh_state="$test_root/fake-gh-state"
mkdir -p "$fake_gh_bin" "$fake_gh_state"
cat >"$fake_gh_bin/gh" <<'FAKE_GH'
#!/usr/bin/env bash
set -euo pipefail
state="${FAKE_GH_STATE:?FAKE_GH_STATE is required}"
command="${1:-}"
shift || true
release_json() {
if [ ! -f "$state/release.json" ]; then
echo "HTTP 404: Not Found" >&2
return 1
fi
assets='[]'
if compgen -G "$state/assets/*" >/dev/null; then
for asset in "$state/assets"/*; do
assets="$(jq -cn --argjson current "$assets" --arg name "$(basename "$asset")" \
'$current + [{name: $name}]')"
done
fi
jq --argjson assets "$assets" '. + {assets: $assets}' "$state/release.json"
}
case "$command" in
api)
release_json
;;
release)
subcommand="${1:-}"
shift || true
case "$subcommand" in
create)
tag="${1:?tag is required}"
shift
title=""
notes_file=""
prerelease=false
while [ "$#" -gt 0 ]; do
case "$1" in
-R | --repo | --title | --notes-file)
option="$1"
value="${2:?$option requires a value}"
shift 2
case "$option" in
--title) title="$value" ;;
--notes-file) notes_file="$value" ;;
esac
;;
--prerelease)
prerelease=true
shift
;;
--latest | --latest=false)
shift
;;
*)
echo "unexpected fake gh release create argument: $1" >&2
exit 2
;;
esac
done
mkdir -p "$state/assets"
jq -n --arg tag "$tag" --arg name "$title" --rawfile body "$notes_file" \
--argjson prerelease "$prerelease" \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: $prerelease}' \
>"$state/release.json"
;;
download)
tag="${1:?tag is required}"
shift
pattern=""
destination=""
while [ "$#" -gt 0 ]; do
case "$1" in
-R | --repo)
shift 2
;;
-p | --pattern)
pattern="${2:?pattern is required}"
shift 2
;;
-D | --dir)
destination="${2:?destination is required}"
shift 2
;;
*)
echo "unexpected fake gh release download argument: $1" >&2
exit 2
;;
esac
done
test -n "$tag"
mkdir -p "$destination"
if [ -n "$pattern" ]; then
cp "$state/assets/$pattern" "$destination/$pattern"
elif compgen -G "$state/assets/*" >/dev/null; then
cp "$state/assets"/* "$destination/"
fi
;;
upload)
tag="${1:?tag is required}"
shift
asset=""
while [ "$#" -gt 0 ]; do
case "$1" in
-R | --repo)
shift 2
;;
*)
asset="$1"
shift
;;
esac
done
test -n "$tag"
mkdir -p "$state/assets"
cp "$asset" "$state/assets/$(basename "$asset")"
;;
*)
echo "unsupported fake gh command: $command $subcommand" >&2
exit 2
;;
esac
;;
*)
echo "unsupported fake gh command: $command" >&2
exit 2
;;
esac
FAKE_GH
chmod +x "$fake_gh_bin/gh"
github_candidate="$test_root/desktop-github-candidate"
github_notes="$test_root/desktop-github-notes.md"
mkdir -p "$github_candidate"
printf 'asset-a\n' >"$github_candidate/a"
printf 'asset-b\n' >"$github_candidate/b"
printf 'Release notes.\n' >"$github_notes"
PATH="$fake_gh_bin:$PATH" FAKE_GH_STATE="$fake_gh_state" \
GITHUB_REPOSITORY=esengine/DeepSeek-Reasonix \
bash "$desktop_github_publisher" desktop-v1.2.3 v1.2.3 false \
"$github_notes" "$github_candidate"
bash "$desktop_directory_verifier" "$github_candidate" "$fake_gh_state/assets"
rm -f "$fake_gh_state/assets/b"
PATH="$fake_gh_bin:$PATH" FAKE_GH_STATE="$fake_gh_state" \
GITHUB_REPOSITORY=esengine/DeepSeek-Reasonix \
bash "$desktop_github_publisher" desktop-v1.2.3 v1.2.3 false \
"$github_notes" "$github_candidate"
bash "$desktop_directory_verifier" "$github_candidate" "$fake_gh_state/assets"
printf 'conflict\n' >"$fake_gh_state/assets/a"
if PATH="$fake_gh_bin:$PATH" FAKE_GH_STATE="$fake_gh_state" \
GITHUB_REPOSITORY=esengine/DeepSeek-Reasonix \
bash "$desktop_github_publisher" desktop-v1.2.3 v1.2.3 false \
"$github_notes" "$github_candidate" >/dev/null 2>&1; then
echo "Desktop GitHub recovery accepted conflicting immutable content" >&2
exit 1
fi
cp "$github_candidate/a" "$fake_gh_state/assets/a"
printf 'unexpected\n' >"$fake_gh_state/assets/unexpected"
if PATH="$fake_gh_bin:$PATH" FAKE_GH_STATE="$fake_gh_state" \
GITHUB_REPOSITORY=esengine/DeepSeek-Reasonix \
bash "$desktop_github_publisher" desktop-v1.2.3 v1.2.3 false \
"$github_notes" "$github_candidate" >/dev/null 2>&1; then
echo "Desktop GitHub recovery accepted an unexpected immutable asset" >&2
exit 1
fi
rm -f "$fake_gh_state/assets/unexpected"
jq '.name = "Wrong title"' "$fake_gh_state/release.json" >"$fake_gh_state/release.json.new"
mv "$fake_gh_state/release.json.new" "$fake_gh_state/release.json"
if PATH="$fake_gh_bin:$PATH" FAKE_GH_STATE="$fake_gh_state" \
GITHUB_REPOSITORY=esengine/DeepSeek-Reasonix \
bash "$desktop_github_publisher" desktop-v1.2.3 v1.2.3 false \
"$github_notes" "$github_candidate" >/dev/null 2>&1; then
echo "Desktop GitHub recovery accepted conflicting release metadata" >&2
exit 1
fi
desktop_validator="$repo_root/scripts/validate-desktop-release-manifest.sh"
desktop_manifest_comparator="$repo_root/scripts/compare-desktop-release-manifests.sh"
test -x "$desktop_validator"
test -x "$desktop_manifest_comparator"
write_desktop_manifest() {
local version="$1"
local base="$2"
local output="$3"
local notes_version="${4:-$version}"
jq -n --arg version "$version" --arg notes_version "$notes_version" \
--arg base "$base" --arg sha "$(printf 'a%.0s' {1..64})" '
def asset($name): {
url: ($base + $name),
sig: ($base + $name + ".minisig"),
size: 42,
sha256: $sha
};
{
version: $version,
download_page: "https://reasonix.io/?download=desktop#start",
release_notes_url: ("https://reasonix.io/changelog/" + $notes_version + "/"),
platforms: {
"darwin-arm64": asset("Reasonix-darwin-arm64.zip"),
"darwin-amd64": asset("Reasonix-darwin-amd64.zip"),
"windows-amd64": asset("Reasonix-windows-amd64-installer.exe"),
"windows-arm64": asset("Reasonix-windows-arm64-installer.exe"),
"linux-amd64": asset("Reasonix-linux-amd64.tar.gz")
},
native_packages: {
"linux-amd64": asset("Reasonix-linux-amd64.deb")
},
downloads: {
"Reasonix-darwin-arm64.dmg": asset("Reasonix-darwin-arm64.dmg"),
"Reasonix-darwin-amd64.dmg": asset("Reasonix-darwin-amd64.dmg"),
"Reasonix-darwin-universal.dmg": asset("Reasonix-darwin-universal.dmg"),
"Reasonix-windows-amd64.zip": asset("Reasonix-windows-amd64.zip")
}
}
' >"$output"
}
desktop_stable_version="v1.2.3"
desktop_stable_base="https://dl.reasonix.io/desktop-${desktop_stable_version}/"
desktop_stable_manifest="$test_root/desktop-stable.json"
write_desktop_manifest "$desktop_stable_version" "$desktop_stable_base" "$desktop_stable_manifest"
bash "$desktop_validator" stable "$desktop_stable_version" "$desktop_stable_base" "$desktop_stable_manifest"
desktop_github_base="https://github.com/esengine/DeepSeek-Reasonix/releases/download/desktop-${desktop_stable_version}/"
desktop_github_manifest="$test_root/desktop-stable-github.json"
write_desktop_manifest "$desktop_stable_version" "$desktop_github_base" "$desktop_github_manifest"
bash "$desktop_validator" stable "$desktop_stable_version" "$desktop_github_base" "$desktop_github_manifest"
desktop_preview_version="v1.3.0-preview.42"
desktop_preview_base="https://dl.reasonix.io/desktop-${desktop_preview_version}/"
desktop_preview_manifest="$test_root/desktop-preview.json"
write_desktop_manifest "$desktop_preview_version" "$desktop_preview_base" "$desktop_preview_manifest"
bash "$desktop_validator" preview "$desktop_preview_version" "$desktop_preview_base" "$desktop_preview_manifest"
desktop_rc_version="v1.3.0-rc.1"
desktop_rc_notes_version="v1.3.0"
desktop_rc_base="https://dl.reasonix.io/desktop-${desktop_rc_version}/"
desktop_rc_manifest="$test_root/desktop-rc.json"
write_desktop_manifest "$desktop_rc_version" "$desktop_rc_base" "$desktop_rc_manifest" \
"$desktop_rc_notes_version"
bash "$desktop_validator" any "$desktop_rc_version" "$desktop_rc_base" \
"$desktop_rc_manifest" "$desktop_rc_notes_version"
desktop_legacy_notes_manifest="$test_root/desktop-stable-legacy-notes.json"
jq 'del(.release_notes_url)' "$desktop_stable_manifest" >"$desktop_legacy_notes_manifest"
bash "$desktop_validator" legacy-stable "$desktop_stable_version" \
"$desktop_stable_base" "$desktop_legacy_notes_manifest"
bash "$desktop_manifest_comparator" "$desktop_stable_manifest" "$desktop_legacy_notes_manifest"
if bash "$desktop_validator" stable "$desktop_stable_version" "$desktop_stable_base" \
"$desktop_legacy_notes_manifest" >/dev/null 2>&1; then
echo "strict Desktop manifest validator accepted a legacy release-notes manifest" >&2
exit 1
fi
jq '.release_notes_url = "https://reasonix.io/changelog/v9.9.9/"' \
"$desktop_stable_manifest" >"$test_root/desktop-wrong-legacy-notes.json"
if bash "$desktop_manifest_comparator" "$desktop_stable_manifest" \
"$test_root/desktop-wrong-legacy-notes.json" >/dev/null 2>&1; then
echo "Desktop manifest comparator ignored a non-legacy difference" >&2
exit 1
fi
expect_invalid_desktop_manifest() {
local description="$1"
local channel="$2"
local version="$3"
local base="$4"
local file="$5"
if bash "$desktop_validator" "$channel" "$version" "$base" "$file" >/dev/null 2>&1; then
echo "Desktop manifest validator accepted $description" >&2
exit 1
fi
}
jq 'del(.platforms["windows-arm64"])' "$desktop_preview_manifest" >"$test_root/desktop-missing-platform.json"
expect_invalid_desktop_manifest "a missing platform" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-missing-platform.json"
jq '.platforms["darwin-arm64"].url = "https://evil.invalid/file"' \
"$desktop_preview_manifest" >"$test_root/desktop-hostile-url.json"
expect_invalid_desktop_manifest "a hostile URL" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-hostile-url.json"
jq '.platforms["darwin-arm64"].sig += "?mirror=1"' \
"$desktop_preview_manifest" >"$test_root/desktop-bad-signature.json"
expect_invalid_desktop_manifest "a mismatched signature URL" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-bad-signature.json"
jq '.native_packages["linux-amd64"].size = 0' \
"$desktop_preview_manifest" >"$test_root/desktop-zero-size.json"
expect_invalid_desktop_manifest "a zero-byte native package" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-zero-size.json"
jq '.native_packages["linux-amd64"].size = 1073741825' \
"$desktop_preview_manifest" >"$test_root/desktop-oversized-asset.json"
expect_invalid_desktop_manifest "an asset above the release size limit" preview \
"$desktop_preview_version" "$desktop_preview_base" \
"$test_root/desktop-oversized-asset.json"
jq '.native_packages["linux-amd64"].size = 9007199254740992' \
"$desktop_preview_manifest" >"$test_root/desktop-unsafe-integer-size.json"
expect_invalid_desktop_manifest "an asset size above the JavaScript safe-integer range" preview \
"$desktop_preview_version" "$desktop_preview_base" \
"$test_root/desktop-unsafe-integer-size.json"
jq '.downloads["Reasonix-darwin-universal.dmg"].sha256 = ("A" * 64)' \
"$desktop_preview_manifest" >"$test_root/desktop-bad-sha.json"
expect_invalid_desktop_manifest "an uppercase SHA-256" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-bad-sha.json"
jq 'del(.downloads["Reasonix-windows-amd64.zip"])' \
"$desktop_preview_manifest" >"$test_root/desktop-missing-download.json"
expect_invalid_desktop_manifest "a missing website download" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-missing-download.json"
jq '.platforms.extra = .platforms["darwin-arm64"]' \
"$desktop_preview_manifest" >"$test_root/desktop-extra-platform.json"
expect_invalid_desktop_manifest "an unexpected platform" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-extra-platform.json"
write_desktop_manifest "$desktop_preview_version" "https://dl.reasonix.io/desktop-preview/" \
"$test_root/desktop-rolling-preview.json"
expect_invalid_desktop_manifest "the legacy mutable Preview directory" preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-rolling-preview.json"
jq 'del(.downloads)' "$test_root/desktop-rolling-preview.json" \
>"$test_root/desktop-legacy-preview.json"
bash "$desktop_validator" legacy-preview "$desktop_preview_version" \
"https://dl.reasonix.io/desktop-preview/" "$test_root/desktop-legacy-preview.json"
jq 'del(.release_notes_url, .downloads)' "$desktop_preview_manifest" \
>"$test_root/desktop-legacy-preview-immutable.json"
bash "$desktop_validator" legacy-preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-legacy-preview-immutable.json"
jq '.release_notes_url = null' "$desktop_preview_manifest" \
>"$test_root/desktop-null-legacy-preview-immutable.json"
bash "$desktop_validator" legacy-preview "$desktop_preview_version" \
"$desktop_preview_base" "$test_root/desktop-null-legacy-preview-immutable.json"
jq 'del(.downloads)' "$desktop_stable_manifest" >"$test_root/desktop-legacy-stable.json"
bash "$desktop_validator" legacy-stable "$desktop_stable_version" \
"$desktop_stable_base" "$test_root/desktop-legacy-stable.json"
jq '.downloads = null' "$desktop_stable_manifest" >"$test_root/desktop-null-legacy-stable.json"
bash "$desktop_validator" legacy-stable "$desktop_stable_version" \
"$desktop_stable_base" "$test_root/desktop-null-legacy-stable.json"
jq '.downloads = {}' "$desktop_stable_manifest" >"$test_root/desktop-empty-downloads.json"
expect_invalid_desktop_manifest "empty downloads in a legacy Stable manifest" legacy-stable \
"$desktop_stable_version" "$desktop_stable_base" "$test_root/desktop-empty-downloads.json"
expect_invalid_desktop_manifest "a legacy Stable manifest as a new publication" stable \
"$desktop_stable_version" "$desktop_stable_base" "$test_root/desktop-legacy-stable.json"
jq 'del(.downloads["Reasonix-windows-amd64.zip"])' \
"$test_root/desktop-rolling-preview.json" >"$test_root/desktop-partial-legacy-downloads.json"
expect_invalid_desktop_manifest "partial downloads in a legacy Preview manifest" legacy-preview \
"$desktop_preview_version" "https://dl.reasonix.io/desktop-preview/" \
"$test_root/desktop-partial-legacy-downloads.json"
expect_invalid_desktop_manifest "a Preview manifest as Stable" stable "$desktop_preview_version" \
"$desktop_preview_base" "$desktop_preview_manifest"
expect_invalid_desktop_manifest "a non-official asset base" preview "$desktop_preview_version" \
"https://cdn.invalid/desktop-${desktop_preview_version}/" "$desktop_preview_manifest"
# Release notes use one deterministic branch per official version. Failure to
# open the PR must preserve that branch and print an exact manual handoff.
prepare_notes="$repo_root/.github/workflows/prepare-release-notes.yml"
generate_notes="$repo_root/scripts/generate-release-notes.mjs"
if grep -Eq '^ (target_pr|from_tag):$' "$prepare_notes"; then
echo "Prepare release must expose only the official version input" >&2
exit 1
fi
grep -Fq 'branch="release-notes/v${VERSION}"' "$prepare_notes"
grep -Fq 'GitHub Actions could not open the PR; the reviewed branch is preserved.' "$prepare_notes"
grep -Fq 'gh pr create --repo ${{ github.repository }} --base main-v2 --head $RELEASE_NOTES_BRANCH --fill' "$prepare_notes"
grep -Eq 'GITHUB_STEP_SUMMARY' "$prepare_notes"
grep -Fq 'node scripts/generate-release-notes.mjs --version "$VERSION" --to "$RELEASE_NOTES_SOURCE_SHA"' "$prepare_notes"
grep -Fq 'thinking: { type: "disabled" }' "$generate_notes"
desktop_candidate_resolver="$repo_root/scripts/resolve-desktop-candidate.sh"
test -x "$desktop_candidate_resolver"
git init --bare -q "$test_root/remote.git"
git clone -q "$test_root/remote.git" "$test_root/repo"
(
cd "$test_root/repo"
git config user.name "Release Workflow Test"
git config user.email "[email protected]"
git commit --allow-empty -q -m "candidate"
git branch -M main-v2
git push -q -u origin main-v2
git tag v1.2.3
git tag npm-v1.2.3
git tag -a desktop-v1.2.3 -m "desktop release"
git tag v1.3.0-preview.42
git push -q origin v1.2.3 npm-v1.2.3 desktop-v1.2.3 v1.3.0-preview.42
GITHUB_OUTPUT="$test_root/stable.out" RELEASE_TAG=v1.2.3 "$repo_root/scripts/resolve-stable-release.sh"
grep -Eq '^version=1\.2\.3$' "$test_root/stable.out"
grep -Eq '^desktop_tag=desktop-v1\.2\.3$' "$test_root/stable.out"
GITHUB_OUTPUT="$test_root/preview.out" RELEASE_TAG=v1.3.0-preview.42 \
"$repo_root/scripts/resolve-preview-release.sh"
grep -Eq '^version=1\.3\.0-preview\.42$' "$test_root/preview.out"
grep -Eq '^desktop_tag=desktop-v1\.3\.0-preview\.42$' "$test_root/preview.out"
grep -Eq '^npm_version=1\.3\.0-canary\.42$' "$test_root/preview.out"
approved_sha="$(git rev-parse HEAD)"
GITHUB_OUTPUT="$test_root/desktop-stable-candidate.out" \
RELEASE_CHANNEL=stable RELEASE_TAG=desktop-v1.2.3 \
IN_ORCHESTRATED=false CALLER_EVENT_NAME=workflow_dispatch \
CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_SHA="$approved_sha" CALLER_WORKFLOW_SHA="$approved_sha" \
"$desktop_candidate_resolver"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/desktop-stable-candidate.out"
GITHUB_OUTPUT="$test_root/desktop-preview-candidate.out" \
RELEASE_CHANNEL=preview RELEASE_TAG=desktop-v1.3.0-preview.42 \
IN_ORCHESTRATED=false CALLER_EVENT_NAME=workflow_dispatch \
CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_SHA="$approved_sha" CALLER_WORKFLOW_SHA="$approved_sha" \
"$desktop_candidate_resolver"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/desktop-preview-candidate.out"
GITHUB_OUTPUT="$test_root/desktop-orchestrated-preview-candidate.out" \
RELEASE_CHANNEL=preview RELEASE_TAG=desktop-v1.3.0-preview.42 \
IN_ORCHESTRATED=true IN_ORCHESTRATOR=preview APPROVED_SHA="$approved_sha" \
"$desktop_candidate_resolver"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/desktop-orchestrated-preview-candidate.out"
if GITHUB_OUTPUT="$test_root/desktop-wrong-orchestrator-candidate.out" \
RELEASE_CHANNEL=preview RELEASE_TAG=desktop-v1.3.0-preview.42 \
IN_ORCHESTRATED=true IN_ORCHESTRATOR=stable APPROVED_SHA="$approved_sha" \
"$desktop_candidate_resolver" >"$test_root/desktop-wrong-orchestrator-candidate.log" 2>&1; then
echo "stable orchestrator unexpectedly authorized a Desktop Preview candidate" >&2
exit 1
fi
grep -Eq 'stable orchestrator cannot authorize a Desktop preview candidate' \
"$test_root/desktop-wrong-orchestrator-candidate.log"
if GITHUB_OUTPUT="$test_root/desktop-unprotected-candidate.out" \
RELEASE_CHANNEL=preview RELEASE_TAG=desktop-v1.3.0-preview.42 \
IN_ORCHESTRATED=false CALLER_EVENT_NAME=workflow_dispatch \
CALLER_REF=refs/heads/topic CALLER_REF_PROTECTED=false \
CALLER_SHA="$approved_sha" CALLER_WORKFLOW_SHA="$approved_sha" \
"$desktop_candidate_resolver" >"$test_root/desktop-unprotected-candidate.log" 2>&1; then
echo "unprotected Desktop candidate unexpectedly passed" >&2
exit 1
fi
grep -Eq 'standalone Desktop releases must run from protected main-v2' \
"$test_root/desktop-unprotected-candidate.log"
git tag desktop-v1.3.0-preview.42
if GITHUB_OUTPUT="$test_root/desktop-tagged-preview-candidate.out" \
RELEASE_CHANNEL=preview RELEASE_TAG=desktop-v1.3.0-preview.42 \
IN_ORCHESTRATED=false CALLER_EVENT_NAME=workflow_dispatch \
CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_SHA="$approved_sha" CALLER_WORKFLOW_SHA="$approved_sha" \
"$desktop_candidate_resolver" >"$test_root/desktop-tagged-preview-candidate.log" 2>&1; then
echo "Git-tagged Desktop Preview candidate unexpectedly passed" >&2
exit 1
fi
grep -Eq 'Desktop Preview uses an immutable asset directory, not a Git tag' \
"$test_root/desktop-tagged-preview-candidate.log"
git tag -d desktop-v1.3.0-preview.42 >/dev/null
ACTUAL_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/tags/v1.2.3' \
EXPECTED_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/tags/v1.2.3' \
CALLER_EVENT_NAME=push CALLER_REF=refs/tags/v1.2.3 CALLER_REF_PROTECTED=true \
CALLER_WORKFLOW_SHA="$approved_sha" CALLER_SHA="$approved_sha" \
APPROVED_CLI_TAG=v1.2.3 APPROVED_SHA="$approved_sha" \
"$repo_root/scripts/verify-release-authorization.sh"
ACTUAL_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-preview.yml@refs/heads/main-v2' \
EXPECTED_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-preview.yml@refs/heads/main-v2' \
CALLER_EVENT_NAME=workflow_dispatch CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_WORKFLOW_SHA="$approved_sha" CALLER_SHA="$approved_sha" \
APPROVED_CHANNEL=preview APPROVED_CLI_TAG=v1.3.0-preview.42 APPROVED_SHA="$approved_sha" \
"$repo_root/scripts/verify-release-authorization.sh"
RELEASE_TAG=desktop-v1.2.3 APPROVED_SHA="$approved_sha" \
"$repo_root/scripts/verify-release-tag.sh"
git commit --allow-empty -q -m "release workflow fix"
git push -q origin main-v2
recovery_workflow_sha="$(git rev-parse HEAD)"
if RELEASE_TAG=v1.3.0-preview.42 \
"$repo_root/scripts/resolve-preview-release.sh" >"$test_root/stale-preview.log" 2>&1; then
echo "stale Preview tag unexpectedly passed normal release resolution" >&2
exit 1
fi
grep -Eq 'must point to current .*main-v2' "$test_root/stale-preview.log"
GITHUB_OUTPUT="$test_root/preview-recovery.out" ALLOW_PREVIEW_RECOVERY=true \
RELEASE_TAG=v1.3.0-preview.42 "$repo_root/scripts/resolve-preview-release.sh"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/preview-recovery.out"
if ALLOW_PREVIEW_RECOVERY=invalid RELEASE_TAG=v1.3.0-preview.42 \
"$repo_root/scripts/resolve-preview-release.sh" >"$test_root/invalid-preview-recovery.log" 2>&1; then
echo "invalid Preview recovery mode unexpectedly passed" >&2
exit 1
fi
grep -Eq 'ALLOW_PREVIEW_RECOVERY must be true or false' \
"$test_root/invalid-preview-recovery.log"
if GITHUB_OUTPUT="$test_root/desktop-stale-preview-candidate.out" \
RELEASE_CHANNEL=preview RELEASE_TAG=desktop-v1.3.0-preview.42 \
IN_ORCHESTRATED=false CALLER_EVENT_NAME=workflow_dispatch \
CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_SHA="$approved_sha" CALLER_WORKFLOW_SHA="$approved_sha" \
"$desktop_candidate_resolver" >"$test_root/desktop-stale-preview-candidate.log" 2>&1; then
echo "stale Desktop Preview candidate unexpectedly passed" >&2
exit 1
fi
grep -Eq 'Desktop Preview must use current main-v2' \
"$test_root/desktop-stale-preview-candidate.log"
GITHUB_OUTPUT="$test_root/desktop-orchestrated-preview-recovery-candidate.out" \
RELEASE_CHANNEL=preview RELEASE_TAG=desktop-v1.3.0-preview.42 \
IN_ORCHESTRATED=true IN_ORCHESTRATOR=preview APPROVED_SHA="$approved_sha" \
"$desktop_candidate_resolver"
grep -Eq '^sha='"$approved_sha"'$' \
"$test_root/desktop-orchestrated-preview-recovery-candidate.out"
GITHUB_OUTPUT="$test_root/desktop-stable-recovery-candidate.out" \
RELEASE_CHANNEL=stable RELEASE_TAG=desktop-v1.2.3 \
IN_ORCHESTRATED=false CALLER_EVENT_NAME=workflow_dispatch \
CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_SHA="$recovery_workflow_sha" CALLER_WORKFLOW_SHA="$recovery_workflow_sha" \
"$desktop_candidate_resolver"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/desktop-stable-recovery-candidate.out"
GITHUB_OUTPUT="$test_root/stale-main.out" RELEASE_TAG=v1.2.3 \
"$repo_root/scripts/resolve-stable-release.sh"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/stale-main.out"
GITHUB_OUTPUT="$test_root/recovery.out" ALLOW_STABLE_RECOVERY=true RELEASE_TAG=v1.2.3 \
"$repo_root/scripts/resolve-stable-release.sh"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/recovery.out"
ACTUAL_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/heads/main-v2' \
EXPECTED_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/heads/main-v2' \
CALLER_EVENT_NAME=workflow_dispatch CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_WORKFLOW_SHA="$recovery_workflow_sha" CALLER_SHA="$recovery_workflow_sha" \
APPROVED_CLI_TAG=v1.2.3 APPROVED_SHA="$approved_sha" \
"$repo_root/scripts/verify-release-authorization.sh"
RELEASE_TAG=v1.2.3 APPROVED_SHA="$approved_sha" VERIFY_RELEASE_CHECKOUT=false \
"$repo_root/scripts/verify-release-tag.sh"
if ACTUAL_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/heads/main-v2' \
EXPECTED_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/heads/main-v2' \
CALLER_EVENT_NAME=workflow_dispatch CALLER_REF=refs/heads/main-v2 CALLER_REF_PROTECTED=true \
CALLER_WORKFLOW_SHA="$approved_sha" CALLER_SHA="$recovery_workflow_sha" \
APPROVED_CLI_TAG=v1.2.3 APPROVED_SHA="$approved_sha" \
"$repo_root/scripts/verify-release-authorization.sh" >"$test_root/stale-workflow.log" 2>&1; then
echo "stale recovery workflow unexpectedly passed release authorization" >&2
exit 1
fi
grep -Eq 'protected caller workflow SHA is' "$test_root/stale-workflow.log"
if ACTUAL_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/heads/topic' \
EXPECTED_CALLER_WORKFLOW_REF='example/reasonix/.github/workflows/release-stable.yml@refs/heads/topic' \
CALLER_EVENT_NAME=workflow_dispatch CALLER_REF=refs/heads/topic CALLER_REF_PROTECTED=false \
CALLER_WORKFLOW_SHA="$recovery_workflow_sha" CALLER_SHA="$recovery_workflow_sha" \
APPROVED_CLI_TAG=v1.2.3 APPROVED_SHA="$approved_sha" \
"$repo_root/scripts/verify-release-authorization.sh" >"$test_root/unprotected.log" 2>&1; then
echo "unprotected caller unexpectedly passed release authorization" >&2
exit 1
fi
grep -Eq 'caller ref is not protected' "$test_root/unprotected.log"
git tag v1.2.4
git tag npm-v1.2.4
git push -q origin v1.2.4 npm-v1.2.4
if RELEASE_TAG=v1.2.4 "$repo_root/scripts/resolve-stable-release.sh" >"$test_root/missing.log" 2>&1; then
echo "missing sibling tag unexpectedly passed" >&2
exit 1
fi
grep -Eq 'required stable release tag is missing: desktop-v1\.2\.4' "$test_root/missing.log"
other_sha="$(git commit-tree HEAD^{tree} -p HEAD -m "other")"
git tag v1.2.6 "$other_sha"
git tag npm-v1.2.6 "$other_sha"
git tag desktop-v1.2.6 "$other_sha"
git push -q origin v1.2.6 npm-v1.2.6 desktop-v1.2.6
if ALLOW_STABLE_RECOVERY=true RELEASE_TAG=v1.2.6 \
"$repo_root/scripts/resolve-stable-release.sh" >"$test_root/non-ancestor.log" 2>&1; then
echo "non-ancestor recovery tags unexpectedly passed release resolution" >&2
exit 1
fi
grep -Eq 'is not an ancestor of' "$test_root/non-ancestor.log"
git tag -f desktop-v1.2.3 "$other_sha" >/dev/null
git push -q -f origin desktop-v1.2.3
git checkout -q --detach "$approved_sha"
if RELEASE_TAG=desktop-v1.2.3 APPROVED_SHA="$approved_sha" \
"$repo_root/scripts/verify-release-tag.sh" >"$test_root/moved-tag.log" 2>&1; then
echo "moved release tag unexpectedly passed approved SHA validation" >&2
exit 1
fi
grep -Eq 'moved to .* after approval' "$test_root/moved-tag.log"
git tag -f desktop-v1.2.3 "$approved_sha" >/dev/null
git push -q -f origin desktop-v1.2.3
git switch -q main-v2
git tag v1.2.5
git tag npm-v1.2.5 "$other_sha"
git tag desktop-v1.2.5
git push -q origin v1.2.5 npm-v1.2.5 desktop-v1.2.5
if RELEASE_TAG=v1.2.5 "$repo_root/scripts/resolve-stable-release.sh" >"$test_root/mismatch.log" 2>&1; then
echo "mismatched sibling tag unexpectedly passed" >&2
exit 1
fi
grep -Eq 'npm-v1\.2\.5 points to .* expected' "$test_root/mismatch.log"
if RELEASE_TAG=v1.2.3-rc.1 "$repo_root/scripts/resolve-stable-release.sh" >"$test_root/prerelease.log" 2>&1; then
echo "prerelease tag unexpectedly passed stable validation" >&2
exit 1
fi
grep -Eq 'stable release tag must be vMAJOR.MINOR.PATCH' "$test_root/prerelease.log"
)
EVENT_NAME=push IN_CHANNEL=stable IN_TAG=desktop-v1.2.3 REF_NAME=v1.2.3 RUN_NUMBER=10 \
GITHUB_OUTPUT="$test_root/desktop-stable.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^tag=desktop-v1\.2\.3$' "$test_root/desktop-stable.out"
grep -Eq '^version=v1\.2\.3$' "$test_root/desktop-stable.out"
grep -Eq '^notes_version=v1\.2\.3$' "$test_root/desktop-stable.out"
EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \
IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=999 IN_PREVIEW_NUMBER=42 \
GITHUB_OUTPUT="$test_root/desktop-preview.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^version=v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out"
grep -Eq '^tag=desktop-v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out"
grep -Eq '^channel=preview$' "$test_root/desktop-preview.out"
grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out"
if EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \
IN_TAG=desktop-v1.3.0-preview.42 REF_NAME=main-v2 RUN_NUMBER=42 \
GITHUB_OUTPUT="$test_root/desktop-preview-tagged.out" \
bash "$repo_root/scripts/resolve-desktop-release.sh" \
>"$test_root/desktop-preview-tagged.log" 2>&1; then
echo "tagged Desktop Preview dispatch unexpectedly passed" >&2
exit 1
fi
grep -Eq 'Desktop Preview versions are synthesized from protected main-v2' \
"$test_root/desktop-preview-tagged.log"
# Legacy signing automation may still dispatch `canary`; normalize it to Preview
# without exposing a third public release path.
EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_SIGNING_PREFLIGHT=true \
IN_CHANNEL=canary IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=43 \
GITHUB_OUTPUT="$test_root/desktop-canary-compat.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^version=v1\.3\.0-preview\.43$' "$test_root/desktop-canary-compat.out"
grep -Eq '^tag=desktop-v1\.3\.0-preview\.43$' "$test_root/desktop-canary-compat.out"
grep -Eq '^channel=preview$' "$test_root/desktop-canary-compat.out"
EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_PRODUCTION_SIGNING_SMOKE=true \
IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=44 \
GITHUB_OUTPUT="$test_root/desktop-preview-smoke.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^version=v1\.3\.0-preview\.44$' "$test_root/desktop-preview-smoke.out"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \
IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=45 \
GITHUB_OUTPUT="$test_root/desktop-preview-direct.out" \
bash "$repo_root/scripts/resolve-desktop-release.sh" \
>"$test_root/desktop-preview-direct.log" 2>&1; then
echo "standalone public Desktop Preview unexpectedly passed" >&2
exit 1
fi
grep -Eq 'public Desktop Preview releases must be dispatched by release-preview.yml' \
"$test_root/desktop-preview-direct.log"
EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=desktop-v1.4.0-rc.1 RUN_NUMBER=50 \
GITHUB_OUTPUT="$test_root/desktop-rc.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^prerelease=true$' "$test_root/desktop-rc.out"
grep -Eq '^notes_version=v1\.4\.0$' "$test_root/desktop-rc.out"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=stable \
IN_TAG=desktop-v1.4.0-rc.1 REF_NAME=main-v2 RUN_NUMBER=50 \
GITHUB_OUTPUT="$test_root/desktop-rc-dispatch.out" \
bash "$repo_root/scripts/resolve-desktop-release.sh" \
>"$test_root/desktop-rc-dispatch.log" 2>&1; then
echo "manual Desktop RC recovery unexpectedly passed" >&2
exit 1
fi
grep -Eq 'manual Desktop recovery accepts only desktop-vMAJOR.MINOR.PATCH' \
"$test_root/desktop-rc-dispatch.log"
if EVENT_NAME=workflow_dispatch IN_CHANNEL=stable IN_TAG=desktop-v1.4.0-preview.1 \
REF_NAME=main-v2 RUN_NUMBER=50 GITHUB_OUTPUT="$test_root/desktop-preview-as-stable.out" \
bash "$repo_root/scripts/resolve-desktop-release.sh" \
>"$test_root/desktop-preview-as-stable.log" 2>&1; then
echo "Desktop Preview tag unexpectedly passed as Stable" >&2
exit 1
fi
grep -Eq 'Desktop Preview versions must use channel=preview without a Git tag' \
"$test_root/desktop-preview-as-stable.log"
if EVENT_NAME=workflow_dispatch IN_CHANNEL=stable IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=50 \
GITHUB_OUTPUT="$test_root/desktop-missing-tag.out" bash "$repo_root/scripts/resolve-desktop-release.sh" \
>"$test_root/desktop-missing-tag.log" 2>&1; then
echo "tag-less desktop stable dispatch unexpectedly passed" >&2
exit 1
fi
grep -Eq 'stable dispatch requires tag' "$test_root/desktop-missing-tag.log"
EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_TAG=v1.3.0-preview.42 \
REF_NAME=main-v2 GITHUB_OUTPUT="$test_root/cli-preview-call.out" \
bash "$repo_root/scripts/resolve-cli-release.sh"
grep -Eq '^tag=v1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out"
grep -Eq '^version=1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out"
grep -Eq '^base_version=1\.3\.0$' "$test_root/cli-preview-call.out"
grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out"
grep -Eq '^channel=preview$' "$test_root/cli-preview-call.out"
grep -Eq '^prerelease=true$' "$test_root/cli-preview-call.out"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \
IN_TAG=v1.3.0-preview.42 REF_NAME=main-v2 CALLER_REF=refs/heads/main-v2 \
CALLER_REF_PROTECTED=true GITHUB_OUTPUT="$test_root/cli-preview-dispatch.out" \
bash "$repo_root/scripts/resolve-cli-release.sh" \
>"$test_root/cli-preview-dispatch.log" 2>&1; then
echo "standalone public CLI Preview unexpectedly passed" >&2
exit 1
fi
grep -Eq 'manual CLI recovery accepts only vMAJOR.MINOR.PATCH' \
"$test_root/cli-preview-dispatch.log"
EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=v1.3.0-rc.1 \
GITHUB_OUTPUT="$test_root/cli-rc.out" bash "$repo_root/scripts/resolve-cli-release.sh"
grep -Eq '^channel=stable$' "$test_root/cli-rc.out"
grep -Eq '^prerelease=true$' "$test_root/cli-rc.out"
grep -Eq '^notes_version=v1\.3\.0$' "$test_root/cli-rc.out"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=stable \
IN_TAG=v1.3.0-rc.1 REF_NAME=main-v2 CALLER_REF=refs/heads/main-v2 \
CALLER_REF_PROTECTED=true GITHUB_OUTPUT="$test_root/cli-rc-dispatch.out" \
bash "$repo_root/scripts/resolve-cli-release.sh" \
>"$test_root/cli-rc-dispatch.log" 2>&1; then
echo "manual CLI RC recovery unexpectedly passed" >&2
exit 1
fi
grep -Eq 'manual CLI recovery accepts only vMAJOR.MINOR.PATCH' \
"$test_root/cli-rc-dispatch.log"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=stable \
IN_TAG=v1.3.0-preview.42 REF_NAME=main-v2 CALLER_REF=refs/heads/main-v2 \
CALLER_REF_PROTECTED=true GITHUB_OUTPUT="$test_root/cli-channel-mismatch.out" \
bash "$repo_root/scripts/resolve-cli-release.sh" >"$test_root/cli-channel-mismatch.log" 2>&1; then
echo "CLI Preview tag unexpectedly passed as Stable" >&2
exit 1
fi
grep -Eq 'manual CLI recovery accepts only vMAJOR.MINOR.PATCH' "$test_root/cli-channel-mismatch.log"
if EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=v1.3.0-preview.latest \
GITHUB_OUTPUT="$test_root/cli-invalid-preview.out" bash "$repo_root/scripts/resolve-cli-release.sh" \
>"$test_root/cli-invalid-preview.log" 2>&1; then
echo "malformed CLI Preview tag unexpectedly passed" >&2
exit 1
fi
grep -Eq 'CLI Preview tag must be vMAJOR.MINOR.PATCH-preview.N' "$test_root/cli-invalid-preview.log"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \
IN_TAG=v1.3.0-preview.42 REF_NAME=topic CALLER_REF=refs/heads/topic \
CALLER_REF_PROTECTED=false GITHUB_OUTPUT="$test_root/cli-unprotected.out" \
bash "$repo_root/scripts/resolve-cli-release.sh" >"$test_root/cli-unprotected.log" 2>&1; then
echo "unprotected CLI Preview dispatch unexpectedly passed" >&2
exit 1
fi
grep -Eq 'manual CLI releases must run from protected main-v2' "$test_root/cli-unprotected.log"
EVENT_NAME=push IN_ORCHESTRATED=false IN_CHANNEL='' IN_BASE_VERSION='' IN_TAG='' \
REF_NAME=npm-v1.4.0-rc.1 RUN_NUMBER=50 GITHUB_OUTPUT="$test_root/npm-rc.out" \
bash "$repo_root/scripts/resolve-npm-release.sh"
grep -Eq '^arg=npm-v1\.4\.0-rc\.1$' "$test_root/npm-rc.out"
EVENT_NAME=push IN_ORCHESTRATED=true IN_CHANNEL=stable IN_BASE_VERSION=1.5.0 \
IN_TAG=npm-v1.5.0 REF_NAME=v1.5.0 RUN_NUMBER=51 GITHUB_OUTPUT="$test_root/npm-stable.out" \
bash "$repo_root/scripts/resolve-npm-release.sh"
grep -Eq '^arg=v1\.5\.0$' "$test_root/npm-stable.out"
EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=canary IN_BASE_VERSION=1.5.0 \
IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=999 IN_PREVIEW_NUMBER=42 \
GITHUB_OUTPUT="$test_root/npm-canary.out" bash "$repo_root/scripts/resolve-npm-release.sh"
grep -Eq '^arg=v1\.5\.0-canary\.42$' "$test_root/npm-canary.out"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=canary IN_BASE_VERSION=1.5.0 \
IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=52 GITHUB_OUTPUT="$test_root/npm-canary-direct.out" \
bash "$repo_root/scripts/resolve-npm-release.sh" \
>"$test_root/npm-canary-direct.log" 2>&1; then
echo "standalone public npm Canary unexpectedly passed" >&2
exit 1
fi
grep -Eq 'public npm Canary releases must be dispatched by release-preview.yml' \
"$test_root/npm-canary-direct.log"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=stable IN_BASE_VERSION=1.5.0 \
IN_TAG=npm-v1.5.1 REF_NAME=main-v2 RUN_NUMBER=52 GITHUB_OUTPUT="$test_root/npm-mismatch.out" \
bash "$repo_root/scripts/resolve-npm-release.sh" >"$test_root/npm-mismatch.log" 2>&1; then
echo "mismatched npm stable dispatch unexpectedly passed" >&2
exit 1
fi
grep -Eq 'does not match requested version' "$test_root/npm-mismatch.log"
e2e_workflow="$repo_root/.github/workflows/e2e-bot.yml"
grep -Fq 'REASONIX_HOME: ${{ runner.temp }}/reasonix-e2e-home' "$e2e_workflow"
grep -Fq 'cp /tmp/reasonix-e2e.toml "$REASONIX_HOME/config.toml"' "$e2e_workflow"
grep -Fq "printf 'DEEPSEEK_API_KEY=%s\\n' \"\$DEEPSEEK_API_KEY\" > \"\$REASONIX_HOME/.env\"" "$e2e_workflow"
grep -Fq -- '-task "compaction,fix-add-bug,fizzbuzz,palindrome,subagent-delegation"' "$e2e_workflow"
grep -Fq 'const unsuccessful = results.filter((result) => !result.Passed || result.Skipped);' "$e2e_workflow"
grep -Fq "if: always() && hashFiles('report.md') != ''" "$e2e_workflow"
if grep -A2 -F 'missing DEEPSEEK_API_KEY secret' "$e2e_workflow" | grep -Fq 'exit 0'; then
echo "e2e bot still treats a missing provider secret as success" >&2
exit 1
fi
node --test "$repo_root/npm/publish.test.mjs"
node --test "$repo_root/scripts/finalize-npm-official-release.test.mjs"
node "$repo_root/scripts/check-desktop-build-contract.mjs"
bash "$repo_root/scripts/release-stable.test.sh"
bash "$repo_root/scripts/check-cache-impact.test.sh"
bash "$repo_root/scripts/check-docs-impact.test.sh"
# Each build orchestrator must gate on the compiled docs identity once. The
# Desktop child consumes the candidate evidence instead of rerunning the guard.
for workflow in release.yml release-npm.yml release-candidate.yml; do
grep -Fq 'bash scripts/verify-embedded-docs.sh "$DOCS_BUILD_VERSION"' \
"$repo_root/.github/workflows/$workflow"
done
grep -Fq 'reasonix/internal/productdocs.linkedVersion={{ .Tag }}' "$repo_root/.goreleaser.yaml"
grep -Fq 'reasonix/internal/productdocs.linkedRevision={{ .Commit }}' "$repo_root/.goreleaser.yaml"
# The Homebrew cask must keep stripping quarantine from the unsigned CLI, but
# through Homebrew's current postflight_steps stanza, never the deprecated
# `postflight do` that GoReleaser's hooks field renders.
homebrew_config="$(sed -n '/^homebrew_casks:/,/^release:/p' "$repo_root/.goreleaser.yaml")"
grep -Fq 'postflight_steps do' <<<"$homebrew_config"
grep -Fq 'com.apple.quarantine' <<<"$homebrew_config"
! grep -Eq '^\s+hooks:|^\s+post:' <<<"$homebrew_config"
grep -Fq 'reasonix/internal/productdocs.linkedVersion=${binaryVersion}' "$repo_root/npm/build.mjs"
grep -Fq 'product_docs_ldflags="-X reasonix/internal/productdocs.linkedVersion=$VERSION' \
"$repo_root/scripts/desktop-build.sh"
echo "release workflow contract tests: PASS"