deploy: „fertig" wird erst beim Deploy „in Produktion" (D30)

Der mitgelieferte Werkbaum-Plan behauptete [^] für Funktionen, die nur auf der
automatisch deployten Pages-Instanz lagen, nicht auf werkbaum.javagil.de
(manueller Deploy, D16). Ausgerechnet das Dokument, das den Stand beschreiben
soll, war ungenau — und „Was ist neu?" (D28) meldete Dinge als live, die es
dort nicht waren.

Die Unterscheidung gibt es längst: SPEC §4 trennt [x] fertig von [^] in
Produktion. Der Plan hat sie für sich selbst nie benutzt. Konvention ab jetzt:
beim Mergen [x], der Deploy befördert — nur er weiß, wann die Aussage wahr wird.

- scripts/promote-shipped.sh schreibt Statusboxen am Zeilenanfang von [x] auf
  [^] und hält das als eigenen Commit fest (-n zeigt nur, -y ohne Rückfrage).
  Bricht ab, wenn die Plandatei uncommittete Änderungen hat; committet nur
  diesen einen Pfad; pusht nicht.
- deploy-prod.sh ruft es als Schritt 0 auf (--no-promote schaltet es ab) und
  warnt, wenn HEAD noch nicht auf origin liegt — der Footer-Versionslink zeigt
  sonst auf einen Commit, den GitHub nicht kennt.

Warum ein Commit und kein Rewrite beim Bauen: Ein Rewrite macht GENAU EINE
Installation ehrlich; Pages untertriebe dauerhaft und die Neu-Anzeige wäre dort
für immer stumm. Der Commit wird von beiden Pipelines gesehen (Pages beim Push,
prod beim rsync), das Artefakt bleibt inhaltsgleich mit dem Repo — die
vorhandenen sed-Regeln (D16) fassen nur Pfade und Version an, Infrastruktur,
kein Rewrite dessen, was das Dokument aussagt. Außerdem Präzedenzfall D16:
VERSION per bewusstem Bump-Commit, „vollständig aus dem Repo reproduzierbar".

Einmalige Nachholung, exakt statt geschätzt: Der Footer der stabilen Instanz
verlinkt den deployten Commit (4061362); alles danach ist dort nicht drin. Es
sind GENAU ZWEI Knoten — „Optional nodes" (D29) und „Show what is new since
your last visit" (D28) —, nicht das Dutzend, das ich vorher grob geschätzt
hatte. Beide stehen jetzt auf [x] und leuchten beim nächsten Prod-Deploy als
neu auf. Eine Demotion [^]→[x] löst kein Falschleuchten aus: freshProdSet
meldet nur Knoten, die JETZT [^] sind (test-abgedeckt).

Umfang: nur example-werkbaum.werkbaum. Die übrigen Beispieldateien sind
erfunden und sagen nichts über ein Deployment aus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-07-28 12:38:21 +02:00
co-authored by Claude Opus 4.8
parent 56c18cb8cb
commit 6836352e84
6 changed files with 214 additions and 10 deletions
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
#
# Werkbaum — „fertig" auf „in Produktion" befördern.
#
# Der mitgelieferte Plan docs/examples/example-werkbaum.werkbaum beschreibt
# Werkbaum selbst. SPEC §4 unterscheidet `[x]` fertig (abgeschlossen) von `[^]`
# in Produktion (deployed/live) — und die zweite Aussage kann erst der Deploy
# wahr machen. Konvention deshalb: beim Mergen `[x]`, unmittelbar VOR dem
# Deploy dieser Lauf, der daraus `[^]` macht und das als Commit festhält.
#
# Warum ein Commit und kein Rewrite beim Bauen: siehe docs/DECISIONS.md D30.
# Kurz — der Commit wird von BEIDEN Pipelines gesehen (GitHub Pages baut ihn
# beim Push, prod beim nächsten rsync), das Deployment-Artefakt bleibt
# inhaltsgleich mit dem Repo, und die „Was ist neu?"-Anzeige (D28) bleibt auf
# der Pages-Instanz erlebbar statt dort für immer stumm zu sein.
#
# Verwendung:
# scripts/promote-shipped.sh [-n] [-y]
#
# -n, --dry-run nur zeigen, was befördert würde
# -y, --yes ohne Rückfrage befördern und committen
#
# Ohne zu befördernde Knoten endet der Lauf mit 0 und ändert nichts.
# Gepusht wird NICHT — das bleibt eine bewusste Handlung.
set -euo pipefail
DRY=0
YES=0
for arg in "$@"; do
case "$arg" in
-n|--dry-run) DRY=1 ;;
-y|--yes) YES=1 ;;
-h|--help)
awk 'NR>2 { if ($0 ~ /^#/) { sub(/^# ?/, ""); print } else exit }' "$0"
exit 0 ;;
*) echo "Unbekannte Option: $arg" >&2; exit 2 ;;
esac
done
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
PLAN="docs/examples/example-werkbaum.werkbaum"
[ -f "$PLAN" ] || { echo "Plan nicht gefunden: $PLAN" >&2; exit 1; }
# Nur Statusboxen am Zeilenanfang (nach optionalem Zeichen -/+/|), damit ein
# „[x]" mitten im Label unangetastet bleibt. `x` auch als `X` (SPEC §4).
MATCH='^([[:space:]]*([-+|][[:space:]]*)?)\[[xX]\]'
mapfile -t HITS < <(grep -nE "$MATCH" "$PLAN" || true)
if [ "${#HITS[@]}" -eq 0 ]; then
echo "==> Nichts zu befördern — kein [x] im Plan."
exit 0
fi
echo "==> ${#HITS[@]} Knoten würden auf [^] befördert:"
printf ' %s\n' "${HITS[@]}"
[ "$DRY" -eq 1 ] && exit 0
# Der Commit fasst genau diese eine Datei an. Ist sie schon geändert, lässt sich
# die Beförderung nicht sauber von der Änderung trennen — dann lieber abbrechen.
if git rev-parse HEAD >/dev/null 2>&1 && [ -n "$(git status --porcelain -- "$PLAN")" ]; then
echo " ! $PLAN hat uncommittete Änderungen — erst committen, dann befördern." >&2
exit 1
fi
if [ "$YES" -ne 1 ]; then
printf '==> Befördern und committen? [y/N] '
read -r ANS
case "$ANS" in y|Y|j|J) : ;; *) echo "Abgebrochen."; exit 1 ;; esac
fi
# Labels für den Commit-Text sammeln (ohne Zeichen, Statusbox und Größe).
mapfile -t LABELS < <(printf '%s\n' "${HITS[@]}" \
| sed -E 's/^[0-9]+:[[:space:]]*([-+|][[:space:]]*)?\[[xX]\][[:space:]]*//' \
| sed -E 's/[[:space:]]*\((XS|S|M|L|XL|XXL)\)[[:space:]]*$//')
sed -i -E "s/$MATCH/\1[^]/" "$PLAN"
if ! git rev-parse HEAD >/dev/null 2>&1; then
echo "==> Kein Git-Repo — Datei geändert, nicht committet."
exit 0
fi
{
echo "docs: Werkbaum-Plan — ${#HITS[@]} Knoten in Produktion"
echo
echo "Beim Deploy der stabilen Instanz gehen diese Knoten live; SPEC §4"
echo "unterscheidet [x] fertig von [^] in Produktion (D30):"
echo
printf -- '- %s\n' "${LABELS[@]}"
} | git commit -q -F - -- "$PLAN"
echo "==> Committet: $(git log --oneline -1)"
echo " Hinweis: nicht gepusht. Ohne Push zeigt der Footer-Versionslink des"
echo " Deploys auf einen Commit, den GitHub noch nicht kennt."