From b444ce0fe6fb9424942858f0da81e835ae04c8dc Mon Sep 17 00:00:00 2001 From: "mj.lee" Date: Mon, 9 Nov 2026 11:45:42 +0900 Subject: [PATCH 1/2] test(gate): reject silent reproducibility comparison errors --- omp-port/test-gate-mutations.sh | 45 +++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/omp-port/test-gate-mutations.sh b/omp-port/test-gate-mutations.sh index fd926f2..d1e5e2a 100755 --- a/omp-port/test-gate-mutations.sh +++ b/omp-port/test-gate-mutations.sh @@ -21,14 +21,15 @@ ncase=0 # harness and the worktree audit. A contracts run reads the case's copy of the tree; a full run # reads the real tree, because the counts and the docs it checks sit outside the plugin root. run_gate() { - local mode=$1 dir=$2 only="" root="" patches="$PORT_DIR/patches" + local mode=$1 dir=$2 only="" root="" patches="$PORT_DIR/patches" path="$PATH" shift 2 [ "$mode" = contracts ] && only=1 root="$dir/plugin" [ -d "$dir/patches" ] && patches="$dir/patches" + [ ! -d "$dir/bin" ] || path="$dir/bin:$path" CHECK_PORT_ROOT="$root" RULES="$dir/port/rules.sed" STALE_EXEMPT="$dir/port/stale-exempt.tsv" \ SHADOW_EXEMPT="$dir/port/shadow-exempt.tsv" SWALLOW="$dir/port/swallow.tsv" OWNED="$dir/port/owned.txt" \ EXCLUDED="$dir/port/excluded.txt" \ - PATCH_DIR="$patches" CHECK_PORT_CONTRACTS_ONLY="$only" CHECK_PORT_NESTED=1 \ + PATH="$path" PATCH_DIR="$patches" CHECK_PORT_CONTRACTS_ONLY="$only" CHECK_PORT_NESTED=1 \ bash "$PORT_DIR/check-port.sh" "$@" } @@ -241,6 +242,40 @@ mutate_owned_drift() { append "$PORT_FIXTURE/owned.txt" "skills/poteto-mode"$'\t'"$(git -C "$CANON" rev-parse "$OLD_TARGET:pstack/skills/poteto-mode")" } +mutate_diff_error() { + local bin="$PORT_FIXTURE/../bin" + mkdir -p "$bin" + printf '#!/usr/bin/env bash\nreal_diff=%q\nerror=%q\n' "$(command -v diff)" "${1:-}" >"$bin/diff" + cat >>"$bin/diff" <<'EOF' +if [ "$1" = -rq ] && [ "$3" = skills ]; then + [ -z "$error" ] || printf '%s\n' "$error" >&2 + exit 2 +fi +"$real_diff" "$@" +status=$? +if [ "$1" = -rq ] && [ "$3" = agents ]; then + printf '%s\n' "$status" >"$(dirname "$0")/agents-status" +fi +exit "$status" +EOF + chmod +x "$bin/diff" +} + +mutate_diff_error_stderr() { + mutate_diff_error 'injected comparison I/O error' +} + +mutate_reproducibility_mismatch() { + mkdir -p "$PATCH_FIXTURE" + cp "$PORT_DIR"/patches/*.patch "$PATCH_FIXTURE/" + cat >"$PATCH_FIXTURE/zzzz-reproducibility-mismatch.patch" <<'EOF' +--- /dev/null ++++ b/agents/reproducibility-mismatch.txt +@@ -0,0 +1 @@ ++This file is not in the checked-in tree. +EOF +} + # A real patch applied a second time. Its hunks are already in, so plain patch asks on the terminal # whether to reverse them and waits, and --batch answers yes and reverts the first copy. mutate_patch_fails() { @@ -305,6 +340,9 @@ if ensure_canon "$PIN" "$OLD_TARGET" >/dev/null 2>&1; then start_case 'swallowed text changed' 'swallow ledger' 'now: **Match the model to the job.**' full mutate_swallow_hash start_case 'wildcard rule with no row' 'swallow ledger' 'has no swallow.tsv row' full mutate_swallow_row_missing start_case 'owned path moved upstream' 'owned drift' 'skills/poteto-mode: upstream moved' full mutate_owned_drift + start_case 'silent comparison error before a successful scope' 'reproducible' 'skills: diff exited with status 2' full mutate_diff_error + start_case 'comparison error on stderr' 'reproducible' 'injected comparison I/O error' full mutate_diff_error_stderr + start_case 'generated tree differs from the build' 'reproducible' 'reproducibility-mismatch.txt' full mutate_reproducibility_mismatch start_case 'patch that no longer applies' 'reproducible' 'failed, rules or a patch' full mutate_patch_fails if command -v script >/dev/null; then start_case 'patch prompt on a terminal' '' 'hunk FAILED' terminal mutate_patch_fails @@ -338,6 +376,9 @@ while IFS=$'\037' read -r name mode label needle dir; do printf 'mutation tests: %s failed for the wrong reason\n' "$name" tr -d '\r' <"$dir/out" fail=1 + elif [ -x "$dir/bin/diff" ] && [ "$(cat "$dir/bin/agents-status" 2>/dev/null)" != 0 ]; then + printf 'mutation tests: %s did not complete the later agents comparison successfully\n' "$name" + fail=1 else printf 'mutation tests: %s rejected\n' "$name" fi From 809caf5bd2d14ca5687394abbf9b48ca44660da6 Mon Sep 17 00:00:00 2001 From: "mj.lee" Date: Mon, 9 Nov 2026 11:45:42 +0900 Subject: [PATCH 2/2] fix(gate): fail reproducibility on diff errors --- PORTING.md | 3 +++ omp-port/check-port.sh | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/PORTING.md b/PORTING.md index 74ef846..74099a6 100644 --- a/PORTING.md +++ b/PORTING.md @@ -40,6 +40,9 @@ upstream text at the pinned sha minus the paths in `omp-port/excluded.txt`, with check, which rebuilds the pin and diffs it against the tree, and the next build deletes it. Put the change in `rules.sed`, in a patch, or in an owned path instead. +The `reproducible` check passes only when every tree comparison exits successfully. +A content difference or a comparison error fails the gate and reports the affected scope. + The `upstream-sync` workflow does this daily. It runs `bash omp-port/check-port.sh` on `main` first, so a red trunk stops the run rather than being merged over, then builds at the newest upstream commit that touched `pstack/`, opens a PR carrying the sync and gate output in its body, and squash diff --git a/omp-port/check-port.sh b/omp-port/check-port.sh index fb50783..767257d 100755 --- a/omp-port/check-port.sh +++ b/omp-port/check-port.sh @@ -669,7 +669,15 @@ else mkdir -p "$(dirname "$scratch/$p")" cp -a "$p" "$scratch/$p" done < <(owned_paths) - delta=$(for d in "${SCOPE[@]}"; do diff -rq "$scratch/$d" "$d" -x node_modules; done) + delta="" + for d in "${SCOPE[@]}"; do + if comparison=$(diff -rq "$scratch/$d" "$d" -x node_modules 2>&1); then + continue + else + status=$? + fi + delta="${delta}${d}: diff exited with status $status"$'\n'"$comparison"$'\n' + done if [ -n "$delta" ]; then report "reproducible" "FAIL" while read -r l; do [ -n "$l" ] && violate "$l"; done <<<"$delta"