fix(migration): remediate 6 MAJOR + 3 MINOR trekreview findings + stale rename test
MAJOR
- 9e97cd5 40-validate-standalone.sh: route a target's sc2_gate to its dedicated
gate (config-audit → 50-config-audit-sc2.sh), mirroring 99-dryrun.sh, so --all
no longer falsely FAILs config-audit on the machine-locked v5.0.0 tests.
- 1708e90 99-dryrun.sh: assert EXACTLY one tag survives (F5); a partial tag-strip
no longer silently reports the wrong tag via head -1.
- 4e494c8 99-dryrun.sh: capture the SC2 standalone failing set from the dry-run's
own prepped extract ($dest), not the 40-validate side-effect clean room.
- aeb6292 00-preflight.sh: assert every map path is whitespace/glob-free, making
the word-split path handling in 99-dryrun.sh sound.
- 5d112cb extract the SC6 DROP + SC2 regression detectors into sc6-check.sh /
sc2-regression.sh and add sc-checks.test.mjs — a negative test proving each
detector FIRES (force-fresh re-extraction would undo a planted file-drop).
- 9e588ca 10-extract.sh re-asserts git filter-repo before use (self-heal runs
preflight only on a missing mirror); RUNBOOK lists git-filter-repo + python3>=3.6.
MINOR
- bc0f8a7 plugin-map.json: reset ms-ai-architect blob_strip_safe to null
(00-preflight.sh populates it per run).
- 8d649e9 99-dryrun.sh: gate SC6 behind extract success; a failed extract is
labelled (extract failed), not a content DROP.
- 4044c49 99-dryrun.sh: guard mktemp — an empty capture is an error, not a
false zero-regression PASS.
Also: 00-preflight.test.mjs asserted all 3 'renamed' plugins carry >=2 paths, but
llm-security became single-path in 836b8e9 (copilot was a coexisting plugin, not a
rename) — a stale pre-existing failure. Aligned the test to the ratified map and
added a positive single-path lock against re-introducing the 87-file-drop defect.
Verified: full dry-run 11/11, 0 pushes; sc-checks/99-dryrun/40-validate/00-preflight/
60-rewrite suites green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
86208dab6c
commit
fef4b33c97
11 changed files with 248 additions and 39 deletions
|
|
@ -42,6 +42,21 @@ python3 -c "import json; json.load(open('$MAP'))" >/dev/null 2>&1 || fail "plugi
|
|||
TARGET_COUNT="$(python3 -c "import json; print(len(json.load(open('$MAP'))['targets']))")"
|
||||
[ "$TARGET_COUNT" = "11" ] || fail "expected 11 targets in plugin-map.json, found $TARGET_COUNT"
|
||||
|
||||
# --- path hygiene (aeb6292): every target path must be whitespace- and glob-free, so the space-joined
|
||||
# `for p in $(mappaths …)` word-splitting in 99-dryrun.sh (live_files / SC6 baseline) is sound. ---
|
||||
python3 - "$MAP" <<'PY' || fail "plugin-map.json has a path with whitespace or a glob metacharacter (breaks word-split path handling in 99-dryrun.sh)"
|
||||
import json, re, sys
|
||||
m = json.load(open(sys.argv[1]))
|
||||
bad = []
|
||||
for k, t in m["targets"].items():
|
||||
for p in t.get("paths", []):
|
||||
if re.search(r"\s", p) or any(c in p for c in "*?[]"):
|
||||
bad.append("%s: %r" % (k, p))
|
||||
if bad:
|
||||
sys.stderr.write("offending paths:\n " + "\n ".join(bad) + "\n")
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
# --- repo baseline (HEAD descends from the ratified brief commit, on main, not behind remote) ---
|
||||
BRANCH="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD)"
|
||||
[ "$BRANCH" = "main" ] || fail "not on main (HEAD on '$BRANCH')"
|
||||
|
|
|
|||
|
|
@ -11,13 +11,18 @@ const map = JSON.parse(readFileSync(join(here, 'plugin-map.json'), 'utf8'));
|
|||
const targets = map.targets;
|
||||
const keys = Object.keys(targets);
|
||||
|
||||
const RENAMED = ['voyage', 'llm-security', 'linkedin-studio'];
|
||||
// voyage ← ultraplan-local and linkedin-studio ← linkedin-thought-leadership are TRUE renames, so each
|
||||
// carries >=2 --path entries (F1). llm-security is NOT a rename: llm-security-copilot was a SEPARATE,
|
||||
// COEXISTING plugin, so llm-security is SINGLE-path (corrected 2026-06-17, commit 836b8e9) — a dual --path
|
||||
// there collided at the coexistence commits and dropped 87 files. The map is authoritative; this suite
|
||||
// tracks it.
|
||||
const RENAMED = ['voyage', 'linkedin-studio'];
|
||||
|
||||
test('plugin-map.json parses and has exactly 11 targets (10 plugins + DS)', () => {
|
||||
assert.equal(keys.length, 11, `expected 11 targets, found ${keys.length}: ${keys.join(', ')}`);
|
||||
});
|
||||
|
||||
test('the 3 renamed plugins each carry >=2 --path entries (F1)', () => {
|
||||
test('the renamed plugins (voyage, linkedin-studio) each carry >=2 --path entries (F1)', () => {
|
||||
for (const k of RENAMED) {
|
||||
assert.ok(targets[k], `missing renamed target ${k}`);
|
||||
assert.ok(
|
||||
|
|
@ -27,6 +32,15 @@ test('the 3 renamed plugins each carry >=2 --path entries (F1)', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('llm-security is SINGLE-path — NOT a rename (copilot was a coexisting plugin, 836b8e9)', () => {
|
||||
// Locks in the 87-file-drop correction: re-introducing a 2nd --path here would re-collide at the
|
||||
// coexistence commits and silently drop files again.
|
||||
assert.equal(
|
||||
targets['llm-security'].paths.length, 1,
|
||||
`llm-security must stay single-path (dual-path dropped 87 files), has ${targets['llm-security'].paths.length}`
|
||||
);
|
||||
});
|
||||
|
||||
test('every target repo_url is https:// (not ssh://) — F2 / #9740', () => {
|
||||
for (const k of keys) {
|
||||
assert.match(targets[k].repo_url, /^https:\/\/git\.fromaitochitta\.com\/open\//, `${k} repo_url not the expected HTTPS form`);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@ rm -f "$ARGS_FILE"
|
|||
|
||||
TAG="$(python3 -c "import json; print(json.load(open('$MAP'))['targets']['$KEY']['tag'])")"
|
||||
|
||||
# Re-assert the critical extension here too: the self-heal above runs preflight ONLY on a missing mirror,
|
||||
# so a present mirror + since-uninstalled git-filter-repo would otherwise die with a raw git error instead
|
||||
# of this actionable message (9e588ca — RUNBOOK lists it under Preconditions).
|
||||
git filter-repo --version >/dev/null 2>&1 \
|
||||
|| fail "git filter-repo not available — brew install git-filter-repo (see RUNBOOK Preconditions)"
|
||||
|
||||
# Fresh --no-local clone from the mirror, then the single composing filter-repo pass.
|
||||
rm -rf "$DEST"
|
||||
git clone --no-local --quiet "$MIRROR" "$DEST"
|
||||
|
|
|
|||
|
|
@ -78,32 +78,45 @@ validate_target() {
|
|||
fi
|
||||
fi
|
||||
|
||||
# --- SC2: run the plugin's declared runner in the clean room ---
|
||||
local test_cmd advisory
|
||||
# --- SC2: route to the target's dedicated gate if it declares one, else run the declared runner in the
|
||||
# clean room. config-audit's full `node --test 'tests/**/*.test.mjs'` FAILs at a fresh-clone path on
|
||||
# the 6 machine-locked v5.0.0 byte-stability tests — the exact blocker its Step-7 gate resolves — so
|
||||
# `--all` MUST delegate to that gate (mirroring 99-dryrun.sh), not run the raw test_cmd. ---
|
||||
local test_cmd advisory sc2_gate
|
||||
test_cmd="$(python3 -c "import json; print(json.load(open('$MAP'))['targets']['$key'].get('test_cmd',''))")"
|
||||
advisory="$(python3 -c "import json; print(json.load(open('$MAP'))['targets']['$key'].get('test_cmd_advisory',''))")"
|
||||
|
||||
# The generic structure validator (Step 7) is referenced by test-less plugins — vendor it into the clean room.
|
||||
case "$test_cmd" in
|
||||
*validate-plugin.generic.sh*)
|
||||
if [ -f "$GENERIC_VALIDATOR" ]; then cp "$GENERIC_VALIDATOR" "$cr/validate-plugin.generic.sh"; fi
|
||||
;;
|
||||
esac
|
||||
sc2_gate="$(python3 -c "import json; print(json.load(open('$MAP'))['targets']['$key'].get('sc2_gate',''))")"
|
||||
|
||||
local sc2_label="standalone-safe"
|
||||
local out status tests
|
||||
out="$(cd "$cr" && eval "$test_cmd" 2>&1)"; status=$?
|
||||
if [ $status -ne 0 ]; then
|
||||
problems="$problems; SC2 runner failed (exit $status)"
|
||||
|
||||
if [ -n "$sc2_gate" ]; then
|
||||
if WORK="$WORK" bash "$SCRIPT_DIR/$sc2_gate" >/dev/null 2>&1; then
|
||||
sc2_label="$sc2_gate, standalone-safe"
|
||||
else
|
||||
problems="$problems; SC2 gate failed ($sc2_gate)"
|
||||
fi
|
||||
else
|
||||
# The generic structure validator (Step 7) is referenced by test-less plugins — vendor it into the clean room.
|
||||
case "$test_cmd" in
|
||||
*validate-plugin.generic.sh*)
|
||||
if [ -f "$GENERIC_VALIDATOR" ]; then cp "$GENERIC_VALIDATOR" "$cr/validate-plugin.generic.sh"; fi
|
||||
;;
|
||||
esac
|
||||
|
||||
local out status tests
|
||||
out="$(cd "$cr" && eval "$test_cmd" 2>&1)"; status=$?
|
||||
if [ $status -ne 0 ]; then
|
||||
problems="$problems; SC2 runner failed (exit $status)"
|
||||
fi
|
||||
case "$test_cmd" in
|
||||
*node\ --test*)
|
||||
# Node 25's default reporter prints "ℹ tests N"; older/TAP prints "# tests N". Match either.
|
||||
tests="$(printf '%s\n' "$out" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)"
|
||||
[ -n "$tests" ] && sc2_label="$tests tests, standalone-safe"
|
||||
;;
|
||||
*validate-plugin*) sc2_label="structure, standalone-safe" ;;
|
||||
esac
|
||||
fi
|
||||
case "$test_cmd" in
|
||||
*node\ --test*)
|
||||
# Node 25's default reporter prints "ℹ tests N"; older/TAP prints "# tests N". Match either.
|
||||
tests="$(printf '%s\n' "$out" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)"
|
||||
[ -n "$tests" ] && sc2_label="$tests tests, standalone-safe"
|
||||
;;
|
||||
*validate-plugin*) sc2_label="structure, standalone-safe" ;;
|
||||
esac
|
||||
|
||||
if [ -n "$problems" ]; then
|
||||
echo "$key: FAIL${problems}"
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ mapget() { python3 -c "import json;print(json.load(open('$MAP'))['targets']['$1'
|
|||
mappaths() { python3 -c "import json;print(' '.join(json.load(open('$MAP'))['targets']['$1']['paths']))"; }
|
||||
|
||||
# Live monorepo git-tracked file count across a target's source paths (SC6 baseline).
|
||||
# Paths are asserted whitespace- and glob-free by 00-preflight.sh (aeb6292), so the word-split below is sound.
|
||||
live_files() {
|
||||
local key="$1" total=0 p n
|
||||
for p in $(mappaths "$key"); do
|
||||
|
|
@ -110,7 +111,11 @@ for key in $KEYS; do
|
|||
commits=0; tag="(none)"; ext_files=0; sc7="clean"; sc6="?"
|
||||
if [ -d "$dest/.git" ]; then
|
||||
commits="$(git -C "$dest" rev-list --count HEAD 2>/dev/null || echo 0)"
|
||||
tag="$(git -C "$dest" tag 2>/dev/null | head -1)"; tag="${tag:-(none)}"
|
||||
# F5 invariant: 10-extract.sh strips all carried-over tags and re-creates EXACTLY one. Assert that —
|
||||
# a partial tag-strip leaves multiple tags, and `head -1` would silently report the lexicographically
|
||||
# -first wrong one (e.g. v1.0.0 instead of v5.1.1) while the dry-run still passed (1708e90).
|
||||
tagn="$(git -C "$dest" tag 2>/dev/null | wc -l | tr -d '[:space:]')"
|
||||
if [ "$tagn" = "1" ]; then tag="$(git -C "$dest" tag 2>/dev/null)"; else tag="(tags=$tagn!)"; ok=0; fi
|
||||
ext_files="$(git -C "$dest" ls-files | wc -l | tr -d '[:space:]')"
|
||||
leak="$(git -C "$dest" ls-files | grep -E 'STATE\.md|\.local\.md$' \
|
||||
| grep -v 'templates/okr\.local\.md\.template' || true)"
|
||||
|
|
@ -119,14 +124,16 @@ for key in $KEYS; do
|
|||
ok=0
|
||||
fi
|
||||
|
||||
# SC6 content retention. Intentional blob-strip targets may legitimately shed >1MB blobs, so a drop
|
||||
# there is reported, not failed; for every other target a drop is a hard FAIL (the llm-security class).
|
||||
# SC6 content retention — delegated to sc6-check.sh (the DROP detector, unit-tested in sc-checks.test.mjs).
|
||||
# Only meaningful when the extract succeeded: a failed extract leaves ext_files=0, which must NOT be
|
||||
# mislabelled as a content DROP (8d649e9) — emit (extract failed) instead. An intentional >1MB blob-strip
|
||||
# target may legitimately shed blobs (reported, not failed); a shortfall elsewhere is a hard DROP (ok=0).
|
||||
lf="$(live_files "$key")"
|
||||
blob="$(mapget "$key" blob_strip)"
|
||||
if [ "$ext_files" -lt "$lf" ]; then
|
||||
if [ "$blob" = "True" ]; then sc6="${ext_files}/${lf} (blob-strip)"; else sc6="${ext_files}/${lf} DROP"; ok=0; fi
|
||||
if [ -d "$dest/.git" ]; then
|
||||
if sc6="$(bash "$SCRIPT_DIR/sc6-check.sh" "$ext_files" "$lf" "$blob")"; then :; else ok=0; fi
|
||||
else
|
||||
sc6="${ext_files}/${lf}"
|
||||
sc6="(extract failed)"
|
||||
fi
|
||||
|
||||
# --- SC2 (regression-relative) ---
|
||||
|
|
@ -140,15 +147,27 @@ for key in $KEYS; do
|
|||
else
|
||||
case "$test_cmd" in
|
||||
*node\ --test*)
|
||||
# Regression-relative: standalone failing set must be a subset of the in-repo failing set.
|
||||
sf="$(mktemp)"; bf="$(mktemp)"
|
||||
capture_fails "/tmp/claude-$key" "$test_cmd" > "$sf" 2>/dev/null
|
||||
capture_fails "$REPO_ROOT/plugins/$key" "$test_cmd" > "$bf" 2>/dev/null
|
||||
regr="$(comm -23 "$sf" "$bf")"
|
||||
pre="$(wc -l < "$bf" | tr -d '[:space:]')"
|
||||
rm -f "$sf" "$bf"
|
||||
if [ -z "$regr" ]; then sc2="PASS (${pre} pre-existing)"; else
|
||||
sc2="FAIL (regression: $(printf '%s' "$regr" | grep -c .))"; ok=0; fi
|
||||
# Regression-relative: the standalone failing set must be a SUBSET of the in-repo failing set.
|
||||
# Capture the standalone set from the dry-run's OWN prepped extract ($dest = $WORK/$key), NOT the
|
||||
# 40-validate side-effect clean room /tmp/claude-$key (4e494c8 — that implicit coupling masked a
|
||||
# real regression when the dir was absent). Guard mktemp: an empty capture is an ERROR, never a
|
||||
# false zero-regression PASS (4044c49). The subset decision is delegated to sc2-regression.sh.
|
||||
if sf="$(mktemp)" && bf="$(mktemp)"; then
|
||||
capture_fails "$dest" "$test_cmd" > "$sf" 2>/dev/null
|
||||
capture_fails "$REPO_ROOT/plugins/$key" "$test_cmd" > "$bf" 2>/dev/null
|
||||
pre="$(wc -l < "$bf" | tr -d '[:space:]')"
|
||||
if regr="$(bash "$SCRIPT_DIR/sc2-regression.sh" "$sf" "$bf")"; then
|
||||
sc2="PASS (${pre} pre-existing)"
|
||||
else
|
||||
rc=$?
|
||||
if [ "$rc" -ge 2 ]; then sc2="FAIL (sc2-regression error)"; else
|
||||
sc2="FAIL (regression: $(printf '%s' "$regr" | grep -c .))"; fi
|
||||
ok=0
|
||||
fi
|
||||
rm -f "$sf" "$bf"
|
||||
else
|
||||
sc2="FAIL (mktemp)"; ok=0; rm -f "$sf" "$bf"
|
||||
fi
|
||||
;;
|
||||
*) sc2="FAIL (structure)"; ok=0 ;; # deterministic structure check genuinely failed
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ test('llm-security retains all content (SC6 — dual-rename file-drop defect is
|
|||
assert.ok(/\bPASS\b/.test(line) && !/FAIL/.test(line), `llm-security must PASS standalone: ${line}`);
|
||||
});
|
||||
|
||||
// This asserts the HAPPY path (the live report shows no DROP / no regression). The NEGATIVE coverage —
|
||||
// proving the SC6 DROP and SC2 regression-FAIL detectors actually FIRE — lives in sc-checks.test.mjs,
|
||||
// which drives sc6-check.sh / sc2-regression.sh (the extracted detectors this dry-run calls) directly,
|
||||
// because force-fresh re-extraction would undo any file-drop planted into $WORK/<key> here (5d112cb).
|
||||
test('no SC6 file-drop and no SC2 regression on any target', { timeout: LONG }, () => {
|
||||
const r = report();
|
||||
assert.ok(!/\bDROP\b/.test(r), 'no target may drop content (SC6)');
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ plus the Step 11 dry-run) was authored and verified locally with **NULL push (D8
|
|||
|
||||
## Preconditions
|
||||
|
||||
- **`git-filter-repo`** (a non-default git extension) and **`python3 >= 3.6`** on `PATH` — the extraction
|
||||
(`10-extract.sh`) and every map-driven script depend on them. `00-preflight.sh` and `10-extract.sh`
|
||||
both assert them, but install first: `brew install git-filter-repo python3`.
|
||||
- All migration scripts committed locally; the Step 11 dry-run is green (it exercises every script below).
|
||||
- `$FORGEJO_TOKEN` is exported (macOS Keychain → `~/.zshenv`) with `write:org` + `repo` scope.
|
||||
- The `pre-polyrepo-archive` tag exists locally (D2) — the rollback anchor for the whole operation.
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
"repo_url": "https://git.fromaitochitta.com/open/ms-ai-architect.git",
|
||||
"has_vendor_ds": true,
|
||||
"blob_strip": true,
|
||||
"blob_strip_safe": true,
|
||||
"blob_strip_safe": null,
|
||||
"test_cmd": "bash tests/validate-plugin.sh",
|
||||
"standalone_caveat": "148 MB screenshot blob-bomb (F3): single filter-repo pass strips >1MB blobs when blob_strip_safe (all under playground/screenshots/), else surgical --path-glob; vendors the design-system"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
// Negative + positive coverage for the two highest-stakes dry-run detectors (5d112cb).
|
||||
//
|
||||
// 99-dryrun.sh force-fresh re-extracts all 11 targets every run (10-extract.sh wipes $dest first), so a
|
||||
// "plant a dropped file in $WORK/<key>" negative test against the full dry-run would be silently undone by
|
||||
// the re-extract. So the two load-bearing detectors are extracted into standalone, side-effect-free scripts
|
||||
// — sc6-check.sh (the SC6 DROP detector, the guard against the llm-security 87-file-drop class) and
|
||||
// sc2-regression.sh (the SC2 regression-FAIL detector) — which 99-dryrun.sh now calls. This suite drives
|
||||
// those exact scripts directly, proving each detector FIRES (DROP / regression → label + non-zero exit) and
|
||||
// does NOT false-positive (retained content / subset failures → exit 0). When the dry-run calls them and
|
||||
// they exit non-zero, it sets ok=0 → exit 1, so the dry-run's "report DROP/regression + exit non-zero"
|
||||
// behaviour follows by construction from this coverage.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const SC6 = path.join(here, 'sc6-check.sh');
|
||||
const SC2 = path.join(here, 'sc2-regression.sh');
|
||||
|
||||
function run(script, args) {
|
||||
return spawnSync('bash', [script, ...args], { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
// --- sc6-check.sh — the SC6 DROP detector ---------------------------------------------------------------
|
||||
|
||||
test('SC6 DROP fires: a real shortfall (llm-security 87-file class) → DROP label + non-zero exit', () => {
|
||||
const r = run(SC6, ['334', '421', 'false']); // the actual dual-rename defect: 334 of 421 files retained
|
||||
assert.notEqual(r.status, 0, 'a non-blob-strip shortfall must exit non-zero (the dry-run sets ok=0)');
|
||||
assert.match(r.stdout, /334\/421 DROP/, `DROP label expected: ${r.stdout}`);
|
||||
});
|
||||
|
||||
test('SC6 blob-strip shortfall is allowed: intentional >1MB strip → (blob-strip) label + exit 0', () => {
|
||||
const r = run(SC6, ['100', '200', 'True']); // ms-ai-architect screenshot blob-bomb: legitimate shortfall
|
||||
assert.equal(r.status, 0, `blob-strip shortfall must exit 0: ${r.stderr}`);
|
||||
assert.match(r.stdout, /100\/200 \(blob-strip\)/, `blob-strip label expected: ${r.stdout}`);
|
||||
assert.ok(!/DROP/.test(r.stdout), 'a blob-strip shortfall must NOT be labelled DROP');
|
||||
});
|
||||
|
||||
test('SC6 retained content: ext == live → no DROP, exit 0', () => {
|
||||
const r = run(SC6, ['421', '421', 'false']);
|
||||
assert.equal(r.status, 0, `full retention must exit 0: ${r.stderr}`);
|
||||
assert.equal(r.stdout.trim(), '421/421');
|
||||
});
|
||||
|
||||
test('SC6 more files than live (e.g. path-rename overlap) → no DROP, exit 0', () => {
|
||||
const r = run(SC6, ['500', '421', 'false']);
|
||||
assert.equal(r.status, 0);
|
||||
assert.ok(!/DROP/.test(r.stdout), `${r.stdout}`);
|
||||
});
|
||||
|
||||
// --- sc2-regression.sh — the SC2 regression-FAIL detector -----------------------------------------------
|
||||
|
||||
function fixtureFiles(standalone, baseline) {
|
||||
const dir = mkdtempSync(path.join(os.tmpdir(), 'sc2-'));
|
||||
const sf = path.join(dir, 'sf');
|
||||
const bf = path.join(dir, 'bf');
|
||||
writeFileSync(sf, standalone.length ? standalone.join('\n') + '\n' : '');
|
||||
writeFileSync(bf, baseline.length ? baseline.join('\n') + '\n' : '');
|
||||
return { dir, sf, bf };
|
||||
}
|
||||
|
||||
test('SC2 regression fires: a standalone-only failure (not in baseline) → printed + non-zero exit', () => {
|
||||
// The extraction introduced a NEW failure absent from the in-repo baseline — a real regression.
|
||||
const { dir, sf, bf } = fixtureFiles(['extract regressed test', 'shared pre-existing fail'], ['shared pre-existing fail']);
|
||||
try {
|
||||
const r = run(SC2, [sf, bf]);
|
||||
assert.equal(r.status, 1, `a regression must exit 1 (the dry-run sets ok=0): ${r.stdout}${r.stderr}`);
|
||||
assert.match(r.stdout, /extract regressed test/, `the regressing test name must be reported: ${r.stdout}`);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('SC2 subset PASS: standalone failures ⊆ baseline (pre-existing red) → exit 0, nothing printed', () => {
|
||||
const { dir, sf, bf } = fixtureFiles(['shared pre-existing fail'], ['shared pre-existing fail', 'other baseline-only fail']);
|
||||
try {
|
||||
const r = run(SC2, [sf, bf]);
|
||||
assert.equal(r.status, 0, `a subset must exit 0 (no migration regression): ${r.stdout}${r.stderr}`);
|
||||
assert.equal(r.stdout.trim(), '', 'no regression should be printed for a subset');
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('SC2 missing capture file is an ERROR (exit 2), never a silent zero-regression PASS (4044c49)', () => {
|
||||
const r = run(SC2, ['/nonexistent/sf', '/nonexistent/bf']);
|
||||
assert.equal(r.status, 2, `a missing capture file must be a hard error, not a false PASS: ${r.stdout}`);
|
||||
});
|
||||
20
docs/marketplace-polyrepo-migration/migration/sc2-regression.sh
Executable file
20
docs/marketplace-polyrepo-migration/migration/sc2-regression.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
# SC2 regression decision — extracted from 99-dryrun.sh so the regression-FAIL detector is independently
|
||||
# unit-testable (5d112cb). Given two SORTED failing-test-NAME files — the standalone extract's set and the
|
||||
# in-repo baseline set — it prints the regressions (standalone failures absent from the baseline) and EXITS
|
||||
# NON-ZERO (1) if any exist. The contract: "standalone failing set ⊆ in-repo failing set" (the extraction
|
||||
# introduced NO new failure; pre-existing in-repo red is the plugin's own concern, not a migration regr).
|
||||
# A missing capture file is a hard ERROR (exit 2), never a silent zero-regression PASS (guards 4044c49).
|
||||
#
|
||||
# Usage: sc2-regression.sh <standalone_fails_file> <baseline_fails_file>
|
||||
set -u
|
||||
sf="${1:?standalone fails file}"
|
||||
bf="${2:?baseline fails file}"
|
||||
[ -f "$sf" ] && [ -f "$bf" ] || { echo "SC2-REGRESSION ERROR: missing capture file ($sf / $bf)" >&2; exit 2; }
|
||||
|
||||
regr="$(comm -23 "$sf" "$bf")"
|
||||
if [ -n "$regr" ]; then
|
||||
printf '%s\n' "$regr"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
23
docs/marketplace-polyrepo-migration/migration/sc6-check.sh
Executable file
23
docs/marketplace-polyrepo-migration/migration/sc6-check.sh
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
# SC6 content-retention decision — extracted from 99-dryrun.sh so the DROP detector (the deterministic
|
||||
# guard against the llm-security 87-file-drop class, commit 836b8e9) is independently unit-testable
|
||||
# (5d112cb). Given the extract's git-tracked file count, the live monorepo's count for the same paths, and
|
||||
# whether an intentional >1MB blob-strip applies to this target, it prints the SC6 report-cell label and
|
||||
# EXITS NON-ZERO on a real DROP — a shortfall NOT explained by a blob-strip. A blob-strip target may
|
||||
# legitimately shed >1MB blobs, so a shortfall there is reported (exit 0), never failed.
|
||||
#
|
||||
# Usage: sc6-check.sh <ext_files> <live_files> <blob_strip>
|
||||
# blob_strip: True/true → intentional >1MB strip (shortfall allowed); anything else → shortfall = DROP.
|
||||
set -u
|
||||
ext="${1:?ext_files}"
|
||||
live="${2:?live_files}"
|
||||
blob="${3:-false}"
|
||||
|
||||
if [ "$ext" -lt "$live" ]; then
|
||||
case "$blob" in
|
||||
True|true) echo "${ext}/${live} (blob-strip)"; exit 0 ;;
|
||||
*) echo "${ext}/${live} DROP"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
echo "${ext}/${live}"
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue