From f86ec963fdd96954b04d9ceeeccac48d976e8d41 Mon Sep 17 00:00:00 2001 From: Mother Seara Date: Wed, 5 Aug 2026 16:05:16 +0900 Subject: [PATCH] fix: make the sealed-condition anchor independent of the close label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pre-registered kill-condition is the only signal in a close that the closing agent did not write: it is sealed in an append-only ledger before the compute. But the harness only injected and checked it when IS_KILL was set β€” and IS_KILL comes from `--stop` and the verdict string, both written by that same agent at closing time. Closing an arc as `converged` therefore switched the anchor off entirely: the sealed bar was never quoted, never compared, never refused. Now the injection keys off the sealed condition existing. A KILL close still gets the πŸ›‘οΈ 5-check; any other close on a sealed arc gets a one-item πŸ”’ sealed-condition cross-check β€” did the result stay clear of the bar you sealed, or did you move the bar? A `.prereg` whose condition no longer resolves is refused rather than passed in silence, since that was a quiet way around the gate. Both paths share one trivial-evasion bar (`check_answers`); kept separate they drift, which this repo has already demonstrated once. Triggering on the word "KILL" appearing in the summary body was considered and rejected on measurement: against an archive of real closes it fired on 3 of 4 label-free arcs for the wrong reason (citations of other arcs' seals, kill-conditions being designed for a next stage) while missing the closes that actually buried an original claim β€” those never use the word. It would also have put a filing cost on exactly the honest self-reporting the gate exists to encourage. Tests cover the regression directly: injection on a non-KILL close, refusal while unfilled, refusal of a trivial answer, refusal of a widened sealed line, refusal of a broken anchor, and two positive controls (a substantive answer seals; an arc with no seal is unaffected). --- CHANGELOG.md | 3 +- README.md | 3 + README_KO.md | 3 + bin/arc-close | 121 ++++++++++++++++++++++++++++++---------- docs/METHODOLOGY.md | 23 ++++++-- mcp/yeoul_mcp/server.py | 9 ++- tests/test_gates.sh | 40 +++++++++++++ 7 files changed, 163 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6936c24..6a02170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ All notable changes to this project are documented here. Initial extraction of the Yeoul harness (de-personalized structure only). -- **Deliberation engine**: `arc-open`/`arc-close` (2-phase close with blank-refusal + KILL-defense 5-check), +- **Deliberation engine**: `arc-open`/`arc-close` (2-phase close with blank-refusal + KILL-defense 5-check + + a label-independent sealed-condition cross-check whenever a prereg seal is linked), ticket/attach/watch/list/roles/join relay helpers, `loop-guard` (round/token/no-progress bounds). - **Lifecycle**: `yeoul-new`, `build-handoff` (verify-gated TODO), `ralph` (autonomous dev loop with a verify-gate), `graduate`, `close-project`, `status`. diff --git a/README.md b/README.md index ccccd48..1d2c48a 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,9 @@ New to the method? Read [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md), then paste - **2-phase close** β€” closing drafts a summary; it only seals once the blanks are filled. - **KILL-defense 5-check** β€” a "failed" verdict cannot be sealed until anchor-reproduction, β‰₯2 independent angles, implementation-defect ruled out, catalog cross-check, and verbatim kill-wording are all recorded. +- **Sealed-condition cross-check** β€” if a pre-registered kill-condition is linked to the arc, closing always + has to answer it, *whatever the close is labelled*. Otherwise closing as `converged` would switch off the + only signal the closing agent did not write. A seal that no longer resolves is refused, not skipped. - **Verify-gated dev loop** β€” each automated development round advances one TODO item and may only check it off after its own machine verification command exits 0, re-run by the harness rather than reported by the agent. Items without a verify command are refused β€” including checked ones, so deleting the clause is not diff --git a/README_KO.md b/README_KO.md index c5c8473..49e8e67 100644 --- a/README_KO.md +++ b/README_KO.md @@ -62,6 +62,9 @@ tests/test_gates.sh # λͺ¨λ“  게이트의 단언 기반 슀λͺ¨ν¬ ν…ŒμŠ€νŠΈ - **2단계 μ’…κ²°** β€” 쒅결은 μš”μ•½ μ΄ˆμ•ˆμ„ λ§Œλ“€κ³ , 빈칸이 μ±„μ›Œμ Έμ•Όλ§Œ λ΄‰μΈλœλ‹€. - **KILL λ°©μ–΄ 5ν•­ 체크** β€” "μ‹€νŒ¨" νŒμ •μ€ 액컀 μž¬ν˜„Β·λ…λ¦½κ°λ„ β‰₯2Β·κ΅¬ν˜„κ²°ν•¨ λ°°μ œΒ·λ„κ° λŒ€μ‘°Β·kill λ¬Έμ–Έ μΌμΉ˜κ°€ μ „λΆ€ 기둝되기 μ „μ—” λ΄‰μΈλ˜μ§€ μ•ŠλŠ”λ‹€. +- **봉인 쑰건 λŒ€μ‘°** β€” μ‚¬μ „λ“±λ‘λœ kill 쑰건이 아크에 연결돼 있으면, 쒅결이 **μ–΄λ–€ 라벨이든** κ·Έ 쑰건에 λ‹΅ν•΄μ•Ό + ν•œλ‹€. μ•ˆ 그러면 `converged`둜 λ‹«λŠ” κ²ƒλ§ŒμœΌλ‘œ *λ‹«λŠ” μ‚¬λžŒμ΄ μ“°μ§€ μ•Šμ€ μœ μΌν•œ μ‹ ν˜Έ*κ°€ κΊΌμ§„λ‹€. 더 이상 μ½νžˆμ§€ μ•ŠλŠ” + 봉인은 κ±΄λ„ˆλ›°μ§€ μ•Šκ³  κ±°λΆ€ν•œλ‹€. - **검증 게이트 개발 루프** β€” 각 μžλ™ 개발 λΌμš΄λ“œλŠ” TODO ν•œ ν•­λͺ©μ„ μ§„ν–‰ν•˜κ³ , κ·Έ ν•­λͺ©μ˜ 기계 검증 λͺ…령이 exit 0일 λ•Œλ§Œ 체크할 수 μžˆλ‹€. νŒμ •μ€ μ—μ΄μ „νŠΈ 보고가 μ•„λ‹ˆλΌ **ν•˜λ„€μŠ€ μž¬μ‹€ν–‰**이닀. 검증 λͺ…λ Ή μ—†λŠ” ν•­λͺ©μ€ κ±°λΆ€λ˜λ©° **체크된 ν•­λͺ©λ„ λ§ˆμ°¬κ°€μ§€** β€” μ ˆμ„ μ§€μš°κ³  μ²΄ν¬ν•˜λŠ” μš°νšŒκ°€ λ§‰νžŒλ‹€. (μ‹€νŒ¨ν•  수 μ—†λŠ” 검증 λͺ…령은 μ—¬μ „νžˆ ꡬ멍이고, diff --git a/bin/arc-close b/bin/arc-close index 99d2289..a33cb25 100755 --- a/bin/arc-close +++ b/bin/arc-close @@ -2,7 +2,9 @@ set -euo pipefail # arc-close "" [--stop=falsified|no-progress|converged|] # β˜…2-phase close: 1st run drafts _SUMMARY only (no seal) β†’ fill blanks β†’ re-run gate-checks & archives. -# Gates: β‘  "(fill in)" left = refuse β‘‘ falsified/KILL β†’ KILL-defense 5-check ("(unfilled)" left = refuse) +# Gates: β‘  "(fill in)" left = refuse β‘‘ falsified/KILL β†’ KILL-defense 5-check ("(unfilled)" left = refuse) +# β‘’ a linked .prereg seal β†’ πŸ”’ sealed-condition cross-check fires REGARDLESS of the label (PASS closes too); +# a .prereg whose condition cannot be read is a broken anchor and is refused. Both paths share one bar. # β†’ guards against premature closure. Then STATE records CLOSED, _SUMMARY is sealed, arc moves to ../_archive/. # β˜…stop_reason distinguishes 'falsified' vs 'no-progress' vs 'converged' (generic β‡’ no-progress only). # β˜…Optional: seal the close into an append-only ledger (mirror-stack `am`, content-hash = _SUMMARY). Best-effort. @@ -65,6 +67,23 @@ if [ -f "$PREREG" ]; then SEAL_KILL="$(extract_kill "$SEAL_LEDGER" "$SEAL_CLAIM")" fi +# ── An external anchor must not depend on a label the examinee writes ────────────────── +# The defect: even when a sealed kill-condition existed, nothing was injected or checked +# unless IS_KILL was set. IS_KILL comes from `--stop` and the verdict string β€” both written +# by the closing agent, at closing time. So the one signal the examinee cannot author (a +# condition sealed in an append-only ledger *before* the compute) was gated behind one they +# could. Closing as a PASS switched the anchor off. +# The fix: SEAL_GATE keys off the sealed condition existing β€” never off the label. +# KILL close β†’ full πŸ›‘οΈ 5-check (anti-premature-closure: "did you kill it too early?") +# other close β†’ πŸ”’ sealed-condition cross-check, one item (the mirror question: +# "did the result actually stay clear of the bar you sealed β€” or did you move it?") +# Note: triggering on the word "KILL" appearing in the body was considered and rejected β€” on the +# archive it misfired on 3 of 4 (citations of other arcs' seals, kill-conditions being *designed*) +# while missing the closes that mattered, which never use the word at all. +SEAL_GATE="no"; [ -n "$SEAL_KILL" ] && SEAL_GATE="yes" +# A .prereg that no longer resolves is a silent way around the gate. Never pass it in silence. +SEAL_BROKEN="no"; [ -f "$PREREG" ] && [ -z "$SEAL_KILL" ] && SEAL_BROKEN="yes" + emit_kill_defense() { # KILL-defense section: sealed β†’ inject verbatim condition; unsealed β†’ attestation-only echo "" echo "## πŸ›‘οΈ KILL-defense check (anti-premature-closure β€” all must be filled to seal)" @@ -84,6 +103,39 @@ emit_kill_defense() { # KILL-defense section: sealed β†’ inject verbatim conditi echo "- **Catalog cross-check**: (unfilled) ← known measurement-illusion pattern? (catalog id, or 'none')." } +emit_seal_check() { # non-KILL close with a seal linked β€” cross-check it regardless of the label + echo "" + echo "## πŸ”’ Sealed-condition cross-check (label-independent β€” fires whenever .prereg is linked)" + echo "**Sealed kill-condition** (verbatim from seal \`$SEAL_CLAIM\` β€” harness-injected, do not edit):" + printf ' > %s\n' "$SEAL_KILL" + echo "- **Result triggers the sealed condition?**: (unfilled) ← if it does, this close should be a KILL. If it does not, give the evidence (number / seal). Do not answer by rewriting the condition (goalpost move)." +} + +# Trivial-evasion check. Both paths share ONE bar β€” kept apart, they drift (proved 2026-08-05: +# the same answer sealed internally and was refused by this file). +check_answers() { # check_answers
+ while IFS= read -r vline; do + case "$vline" in "- **"*"**:"*) ;; *) continue ;; esac + vlabel="$(printf '%s' "$vline" | sed -E 's/^- \*\*([^*]+)\*\*.*/\1/')" + # answer extraction via python (Unicode-safe β€” sed's multibyte ← match is locale-fragile) + vans="$(printf '%s' "$vline" | python3 -c 'import sys,re; s=sys.stdin.read(); s=re.sub(r"^- \*\*[^*]+\*\*:\s*","",s); s=re.sub(r"\s*←.*$","",s); sys.stdout.write(s.strip())')" + vlow="$(printf '%s' "$vans" | tr '[:upper:]' '[:lower:]')" + case "$vlow" in ""|y|yes|ok|okay|na|n/a|done|.|-|x|pass|true|good|yep|sure|n) + echo "β›” seal refused: '$vlabel' answer too trivial: '$vans'"; exit 5 ;; + esac + case "$vlabel" in + *[Cc]atalog*) # a catalog id, or the literal 'none' + case "$vlow" in none) : ;; *) printf '%s' "$vans" | grep -qE '[A-Za-z0-9_]{3,}' \ + || { echo "β›” seal refused: catalog cross-check needs a catalog id or 'none': '$vans'"; exit 5; } ;; esac ;; + *[Aa]nchor*) # a number, or a seal/reproduction reference + printf '%s' "$vlow" | grep -qE '[0-9]|seal|anchor|reproduc|converg|hash' \ + || { echo "β›” seal refused: anchor answer needs a number or a seal/reproduction reference: '$vans'"; exit 5; } ;; + *) # otherwise require a little substance + [ "${#vans}" -ge 6 ] || { echo "β›” seal refused: '$vlabel' answer too short: '$vans'"; exit 5; } ;; + esac + done < <(sed -n "/$1/,\$p" "$SUMMARY") +} + SUMMARY="$ARC_DIR/_SUMMARY_${ARC}.md" # ── 1st run: draft only, stop (no seal) ── @@ -108,11 +160,18 @@ if [ ! -f "$SUMMARY" ]; then > Discipline: negatives and retractions are recorded indelibly. Numbers only after checking against evidence. MDEOF - [ "$IS_KILL" = "yes" ] && emit_kill_defense >> "$SUMMARY" + if [ "$IS_KILL" = "yes" ]; then emit_kill_defense >> "$SUMMARY" + elif [ "$SEAL_GATE" = "yes" ]; then emit_seal_check >> "$SUMMARY"; fi echo "πŸ“ _SUMMARY drafted (not sealed): $SUMMARY" - [ "$IS_KILL" = yes ] && [ -n "$SEAL_KILL" ] && echo " πŸ”’ sealed kill-condition injected from seal $SEAL_CLAIM (not agent-authored)." - [ "$IS_KILL" = yes ] && [ -z "$SEAL_KILL" ] && echo " ⚠️ UNSEALED β€” no prereg linked (arc-prereg); kill-wording is attestation-only." - echo " Fill the blanks (fill in$([ "$IS_KILL" = yes ] && echo ' / unfilled')), then re-run the same command to gate-check and archive." + if [ "$SEAL_GATE" = "yes" ]; then + echo " πŸ”’ sealed kill-condition injected from seal $SEAL_CLAIM (not agent-authored)." + [ "$IS_KILL" = "yes" ] || echo " note: not a KILL close, but a seal is linked β€” the πŸ”’ cross-check fires regardless of the label." + elif [ "$SEAL_BROKEN" = "yes" ]; then + echo " πŸ”΄ .prereg is linked but its kill_condition could not be read β€” anchor broken (claim=$SEAL_CLAIM, ledger=$SEAL_LEDGER)." + elif [ "$IS_KILL" = "yes" ]; then + echo " ⚠️ UNSEALED β€” no prereg linked (arc-prereg); kill-wording is attestation-only." + fi + echo " Fill the blanks (fill in$([ "$IS_KILL" = yes ] || [ "$SEAL_GATE" = yes ] && echo ' / unfilled')), then re-run the same command to gate-check and archive." exit 0 fi @@ -122,43 +181,45 @@ if grep -q '(fill in)' "$SUMMARY"; then grep -n 'fill in' "$SUMMARY" | sed 's/^/ /' exit 4 fi +# A broken anchor is a silent way around the gate β€” refuse rather than pass quietly. +if [ "$SEAL_BROKEN" = "yes" ]; then + echo "β›” seal refused: .prereg is linked but its sealed kill-condition could not be read β€” anchor broken." + echo " claim=$SEAL_CLAIM Β· ledger=$SEAL_LEDGER" + echo " Re-link with arc-prereg, or remove .prereg deliberately if there really is no seal (both are auditable acts)." + exit 5 +fi + +# β‘  the harness owns the sealed-condition line β€” it must remain verbatim (no silent widening). Both paths. +if [ "$SEAL_GATE" = "yes" ] && ! grep -qxF " > $SEAL_KILL" "$SUMMARY"; then + echo "β›” seal refused: the harness-injected sealed kill-condition line was edited or removed β€” it must stay verbatim from seal \`$SEAL_CLAIM\`." + exit 5 +fi + if [ "$IS_KILL" = "yes" ]; then if ! grep -q 'KILL-defense check' "$SUMMARY"; then emit_kill_defense >> "$SUMMARY" echo "β›” seal refused: KILL-type close without a πŸ›‘οΈ KILL-defense check β€” section added. Fill it and re-run." exit 5 fi - # β‘  the harness owns the sealed-condition line β€” it must remain the verbatim seal (no silent widening) - if [ -n "$SEAL_KILL" ] && ! grep -qxF " > $SEAL_KILL" "$SUMMARY"; then - echo "β›” seal refused: the harness-injected sealed kill-condition line was edited β€” it must stay verbatim from seal \`$SEAL_CLAIM\`." + if grep -q '(unfilled)' "$SUMMARY"; then + echo "β›” seal refused: πŸ›‘οΈ KILL-defense items remain unfilled β€” anti-premature-closure gate." + grep -n 'unfilled' "$SUMMARY" | sed 's/^/ /' + exit 5 + fi + check_answers 'KILL-defense check' +elif [ "$SEAL_GATE" = "yes" ]; then + # non-KILL close with a seal linked β€” the cross-check fires independently of the label + if ! grep -q 'Sealed-condition cross-check' "$SUMMARY"; then + emit_seal_check >> "$SUMMARY" + echo "β›” seal refused: a seal (.prereg) is linked but the πŸ”’ sealed-condition cross-check is missing β€” section added. Fill it and re-run." exit 5 fi if grep -q '(unfilled)' "$SUMMARY"; then - echo "β›” seal refused: πŸ›‘οΈ KILL-defense items remain unfilled β€” anti-premature-closure gate." + echo "β›” seal refused: πŸ”’ sealed-condition cross-check unfilled β€” state whether the result met the bar you sealed." grep -n 'unfilled' "$SUMMARY" | sed 's/^/ /' exit 5 fi - # minimal substance check β€” can't judge truth, but reject self-evident evasions (a bare "yes", ".", etc.) - while IFS= read -r vline; do - case "$vline" in "- **"*"**:"*) ;; *) continue ;; esac - vlabel="$(printf '%s' "$vline" | sed -E 's/^- \*\*([^*]+)\*\*.*/\1/')" - # answer extraction via python (Unicode-safe β€” sed's multibyte ← match is locale-fragile) - vans="$(printf '%s' "$vline" | python3 -c 'import sys,re; s=sys.stdin.read(); s=re.sub(r"^- \*\*[^*]+\*\*:\s*","",s); s=re.sub(r"\s*←.*$","",s); sys.stdout.write(s.strip())')" - vlow="$(printf '%s' "$vans" | tr '[:upper:]' '[:lower:]')" - case "$vlow" in ""|y|yes|ok|okay|na|n/a|done|.|-|x|pass|true|good|yep|sure|n) - echo "β›” seal refused: KILL-defense '$vlabel' answer too trivial: '$vans'"; exit 5 ;; - esac - case "$vlabel" in - *[Cc]atalog*) # a catalog id, or the literal 'none' - case "$vlow" in none) : ;; *) printf '%s' "$vans" | grep -qE '[A-Za-z0-9_]{3,}' \ - || { echo "β›” seal refused: catalog cross-check needs a catalog id or 'none': '$vans'"; exit 5; } ;; esac ;; - *[Aa]nchor*) # a number, or a seal/reproduction reference - printf '%s' "$vlow" | grep -qE '[0-9]|seal|anchor|reproduc|converg|hash' \ - || { echo "β›” seal refused: anchor answer needs a number or a seal/reproduction reference: '$vans'"; exit 5; } ;; - *) # otherwise require a little substance - [ "${#vans}" -ge 6 ] || { echo "β›” seal refused: KILL-defense '$vlabel' answer too short: '$vans'"; exit 5; } ;; - esac - done < <(sed -n '/KILL-defense check/,$p' "$SUMMARY") + check_answers 'Sealed-condition cross-check' fi echo "ARC_CLOSED ${TODAY} stop=${STOP} β€” ${VERDICT}" >> "$ARC_DIR/STATE.md" diff --git a/docs/METHODOLOGY.md b/docs/METHODOLOGY.md index c36a1ec..0b4d848 100644 --- a/docs/METHODOLOGY.md +++ b/docs/METHODOLOGY.md @@ -54,15 +54,28 @@ Each round, the relay summarizes and judges. **Every round summary must include call, not the guard's job. **More rounds do not improve quality** β€” the guards exist because debate drifts. Close with `bin/arc-close` (2-phase): it drafts a summary; you fill the blanks; re-running seals only when -they're filled. A **falsified/KILL** close additionally requires the **πŸ›‘οΈ KILL-defense 5-check**: +they're filled. A **falsified/KILL** close (and only that close) additionally requires the **πŸ›‘οΈ KILL-defense 5-check**: anchor (positive control) reproduced Β· β‰₯2 independent angles converged Β· implementation defect ruled out Β· catalog cross-check Β· verbatim kill-wording. This is the anti-premature-closure gate. **Sealed kill-condition injection.** If you sealed the kill-condition (mirror-stack `mm_preregister`) and -linked it to the arc (`bin/arc-prereg [ledger]`), the KILL-defense check no longer asks -the agent to *type* whether the verdict matches the pre-registration. Instead the harness reads the sealed -kill-condition from the ledger and **injects it verbatim** into the record; on close it refuses if that line -was edited. The agent is not the author of the condition, so it cannot be silently widened at close. +linked it to the arc (`bin/arc-prereg [ledger]`), the check no longer asks the agent to +*type* whether the verdict matches the pre-registration. Instead the harness reads the sealed kill-condition +from the ledger and **injects it verbatim** into the record; on close it refuses if that line was edited or +removed. The agent is not the author of the condition, so it cannot be silently widened at close. + +This injection is **independent of how the close is labelled**. That matters more than it sounds: the label +(`--stop`, the verdict string) is written by the closing agent at closing time, so keying the anchor to it +would let a `converged` close switch off the one signal the agent cannot author. So a linked seal always +produces a check β€” the πŸ›‘οΈ 5-check on a KILL close, and on any other close a one-item **πŸ”’ sealed-condition +cross-check**: *did the result stay clear of the bar you sealed, or did you move the bar?* A `.prereg` whose +condition can no longer be read is treated as a broken anchor and refused rather than passed in silence. + +We considered instead triggering on the word "KILL" appearing anywhere in the summary. Measured against our +own archive it was wrong in both directions β€” it fired on citations of other arcs' seals and on kill-conditions +being *designed* for the next stage (3 of 4 hits), while the closes that actually buried an original claim +never used the word at all. It would also have taxed exactly the honest reporting we want. A trigger the +examinee writes is not a trigger; anchoring on the seal is the only signal here that predates the close. Scope, honestly: this fixes the *condition* by reference β€” whether the result actually triggers it remains a judgment the agent asserts, and this is **not** "machine-verified honesty", only a removed goalpost. Without a linked seal the field stays attestation-only and the close is stamped `⚠️ UNSEALED` β€” the missing seal is itself diff --git a/mcp/yeoul_mcp/server.py b/mcp/yeoul_mcp/server.py index ca67098..8ff7c0b 100644 --- a/mcp/yeoul_mcp/server.py +++ b/mcp/yeoul_mcp/server.py @@ -90,9 +90,12 @@ def loop_guard_init(arc_dir: str, max_rounds: int = 3, token_budget: int = 20000 @mcp.tool() def arc_close(arc_dir: str, verdict: str, stop: str = "converged") -> dict: - """Close an arc (2-phase, GATE-ENFORCED). 1st call drafts _SUMMARY; fill blanks (+ KILL-defense 5-check - if stop=falsified) then call again to seal. Returns the script's refusal (exit 4 blanks / exit 5 KILL-defense) - if not ready β€” that refusal is authoritative, do not override it.""" + """Close an arc (2-phase, GATE-ENFORCED). 1st call drafts _SUMMARY; fill the blanks, then call again to seal. + Extra sections are required depending on the close: a KILL close (stop=falsified, or KILL in the verdict) gets + the πŸ›‘οΈ 5-check; ANY close on an arc with a linked prereg seal gets the πŸ”’ sealed-condition cross-check β€” + that one fires regardless of the label, so closing as `converged` does not switch the anchor off. + Returns the script's refusal (exit 4 blanks / exit 5 gate) if not ready β€” that refusal is authoritative, + do not override it.""" return _run("arc-close", arc_dir, verdict, f"--stop={stop}") diff --git a/tests/test_gates.sh b/tests/test_gates.sh index f11cc0f..64dd114 100755 --- a/tests/test_gates.sh +++ b/tests/test_gates.sh @@ -62,6 +62,46 @@ sedi 's/^- \*\*Implementation.*/- **Implementation defect ruled out**: mechanism sedi 's/^- \*\*Catalog.*/- **Catalog cross-check**: none/' "$SSUM" "$BIN/arc-close" "$SARC" "KILL β€” sealed" --stop=falsified >/dev/null 2>&1; assert "sealed close seals once verbatim intact + filled" 0 $? +# --- the sealed anchor must not depend on the label the closing agent writes --- +# Regression: a `converged` close used to switch the injection off entirely, so a sealed arc could be +# closed as a PASS with the pre-registered bar never mentioned. The label is agent-written; the seal is not. +"$BIN/arc-open" pv --topic="sealed pass" --arcs-dir="$WS/arcs" >/dev/null 2>&1 +PARC="$(ls -d "$WS"/arcs/*_pv)" +YEOUL_LEDGER="$LEDGER" "$BIN/arc-prereg" "$PARC" c1 >/dev/null 2>&1 +"$BIN/arc-close" "$PARC" "converged β€” design settled" --stop=converged >/dev/null 2>&1 # draft +PSUM="$(ls "$PARC"/_SUMMARY_*.md)" +grep -qF "effect size d < 0.2 over >= 3 seeds" "$PSUM" \ + && echo " βœ“ sealed condition injected on a non-KILL close too" \ + || { echo " βœ— non-KILL close skipped the sealed condition"; FAIL=1; } +sedi 's/- (fill in)/- concrete conclusion/' "$PSUM" +"$BIN/arc-close" "$PARC" "converged β€” design settled" --stop=converged >/dev/null 2>&1; assert "PASS close refuses while the cross-check is unfilled" 5 $? +sedi 's/(unfilled)/yes/' "$PSUM" +"$BIN/arc-close" "$PARC" "converged β€” design settled" --stop=converged >/dev/null 2>&1; assert "PASS close refuses a trivial cross-check answer" 5 $? +sedi 's/^- \*\*Result triggers.*/- **Result triggers the sealed condition?**: no β€” measured d = 0.61, well clear of the 0.2 bar/' "$PSUM" +sedi 's/^ > effect size.*/ > effect size d < 0.9 (widened)/' "$PSUM" +"$BIN/arc-close" "$PARC" "converged β€” design settled" --stop=converged >/dev/null 2>&1; assert "PASS close refuses a widened sealed line" 5 $? +sedi 's/^ > effect size.*/ > effect size d < 0.2 over >= 3 seeds/' "$PSUM" +"$BIN/arc-close" "$PARC" "converged β€” design settled" --stop=converged >/dev/null 2>&1; assert "PASS close seals once the cross-check is answered" 0 $? + +# a .prereg that no longer resolves is a silent way around the gate β€” it must be refused, not skipped +"$BIN/arc-open" br --topic="broken anchor" --arcs-dir="$WS/arcs" >/dev/null 2>&1 +BARC="$(ls -d "$WS"/arcs/*_br)" +printf 'c1\n%s\n' "$LEDGER" > "$BARC/.prereg" +"$BIN/arc-close" "$BARC" "converged β€” ok" --stop=converged >/dev/null 2>&1 # draft +BSUM="$(ls "$BARC"/_SUMMARY_*.md)" +sedi 's/- (fill in)/- concrete conclusion/' "$BSUM" +sedi 's/^- \*\*Result triggers.*/- **Result triggers the sealed condition?**: no β€” measured d = 0.61, clear of the bar/' "$BSUM" +printf 'c_typo\n%s\n' "$LEDGER" > "$BARC/.prereg" # anchor now unresolvable +"$BIN/arc-close" "$BARC" "converged β€” ok" --stop=converged >/dev/null 2>&1; assert "broken .prereg anchor is refused, not silently skipped" 5 $? + +# positive control: an arc with no seal at all still closes normally (we tightened, not bricked) +"$BIN/arc-open" ns --topic="no seal" --arcs-dir="$WS/arcs" >/dev/null 2>&1 +NARC="$(ls -d "$WS"/arcs/*_ns)" +"$BIN/arc-close" "$NARC" "converged β€” design settled" --stop=converged >/dev/null 2>&1 # draft +NSUM="$(ls "$NARC"/_SUMMARY_*.md)" +sedi 's/- (fill in)/- concrete conclusion/' "$NSUM" +"$BIN/arc-close" "$NARC" "converged β€” design settled" --stop=converged >/dev/null 2>&1; assert "unsealed PASS close is unaffected" 0 $? + # --- ralph verify-gate --- "$BIN/yeoul-new" p --no-arc >/dev/null 2>&1 printf -- '- [ ] no verify command here\n' > "$WS/projects/p/dev/TODO.md"