Skills › gstack › gstack-upgrade › v1.40.0.0.sh
v1.40.0.0.sh
144 lines · gstack-upgrade/migrations/v1.40.0.0.sh at 3f79d81
1#!/usr/bin/env bash
2# Migration: v1.40.0.0 — add eng-review-test-plan project-root pattern to
3# .brain-allowlist, .brain-privacy-map.json, and .gitattributes (#1452 follow-on).
4#
5# Why a second migration: v1.38.1.0 shipped two of three filenames for #1452
6# (`*-design-*.md` and `*-test-plan-*.md`) but missed `/plan-eng-review`'s
7# actual filename: `*-eng-review-test-plan-*.md`. The v1.38.1.0 migration has
8# a done-marker, so a "fix v1.38.1.0 and re-run" approach silently no-ops on
9# existing users. v1.40.0.0 needs its own migration to patch installs that
10# already ran v1.38.1.0.
11#
12# Per-file independent — if one file is missing we still repair the others.
13#
14# Idempotent: each insertion is gated on `not already present` so re-running
15# the migration is a no-op.
16#
17# Done-marker discipline (#1581): the marker is only written when every
18# required repair either succeeded or was provably unnecessary. Tracking
19# happens via the `incomplete` flag; on any failure path (missing jq, broken
20# JSON, append failure, mv failure) we set `incomplete=1` and skip the touch
21# so the migration runner retries on the next /gstack-upgrade.
23set -u
25_gstack_migration_dir="${BASH_SOURCE[0]//\\//}"; _gstack_migration_dir="${_gstack_migration_dir%/*}"
26. "${_gstack_migration_dir}/../../bin/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: ${_gstack_migration_dir}/../../bin/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
27gstack_state_root_select; GSTACK_HOME="$_gstack_sr_root"
28ALLOWLIST="${GSTACK_HOME}/.brain-allowlist"
29PRIVACY="${GSTACK_HOME}/.brain-privacy-map.json"
30GITATTRS="${GSTACK_HOME}/.gitattributes"
32MIGRATION_DIR="${GSTACK_HOME}/.migrations"
33DONE="${MIGRATION_DIR}/v1.40.0.0.done"
35mkdir -p "${MIGRATION_DIR}" 2>/dev/null || true
36if [ -f "${DONE}" ]; then
37 exit 0
38fi
40NEW_PATTERNS=(
41 'projects/*/*-eng-review-test-plan-*.md'
42)
44added_any=0
45incomplete=0
47# ----- .brain-allowlist ---------------------------------------------------
48if [ -f "${ALLOWLIST}" ]; then
49 for PATTERN in "${NEW_PATTERNS[@]}"; do
50 if ! grep -Fq -- "${PATTERN}" "${ALLOWLIST}" 2>/dev/null; then
51 if grep -q '^# ---- USER ADDITIONS BELOW' "${ALLOWLIST}" 2>/dev/null; then
52 if sed -i.bak "/^# ---- USER ADDITIONS BELOW/i\\
53${PATTERN}
54" "${ALLOWLIST}" 2>/dev/null; then
55 rm -f "${ALLOWLIST}.bak"
56 added_any=1
57 else
58 echo " [v1.40.0.0] WARN: failed to insert ${PATTERN} into ${ALLOWLIST}; will retry on next upgrade." >&2
59 rm -f "${ALLOWLIST}.bak" 2>/dev/null || true
60 incomplete=1
61 fi
62 else
63 if printf '%s\n' "${PATTERN}" >> "${ALLOWLIST}" 2>/dev/null; then
64 added_any=1
65 else
66 echo " [v1.40.0.0] WARN: failed to append ${PATTERN} to ${ALLOWLIST}; will retry on next upgrade." >&2
67 incomplete=1
68 fi
69 fi
70 fi
71 done
72fi
74# ----- .brain-privacy-map.json -------------------------------------------
75if [ -f "${PRIVACY}" ]; then
76 if command -v jq >/dev/null 2>&1; then
77 # Validate JSON shape up front. We won't try to repair a corrupt file —
78 # bail out and leave for manual fix.
79 if ! jq -e . "${PRIVACY}" >/dev/null 2>&1; then
80 echo " [v1.40.0.0] WARN: ${PRIVACY} is not valid JSON; skipping privacy-map repair. Fix manually or run gstack-artifacts-init." >&2
81 incomplete=1
82 else
83 for PATTERN in "${NEW_PATTERNS[@]}"; do
84 if ! jq -e --arg p "${PATTERN}" 'map(select(.pattern == $p)) | length > 0' "${PRIVACY}" >/dev/null 2>&1; then
85 tmp=$(mktemp "${PRIVACY}.tmp.XXXXXX" 2>/dev/null)
86 if [ -z "${tmp}" ] || [ ! -f "${tmp}" ]; then
87 echo " [v1.40.0.0] WARN: failed to create tempfile for ${PRIVACY}; skipping pattern ${PATTERN}." >&2
88 incomplete=1
89 continue
90 fi
91 if jq --arg p "${PATTERN}" '. += [{"pattern": $p, "class": "artifact"}]' "${PRIVACY}" > "${tmp}" 2>/dev/null; then
92 if mv "${tmp}" "${PRIVACY}" 2>/dev/null; then
93 added_any=1
94 else
95 echo " [v1.40.0.0] WARN: failed to rewrite ${PRIVACY}; skipping pattern ${PATTERN}." >&2
96 rm -f "${tmp}"
97 incomplete=1
98 fi
99 else
100 echo " [v1.40.0.0] WARN: jq mutation failed for ${PRIVACY}; skipping pattern ${PATTERN}." >&2
101 rm -f "${tmp}"
102 incomplete=1
103 fi
104 fi
105 done
106 fi
107 else
108 echo " [v1.40.0.0] WARN: jq not found; skipping privacy-map repair. Install jq and re-run gstack-upgrade, or run gstack-artifacts-init manually." >&2
109 incomplete=1
110 fi
111fi
113# ----- .gitattributes -----------------------------------------------------
114if [ -f "${GITATTRS}" ]; then
115 for PATTERN in "${NEW_PATTERNS[@]}"; do
116 RULE="${PATTERN} merge=union"
117 if ! grep -Fq -- "${RULE}" "${GITATTRS}" 2>/dev/null; then
118 if printf '%s\n' "${RULE}" >> "${GITATTRS}" 2>/dev/null; then
119 added_any=1
120 else
121 echo " [v1.40.0.0] WARN: failed to append rule to ${GITATTRS}; will retry on next upgrade." >&2
122 incomplete=1
123 fi
124 fi
125 done
126fi
128if [ "${incomplete}" = "0" ]; then
129 # Mark done — every required repair either succeeded or was provably
130 # unnecessary. A fresh-init user's bin/gstack-artifacts-init now writes the
131 # pattern directly, so re-runs no-op. The touchfile keeps the migration
132 # runner from looping.
133 touch "${DONE}"
134 if [ "${added_any}" = "1" ]; then
135 echo " [v1.40.0.0] allowlist/privacy-map/gitattributes patched for /plan-eng-review test plans (idempotent)" >&2
136 fi
137else
138 echo " [v1.40.0.0] INFO: marker not written; gstack-upgrade will retry once prerequisites are met." >&2
139fi
141# NEVER `git commit + push` from this migration. The user controls when the
142# patches ship into their federated artifacts repo.
144exit 0