From fba5eff3fff4f2569b064b6222eb78db1024f1c3 Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Tue, 25 Aug 2026 11:00:12 +0900 Subject: [PATCH 1/7] fix(bin): resolve the Python interpreter by running one, not by looking up a name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows ships a Microsoft Store stub that answers to `python3` and exits 49 without running anything. `command -v python3` is satisfied by it, so `bin/arc-open` decided an interpreter was present and then handed every python-backed gate a 49. Measured on two Windows machines (2026-08-25, [거울/트리아지]): the gate suite scored **34/48 on both**, and on the second machine a working `python` 3.11.15 was on PATH the entire time — the only blocking name was `python3`. The failures printed as `✗ expected 0, got 49`, which reads like a verdict and is the absence of a measurement: on Windows these gates were not failing, they were not running. Reproduced off Windows by putting a stub named `python3` first on PATH — the suite scores the same 34/48, so this is now testable on any platform rather than on a claim about someone else's laptop. bin/_pybin.sh resolves an interpreter by executing each candidate (`-c 'import sys'`) in order: $YEOUL_PYTHON, python3, python, py -3. The nine scripts that shell out to Python source it and use $PY. Both `command -v python3` name-checks are gone; there is no longer any place where existence stands in for execution. When no candidate runs, the run stops with exit 127 and a message naming what was tried and why a name-check would have passed. It does not skip the step — a skipped check that reads as a pass is how this stayed invisible. tests/test_gates.sh 52/52, including four checks that plant a stub: the stub is rejected, a working interpreter under another name is found (the second machine's configuration), resolution fails when every candidate is stubbed, and a script then stops with a named cause instead of continuing. Co-Authored-By: Claude Opus 5 (1M context) --- bin/_pybin.sh | 39 +++++++++++++++++++++++++++++++ bin/arc-close | 10 +++++--- bin/arc-open | 10 ++++++-- bin/arc-prereg | 6 ++++- bin/index-append | 10 +++++--- bin/ralph | 8 +++++-- bin/status | 8 +++++-- bin/verify-gate | 6 ++++- setup/pre-publish-check.sh | 8 +++++-- tests/test_gates.sh | 48 +++++++++++++++++++++++++++++++++----- 10 files changed, 131 insertions(+), 22 deletions(-) create mode 100755 bin/_pybin.sh diff --git a/bin/_pybin.sh b/bin/_pybin.sh new file mode 100755 index 0000000..1ffba32 --- /dev/null +++ b/bin/_pybin.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Resolve a Python interpreter by RUNNING one — not by finding a name. +# +# 🔴 `command -v python3` is satisfied by the Microsoft Store stub that ships on Windows: the name +# resolves, and the "interpreter" then exits 49 without running anything. Existence is not +# execution. Measured on two Windows machines (2026-08-25): the gate suite scored 34/48 on both, +# and on the second one a perfectly good `python` 3.11.15 was on PATH the whole time — the only +# blocking name was `python3`. The failures printed as `✗ expected 0, got 49`, which reads like a +# verdict but is the absence of a measurement. +# +# Usage — source it, then use $PY unquoted (the value may carry an argument, e.g. `py -3`): +# . "$(dirname "${BASH_SOURCE[0]}")/_pybin.sh" +# PY="$(yeoul_pybin)" || yeoul_pybin_die +# $PY script.py +# +# Override with YEOUL_PYTHON if you need a specific interpreter. + +yeoul_pybin() { + local c + for c in ${YEOUL_PYTHON:+"$YEOUL_PYTHON"} python3 python "py -3"; do + # word-splitting on $c is intentional: "py -3" is a command plus an argument. + # shellcheck disable=SC2086 + if $c -c 'import sys; raise SystemExit(0)' >/dev/null 2>&1; then + printf '%s' "$c" + return 0 + fi + done + return 1 +} + +# Fail loudly. A missing interpreter must stop the run, not quietly skip the step it powers — +# a skipped check that reads as a pass is how this stayed invisible on Windows. +yeoul_pybin_die() { + echo "no working Python interpreter found." >&2 + echo " tried: ${YEOUL_PYTHON:+$YEOUL_PYTHON, }python3, python, py -3 — each by running \`-c 'import sys'\`," >&2 + echo " not by looking it up. On Windows the Microsoft Store stub answers to \`python3\` and exits 49." >&2 + echo " Install Python, or point YEOUL_PYTHON at a real interpreter." >&2 + exit 127 +} diff --git a/bin/arc-close b/bin/arc-close index 2991aea..88a5943 100755 --- a/bin/arc-close +++ b/bin/arc-close @@ -10,6 +10,10 @@ set -euo pipefail # ★Optional: seal the close into an append-only ledger (mirror-stack `am`, content-hash = _SUMMARY). Best-effort. # Negatives / retractions are recorded indelibly (no silent edits). +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_pybin.sh" +PY="$(yeoul_pybin)" || yeoul_pybin_die + ARC_DIR="${1:-}"; VERDICT="${2:-}"; STOP="converged" for arg in "${@:3}"; do case "$arg" in --stop=*) STOP="${arg#--stop=}" ;; esac @@ -48,7 +52,7 @@ case "$VERDICT" in *KILL*|*kill*) IS_KILL="yes" ;; esac # NOTE: this fixes the *condition* by reference; whether the result triggers it remains a judgment. extract_kill() { # extract_kill → verbatim kill_condition (whitespace-collapsed), else empty [ -f "$1" ] || return 0 - python3 - "$1" "$2" 2>/dev/null <<'PY' + $PY - "$1" "$2" 2>/dev/null <<'PY' import json, sys led, cid = sys.argv[1], sys.argv[2] for line in open(led, encoding='utf-8', errors='ignore'): @@ -127,7 +131,7 @@ check_answers() { # check_answers
# If the checker cannot separate planted violations from planted genuine answers, we do NOT # interpret the real answers — we die. An instrument is not trusted because it is green; # it is trusted because it just proved it can still say no. - if ! st="$(python3 "$SUBSTANCE" --selftest 2>&1)"; then + if ! st="$($PY "$SUBSTANCE" --selftest 2>&1)"; then echo "⛔ seal refused: the substance checker failed its own positive control — the instrument is broken, so the real verdict is not interpreted." printf '%s\n' "$st" | sed 's/^/ /' exit 6 @@ -139,7 +143,7 @@ check_answers() { # check_answers
# 🔴 Read the verdict from the emitted CODE, not the exit status. If the checker cannot run at # all the output is empty — which is not OK — so this **fails closed**. A false default would # turn "never measured" into "measured and fine". - vout="$(printf '%s' "$vline" | python3 "$SUBSTANCE" --label "$vlabel" 2>/dev/null || true)" + vout="$(printf '%s' "$vline" | $PY "$SUBSTANCE" --label "$vlabel" 2>/dev/null || true)" vcode="${vout%%"$TAB"*}"; vans="${vout#*"$TAB"}" [ "$vcode" = "OK" ] && continue case "$vcode" in diff --git a/bin/arc-open b/bin/arc-open index cd60e77..dc7ca85 100755 --- a/bin/arc-open +++ b/bin/arc-open @@ -5,6 +5,10 @@ set -euo pipefail # ARC/.md (deliberation thread) + tickets// + STATE.md + ROSTER.md + JOIN_PROMPTS.md + 0001_spec.md # Generic file-based deliberation engine. Runtime-agnostic. Callers pass --arcs-dir to place the arc. +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_pybin.sh" +PY="$(yeoul_pybin)" || yeoul_pybin_die + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SLUG="${1:-}"; TOPIC=""; ROLES="analysis impl repro"; BACKEND="both"; ARCS_DIR=""; RELAY="orchestrator" @@ -27,8 +31,10 @@ case "$BACKEND" in a|b|both) ;; *) echo "backend must be a|b|both"; exit 1 ;; es # ── Gate-1: prior-art / closed-question check (optional; warn-only, non-blocking; skipped if no registry) ── REGISTRY="${YEOUL_CLOSED_REGISTRY:-$SCRIPT_DIR/../registry/closed_questions.jsonl}" -if command -v python3 >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/closed_check.py" ] && [ -f "$REGISTRY" ]; then - G1WARN="$(python3 "$SCRIPT_DIR/closed_check.py" "$TOPIC" "$REGISTRY" 2>/dev/null || true)" +# no interpreter name-check here: $PY was resolved by running one, and a run with no working +# interpreter has already stopped at yeoul_pybin_die. +if [ -f "$SCRIPT_DIR/closed_check.py" ] && [ -f "$REGISTRY" ]; then + G1WARN="$($PY "$SCRIPT_DIR/closed_check.py" "$TOPIC" "$REGISTRY" 2>/dev/null || true)" if [ -n "$G1WARN" ]; then echo "── ⚠️ Gate-1 closed-question match ──" echo "$G1WARN" diff --git a/bin/arc-prereg b/bin/arc-prereg index 49348c0..4c4769f 100755 --- a/bin/arc-prereg +++ b/bin/arc-prereg @@ -8,6 +8,10 @@ set -euo pipefail # # Typical flow: seal the kill-condition with mirror-stack (mm_preregister) → arc-prereg . +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_pybin.sh" +PY="$(yeoul_pybin)" || yeoul_pybin_die + ARC_DIR="${1:-}"; CLAIM="${2:-}"; LEDGER="${3:-${YEOUL_LEDGER:-}}" if [ -z "$ARC_DIR" ] || [ -z "$CLAIM" ]; then echo "usage: arc-prereg [ledger] (ledger defaults to \$YEOUL_LEDGER)"; exit 1 @@ -16,7 +20,7 @@ fi [ -n "$LEDGER" ] || { echo "no ledger given (pass one or set \$YEOUL_LEDGER)"; exit 1; } [ -f "$LEDGER" ] || { echo "ledger not found: $LEDGER"; exit 1; } -KILL="$(python3 - "$LEDGER" "$CLAIM" 2>/dev/null <<'PY' +KILL="$($PY - "$LEDGER" "$CLAIM" 2>/dev/null <<'PY' import json, sys led, cid = sys.argv[1], sys.argv[2] for line in open(led, encoding='utf-8', errors='ignore'): diff --git a/bin/index-append b/bin/index-append index 3b89a2f..6f5d1e1 100755 --- a/bin/index-append +++ b/bin/index-append @@ -6,6 +6,10 @@ set -uo pipefail # (never rots; not a new store). Index path = $YEOUL_INDEX, default /KNOWLEDGE_INDEX.md. # Best-effort: a failure here never affects the close. +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_pybin.sh" +PY="$(yeoul_pybin)" || yeoul_pybin_die + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ARC_DIR="${1:-}"; [ -n "$ARC_DIR" ] && [ -d "$ARC_DIR" ] || exit 0 ARC="$(basename "$ARC_DIR")" @@ -14,7 +18,7 @@ INDEX="${YEOUL_INDEX:-$SCRIPT_DIR/../KNOWLEDGE_INDEX.md}" CLAIM="-"; [ -f "$ARC_DIR/.prereg" ] && CLAIM="$(sed -n '1p' "$ARC_DIR/.prereg")" -read -r DATE STOP < <(python3 - "$SUM" <<'PY' +read -r DATE STOP < <($PY - "$SUM" <<'PY' import re,sys t=open(sys.argv[1],encoding='utf-8',errors='ignore').read() def g(p,d='-'): @@ -22,7 +26,7 @@ def g(p,d='-'): print(g(r'\*\*Closed\*\*:\s*([0-9-]+)'), g(r'\*\*stop_reason\*\*:\s*(\S+)')) PY ) -VERDICT="$(python3 - "$SUM" <<'PY' +VERDICT="$($PY - "$SUM" <<'PY' import re,sys t=open(sys.argv[1],encoding='utf-8',errors='ignore').read() m=re.search(r'\*\*Verdict\*\*:\s*(.+)',t); print((m.group(1).strip() if m else '-')[:160]) @@ -31,7 +35,7 @@ PY # The old regex assumed the first item was a `- ` bullet, so a numbered list (`1. …`) matched # nothing → '-'. The close still succeeded and the log still printed "appended" — the failure # was invisible. Take the section's first substantive line and strip only the list marker. -CLOSED="$(python3 - "$SUM" <<'PY' +CLOSED="$($PY - "$SUM" <<'PY' import re,sys t=open(sys.argv[1],encoding='utf-8',errors='ignore').read() m=re.search(r'##\s*What was closed[^\n]*\n(.*?)(?=\n##\s|\Z)', t, re.S) diff --git a/bin/ralph b/bin/ralph index 4140807..d1373b7 100755 --- a/bin/ralph +++ b/bin/ralph @@ -11,6 +11,10 @@ set -euo pipefail # ⚠️ Loop-forbidden (belongs to the human/session): measurement runs, sealing, PASS/KILL judgment. # Items without a `verify:` command are refused (exit 3). +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_pybin.sh" +PY="$(yeoul_pybin)" || yeoul_pybin_die + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECTS_DIR="${YEOUL_PROJECTS:-./projects}" @@ -76,8 +80,8 @@ $(cat "$TODO") "$SCRIPT_DIR/verify-gate" "$TODO" --revert --require-verify || echo " ⚠ harness reverted a checked item (verify failed on re-run, or its verify clause was missing)" TOK=0 - if command -v python3 >/dev/null 2>&1; then - TOK=$(python3 -c " + if [ -n "$PY" ]; then + TOK=$($PY -c " import json,sys try: d=json.load(open('$OUT')); u=d.get('usage',{}) diff --git a/bin/status b/bin/status index 15ad63c..f1e686d 100755 --- a/bin/status +++ b/bin/status @@ -4,6 +4,10 @@ set -euo pipefail # One line per active project (path without _archive): name · latest arc verdict · dev TODO progress. # Projects root ./projects (override YEOUL_PROJECTS). +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_pybin.sh" +PY="$(yeoul_pybin)" || yeoul_pybin_die + PROJECTS_DIR="${YEOUL_PROJECTS:-./projects}" MD=0 for a in "$@"; do case "$a" in --md) MD=1 ;; esac; done @@ -11,9 +15,9 @@ for a in "$@"; do case "$a" in --md) MD=1 ;; esac; done [ -d "$PROJECTS_DIR" ] || { echo "no projects dir: $PROJECTS_DIR"; exit 0; } # truncate to 100 chars on a codepoint boundary (never splits a multibyte char). -# python3 is already a project dependency; if absent, degrade to no truncation (never mojibake). +# $PY is already a project dependency; if absent, degrade to no truncation (never mojibake). _trunc100() { - python3 -c 'import sys; s=sys.stdin.read().strip(); print(s[:100]+("…" if len(s)>100 else ""))' 2>/dev/null || cat + $PY -c 'import sys; s=sys.stdin.read().strip(); print(s[:100]+("…" if len(s)>100 else ""))' 2>/dev/null || cat } verdict_of() { local proj="$1" diff --git a/bin/verify-gate b/bin/verify-gate index fe8c776..8572ce2 100755 --- a/bin/verify-gate +++ b/bin/verify-gate @@ -13,6 +13,10 @@ set -uo pipefail # has already established that every item in a loop-driven TODO carries a verify command. # Default stays off so a mixed TODO (loop items + manual items) can still be scanned standalone. +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_pybin.sh" +PY="$(yeoul_pybin)" || yeoul_pybin_die + TODO="${1:-}"; REVERT=0; REQUIRE=0 for a in "${@:2}"; do case "$a" in --revert) REVERT=1 ;; @@ -37,7 +41,7 @@ while IFS= read -r line || [ -n "$line" ]; do elif printf '%s' "$line" | grep -qE '^- \[x\].*verify:[[:space:]]*`[^`]+`'; then # extract the FIRST verify block via python (sed's greedy `.*verify:` grabbed the LAST block, # letting an appended decoy `verify: `true`` override a real failing command). - cmd="$(printf '%s' "$line" | python3 -c 'import sys,re; m=re.search(r"verify:\s*`([^`]+)`", sys.stdin.read()); sys.stdout.write(m.group(1) if m else "")')" + cmd="$(printf '%s' "$line" | $PY -c 'import sys,re; m=re.search(r"verify:\s*`([^`]+)`", sys.stdin.read()); sys.stdout.write(m.group(1) if m else "")')" if ! bash -c "$cmd" >/dev/null 2>&1; then FAIL=1 short="$(printf '%s' "$line" | sed -E 's/^(- \[x\][^:]{0,50}).*/\1/')" diff --git a/setup/pre-publish-check.sh b/setup/pre-publish-check.sh index 7b2af1f..70ec92e 100755 --- a/setup/pre-publish-check.sh +++ b/setup/pre-publish-check.sh @@ -6,6 +6,10 @@ set -uo pipefail # 3) empty-scaffolding guard (a runnable worked example must exist) # Exit 0 = clean · non-zero = issues found (do not publish yet). +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")/../bin" && pwd)"/_pybin.sh +PY="$(yeoul_pybin)" || yeoul_pybin_die + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO="$(cd "$SCRIPT_DIR/.." && pwd)" FAIL=0 @@ -29,7 +33,7 @@ echo "── 1) personalization leak scan ──" # (b) private absolute paths (/home/... or /data/...) that must not ship. # Intentional localizations are allowed (README_KO.md, *.ko.md, docs/ko/); Hangul anywhere else is a leak. # Hangul detection via python (portable — GNU grep's -P is unavailable on macOS/BSD). -# NB: the file list goes through a temp file, not a pipe. `python3 - < "$FILELIST" @@ -41,7 +45,7 @@ if [ "${SCANNED:-0}" -eq 0 ]; then echo " ✗ scanned 0 files — an empty scan is a failure, not a pass" FAIL=1 fi -HANGUL="$(python3 - "$REPO" "$FILELIST" <<'PY' +HANGUL="$($PY - "$REPO" "$FILELIST" <<'PY' import os, re, sys root = sys.argv[1]; h = re.compile('[\uac00-\ud7a3]') # Hangul syllables (escaped → this file stays Hangul-free) rels = [x.rstrip('\n') for x in open(sys.argv[2], encoding='utf-8') if x.strip()] diff --git a/tests/test_gates.sh b/tests/test_gates.sh index 76da5f3..0ba1c4c 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -6,6 +6,10 @@ set -uo pipefail # 2. ralph verify-gate: unchecked item without `verify:` is refused (3) # Exit 0 = all gates behaved as specified. +# Resolve the interpreter by running one — `command -v python3` also finds the Windows Store stub. +. "$(cd "$(dirname "${BASH_SOURCE[0]}")/../bin" && pwd)"/_pybin.sh +PY="$(yeoul_pybin)" || yeoul_pybin_die + BIN="$(cd "$(dirname "${BASH_SOURCE[0]}")/../bin" && pwd)" WS="$(mktemp -d)"; trap 'rm -rf "$WS"' EXIT cd "$WS"; export YEOUL_PROJECTS="$WS/projects" @@ -189,7 +193,7 @@ EARC="$(ls -d "$WS"/arcs/*_enc)" ESUM="$(ls "$EARC"/_SUMMARY_*.md)" sedi 's/- (fill in)/- concrete conclusion here/' "$ESUM" sedi 's/(unfilled)/yes/g' "$ESUM" -python3 - "$ESUM" <<'PYDAMAGE' +$PY - "$ESUM" <<'PYDAMAGE' import sys p = sys.argv[1] out = [] @@ -222,8 +226,8 @@ esac # 28 of 28 evasive answers sealed. These assertions pin the class in BOTH directions — a # repair that only tightened would pass the top half and quietly reject real answers. SUBSTANCE="$BIN/substance_check.py" -python3 "$SUBSTANCE" --selftest >/dev/null 2>&1; assert "substance checker passes its own positive control" 0 $? -echo " $(python3 "$SUBSTANCE" --selftest 2>&1 | tail -1)" # 🔴 print the denominator, not just green +$PY "$SUBSTANCE" --selftest >/dev/null 2>&1; assert "substance checker passes its own positive control" 0 $? +echo " $($PY "$SUBSTANCE" --selftest 2>&1 | tail -1)" # 🔴 print the denominator, not just green subst_case() { # subst_case "$BIN/arc-open" sc --topic="substance" --arcs-dir="$WS/arcs" >/dev/null 2>&1 @@ -232,7 +236,7 @@ subst_case() { # subst_case local S; S="$(ls "$A"/_SUMMARY_*.md)" sedi 's/- (fill in)/- concrete conclusion here/' "$S" # anchor/catalog have their own branches; drive the general branch with the candidate - python3 - "$S" "$2" <<'PYFILL' + $PY - "$S" "$2" <<'PYFILL' import sys, re p, ans = sys.argv[1], sys.argv[2] out = [] @@ -267,7 +271,7 @@ subst_case "genuine answer carrying a deferral CLAUSE still seals" \ # A documented fallback is untested code. Sabotage the checker so it can never say no, then # assert the gate REFUSES TO INTERPRET (exit 6) instead of sealing an evasive answer. SBIN="$(mktemp -d)"; cp "$BIN"/* "$SBIN/" 2>/dev/null -python3 - "$SBIN/substance_check.py" <<'PYSAB' +$PY - "$SBIN/substance_check.py" <<'PYSAB' import sys p = sys.argv[1]; s = open(p, encoding="utf-8").read() i = s.index("def judge(label, ans):") @@ -275,7 +279,7 @@ s = s[:i] + 'def judge(label, ans):\n return "OK", ans\n\ndef _judge_disabled open(p, "w", encoding="utf-8").write(s) PYSAB # the sabotage must actually have landed, or every assertion below passes vacuously -python3 "$SBIN/substance_check.py" --selftest >/dev/null 2>&1 \ +$PY "$SBIN/substance_check.py" --selftest >/dev/null 2>&1 \ && { echo " ✗ sabotage did NOT land — the checks below would pass for the wrong reason"; FAIL=1; } \ || echo " ✓ sabotage landed (checker can no longer fail a planted violation)" "$SBIN/arc-open" sb --topic="sabotage" --arcs-dir="$WS/arcs" >/dev/null 2>&1 @@ -374,6 +378,38 @@ if grep -rq 'install mirror-stack for sealing' "$BIN"/arc-close "$BIN"/close-pro bad "[YL-09] seal message still blames installation for a PATH test" else ok "[YL-09] seal message names the tested condition (\`am\` on PATH), not an assumed cause"; fi +# ── interpreter resolution: a name that resolves is not an interpreter that runs ────────────── +# Windows ships a Microsoft Store stub that answers to `python3` and exits 49 without running +# anything. `command -v python3` is satisfied by it, so the whole gate suite scored 34/48 on two +# Windows machines — printed as `✗ expected 0, got 49`, which reads like a verdict and is actually +# the absence of a measurement. Reproduced here with a stub, so this is testable off Windows. +echo +echo "── interpreter resolution ──" +STUBD="$WS/stub"; mkdir -p "$STUBD" +mkstub() { printf '#!/usr/bin/env bash\necho "Python" >&2\nexit 49\n' > "$STUBD/$1"; chmod +x "$STUBD/$1"; } + +mkstub python3 +if ( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; [ "$(yeoul_pybin)" != "python3" ] ) 2>/dev/null; then + ok "[PY-01] a stub that answers to the name is rejected (resolved by running, not by lookup)" +else bad "[PY-01] the stub was accepted as an interpreter"; fi + +# the second Windows machine had a perfectly good `python` on PATH the whole time +REALPY="$(command -v python3 || command -v python)" +ln -sf "$REALPY" "$STUBD/python" 2>/dev/null +if ( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; P="$(yeoul_pybin)"; $P -c 'raise SystemExit(0)' ) 2>/dev/null; then + ok "[PY-02] falls through to a working interpreter under another name" +else bad "[PY-02] did not find the working interpreter that was on PATH"; fi + +# and when nothing runs, it must stop the run — not skip the step and let the skip read as a pass +rm -f "$STUBD/python"; for n in python3 python py; do mkstub "$n"; done +( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; yeoul_pybin >/dev/null 2>&1 ) +if [ $? -ne 0 ]; then ok "[PY-03] no working interpreter => resolution fails"; else bad "[PY-03] claimed success with every candidate stubbed"; fi +OUT="$( PATH="$STUBD:$PATH" bash "$BIN/status" 2>&1 )"; RC=$? +if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q 'no working Python interpreter'; then + ok "[PY-03] a script stops with a named cause (exit $RC), it does not skip silently" +else bad "[PY-03] script exited $RC without naming the missing interpreter"; fi +rm -rf "$STUBD" + echo if [ "$CHECKS" -eq 0 ]; then echo "⛔ 0/0 — no checks were collected; an empty run is a failure, not a pass" From 4713a7640b677b8f368e5f6b603fc846ce6e7b9a Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Tue, 25 Aug 2026 11:05:25 +0900 Subject: [PATCH 2/7] test(gates): find the real interpreter by running it, not by name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PY-02 check located a working interpreter with `command -v python3`, which hands back the stub when the suite itself runs under a stubbed PATH — a name lookup used to find a real interpreter, inside the test for that exact bug. It now runs each candidate. Co-Authored-By: Claude Opus 5 (1M context) --- tests/test_gates.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_gates.sh b/tests/test_gates.sh index 0ba1c4c..f8f8b7f 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -394,7 +394,13 @@ if ( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; [ "$(yeoul_pybin)" != "python3" ] else bad "[PY-01] the stub was accepted as an interpreter"; fi # the second Windows machine had a perfectly good `python` on PATH the whole time -REALPY="$(command -v python3 || command -v python)" +# 🔴 find the real interpreter by RUNNING candidates. `command -v python3` here would hand back +# the stub when this suite is itself run under a stubbed PATH — using a name lookup to locate a +# real interpreter, inside the test for that exact bug. +REALPY="" +for c in "$(command -v python3)" "$(command -v python)" /usr/bin/python3 /usr/bin/python; do + [ -n "$c" ] && "$c" -c 'raise SystemExit(0)' >/dev/null 2>&1 && { REALPY="$c"; break; } +done ln -sf "$REALPY" "$STUBD/python" 2>/dev/null if ( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; P="$(yeoul_pybin)"; $P -c 'raise SystemExit(0)' ) 2>/dev/null; then ok "[PY-02] falls through to a working interpreter under another name" From 64dd3a00c9080feb04d5f5e075bfd602b641ba15 Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Tue, 25 Aug 2026 11:31:07 +0900 Subject: [PATCH 3/7] test(gates): drop the suite's only symlink, and name the interpreter it resolved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes aimed at a Windows failure report (50/52 on one machine, 52/52 on another with the same interpreter layout), neither of which assumes what the two failures are. The PY-02 check built its working-interpreter-under-another-name with `ln -sf`. That is the only symlink in the suite, and MSYS/Git Bash copies the target instead of linking unless winsymlinks is set — copying a Windows python.exe yields a broken standalone, so the check could fail for a reason unrelated to what it tests. It now writes a wrapper script, which behaves the same everywhere. The suite also never printed which interpreter it resolved to. A failure report from another machine therefore cannot distinguish 'resolved a different interpreter' from 'this check does not work there' — the run now prints `resolved: ( )`. 52/52 locally in all three interpreter configurations. Co-Authored-By: Claude Opus 5 (1M context) --- tests/test_gates.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_gates.sh b/tests/test_gates.sh index f8f8b7f..c8f2576 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -385,6 +385,9 @@ else ok "[YL-09] seal message names the tested condition (\`am\` on PATH), not a # the absence of a measurement. Reproduced here with a stub, so this is testable off Windows. echo echo "── interpreter resolution ──" +# Say which interpreter this run actually used. Without it a failure report from another machine +# cannot distinguish "resolved a different interpreter" from "the check itself is broken there". +echo " ℹ resolved: $PY ($($PY -c 'import sys,platform; print(platform.python_version(), sys.platform)' 2>&1 | head -1))" STUBD="$WS/stub"; mkdir -p "$STUBD" mkstub() { printf '#!/usr/bin/env bash\necho "Python" >&2\nexit 49\n' > "$STUBD/$1"; chmod +x "$STUBD/$1"; } @@ -401,7 +404,11 @@ REALPY="" for c in "$(command -v python3)" "$(command -v python)" /usr/bin/python3 /usr/bin/python; do [ -n "$c" ] && "$c" -c 'raise SystemExit(0)' >/dev/null 2>&1 && { REALPY="$c"; break; } done -ln -sf "$REALPY" "$STUBD/python" 2>/dev/null +# 🔴 a wrapper, not a symlink. `ln -s` is the only symlink this suite would use, and MSYS/Git Bash +# copies the target instead of linking unless winsymlinks is set — copying a Windows python.exe +# produces a broken standalone, so this check would fail for a reason that has nothing to do with +# what it is testing. +printf '#!/usr/bin/env bash\nexec "%s" "$@"\n' "$REALPY" > "$STUBD/python"; chmod +x "$STUBD/python" if ( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; P="$(yeoul_pybin)"; $P -c 'raise SystemExit(0)' ) 2>/dev/null; then ok "[PY-02] falls through to a working interpreter under another name" else bad "[PY-02] did not find the working interpreter that was on PATH"; fi From ac15bd334ea427c5c17c83ac63481ba81f1f0faf Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Tue, 25 Aug 2026 11:37:26 +0900 Subject: [PATCH 4/7] test(gates): PY-03 must establish its precondition, not assume it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported from a Windows machine: 50/52, where a second machine with the same interpreter layout scored 52/52. Both failures were the two PY-03 checks, and both were this test's fault. PY-03 stubs the interpreter names and then asserts that nothing can run. It never checked that. $YEOUL_PYTHON is tried before any PATH name and is not a PATH name at all, and on Windows the .exe forms are separate files — so an interpreter could survive the stubbing, the resolver would correctly succeed, and the checks would report the product as broken. Reproduced locally by running the suite with YEOUL_PYTHON set: 50/52, the same score, with the same two checks red. The precondition is now measured: the stub set covers the .exe forms, the subshells unset YEOUL_PYTHON, and if an interpreter still survives, both checks report inconclusive and fail the run rather than passing or blaming the product — neither verdict would mean anything there. A precondition that is assumed instead of measured is the defect this suite exists to catch. 52/52 in all four configurations: normal, stub-first, stub plus working python, and YEOUL_PYTHON set. Co-Authored-By: Claude Opus 5 (1M context) --- tests/test_gates.sh | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/tests/test_gates.sh b/tests/test_gates.sh index c8f2576..7ef74ad 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -413,14 +413,30 @@ if ( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; P="$(yeoul_pybin)"; $P -c 'raise S ok "[PY-02] falls through to a working interpreter under another name" else bad "[PY-02] did not find the working interpreter that was on PATH"; fi -# and when nothing runs, it must stop the run — not skip the step and let the skip read as a pass -rm -f "$STUBD/python"; for n in python3 python py; do mkstub "$n"; done -( PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; yeoul_pybin >/dev/null 2>&1 ) -if [ $? -ne 0 ]; then ok "[PY-03] no working interpreter => resolution fails"; else bad "[PY-03] claimed success with every candidate stubbed"; fi -OUT="$( PATH="$STUBD:$PATH" bash "$BIN/status" 2>&1 )"; RC=$? -if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q 'no working Python interpreter'; then - ok "[PY-03] a script stops with a named cause (exit $RC), it does not skip silently" -else bad "[PY-03] script exited $RC without naming the missing interpreter"; fi +# and when nothing runs, it must stop the run — not skip the step and let the skip read as a pass. +# 🔴 This block must first ESTABLISH "nothing runs", and then check that it was established. The +# earlier version asserted it: it stubbed python3/python/py and assumed that covered every +# candidate. It did not — $YEOUL_PYTHON is tried first and is not a PATH name at all, and on +# Windows the .exe forms are separate files. With an interpreter still reachable the resolver +# correctly succeeded and these two checks failed, blaming the product for the test's own gap. +# (Reproduced by running the suite with YEOUL_PYTHON set: 50/52, the same score reported from a +# Windows machine.) A precondition that is assumed instead of measured is the defect this whole +# suite is about, so it is now measured — and if it cannot be met the checks report inconclusive +# rather than passing or failing, because neither verdict would mean anything. +rm -f "$STUBD/python" +for n in python3 python py python3.exe python.exe py.exe; do mkstub "$n"; done +if ( unset YEOUL_PYTHON; PATH="$STUBD:$PATH"; . "$BIN/_pybin.sh"; yeoul_pybin >/dev/null 2>&1 ); then + CHECKS=$((CHECKS+2)) + echo " ? [PY-03] could not establish 'no interpreter reachable' — an interpreter survived the" + echo " stubbing, so neither PY-03 check is evidence here. Not counted as a pass." + FAIL=1 +else + ok "[PY-03] no working interpreter => resolution fails" + OUT="$( unset YEOUL_PYTHON; PATH="$STUBD:$PATH" bash "$BIN/status" 2>&1 )"; RC=$? + if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q 'no working Python interpreter'; then + ok "[PY-03] a script stops with a named cause (exit $RC), it does not skip silently" + else bad "[PY-03] script exited $RC without naming the missing interpreter"; fi +fi rm -rf "$STUBD" echo From 81a4e47d00963f3e756482af153fe5e113f4b17e Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Tue, 25 Aug 2026 11:40:12 +0900 Subject: [PATCH 5/7] test(gates): print the resolved interpreter's path, not just its name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Windows machines reported different results with the same resolved name and the same version. The interpreter each had picked was a different venv sitting ahead on PATH — invisible in a line that printed only name and version, which is what made the difference hard to see. Co-Authored-By: Claude Opus 5 (1M context) --- tests/test_gates.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_gates.sh b/tests/test_gates.sh index 7ef74ad..99829b2 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -387,7 +387,10 @@ echo echo "── interpreter resolution ──" # Say which interpreter this run actually used. Without it a failure report from another machine # cannot distinguish "resolved a different interpreter" from "the check itself is broken there". -echo " ℹ resolved: $PY ($($PY -c 'import sys,platform; print(platform.python_version(), sys.platform)' 2>&1 | head -1))" +# 🔴 print the PATH too, not just the name and version. Two machines reported different results +# with the same name and the same version — the interpreter each had resolved was a *different +# venv* that happened to sit ahead on PATH, and the name+version line could not show that. +echo " ℹ resolved: $PY -> $($PY -c 'import sys,platform; print(sys.executable, platform.python_version(), sys.platform)' 2>&1 | head -1)" STUBD="$WS/stub"; mkdir -p "$STUBD" mkstub() { printf '#!/usr/bin/env bash\necho "Python" >&2\nexit 49\n' > "$STUBD/$1"; chmod +x "$STUBD/$1"; } From d975d733a3ef08b297b2a99457d8de5e325042d3 Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Tue, 25 Aug 2026 12:03:59 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix(gate):=20return=20the=20verdict=20as=20?= =?UTF-8?q?bytes=20=E2=80=94=20the=20checker=20was=20dying=20while=20repor?= =?UTF-8?q?ting=20its=20own=20result?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on Windows 2026-08-25 by [거울/트리아지], on two machines. Two gate checks failed with `could not run the substance checker`, and the cause was the last line of the checker rather than any judgement it made: having finished judging, it wrote `code + TAB + answer` back through `sys.stdout`, whose encoding is the console code page. On cp949 an em-dash in the answer raised UnicodeEncodeError, the process died, stdout came back empty, and arc-close correctly refused ("an unmeasured field is not a passing field"). A genuine answer was rejected, and nothing in the refusal pointed at encoding. The judge had already reached its verdict. Only the channel carrying it back was broken. The other machine passed, and that green was a coincidence: cp1252 contains the em-dash, cp949 does not. Neither contains Hangul, and our summaries are written in Korean — so both machines carry this defect and only one happened to show it. The suite could not see it either, because every test answer was ASCII apart from a single em-dash: the test corpus shared the code's blind spot. - `emit()` writes UTF-8 bytes to `sys.stdout.buffer`, so the verdict channel does not depend on where it is read. Not a stream reconfigure with errors="replace": that keeps the code alive and hands back an answer full of `?`, and the gate would then judge, report and quote mangled evidence. A test asserts the answer round-trips intact, and fails against exactly that approach. - `_pybin.sh` pins PYTHONUTF8/PYTHONIOENCODING for every gate script. The MCP wrapper already did this for the tools it launches; the shell scripts called Python directly and bypassed it, so index-append, arc-prereg and status had the same exposure on any non-UTF-8 console. - The checker's `--selftest` now echoes a non-ASCII probe through `emit()` — the same function the real call uses. It scored 27/27 on the machine where every real call carrying an em-dash was dying, because it never wrote an answer back. A control that does not travel the path it vouches for vouches for nothing. Its failure report writes ASCII to stderr: the first version used `%r` of the exception, which contains the offending character, so the diagnostic died reporting the fault it exists to report. tests/test_gates.sh 61/61. The encoding checks run against `ascii`, `cp949` and `cp1252` — the two real code pages, not one synthetic stand-in — on a payload built to be lethal to both: U+2014 is absent from cp949, U+AC00 from cp1252. With the fix reverted all nine go red on both. Internal and OSS copies of substance_check.py remain byte-identical. Co-Authored-By: Claude Opus 5 (1M context) --- bin/_pybin.sh | 9 +++++++++ bin/substance_check.py | 39 ++++++++++++++++++++++++++++++++++++++- tests/test_gates.sh | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/bin/_pybin.sh b/bin/_pybin.sh index 1ffba32..4e81e8f 100755 --- a/bin/_pybin.sh +++ b/bin/_pybin.sh @@ -15,6 +15,15 @@ # # Override with YEOUL_PYTHON if you need a specific interpreter. +# 🔴 Every gate script here shells out to Python and echoes text back through it — verdicts, kill +# conditions, arc titles. Python encodes stdout with the *caller's console encoding*, so on a +# console that is not UTF-8 a single non-ASCII character (an em-dash was enough) raises +# UnicodeEncodeError, kills the child, and hands the caller EMPTY output. The MCP wrapper already +# pins these for the tools it launches; the shell scripts called Python directly and bypassed it. +# Pin them here, where every script picks up the interpreter. +export PYTHONUTF8=1 +export PYTHONIOENCODING=utf-8 + yeoul_pybin() { local c for c in ${YEOUL_PYTHON:+"$YEOUL_PYTHON"} python3 python "py -3"; do diff --git a/bin/substance_check.py b/bin/substance_check.py index 7e69718..0fd1d81 100755 --- a/bin/substance_check.py +++ b/bin/substance_check.py @@ -293,10 +293,47 @@ def selftest(verbose=False): return total - len(fails), total, fails +def emit(code, ans): + """The verdict channel. ASCII code, TAB, answer — written as UTF-8 BYTES. + + 🔴 This used to be `sys.stdout.write(...)`, whose encoding is the caller's console encoding. On a + console that is not UTF-8, echoing back an answer containing any non-ASCII character (an + em-dash was enough) raised UnicodeEncodeError, killed the process, and left stdout EMPTY. The + gate reads its verdict from this output and correctly refuses on empty ("an unmeasured field is + not a passing field") — so a genuine answer was refused, on one machine and not another, and + nothing in the refusal pointed at encoding. Measured on Windows 2026-08-25; reproduced with + PYTHONIOENCODING=ascii. The contract is bytes, so it cannot depend on where it is being read. + """ + sys.stdout.buffer.write((code + "\t" + ans).encode("utf-8", "replace")) + sys.stdout.buffer.flush() + + def main(argv): if "--selftest" in argv: ok, total, fails = selftest(verbose=True) # 🔴 Print the DENOMINATOR. A checker that measured nothing also prints green. + # 🔴 Echo a non-ASCII answer through the real output path before declaring the checker sound. + # The selftest prints an ASCII-only summary, so it scored 27/27 on a machine where every + # real call carrying an em-dash died writing its result. A positive control that never + # exercises the path under test vouches for nothing. + # 🔴 through emit(), the SAME function the real call uses. An earlier version of this + # control wrote the probe with sys.stdout.buffer directly — it therefore vouched for a + # path the product does not take, and stayed green with the defect reinstated. + probe = "em-dash \u2014 and hangul \uac00 must survive the verdict channel" + try: + emit("SELFTEST_ECHO", probe) + sys.stdout.buffer.write(b"\n") + sys.stdout.buffer.flush() + except Exception as e: # pragma: no cover - the failure this exists to catch + # 🔴 report on stderr, in pure ASCII. The first version used print(... %r) — repr of a + # UnicodeEncodeError contains the offending character, sent through the very channel + # that just failed, so the diagnostic died while reporting the fault it exists to + # report. A failure message must not depend on what it is reporting about. + msg = ("substance_check selftest: FAILED to write a non-ASCII verdict: %s\n" + % type(e).__name__) + sys.stderr.buffer.write(msg.encode("ascii", "replace")) + sys.stderr.buffer.flush() + return 9 print("substance_check selftest: %d/%d (violations %d - genuine %d - raw %d)" % (ok, total, len(_PLANT_VIOLATIONS), len(_PLANT_GENUINE), len(_PLANT_RAW))) return 0 if not fails else 9 @@ -306,7 +343,7 @@ def main(argv): code, ans = extract(sys.stdin.buffer.read()) if code == "OK": code, _ = judge(label, ans) - sys.stdout.write(code + "\t" + ans) + emit(code, ans) return 0 if code == "OK" else 1 diff --git a/tests/test_gates.sh b/tests/test_gates.sh index 99829b2..59e56c7 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -442,6 +442,44 @@ else fi rm -rf "$STUBD" +# ── the verdict channel must not depend on the console encoding ─────────────────────────────── +# Measured on Windows 2026-08-25: two machines, same PR. On one, two checks failed with +# `could not run the substance checker` — the checker had finished judging and then died *writing +# its result back*, because the answer contained an em-dash and stdout was cp949. stdout came back +# empty, the gate refused (correctly: an unmeasured field is not a passing field), and a genuine +# answer was rejected with nothing in the refusal pointing at encoding. +# +# 🔴 The second machine passed, and that green was a coincidence: cp1252 happens to contain the +# em-dash, cp949 does not. Neither contains Hangul, and our summaries are written in Korean — so +# both machines carry the same defect and only one showed it. Testing one synthetic encoding +# would repeat the mistake, so this runs the two real code pages and asserts on a payload built +# to be lethal to both: U+2014 (absent from cp949) and U+AC00 (absent from cp1252). +echo +echo "── verdict channel encoding ──" +NONASCII="$(printf '— 가')" +ENCLINE="- **Sealed-condition cross-check**: converged ${NONASCII} design settled, non-ascii included" +TAB_="$(printf '\t')" +for CP in ascii cp949 cp1252; do + EOUT="$( printf '%s' "$ENCLINE" | env -u PYTHONUTF8 PYTHONIOENCODING="$CP" \ + $PY "$BIN/substance_check.py" --label 'Sealed-condition cross-check' 2>/dev/null )" + if [ -n "$EOUT" ] && [ "${EOUT%%"$TAB_"*}" = "OK" ]; then + ok "[ENC-01/$CP] a non-ASCII answer still returns its verdict" + else bad "[ENC-01/$CP] verdict channel produced [$EOUT]"; fi + # 🔴 surviving is not enough — the answer must come back INTACT. Pinning the stream with + # errors="replace" would keep the code alive and hand back an answer full of `?`: the gate + # would then judge, report, and quote mangled evidence. Writing UTF-8 bytes to stdout.buffer + # sidesteps the console code page entirely, so this asserts the characters are still there. + if printf '%s' "${EOUT#*"$TAB_"}" | grep -qF "$NONASCII"; then + ok "[ENC-02/$CP] the answer round-trips intact, not replaced with '?'" + else bad "[ENC-02/$CP] answer came back mangled: [${EOUT#*"$TAB_"}]"; fi + # and the checker's own positive control has to run through that same channel, or it vouches for + # nothing — it scored 27/27 on the machine where every real call carrying an em-dash was dying. + ESELF="$( env -u PYTHONUTF8 PYTHONIOENCODING="$CP" $PY "$BIN/substance_check.py" --selftest 2>&1 )" + if printf '%s' "$ESELF" | grep -q 'selftest: [0-9]*/[0-9]*'; then + ok "[ENC-03/$CP] the checker's positive control passes through the channel it vouches for" + else bad "[ENC-03/$CP] selftest did not survive: $(printf '%s' "$ESELF" | tail -1)"; fi +done + echo if [ "$CHECKS" -eq 0 ]; then echo "⛔ 0/0 — no checks were collected; an empty run is a failure, not a pass" From 32a8de89dae875ccb30240f978637b00b5a7adc7 Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Tue, 25 Aug 2026 12:07:54 +0900 Subject: [PATCH 7/7] test(gates): build the non-ASCII payload from escapes A literal Hangul sample is a personalization leak in an English-only repo and the pre-publish guard rejects it, correctly. Rewriting the block put the literal characters back; they are escapes again. Verified the payload still carries the real bytes and still turns all nine ENC checks red when the fix is reverted, so escaping did not make it toothless. Co-Authored-By: Claude Opus 5 (1M context) --- tests/test_gates.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_gates.sh b/tests/test_gates.sh index 59e56c7..b5747d2 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -456,7 +456,10 @@ rm -rf "$STUBD" # to be lethal to both: U+2014 (absent from cp949) and U+AC00 (absent from cp1252). echo echo "── verdict channel encoding ──" -NONASCII="$(printf '— 가')" +# built from escapes on purpose: a literal Hangul sample would be a personalization leak in an +# English-only repo and setup/pre-publish-check.sh rejects it, correctly. U+2014 em-dash (absent +# from cp949) and U+AC00 (absent from cp1252) — one character for each machine's code page. +NONASCII="$(printf '\u2014 \uac00')" ENCLINE="- **Sealed-condition cross-check**: converged ${NONASCII} design settled, non-ascii included" TAB_="$(printf '\t')" for CP in ascii cp949 cp1252; do