Skip to content
55 changes: 41 additions & 14 deletions .github/scripts/test-release-security-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ trap 'rm -rf "$tmp"' EXIT
cat > "$tmp/gh" <<'MOCK'
#!/usr/bin/env bash
set -euo pipefail
[[ "$1" == api && "$2" == repos/tse-wei-chen/hs-sql-agent/issues/575 ]] || exit 32
[[ "${MOCK_ISSUE_API_FAIL:-}" != 1 ]] || exit 33
if [[ "$#" -eq 4 && "$3" == --jq && "$4" == .state ]]; then
# Historical behavior: state-only inspection allows an incomplete close.
printf '%s\n' "${MOCK_ISSUE_STATE:-open}"
elif [[ "$#" -eq 2 ]]; then
# Full issue response is necessary to check closure reason and checklist.
printf '{"state":"%s","state_reason":"%s","body":"%s"}\n' \
"${MOCK_ISSUE_STATE:-open}" "${MOCK_ISSUE_REASON:-completed}" \
"${MOCK_ISSUE_BODY-- [x] completed gate}"
else
exit 32
fi
[[ "$1" == api && "$#" -eq 2 ]] || exit 32
case "$2" in
repos/tse-wei-chen/hs-sql-agent/issues/575)
[[ "${MOCK_ISSUE_API_FAIL:-0}" != 1 ]] || exit 33
state="${MOCK_ISSUE_STATE:-open}"
reason="${MOCK_ISSUE_REASON:-completed}"
body="${MOCK_ISSUE_BODY-- [x] completed develop security gate}"
;;
repos/tse-wei-chen/hs-sql-agent/issues/634)
[[ "${MOCK_PUB_API_FAIL:-0}" != 1 ]] || exit 34
state="${MOCK_PUB_STATE:-closed}"
reason="${MOCK_PUB_REASON:-completed}"
body="${MOCK_PUB_BODY-- [x] owner authorized main promotion and release}"
;;
*) exit 32 ;;
esac
printf '{"state":"%s","state_reason":"%s","body":"%s"}\n' "$state" "$reason" "$body"
MOCK
chmod +x "$tmp/gh"
export PATH="$tmp:$PATH"
Expand Down Expand Up @@ -83,9 +87,32 @@ unset MOCK_ISSUE_BODY
expect_allow v3.1.0
expect_allow 3.1.0-beta

# Owner-gated publication issue #634 is a distinct, required security
# preflight. A completed develop gate #575 alone MUST NOT authorize 3.1.0.
export MOCK_ISSUE_STATE=closed
export MOCK_PUB_STATE=open
expect_deny v3.1.0
expect_deny 3.1.0-rc.1
export MOCK_PUB_STATE=closed
export MOCK_PUB_REASON=not_planned
expect_deny v3.1.0
unset MOCK_PUB_REASON
export MOCK_PUB_BODY='- [x] main passed\n- [ ] owner did not approve release'
expect_deny v3.1.0
unset MOCK_PUB_BODY
export MOCK_PUB_BODY=''
expect_deny v3.1.0
unset MOCK_PUB_BODY
export MOCK_PUB_API_FAIL=1
expect_deny v3.1.0
unset MOCK_PUB_API_FAIL

# Both security/develop completion and owner/publication sign-off needed.
expect_allow v3.1.0

# Never change the release policy of earlier, unrelated versions.
export MOCK_ISSUE_STATE=open
expect_allow v3.0.0
expect_allow 3.1.1

echo "3.1.0 release blocker regression: 16 scenarios passed."
echo "3.1.0 two-issue publication blocker regression: 23 scenarios passed."
29 changes: 16 additions & 13 deletions .github/scripts/verify-release-security-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,26 @@ if [[ -z "${GH_TOKEN:-}" ]]; then
exit 1
fi

# A manually closed issue is not proof that every blocking release gate
# passed. Require a completed closure and a nonempty, fully checked Markdown
# checklist. Read the full structured issue in one API request and deny on
# missing/malformed fields, API failures and unchecked tasks.
if ! issue="$(gh api "repos/${GITHUB_REPOSITORY}/issues/575")"; then
echo "::error::Could not verify security release gate #575; refusing publication." >&2
exit 1
fi
if ! jq -e '
# Security completion and publication authorization are distinct trust boundaries.
# Closing #575 after exact-develop security acceptance must never authorize
# NuGet/Docker publication without the owner's independently completed #634.
# Both are required even on a matching main SHA; the separate strict
# verify-release-head-ci-gate.sh additionally checks that main push.
for issue_number in 575 634; do
if ! issue="$(gh api "repos/${GITHUB_REPOSITORY}/issues/$issue_number")"; then
echo "::error::Cannot inspect security/publication gate #$issue_number; refusing v3.1.0 publication." >&2
exit 1
fi
if ! jq -e '
.state == "closed"
and .state_reason == "completed"
and (.body | type == "string")
and (.body | split("\n") | any(.[]; test("^[ \\t]*[-*][ \\t]+\\[[xX]\\]")))
and (.body | split("\n") | all(.[]; test("^[ \\t]*[-*][ \\t]+\\[[ \\t]+\\]") | not))
' <<< "$issue" >/dev/null; then
echo "::error::Security release gate #575 must be closed as completed with a nonempty, fully checked checklist; refusing v3.1.0 publication." >&2
exit 1
fi
echo "::error::Security/publication gate #$issue_number must be completed with a nonempty, fully checked checklist; refusing v3.1.0 publication." >&2
exit 1
fi
done

echo "Security release gate #575 is completed with no unchecked tasks. Continue only with exact-head CI and live-provider evidence verified."
echo "Security develop gate #575 and owner publication gate #634 are both completed with no unchecked tasks. Exact main-head CI and release evidence remain separately mandatory."
27 changes: 19 additions & 8 deletions .github/test-suites/RELEASE-CANDIDATE-3.1.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Release stage: PREPARE / develop only.** This is a finite, reproducible
security and integration acceptance plan. A green release-candidate check
does **not** merge into `main`, close #575, create a Git tag, publish NuGet
does **not** merge into `main`, create a Git tag, publish NuGet
packages, create a GitHub Release, or push an OCI/Docker image. Promotion
to `main` is a separate, owner-authorized action.

Expand Down Expand Up @@ -74,11 +74,22 @@ provide independent bounded coverage, not an F# CodeQL claim.

## What is deliberately not performed at this stage

The existing **publication** gates still require an owner-approved promotion
to `main`, successful **18/18 Tests + SqlCore baseline on the *actual promoted
main push SHA***, and completed/fully-checked #575. Those facts do not exist
while the user is preparing release solely on `develop`. Therefore the
candidate gate does **not** close #575, overwrite/check the final main-only
checkbox, authorize NuGet or Docker publication, or bypass either existing
release gate. Mark this stage READY only when the exact-`develop` candidate
**Two-stage release contract (no implicit main authorization):** #575 owns
bounded six-provider safety, supported-mode limitations and exact-`develop`
Release Candidate evidence. It can be marked `completed` **only after**
those gates actually pass on the merged develop SHA; a completed #575 alone
does not permit a release. The independent owner/publication issue
[#634](https://github.com/tse-wei-chen/hs-sql-agent/issues/634) must remain
open until the owner explicitly approves main promotion and verifies the
*actual* promoted main push SHA: 18/18 named Tests jobs and a successful
SqlCore baseline. The 3.1.0 publication preflight now requires **both**
#575 and #634 `closed/completed` with every checkbox checked, *plus* the
unchanged strict exact-main CI gate. Any open/unchecked issue fails closed.
Neither issue closure nor a read-only candidate workflow merges main, creates
tags, publishes packages, or bypasses credential/registry preflights.

Mark this develop candidate READY only when the exact-`develop` candidate
workflow finishes green; otherwise treat it as BLOCKED and inspect its logs.
The physical Query/UDF and Redis/ACK-loss evidence is scoped to explicitly
supported configurations; unsupported HA, global pool/failover and arbitrary
external effects must not be advertised as proven.
4 changes: 4 additions & 0 deletions .github/test-suites/physical-fault-release-evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,7 @@ that exact final SHA before release publication.
3. Release verification: six-provider positive and negative ledgers, dependency/CVE and compiler warning review, CodeQL's lack of F# semantic analysis, followed by 18/18 Tests jobs and SqlCore baseline on the actual promoted main push SHA.

**Batch stop rule:** Four added provider physical partition tests pass with zero skips, the existing PostgreSQL/Redis cases remain mandatory, and all 18 jobs plus SqlCore baseline pass for the identical PR head. This closes a bounded real TCP admission-partition evidence gap, **not** the entirety of issue #575 or the v3.1.0 release gate.

## Two-stage v3.1.0 release gates

Develop security acceptance is tracked in #575 with native, limited-scope physical evidence and the read-only exact-develop candidate gate. Owner-authorized main promotion and actual main-push 18/18 Tests + SqlCore baseline belong to #634. The 3.1.0 publication script refuses access unless **both** issues are closed as completed with every task checked, and the independent exact-main SHA gate passes. A completed #575 never authorizes publication, a main merge, or an HA/externally effectful UDF safety claim.
4 changes: 4 additions & 0 deletions .github/test-suites/query-security-deployment-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@ Release 3.1.0 remains blocked until every #575 checkbox is supported, the
issue is closed as completed, and the *actual promoted main push SHA* passes
all 18 Tests jobs and the SqlCore baseline. Develop-PR success is necessary
but not sufficient. Never bypass this preflight for publication.

## Two-stage v3.1.0 release gates

Develop security acceptance is tracked in #575 with native, limited-scope physical evidence and the read-only exact-develop candidate gate. Owner-authorized main promotion and actual main-push 18/18 Tests + SqlCore baseline belong to #634. The 3.1.0 publication script refuses access unless **both** issues are closed as completed with every task checked, and the independent exact-main SHA gate passes. A completed #575 never authorizes publication, a main merge, or an HA/externally effectful UDF safety claim.
Loading