Files
DeepSeek-Reasonix/scripts/release-workflows.test.sh
T
esengine d67f2835ac ci(release): publish 1.x without deploying or verifying the website
The website is released from its own branch and renders the changelog from
that branch's catalog, so the 1.x publication could dispatch Pages and still
fail on a changelog page it does not own (v1.39.2: "public changelog returned
HTTP 404" after every product surface had shipped).

- Postflight now verifies tags, CLI and Desktop releases, npm, the Desktop
  Stable manifest on dl.reasonix.io and the Homebrew cask, attaches
  release-event.json, and completes. sync-release-site.sh becomes
  reconcile-release-publication.sh without the Pages dispatch/watch stages.
- The stable manifest check stays: it reads dl.reasonix.io/latest/latest.json,
  the Desktop updater pointer on R2, not the website. The Homebrew check moves
  out of the site verifier and reads the tap through the contents API.
- Drop the homepage/changelog/hydrated-DOM probes, observe-release-site.sh,
  release-site-fetch, and the pages.yml dispatches in release-stable.yml and
  release-desktop.yml together with the actions: write they needed.
- Ledger: the site fragment becomes a pointer fragment (stableManifest,
  homebrew); homepage and changelog surfaces are gone.
2026-09-27 05:55:11 -07:00

1795 lines
96 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"
node --test "$repo_root/scripts/release-publication-ledger.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
# The public disclosure names the release it belongs to and normalizes the
# v prefix, so it can never print a doubled version or a stale one.
disclosure = desktop.split('name: Disclose manual Desktop distribution', 1)[1].split('- name:', 1)[0]
assert 'version="v${MANUAL_VERSION#v}"' in disclosure
assert 'This ${version} desktop release' in disclosure
assert 'v${MANUAL_VERSION}' not in disclosure
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"
python3 - "$repo_root" <<'PY'
import pathlib, re, sys
root = pathlib.Path(sys.argv[1]) / '.github/workflows'
levels = {'none': 0, 'read': 1, 'write': 2}
def permissions(text, indent):
match = re.search(r'(?m)^' + ' ' * indent + r'permissions:\n((?:' + ' ' * (indent + 2) + r'[^\n]*\n)+)', text)
if not match:
return None
return {key: levels[value] for key, value in re.findall(r'([\w-]+): (none|read|write)', match[1])}
desktop = (root / 'release-desktop.yml').read_text()
required = permissions(desktop, 0)
for block in re.split(r'(?m)^ [\w-]+:\n', desktop.split('\njobs:\n', 1)[1])[1:]:
for key, value in (permissions(block, 4) or {}).items():
required[key] = max(required.get(key, 0), value)
# Conditions cannot hide a nested permission escalation at workflow parsing.
# Cover both preparation and the current/legacy publishing entrypoints.
for name in ('release-candidate.yml', 'release-promote.yml', 'release-stable.yml'):
workflow = (root / name).read_text()
default = permissions(workflow, 0)
calls = 0
for block in re.split(r'(?m)^ [\w-]+:\n', workflow.split('\njobs:\n', 1)[1])[1:]:
if 'uses: ./.github/workflows/release-desktop.yml' not in block:
continue
calls += 1
explicit = permissions(block, 4)
effective = default if explicit is None else explicit
for key, value in required.items():
assert effective.get(key, 0) >= value, f'{name}: Desktop call cannot grant {key} at required level {value}'
assert calls, f'{name}: expected a Desktop call'
PY
promote="$repo_root/.github/workflows/release-promote.yml"
verify="$repo_root/.github/workflows/release-verify.yml"
rehearsal_verify="$repo_root/.github/workflows/release-candidate-verify.yml"
for workflow in "$candidate" "$promote" "$verify" "$rehearsal_verify"; do test -s "$workflow"; done
candidate_dispatch="$(sed -n '/workflow_dispatch:/,/^ push:/p' "$candidate")"
[ "$(grep -Ec '^ [a-z_]+:$' <<<"$candidate_dispatch")" = "2" ]
grep -Fq 'required: true' <<<"$(sed -n '/^ version:/,/^ rehearsal:/p' <<<"$candidate_dispatch")"
grep -Fq 'required: false' <<<"$(sed -n '/^ rehearsal:/,$p' <<<"$candidate_dispatch")"
! 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 -Eq 'actions/attest-build-provenance@[0-9a-f]{40} # v3$' "$candidate"
grep -Fq 'bash scripts/validate-release-control-plane.sh' "$candidate"
preflight_line="$(grep -n -m1 'bash scripts/validate-release-control-plane.sh' "$candidate" | cut -d: -f1)"
source_ci_line="$(grep -n -m1 'run: bash scripts/verify-release-push-ci.sh' "$candidate" | cut -d: -f1)"
[ "$preflight_line" -lt "$source_ci_line" ]
! grep -Fq 'source_sha="$(git rev-parse origin/main-v2)"' "$candidate"
grep -Fq 'retention-days: 30' "$candidate"
grep -Fq 'retention-days: 90' "$candidate"
grep -Fq 'name: ${{ needs.resolve.outputs.artifact_namespace }}-evidence-${{ needs.resolve.outputs.candidate_id }}' "$candidate"
grep -Fq 'RELEASE_EVIDENCE_ARTIFACT_ID: ${{ steps.evidence.outputs.artifact-id }}' "$candidate"
grep -Fq 'node scripts/resolve-release-candidate.mjs resolve-rehearsal' "$rehearsal_verify"
grep -Fq 'node scripts/release-candidate.mjs verify-rehearsal' "$rehearsal_verify"
grep -Fq 'node scripts/verify-release-artifact-archive.mjs' "$rehearsal_verify"
! grep -Eq 'environment: release|release-candidate-tags\.sh activate|publish-(desktop|homebrew)|npm publish' "$rehearsal_verify"
grep -Fq 'gh attestation verify' "$promote"
grep -Fq 'bash scripts/release-candidate-tags.sh check' "$promote"
grep -Fq 'bash scripts/release-candidate-tags.sh activate' "$promote"
grep -Fq 'git push --atomic "$remote"' "$repo_root/scripts/release-candidate-tags.sh"
[ "$(grep -Ec '^ environment: release$' "$promote")" = "1" ]
grep -Fq 'group: stable-release-publication' "$promote"
grep -Fq 'run: bash scripts/reconcile-release-publication.sh' "$promote"
grep -Fq 'VERIFY_PUBLIC_POINTERS=true' "$repo_root/scripts/reconcile-release-publication.sh"
! grep -Eq 'pages\.yml|--ref website' "$promote" "$repo_root/.github/workflows/release-stable.yml" "$repo_root/.github/workflows/release-desktop.yml"
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' "$repo_root/scripts/reconcile-release-publication.sh"
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 'VERIFY_PUBLIC_POINTERS: "true"' "$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"
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"
cli_channel="$(grep -A6 -E '^ channel:' "$repo_root/.github/workflows/release.yml")"
grep -Eq 'default: stable' <<<"$cli_channel"
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"
windows_sign="$(sed -n '/^ windows-sign:/,/^ windows-runtime-acceptance:/p' "$repo_root/.github/workflows/release-desktop.yml")"
grep -Fq 'needs: [resolve, windows-build, signing-contract]' <<<"$windows_sign"
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@/p' "$repo_root/.github/workflows/release-npm.yml")"
[ -n "$npm_control_step" ]
if grep -q 'if:' <<<"$npm_control_step"; 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_with_compat="$test_root/cli-publication-with-compat-release.json"
jq '.assets += [{name: "latest.json", state: "uploaded", size: 1,
browser_download_url: "https://github.com/esengine/DeepSeek-Reasonix/releases/download/v1.2.3/latest.json",
digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000"}]' \
"$publication_release" >"$publication_with_compat"
[ "$(bash "$publication_decider" stable v1.2.3 esengine/DeepSeek-Reasonix \
"$publication_with_compat" "$publication_checksums")" = "reuse" ]
publication_unexpected_asset="$test_root/cli-publication-unexpected-asset-release.json"
jq '.assets += [{name: "unexpected.zip", state: "uploaded", size: 1,
browser_download_url: "https://github.com/esengine/DeepSeek-Reasonix/releases/download/v1.2.3/unexpected.zip",
digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000"}]' \
"$publication_release" >"$publication_unexpected_asset"
if bash "$publication_decider" stable v1.2.3 esengine/DeepSeek-Reasonix \
"$publication_unexpected_asset" "$publication_checksums" >/dev/null 2>&1; then
echo "CLI publication decider accepted an unexpected asset" >&2
exit 1
fi
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"
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-rehearsal.out" \
RELEASE_CHANNEL=stable RELEASE_TAG=desktop-v1.2.3 \
IN_ORCHESTRATED=true IN_ORCHESTRATOR=candidate APPROVED_SHA="$approved_sha" \
CANDIDATE_PREPARATION=true CANDIDATE_REHEARSAL=true \
"$desktop_candidate_resolver"
grep -Eq '^sha='"$approved_sha"'$' "$test_root/desktop-rehearsal.out"
if GITHUB_OUTPUT="$test_root/desktop-unsafe-rehearsal.out" \
RELEASE_CHANNEL=stable RELEASE_TAG=desktop-v1.2.3 \
IN_ORCHESTRATED=true IN_ORCHESTRATOR=promote APPROVED_SHA="$approved_sha" \
CANDIDATE_REHEARSAL=true \
"$desktop_candidate_resolver" >"$test_root/desktop-unsafe-rehearsal.log" 2>&1; then
echo "rehearsal without candidate preparation unexpectedly passed" >&2
exit 1
fi
grep -Fq 'rehearsal requires non-publishing candidate preparation' "$test_root/desktop-unsafe-rehearsal.log"
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"
e2e_missing_key="$(grep -A2 -F 'missing DEEPSEEK_API_KEY secret' "$e2e_workflow")"
if grep -Fq 'exit 0' <<<"$e2e_missing_key"; 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 --test "$repo_root/scripts/package-desktop-dmg.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"