@@ -369,6 +369,102 @@ if git rev-parse --is-inside-work-tree &>/dev/null; then
369369 fi
370370fi
371371
372+ # =============================================================================
373+ # Pre-bundle PHASE_HISTORY <-> git-tag drift check (Phase 13.46.DF candidate)
374+ # =============================================================================
375+ # Catches a class of bugs where docs/PHASE_HISTORY.md and the actual git tags
376+ # disagree about phase closures. Two real incidents motivated this (both found
377+ # 2026-05-27 while triaging Phase 13.25.DF):
378+ #
379+ # (1) DOCUMENTED-BUT-UNTAGGED: PHASE_HISTORY.md recorded
380+ # "FIX2 ... tag PHASE_13_25_DF_FIX2_END" but no such tag existed in the
381+ # repo (the FIX2 commit existed; the tag was never created). The history
382+ # claimed a closure the repo could not prove.
383+ #
384+ # (2) MISPLACED FIX TAG: PHASE_13_25_DF_FIX1_END was sitting on the FIX2
385+ # commit (subject "Phase 13.25.DF FIX2: ...") instead of the FIX1 commit.
386+ # Anyone checking out the FIX1 tag would have gotten FIX2 code.
387+ #
388+ # Check (1) is a BLOCK: any PHASE_*_END string mentioned in PHASE_HISTORY.md
389+ # that is NOT a real git tag stops bundle creation. This is unambiguous —
390+ # the doc asserts a closure the repo doesn't have.
391+ # Check (2) is a WARNING (heuristic): for each repo PHASE_*_FIX<N>_END tag,
392+ # if the tagged commit's subject line mentions a DIFFERENT FIX<M> (M != N),
393+ # the tag is likely on the wrong commit. Warn (don't block) because commit
394+ # subjects are free-form; promote to a block later if it proves reliable.
395+ #
396+ # Note: the reverse direction (repo tags NOT cited in PHASE_HISTORY.md) is NOT
397+ # flagged — many tags (GB/ADF/older phases) are intentionally not cited by
398+ # exact string in the dfdraw history narrative. That direction is noise.
399+ #
400+ # Override: DFDRAW_SKIP_TAG_DRIFT_CHECK=1 bash run_tests.sh
401+ # (for development runs before PHASE_HISTORY.md has been updated).
402+
403+ PHASE_HISTORY_FILE=" docs/PHASE_HISTORY.md"
404+ if git rev-parse --is-inside-work-tree & > /dev/null \
405+ && [[ -f " $PHASE_HISTORY_FILE " ]] \
406+ && [[ -z " $DFDRAW_SKIP_TAG_DRIFT_CHECK " ]]; then
407+
408+ # _END tags claimed in the history doc vs _END tags actually in the repo.
409+ DOC_END_TAGS=$( grep -oE ' PHASE_[A-Z0-9_]+_END' " $PHASE_HISTORY_FILE " \
410+ | sort -u || true)
411+ REPO_END_TAGS=$( git tag --list ' PHASE_*_END' | sort -u || true)
412+
413+ # (1) Documented-but-untagged: lines in DOC not in REPO.
414+ MISSING_TAGS=$( comm -23 \
415+ <( printf ' %s\n' " $DOC_END_TAGS " ) \
416+ <( printf ' %s\n' " $REPO_END_TAGS " ) | grep -v ' ^$' || true)
417+
418+ if [[ -n " $MISSING_TAGS " ]]; then
419+ echo " "
420+ echo " ${RED}${BOLD} ❌ BUNDLE BLOCKED — PHASE_HISTORY.md claims tags that do not exist:${RESET} "
421+ echo " $MISSING_TAGS " | sed ' s/^/ /'
422+ echo " "
423+ echo " ${YELLOW} docs/PHASE_HISTORY.md documents these phase closures, but the${RESET} "
424+ echo " ${YELLOW} corresponding git tags are absent. Either the closure was never${RESET} "
425+ echo " ${YELLOW} tagged, or the history entry is premature/incorrect.${RESET} "
426+ echo " "
427+ echo " Resolve each by ONE of:"
428+ echo " git tag <PHASE_..._END> <commit> # if the work landed but tagging was missed"
429+ echo " (edit PHASE_HISTORY.md) # if the entry is premature/wrong"
430+ echo " "
431+ echo " Verify the intended commit first: git log --oneline -1 <commit>"
432+ echo " "
433+ echo " Override (development only): DFDRAW_SKIP_TAG_DRIFT_CHECK=1 bash run_tests.sh"
434+ echo " "
435+ echo " Test results from this run are saved to:"
436+ echo " $LOG_DIR /"
437+ echo " Bundle .zip was NOT created."
438+ exit 1
439+ fi
440+
441+ # (2) Misplaced FIX tag (heuristic warning, non-blocking).
442+ TAG_PLACEMENT_WARNINGS=" "
443+ while IFS= read -r tag; do
444+ [[ -z " $tag " ]] && continue
445+ # Extract FIX<N> from the tag name, if present.
446+ tag_fix=$( printf ' %s' " $tag " | grep -oE ' FIX[0-9]+' | head -1 || true)
447+ [[ -z " $tag_fix " ]] && continue
448+ subject=$( git log -1 --format=' %s' " $tag " 2> /dev/null || true)
449+ # Find any FIX<M> mentioned in the tagged commit's subject.
450+ subj_fix=$( printf ' %s' " $subject " | grep -oE ' FIX[0-9]+' | head -1 || true)
451+ if [[ -n " $subj_fix " ]] && [[ " $subj_fix " != " $tag_fix " ]]; then
452+ TAG_PLACEMENT_WARNINGS+=" $tag -> commit subject mentions $subj_fix (\" $subject \" )" $' \n '
453+ fi
454+ done <<< " $REPO_END_TAGS"
455+
456+ if [[ -n " $TAG_PLACEMENT_WARNINGS " ]]; then
457+ echo " "
458+ echo " ${YELLOW}${BOLD} ⚠️ TAG PLACEMENT WARNING — FIX tag(s) may be on the wrong commit:${RESET} "
459+ printf ' %s' " $TAG_PLACEMENT_WARNINGS "
460+ echo " "
461+ echo " ${YELLOW} The tag name's FIX number does not match the tagged commit's${RESET} "
462+ echo " ${YELLOW} subject. Verify with: git log --oneline -1 <tag>${RESET} "
463+ echo " ${YELLOW} (Warning only — bundle still created.)${RESET} "
464+ echo " "
465+ fi
466+ fi
467+
372468# =============================================================================
373469# Package reviewer.zip
374470# =============================================================================
0 commit comments