ktg-plugin-marketplace/docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.sh
Kjell Tore Guttormsen 690bbbd68d fix(migration): config-audit SC2 gate (re-seed + back-compat exclusion) + generic validator
50-config-audit-sc2.sh: re-seeds snapshot-default-output in-clone (UPDATE_SNAPSHOT=1), then runs the SC2 gate = full 'find tests -name *.test.mjs' MINUS the 6-file machine-locked v5.0.0 byte-stability surface. 730 tests across 46 files pass standalone.

templates/validate-plugin.generic.sh: ported, parameterized structure validator for the two test-less plugins (okr, human-friendly-style) — STRUCTURE OK on valid plugin.json + non-empty surface + parseable frontmatter; STRUCTURE FAIL otherwise. Reuses the claude-design/tests/validate-plugin.sh pattern.

Brief-correction (operator-ratified 2026-06-17): plan F4 named only json-backcompat + raw-backcompat (2 files). The verified machine-locked surface is 6 — the v5.0.0 fixtures embed the original absolute path AND the claude_md/plugin_hygiene scanners key off a plugins/ ancestor, so findings drift by path (behavioral, not a string rewrite). Dropped clean-room SC2 coverage = config-audit's humanizer/posture-humanizer/scan-orchestrator-humanizer prose-snapshot surface, recorded in the script header + plugin-map.json (not silent). The 3 other v5.0.0-referencing tests (posture, scoring-humanizer, scenario-read-test) are path-independent and stay in the gate. config-audit backlog (out of migration scope): normalize the v5.0.0 fixtures + path-agnostic scanners, then re-include.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:23:38 +02:00

93 lines
5.8 KiB
Bash

#!/usr/bin/env bash
# Step 7 — Resolve the config-audit SC2 blocker for standalone extraction.
#
# config-audit declares its runner as `node --test 'tests/**/*.test.mjs'` (52 files, CLAUDE.md:109).
# Two distinct, directly-verified portability defects block a clean-room SC2 gate at a new clone path:
#
# (a) REBASABLE — tests/snapshot-default-output.test.mjs asserts byte-equal CLI stdout whose deep
# per-file paths are NOT normalized (normalizeScanOrchestrator scrubs only meta.target/timestamp/
# duration_ms), so it breaks at a fresh clone path. FIX: re-seed in-clone via the test's own
# intended re-approval seam — `UPDATE_SNAPSHOT=1 node --test tests/snapshot-default-output.test.mjs`
# (seam documented at its line 33). After re-seeding it asserts byte-equal against the clone's
# own path and passes.
#
# (b) FROZEN / MACHINE-LOCKED — a family of tests assert byte/structure equality against the
# tests/snapshots/v5.0.0/ fixtures, which deliberately embed the ORIGINAL capture machine's
# absolute path + a sibling marketplace + deleted plugins. At a fresh clone path they break in
# TWO ways (both verified directly, 2026-06-17): a literal embedded `path:` mismatch, AND a
# BEHAVIORAL drift — the claude_md / plugin_hygiene scanners key off whether a `plugins/<name>/`
# ancestor exists in the absolute path, so the clone produces different findingCount/score than
# the monorepo capture (e.g. posture-humanizer). drift-cli's baseline diff additionally leaks the
# clone path into humanized prose, which trips lint-default-output's tier1/tier3 prose gate.
# Regenerating these would defeat their byte-stability purpose and "normalize the path" is not a
# string rewrite (the scan BEHAVIOR differs by path) — so the correct migration-scope fix is to
# EXCLUDE the machine-locked surface by name (deterministic enumeration, no invented env var).
#
# OPERATOR-RATIFIED 2026-06-17 (brief-correction): plan F4 named only json-backcompat +
# raw-backcompat (2 files). The verified machine-locked surface is SIX files (the 2 + the 4 that
# still fail at clone path). Dropped clean-room SC2 coverage = config-audit's humanizer / posture-
# humanizer / scan-orchestrator-humanizer prose-snapshot surface (NOT silent — recorded here +
# in plugin-map.json's standalone_caveat). The three OTHER v5.0.0-referencing tests
# (posture, scoring-humanizer, scenario-read-test) assert path-INDEPENDENT aspects, pass at the
# clone path, and remain IN the gate. config-audit backlog (out of migration scope): normalize
# the v5.0.0 fixtures' embedded paths + make the scanners path-agnostic, then re-include.
#
# SC2 gate (config-audit) := full `find tests -name '*.test.mjs'` MINUS the six machine-locked tests
# below, run AFTER the in-clone snapshot re-seed. NULL push (D8). Prints "config-audit: SC2 PASS (...)"
# + exit 0 on success; non-zero on any failure (escalate — never mask).
#
# Usage: 50-config-audit-sc2.sh
set -uo pipefail
unset NODE_TEST_CONTEXT 2>/dev/null || true # un-nest the internal `node --test` (Node 25 count suppression)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK="${WORK:-/tmp/polyrepo-migration}"
KEY="config-audit"
DEST="$WORK/$KEY"
CR="/tmp/claude-${KEY}-sc2"
# The machine-locked v5.0.0 byte-stability surface excluded from the SC2 gate (operator-ratified).
# Anchored to each test file's basename; scan-orchestrator-humanizer is matched WITHOUT catching the
# portable scan-orchestrator.test.mjs, and posture-humanizer WITHOUT catching posture.test.mjs.
EXCLUDE_RE='(json-backcompat|raw-backcompat|cli-humanizer|posture-humanizer|scan-orchestrator-humanizer|lint-default-output)\.test\.mjs$'
# --- 1. Prep the extract (idempotent), reusing the Step 3-5 drivers (same pattern as 40-validate-standalone.sh) ---
if [ ! -d "$DEST/.git" ]; then
WORK="$WORK" bash "$SCRIPT_DIR/10-extract.sh" "$KEY" >/dev/null || { echo "config-audit: SC2 FAIL (extract error)"; exit 1; }
fi
WORK="$WORK" bash "$SCRIPT_DIR/20-rehome-config.sh" "$KEY" >/dev/null || { echo "config-audit: SC2 FAIL (rehome error)"; exit 1; }
WORK="$WORK" node "$SCRIPT_DIR/30-fix-references.mjs" "$KEY" >/dev/null || { echo "config-audit: SC2 FAIL (fix-references error)"; exit 1; }
# --- 2. Clean room (no marketplace parent) ---
rm -rf "$CR"
cp -R "$DEST" "$CR"
# --- 3. Re-seed the rebasable snapshot at the clone's own path (intended re-approval seam) ---
if [ ! -f "$CR/tests/snapshot-default-output.test.mjs" ]; then
echo "config-audit: SC2 FAIL (snapshot test missing from extract)"; exit 1
fi
( cd "$CR" && UPDATE_SNAPSHOT=1 node --test tests/snapshot-default-output.test.mjs ) >/dev/null 2>&1 \
|| { echo "config-audit: SC2 FAIL (snapshot re-seed error)"; exit 1; }
# --- 4. Build the gate: full suite MINUS the machine-locked v5.0.0 back-compat tests (excluded by name) ---
GATE_FILES="$(cd "$CR" && find tests -name '*.test.mjs' | grep -vE "$EXCLUDE_RE" | sort)"
if [ -z "$GATE_FILES" ]; then echo "config-audit: SC2 FAIL (no gate files enumerated)"; exit 1; fi
# Defense-in-depth: none of the machine-locked tests may leak into the gate.
if printf '%s\n' "$GATE_FILES" | grep -qE "$EXCLUDE_RE"; then
echo "config-audit: SC2 FAIL (machine-locked exclusion leaked into the gate)"; exit 1
fi
GATE_COUNT="$(printf '%s\n' "$GATE_FILES" | wc -l | tr -d '[:space:]')"
# --- 5. Run the gate in the clean room ---
OUT="$(cd "$CR" && node --test $GATE_FILES 2>&1)"; STATUS=$?
TESTS="$(printf '%s\n' "$OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)"
if [ "$STATUS" -ne 0 ]; then
echo "config-audit: SC2 FAIL (gate exit $STATUS)"
printf '%s\n' "$OUT" | tail -25
exit 1
fi
echo "config-audit: SC2 PASS (${TESTS:-?} tests across ${GATE_COUNT} files, full suite minus the 6-file v5.0.0 byte-stability surface, standalone-safe)"
exit 0