diff --git a/.claude/memory/blockers.md b/.claude/memory/blockers.md index 8f45b50..b2322c5 100644 --- a/.claude/memory/blockers.md +++ b/.claude/memory/blockers.md @@ -42,7 +42,7 @@ rules: | BLK-020 | 2026-09-02 | notify-attention: both channels dead on one VS Code client — 2026-09-02 | resolved | | BLK-021 | 2026-09-22 | Bash tool dead mid-session ("every command exits 1"): /tmp usrquota blown by a dead session's probe HOMEs — 2… | open | | BLK-022 | 2026-09-22 | `hooks/guard-bash.sh` withheld by the safety classifier; executable spec shipped instead — 2026-09-22 | open | -| BLK-023 | 2026-09-28 | floor-guard SKIP pattern `xit(` (Jasmine) matches any `exit(` in python/JS test helpers → false ECARTS; workaround: no `exit(` in inline python, bash derives rc from output — 2026-09-28 | open | +| BLK-023 | 2026-09-28 | floor-guard SKIP pattern `xit(` (Jasmine) matches any `exit(` in python/JS test helpers → false ECARTS; workaround: no `exit(` in inline python, bash derives rc from output — 2026-09-28 | resolved | --- @@ -267,4 +267,4 @@ rules: - **Friction**: fresh verifier returned ECARTS(1) on a fully conform diff: `FLOOR SKIP lib/tests/profile-census.test.sh:116 sys.exit(1 if violations else 0)`. One re-dispatch spent on a tool artefact. - **Real cause**: `lib/floor-guard.sh` SKIP_SUBSTRINGS holds the bare fragment `'xit('` to catch Jasmine's `xit(…)`; `skip_kind()` is a plain substring match, so `sys.exit(`, `SystemExit(`, `process.exit(` all hit. - **Solution**: workaround applied — the inline python prints violations only, the bash wrapper derives the return code from the captured output (no `exit(` anywhere). Root fix pending: word-bound the pattern (`(^|[^a-zA-Z_.])xit\(`) or match `xit(` only in JS/TS test files; hotfix-sized. -- **Status**: open. Links [[BDR-105]], [[BDR-102]] (floor-guard origin), [[EVAL-034]]. +- **Status**: resolved 2026-09-28 — hotfix 0deb559 (bugfix/floor-guard-xit-boundary): the four bare Jasmine identifiers moved into `SKIP_IDENT_RE` with lookbehind `(? fais le hotfix du floor-guard +> BLK-023: lib/floor-guard.sh SKIP pattern `xit(` (meant for Jasmine's xit) matches any `exit(` / `SystemExit(` / `process.exit(` in python or JS test helpers → false FLOOR SKIP finding (ECARTS on a conform diff, 2026-09-28). Fix: make the Jasmine match word-bounded so `sys.exit(` no longer trips it; keep `xit(` detection for a real Jasmine `xit(` at line start or after a non-identifier char. Add the two regression cases to lib/tests/floor-guard.test.sh (a python `sys.exit(1)` line must NOT flag; a JS ` xit('skipped', ...)` line MUST flag). + +## CLARIFICATIONS +- Pass A: silent autofill (hotfix). Pass B: nothing visible or public is open (an internal matcher; message text unchanged). +- [challenge 2026-09-28: simplicity SOLID, correctness SOLID, robustness CONCERNS(2), all closed by named plan changes, r2] fixture echo lines in lib/tests/floor-guard.test.sh carry `# floor-guard: allow flip-test fixture` outside the echoed string (a test-path diff scan would flag the fixture itself; WAIVED on a test file is informational and authorized here); the vacuous live clause left criterion 2; `def fit(` / `function xit(` / `xit.each(` behave as before and are recorded as a `shortcut:` comment (upgrade path named there), out of hotfix scope. +- Root cause (LOCATE): `skip_kind` (lib/floor-guard.sh:188-189) is a plain substring test over SKIP_SUBSTRINGS; the bare-identifier entries `'xit('`, `'fit('`, `'xdescribe('`, `'fdescribe('` therefore match inside longer identifiers (`exit(`, `SystemExit(`, `process.exit(`, `model.fit(`, `profit(`). The dotted/decorator entries (`.skip(`, `.only(`, `it.todo(`, `@pytest.mark.skip`, `@unittest.skip`, `t.Skip(`) are unaffected. +- Fix (closed): the four bare identifiers move out of SKIP_SUBSTRINGS into one compiled regex with an identifier-boundary lookbehind, `(?&1); echo "$out" | grep -qE 'FAIL=[1-9]' && { echo "$out" | tail -12; exit 1; }; for k in SKIP SKIP_EXIT_CLEAN SKIP_XIT_FLAGS SKIP_FIT_FLAGS SKIP_FDESCRIBE_FLAGS; do echo "$out" | grep -q "PASS $k" || { echo "missing PASS $k"; exit 1; }; done; echo FLOOR_SUITE_GREEN + EXPECT: FLOOR_SUITE_GREEN + EVIDENCE: MET exit=0 marker-found :: FLOOR_SUITE_GREEN +2. Build/tests green: shellcheck on the test, bash syntax of the guard, the regex compiles, and the guard's own diff is clean (its new lines are not on a test path). [challenge: the former census clause was vacuous — the file no longer holds an `exit(` — and is dropped; SKIP_EXIT_CLEAN in criterion 1 is the regression proof] + CHECK: shellcheck lib/tests/floor-guard.test.sh && bash -n lib/floor-guard.sh && python3 -c "import re;re.compile(r'(?&1 | grep -q 'FLOOR GUARD: clean' && echo BUILD_OK + EXPECT: BUILD_OK + EVIDENCE: MET exit=0 marker-found :: BUILD_OK + +## FILE SCOPE +- lib/floor-guard.sh (SKIP_SUBSTRINGS + skip_kind), lib/tests/floor-guard.test.sh (two cases) diff --git a/.claude/tasks/plans/2026-09-28-floor-guard-xit-boundary-1648.md b/.claude/tasks/plans/2026-09-28-floor-guard-xit-boundary-1648.md new file mode 100644 index 0000000..f8d3a81 --- /dev/null +++ b/.claude/tasks/plans/2026-09-28-floor-guard-xit-boundary-1648.md @@ -0,0 +1,63 @@ +# PLAN — floor-guard-xit-boundary (hotfix, logic fix → challenged) — r2 +- r2 after 3 challengers (simplicity SOLID, correctness SOLID, robustness + CONCERNS(2)): SKIP_IDENT_RE sits right under SKIP_SUBSTRINGS; the waiver + instruction on the guard's own lines is gone (SKIP never scans + lib/floor-guard.sh: no `test`/`spec` in its path); the new fixture echo + lines in the TEST file carry `# floor-guard: allow flip-test fixture` + OUTSIDE the echoed string (the test path contains `test`, so a later scan + of that diff would flag the fixture itself); `def fit(` / `function xit(` + / `xit.each(` stay unmatched or matched as before and are recorded as a + `shortcut:` comment; fixtures extended to prove the whole alternation. +- date: 2026-09-28 | contract: contracts/2026-09-28-floor-guard-xit-boundary-1648.md +- branch: bugfix/floor-guard-xit-boundary | executor: hotfixer (sonnet) + +## Root cause +lib/floor-guard.sh:99-102 SKIP_SUBSTRINGS = ('.skip(', '.only(', 'xit(', +'xdescribe(', 'fit(', 'fdescribe(', 'it.todo(', '@pytest.mark.skip', +'@unittest.skip', 't.Skip('); :188-189 `skip_kind(text)` = `any(p in text …)`. +Plain substring: `xit(` ⊂ `exit(`, `SystemExit(`, `process.exit(`; `fit(` ⊂ +`profit(`, `model.fit(`. Only test files are scanned for SKIP (line_findings, +`if test_file:`), so the false positive hits inline python/JS helpers inside +test files — the 2026-09-28 case: `sys.exit(1 if violations else 0)` in +lib/tests/profile-census.test.sh (BLK-023). + +## The exact edit (lib/floor-guard.sh) +1. SKIP_SUBSTRINGS keeps only the dotted/decorator forms: + ('.skip(', '.only(', 'it.todo(', '@pytest.mark.skip', '@unittest.skip', 't.Skip('). +2. New `SKIP_IDENT_RE = re.compile(r'(? {});` + (leading spaces on purpose); run; `check_kind SKIP_XIT_FLAGS "$rc" 2 + "$out" 'FLOOR SKIP'`. Then two more repos in the same block proving the + rest of the alternation: `fit('focused', () => {});` → `check_kind + SKIP_FIT_FLAGS … 2 … 'FLOOR SKIP'`; `fdescribe('focused', () => {});` → + `check_kind SKIP_FDESCRIBE_FLAGS … 2 … 'FLOOR SKIP'`. +Header comment (:2-3) gains "plus boundary cases for SKIP" after "one CLEAN +fixture". + +## Not changed +Messages, exit codes, waiver syntax, other kinds, test harness helpers. diff --git a/CHANGELOG.md b/CHANGELOG.md index 46be703..29876a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,7 +81,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). `floor-guard: allow ` waiver, rc 0/2/3. Mandatory verifier STEP 3 (`agents/verifier.md`), documented under GATE 1 of `lib/verify-secure-loop.md`. Suite `lib/tests/floor-guard.test.sh`: 6 - kinds plus a WAIVED and a CLEAN fixture, each flip-tested. Waivers + kinds plus a WAIVED and a CLEAN fixture, each flip-tested, and SKIP + boundary fixtures (bare `xit(`/`fit(`/`xdescribe(`/`fdescribe(` are + word-bounded, so `exit(`, `model.fit(`, `profit(` stay clean). Waivers outside test files count as gaps unless the contract's CLARIFICATIONS names them (security-gate MEDIUM, user chose strict). Adapted from agent-skills `constraint-driven-development`. diff --git a/lib/floor-guard.sh b/lib/floor-guard.sh index a573fe0..6a35df9 100755 --- a/lib/floor-guard.sh +++ b/lib/floor-guard.sh @@ -97,9 +97,15 @@ SUPPRESS_SUBSTRINGS = ( TS_EXPECT_ERROR = '@ts-expect-error' # floor-guard: allow pattern table SKIP_SUBSTRINGS = ( - '.skip(', '.only(', 'xit(', 'xdescribe(', 'fit(', 'fdescribe(', - 'it.todo(', '@pytest.mark.skip', '@unittest.skip', 't.Skip(', + '.skip(', '.only(', 'it.todo(', '@pytest.mark.skip', '@unittest.skip', + 't.Skip(', ) +# bare Jasmine/Jest focus-or-skip calls (xit/fit/xdescribe/fdescribe); the +# lookbehind keeps `exit(`, `SystemExit(`, `model.fit(` out (BLK-023). +SKIP_IDENT_RE = re.compile(r'(? {});" >> "$d/sample.test.js" out=$(cd "$d" && bash "$LIB" "$base" 2>&1); rc=$? check_kind SKIP "$rc" 2 "$out" 'FLOOR SKIP' +# ── SKIP_EXIT_CLEAN ─────────────────────────────────────────────────────── +d=$(mk_repo skipexit); base=$(git -C "$d" rev-parse HEAD) +{ + echo 'process.exit(1); // sys.exit(1)' # floor-guard: allow flip-test fixture + echo 'model.fit(x);' # floor-guard: allow flip-test fixture + echo 'const p = profit(1);' # floor-guard: allow flip-test fixture +} >> "$d/sample.test.js" +out=$(cd "$d" && bash "$LIB" "$base" 2>&1); rc=$? +check_kind SKIP_EXIT_CLEAN "$rc" 0 "$out" 'FLOOR GUARD: clean' + +# ── SKIP_XIT_FLAGS / SKIP_FIT_FLAGS / SKIP_FDESCRIBE_FLAGS ──────────────── +d=$(mk_repo skipxit); base=$(git -C "$d" rev-parse HEAD) +echo " xit('skipped', () => {});" >> "$d/sample.test.js" # floor-guard: allow flip-test fixture +out=$(cd "$d" && bash "$LIB" "$base" 2>&1); rc=$? +check_kind SKIP_XIT_FLAGS "$rc" 2 "$out" 'FLOOR SKIP' + +d=$(mk_repo skipfit); base=$(git -C "$d" rev-parse HEAD) +echo "fit('focused', () => {});" >> "$d/sample.test.js" # floor-guard: allow flip-test fixture +out=$(cd "$d" && bash "$LIB" "$base" 2>&1); rc=$? +check_kind SKIP_FIT_FLAGS "$rc" 2 "$out" 'FLOOR SKIP' + +d=$(mk_repo skipfdescribe); base=$(git -C "$d" rev-parse HEAD) +echo "fdescribe('focused', () => {});" >> "$d/sample.test.js" # floor-guard: allow flip-test fixture +out=$(cd "$d" && bash "$LIB" "$base" 2>&1); rc=$? +check_kind SKIP_FDESCRIBE_FLAGS "$rc" 2 "$out" 'FLOOR SKIP' + # ── DELETED_TEST ────────────────────────────────────────────────────────── d=$(mk_repo deleted); base=$(git -C "$d" rev-parse HEAD) rm "$d/sample.test.js"