fix(19.7): RA-WR-06 qualify the gate verdict while demo pins are unreleased

demo_pins records every plugin submodule it accepts ahead of its go.mod pin
by non-test commits (UNRELEASED_PINS, and PHASE197_PINS_FILE for run_all).
The consumers stage then ends with "phase19.7 consumers passed WITH
UNRELEASED PINS: <names> (the release pins are not yet proven)", and --all
ends with a notice plus "Phase 19.7 gate passed WITH UNRELEASED PINS: the
release pins are not yet proven (<names>)" instead of the unqualified
"Phase 19.7 gate passed". The exit status stays 0 so the pre-release
verification can run; only a PHASE197_RELEASED=1 run proves the pins.

The self-test pins that accepted unreleased drift is recorded, test-only
drift is not, and gate_verdict is unqualified only with no recorded pin.
This commit is contained in:
2026-10-23 20:15:50 +02:00
parent 788d4a49fa
commit 2fdda99c08

View File

@@ -64,6 +64,14 @@
# canonical checkout (USER_PLUGIN, OAUTHSERVER_PLUGIN or JOURNAL_PLUGIN),
# and is reported as unreleased commits; anything else fails.
#
# Accepted unreleased drift is not a release: the consumers stage then ends
# with "phase19.7 consumers passed WITH UNRELEASED PINS", and --all ends with
# "Phase 19.7 gate passed WITH UNRELEASED PINS: the release pins are not yet
# proven" instead of "Phase 19.7 gate passed" (the exit status stays 0, so
# the pre-release verification can run). A GOWORK=off release build of the
# demo compiles the older pinned code until the plugins are tagged and the
# pins bumped; only a PHASE197_RELEASED=1 run proves the release pins.
#
# Environment overrides (default: the sibling checkouts next to this
# repository): PHASE197_ROOT (this repository), USER_PLUGIN,
# OAUTHSERVER_PLUGIN, JOURNAL_PLUGIN, DEMO_APP, FONOTEKA, BM_APP, the
@@ -568,6 +576,8 @@ removed_exports() {
run_self_test() {
bash -n "${BASH_SOURCE[0]}"
# The planted pin checks below must not reach run_all's verdict.
unset PHASE197_PINS_FILE
expect_detector detect_go pass 0 '{"Action":"pass","Package":"p","Test":"TestToolsList"}'
expect_detector detect_go fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
@@ -775,14 +785,24 @@ run_self_test() {
"$(git -C "$sub" rev-parse --short=12 HEAD)" "${pin:0:12}" >"$demo/scripts/check-pins.sh"
}
planted_pins
expect_mode pins-test-only 0 "by test-only commits" --demo-pins "DEMO_APP=$demo" "USER_PLUGIN=$sub" PHASE197_RELEASED=0
expect_mode pins-test-only 0 "by test-only commits" --demo-pins "DEMO_APP=$demo" "USER_PLUGIN=$sub" PHASE197_RELEASED=0 "PHASE197_PINS_FILE=$demo/pins.txt"
if [[ -s "$demo/pins.txt" ]]; then
echo "refuse: self-test pins-test-only-unrecorded: test-only drift was recorded as unreleased" >&2
return 1
fi
echo "PASS self-test pins-test-only-unrecorded"
expect_mode pins-released-test-only 1 "refuse: PHASE197_RELEASED=1" --demo-pins "DEMO_APP=$demo" "USER_PLUGIN=$sub" PHASE197_RELEASED=1
printf 'package user\n\nfunc Added() {}\n' >"$sub/user.go"
git -C "$sub" -c user.name=t -c user.email=t@example.test commit -q -am feature
planted_pins
canon="$demo/canonical"
git clone -q "$sub" "$canon"
expect_mode pins-unreleased-accepted 0 "is ahead of its pin by unreleased commits" --demo-pins "DEMO_APP=$demo" "USER_PLUGIN=$canon" PHASE197_RELEASED=0
expect_mode pins-unreleased-accepted 0 "is ahead of its pin by unreleased commits" --demo-pins "DEMO_APP=$demo" "USER_PLUGIN=$canon" PHASE197_RELEASED=0 "PHASE197_PINS_FILE=$demo/pins.txt"
if ! grep -qx "plugins/golem15/user" "$demo/pins.txt" 2>/dev/null; then
echo "refuse: self-test pins-unreleased-recorded: accepted unreleased drift was not recorded for the verdict" >&2
return 1
fi
echo "PASS self-test pins-unreleased-recorded"
git -C "$canon" checkout -q "$pin"
expect_mode pins-unreleased-refused 1 "does not hold" --demo-pins "DEMO_APP=$demo" "USER_PLUGIN=$canon" PHASE197_RELEASED=0
git -C "$canon" checkout -q -
@@ -790,6 +810,24 @@ run_self_test() {
unset -f planted_pins
rm -rf "$demo"
# The final verdict: unqualified only without unreleased drift.
local verdict_file verdict
verdict_file="$(mktemp)"
verdict="$(gate_verdict "$verdict_file")"
if [[ "$verdict" != "Phase 19.7 gate passed" ]]; then
rm -f "$verdict_file"
echo "refuse: self-test verdict-released: \"$verdict\"" >&2
return 1
fi
printf 'plugins/golem15/user\n' >"$verdict_file"
verdict="$(gate_verdict "$verdict_file" 2>/dev/null)"
rm -f "$verdict_file"
if [[ "$(tail -n 1 <<<"$verdict")" != "Phase 19.7 gate passed WITH UNRELEASED PINS: the release pins are not yet proven (plugins/golem15/user)" ]]; then
echo "refuse: self-test verdict-unreleased: \"$verdict\"" >&2
return 1
fi
echo "PASS self-test verdict (unqualified only without unreleased pins)"
local mode
for mode in self-test go plugins consumers docker admin docs marker all; do
grep -q -- "^ --$mode" "${BASH_SOURCE[0]}" || {
@@ -922,9 +960,40 @@ run_consumers() {
vue_checked
# T-19.7-37: the demo's vhost opens exactly the MCP paths, unbuffered.
(cd "$DEMO_APP" && bash scripts/check-deploy.sh)
if [[ "${#UNRELEASED_PINS[@]}" -gt 0 ]]; then
echo "phase19.7 consumers passed WITH UNRELEASED PINS: ${UNRELEASED_PINS[*]} (the release pins are not yet proven)"
return 0
fi
echo "phase19.7 consumers passed"
}
# UNRELEASED_PINS lists the demo plugin submodules demo_pins accepted ahead
# of their pins by non-test commits; with PHASE197_PINS_FILE set, demo_pins
# also appends each name there, so run_all's verdict sees them.
UNRELEASED_PINS=()
# gate_verdict FILE prints the --all verdict: "Phase 19.7 gate passed" when
# FILE lists no unreleased pin, else a qualified line naming them.
gate_verdict() {
local file="$1" names
names="$(sort -u "$file" 2>/dev/null | paste -sd ' ' -)"
if [[ -z "$names" ]]; then
echo "Phase 19.7 gate passed"
return 0
fi
cat >&2 <<EOF
************************************************************************
UNRELEASED PINS: $names
are ahead of the demo's go.mod pins by non-test commits. The builds and
tests ran against that code; a GOWORK=off release build of the demo still
compiles the older pinned versions. The phase is not releasable until the
plugins are tagged, the pins bumped and this passes:
PHASE197_RELEASED=1 bash scripts/check-phase19.7.sh --consumers
************************************************************************
EOF
echo "Phase 19.7 gate passed WITH UNRELEASED PINS: the release pins are not yet proven ($names)"
}
# canonical_checkout NAME maps a demo plugin submodule to the checkout its
# commits are developed in.
canonical_checkout() {
@@ -977,7 +1046,11 @@ demo_pins() {
canonical="$(canonical_checkout "$name")"
full="$(git -C "$DEMO_APP/$name" rev-parse HEAD)"
if [[ -n "$canonical" ]] && git -C "$canonical" merge-base --is-ancestor "$full" HEAD 2>/dev/null; then
echo "pins: $name is ahead of its pin by unreleased commits (release after verification)"
echo "pins: $name is ahead of its pin by unreleased commits (release after verification; the release pins are not yet proven)"
UNRELEASED_PINS+=("$name")
if [[ -n "${PHASE197_PINS_FILE:-}" ]]; then
printf '%s\n' "$name" >>"$PHASE197_PINS_FILE"
fi
continue
fi
printf '%s\n' "$changed" >&2
@@ -1156,7 +1229,11 @@ run_docs() {
}
run_all() {
local stage
local stage pins_file
pins_file="$(mktemp)"
# shellcheck disable=SC2064 # expand now: the path is local to run_all
trap "rm -f '$pins_file'" EXIT
export PHASE197_PINS_FILE="$pins_file"
for stage in self-test go plugins consumers admin docs marker; do
if bash "${BASH_SOURCE[0]}" "--$stage"; then
echo "PASS $stage"
@@ -1166,7 +1243,7 @@ run_all() {
fi
done
docker_gate || exit 1
echo "Phase 19.7 gate passed"
gate_verdict "$pins_file"
}
case "${1:---all}" in