#!/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//` # 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