diff --git a/gstack-upgrade/migrations/v1.27.0.0.sh b/gstack-upgrade/migrations/v1.27.0.0.sh index 2fbd273c0..021c6a566 100755 --- a/gstack-upgrade/migrations/v1.27.0.0.sh +++ b/gstack-upgrade/migrations/v1.27.0.0.sh @@ -45,6 +45,13 @@ JOURNAL="${MIGRATION_DIR}/v1.27.0.0.journal" DONE="${MIGRATION_DIR}/v1.27.0.0.done" SKIPPED="${MIGRATION_DIR}/v1.27.0.0.skipped-by-user" +# Real, copy-pasteable re-run command for every remediation message below. +# There is no runner re-ask: the upgrade runners' version windows never +# re-select an already-passed migration, so the only honest remediation is +# a direct invocation of this script ($0-derived so it survives any cwd). +SELF_PATH="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")" +RERUN_CMD="GSTACK_MIGRATE_ASSUME_YES=1 bash ${SELF_PATH}" + USER_NAME="${USER:-$(whoami 2>/dev/null || echo unknown)}" OLD_REPO_NAME="gstack-brain-${USER_NAME}" NEW_REPO_NAME="gstack-artifacts-${USER_NAME}" @@ -61,8 +68,8 @@ mkdir -p "$MIGRATION_DIR" # Already done? exit silently. [ -f "$DONE" ] && exit 0 -# User opted out previously? exit silently. (Re-invoke via -# `/setup-gbrain --rerun-migration` removes this marker.) +# User opted out previously? exit silently. (To re-run after an opt-out: +# rm the skipped-by-user marker, then invoke this script directly.) [ -f "$SKIPPED" ] && exit 0 journal_done() { @@ -119,13 +126,16 @@ EOF read -r REPLY || REPLY="" case "$REPLY" in n|N|no|No|NO) - echo " Skipping migration. Re-run via /setup-gbrain --rerun-migration." >&2 + echo " Skipping migration. To re-run later:" >&2 + echo " rm ${SKIPPED} && ${RERUN_CMD}" >&2 touch "$SKIPPED" exit 0 ;; skip|skip-for-now|s) - echo " Skipping for now. Will ask again next upgrade." >&2 - # Don't write SKIPPED — leave both old + new state untouched, ask again next time. + echo " Skipping for now. Re-run manually with: ${RERUN_CMD}" >&2 + # Don't write SKIPPED — leave both old + new state untouched. The + # upgrade runner will NOT re-select this migration, so re-running is + # manual via the command above. exit 0 ;; esac @@ -138,9 +148,9 @@ EOF if [ "${GSTACK_MIGRATE_ASSUME_YES:-0}" = "1" ]; then echo " (non-interactive: proceeding — GSTACK_MIGRATE_ASSUME_YES=1)" >&2 else - echo " Non-interactive session: skipping for now (will ask again next upgrade)." >&2 - echo " To proceed unattended: GSTACK_MIGRATE_ASSUME_YES=1 ./setup" >&2 - echo " To run interactively: /setup-gbrain --rerun-migration" >&2 + echo " Non-interactive session: skipping for now." >&2 + echo " Re-run manually with: ${RERUN_CMD}" >&2 + echo " To run interactively: bash ${SELF_PATH}" >&2 exit 0 fi fi @@ -357,7 +367,7 @@ done if [ -n "$INCOMPLETE" ]; then echo " [v1.27.0.0] migration INCOMPLETE — pending step(s):$INCOMPLETE" >&2 echo " Completed steps are journaled and will be skipped on re-run." >&2 - echo " Re-run via: /setup-gbrain --rerun-migration" >&2 + echo " Re-run manually with: ${RERUN_CMD}" >&2 exit 1 fi touch "$DONE" diff --git a/test/migrations-v1.27.0.0.test.ts b/test/migrations-v1.27.0.0.test.ts index fa2206ea8..0c62a8c22 100644 --- a/test/migrations-v1.27.0.0.test.ts +++ b/test/migrations-v1.27.0.0.test.ts @@ -190,6 +190,13 @@ describe('v1.27.0.0 migration — #1383 consent + failure-stays-pending contract expect(r.code).toBe(0); expect(r.stderr).toContain('skipping for now'); expect(r.stderr).toContain('GSTACK_MIGRATE_ASSUME_YES=1'); + // The remediation must be REAL: a direct invocation of this script. + // `/setup-gbrain --rerun-migration` never existed, and the runners' + // version windows never re-select a passed migration, so "will ask + // again next upgrade" was false. + expect(r.stderr).toContain('v1.27.0.0.sh'); + expect(r.stderr).not.toContain('--rerun-migration'); + expect(r.stderr).not.toContain('ask again'); // Old state untouched, nothing recorded as done. expect(fs.existsSync(path.join(tmpHome, '.gstack-brain-remote.txt'))).toBe(true); expect(fs.existsSync(path.join(tmpHome, '.gstack-artifacts-remote.txt'))).toBe(false); @@ -205,6 +212,11 @@ describe('v1.27.0.0 migration — #1383 consent + failure-stays-pending contract expect(r.code).toBe(1); expect(r.stderr).toContain('PENDING'); expect(r.stderr).toContain('INCOMPLETE'); + // Honest remediation: direct invocation, not the nonexistent + // /setup-gbrain --rerun-migration flag. + expect(r.stderr).toContain('Re-run manually with:'); + expect(r.stderr).toContain('GSTACK_MIGRATE_ASSUME_YES=1'); + expect(r.stderr).not.toContain('--rerun-migration'); expect(fs.existsSync(path.join(tmpHome, '.gstack/.migrations/v1.27.0.0.done'))).toBe(false); const journal = fs.readFileSync( path.join(tmpHome, '.gstack/.migrations/v1.27.0.0.journal'),