-
-
Notifications
You must be signed in to change notification settings - Fork 0
fix(ci): the invisible-character gate never matched anything #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
57ad5da
b3fbe01
bd73a39
f045828
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| = Invisible Character Detection | ||
| :toc: | ||
| :toclevels: 3 | ||
|
|
||
| == Overview | ||
|
|
||
| The invisible character detection gate (empty-linter) enforces file hygiene by detecting C0 control characters, NUL bytes, and leading UTF-8 BOM markers that indicate file corruption or encoding issues. | ||
|
|
||
| == Implementation | ||
|
|
||
| === Location | ||
|
|
||
| The canonical implementation for anvomidaviser is in `.github/workflows/dogfood-gate.yml` (Job 3: empty-lint). | ||
|
|
||
| === Detection Rules | ||
|
|
||
| ==== BLOCKING (fails the CI gate) | ||
|
|
||
| * *C0 Control Characters*: `\x00-\x08`, `\x0B`, `\x0C`, `\x0E-\x1F` | ||
| ** These indicate file corruption (backspace mangled LaTeX, made workflows unloadable) | ||
| ** Detection: PCRE byte-wise pattern `\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]` | ||
| ** Locale-independent (uses exact hex bytes, not character classes) | ||
|
|
||
| * *Leading UTF-8 BOM*: EF BB BF at file position 0 | ||
| ** Not needed for UTF-8 files (UTF-8 is self-identifying) | ||
| ** Causes parser issues in some tools | ||
| ** Detection: byte-wise check `head -c 3 file | od -An -tx1 | tr -d ' '` equals `efbbbf` | ||
|
|
||
| ==== ADVISORY (warning only, does not fail) | ||
|
|
||
| * *Invisible Unicode*: NBSP (`\x{a0}`), soft hyphen (`\x{ad}`), zero-width marks, BOM mid-file | ||
| ** About 2,100 estate files carry these as legitimate typography in prose | ||
| ** Detection included in main PCRE pattern but not enforced | ||
|
|
||
| === Pattern Evolution | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| # Before bd73a39 (broken, never matched anything): | ||
| PATTERNS='\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]|...' | ||
|
|
||
| # After bd73a39 (locale-independent, consolidated ranges): | ||
| PATTERNS='(*UTF)[\x00-\x08\x0B\x0C\x0E-\x1F\x{a0}\x{ad}\x{200b}-\x{200f}\x{202a}-\x{202f}\x{2060}\x{2066}-\x{2069}\x{feff}]' | ||
| ---- | ||
|
|
||
| The `(*UTF)` prefix enables UTF-8 mode in PCRE while keeping the byte-level C0 detection locale-independent. | ||
|
|
||
| === Enforcement Strategy | ||
|
|
||
| Per commit b3fbe01, enforcement happens *inside* the scan step: | ||
|
|
||
| 1. Main pattern detects all invisible characters | ||
| 2. Blocking subset re-greps only flagged files for C0/NUL | ||
| 3. Leading-BOM check uses byte-wise `head | od` on flagged files | ||
| 4. If scanner crashes, job fails directly (no silent pass) | ||
|
|
||
| This prevents drift between detection and enforcement expressions. | ||
|
|
||
| === Cross-Repo Synchronization | ||
|
|
||
| NOTE: `stdlib/ByteDetector.affine` and `config.ncl` do not exist in anvomidaviser. The formal/shared implementation lives in the estate-wide iseriser repository and will be updated to match these rules in a separate cross-repo PR. | ||
|
|
||
| == Testing | ||
|
|
||
| === Test Fixtures | ||
|
|
||
| Test fixtures are in `tests/fixtures/invisible-chars/`: | ||
|
|
||
| * `clean.rs` — No invisible characters (should PASS) | ||
| * `legitimate-whitespace.rs` — Tabs, newlines, spaces (should PASS) | ||
| * `leading-bom.rs` — UTF-8 BOM at position 0 (should BLOCK) | ||
| * `nul-byte.rs` — NUL byte (should BLOCK) | ||
| * `backspace.rs` — Backspace C0 control (should BLOCK) | ||
| * `vertical-tab.rs` — Vertical tab C0 control (should BLOCK) | ||
| * `form-feed.rs` — Form feed C0 control (should BLOCK) | ||
| * `escape.rs` — Escape character C0 control (should BLOCK) | ||
|
|
||
| === Running Tests Locally | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| ./tests/test-invisible-char-detection.sh | ||
| ---- | ||
|
|
||
| This script verifies both C0 detection and leading-BOM detection against all fixtures. | ||
|
|
||
| === CI Exclusions | ||
|
|
||
| The CI gate excludes test fixtures via: | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| find ... -not -path '*/tests/fixtures/invisible-chars/*' ... | ||
| ---- | ||
|
|
||
| == Design Rationale | ||
|
|
||
| === Why Block Leading BOM? | ||
|
|
||
| 1. UTF-8 BOM (EF BB BF) is *not* required for UTF-8 files | ||
| 2. UTF-8 is self-identifying (no BOM needed unlike UTF-16/32) | ||
| 3. Some tools (parsers, compilers) choke on leading BOM | ||
| 4. Mid-file BOM stays advisory (may be legitimate in prose/data) | ||
|
|
||
| === Why Byte-Wise Detection? | ||
|
|
||
| 1. *Locale independence*: PCRE character classes like `[:cntrl:]` vary by locale | ||
| 2. *Precision*: Exact hex bytes ensure consistent behavior across systems | ||
| 3. *Safety*: No risk of misinterpretation in different environments | ||
|
|
||
| === Why Separate Detection from Enforcement? | ||
|
|
||
| 1. *Full visibility*: Detect everything first (2,100+ files have legit invisible Unicode) | ||
| 2. *Targeted blocking*: Only fail on proven-dangerous corruption | ||
| 3. *Fail-safe*: Scanner crash fails the job (no silent pass on empty results) | ||
|
|
||
| == Related Commits | ||
|
|
||
| * `bd73a39`: Make invisible-character PCRE locale-independent | ||
| * `b3fbe01`: Enforce C0/NUL corruption in-step; warn on invisible Unicode | ||
| * `57ad5da`: Fix invisible-character gate (never matched anything before) | ||
| * Current commit: Add leading-BOM byte-wise check | ||
|
|
||
| == References | ||
|
|
||
| * Owner ruling 2026-08-28: C0/NUL/leading-BOM blocks; other invisible Unicode advisory | ||
| * Gate-lens census: ~2,100 estate files carry NBSP/zero-width as legit typography | ||
| * LaTeX/workflow corruption incidents: Backspace bytes caused silent mangling |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| = Invisible Character Test Fixtures | ||
|
|
||
| Test files for the invisible character detection gate (empty-linter). | ||
|
|
||
| == File Inventory | ||
|
|
||
| === Clean Files (Should NOT be flagged) | ||
|
|
||
| * `clean.rs` — No invisible characters at all | ||
| * `legitimate-whitespace.rs` — Tabs, newlines, spaces (all legitimate) | ||
|
|
||
| === Files with C0 Control Characters (BLOCKING - corruption) | ||
|
|
||
| * `nul-byte.rs` — Contains NUL byte (\x00) | ||
| * `backspace.rs` — Contains backspace (\x08) | ||
| * `vertical-tab.rs` — Contains vertical tab (\x0B) | ||
| * `form-feed.rs` — Contains form feed (\x0C) | ||
| * `escape.rs` — Contains escape character (\x1B) | ||
|
|
||
| === Files with Leading BOM (BLOCKING - see CI gate rule) | ||
|
|
||
| * `leading-bom.rs` — UTF-8 BOM at file start (EF BB BF / U+FEFF) | ||
|
|
||
| == Usage | ||
|
|
||
| These fixtures are used to test the invisible character detection in `.github/workflows/dogfood-gate.yml`. | ||
|
|
||
| The gate distinguishes: | ||
|
|
||
| * *BLOCKING*: C0 control characters (\x00-\x08, \x0B, \x0C, \x0E-\x1F) and leading BOM | ||
| * *ADVISORY*: Other invisible Unicode (NBSP, zero-width marks, BOM not at file start) | ||
|
|
||
| == Notes | ||
|
|
||
| The leading BOM check enforces byte-wise detection to catch UTF-8 BOM (EF BB BF) at position zero, as it can cause parser issues in some tools and is not needed for UTF-8 files. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // File with backspace | ||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // Clean Rust file with no invisible characters | ||
| fn main() { | ||
| println!("Hello, world!"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // File with escape | ||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // File with form feed | ||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // Rust file with leading BOM | ||
| fn main() { | ||
| println\!("BOM at start"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // File with legitimate whitespace (tabs, newlines, spaces) | ||
| fn calculate() -> i32 { | ||
| let x = 42; // tab before this comment | ||
| let y = 10; | ||
|
|
||
| x + y | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // File with vertical tab | ||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| #!/usr/bin/env bash | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # Test script for invisible character detection (local verification) | ||
| # This tests the logic from .github/workflows/dogfood-gate.yml | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| FIXTURES_DIR="${SCRIPT_DIR}/fixtures/invisible-chars" | ||
|
|
||
| echo "Testing invisible character detection..." | ||
| echo | ||
|
|
||
| # Test 1: Clean file should not trigger blocking | ||
| echo "Test 1: Clean file (should PASS)" | ||
| if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "${FIXTURES_DIR}/clean.rs"; then | ||
| echo " ❌ FAIL: Clean file triggered C0 detection" | ||
| exit 1 | ||
| fi | ||
| if [ "$(head -c 3 "${FIXTURES_DIR}/clean.rs" | od -An -tx1 | tr -d ' ')" = "efbbbf" ]; then | ||
|
Check failure on line 20 in tests/test-invisible-char-detection.sh
|
||
| echo " ❌ FAIL: Clean file triggered BOM detection" | ||
| exit 1 | ||
| fi | ||
| echo " ✓ PASS" | ||
| echo | ||
|
|
||
| # Test 2: Legitimate whitespace should not trigger blocking | ||
| echo "Test 2: Legitimate whitespace (should PASS)" | ||
| if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "${FIXTURES_DIR}/legitimate-whitespace.rs"; then | ||
| echo " ❌ FAIL: Legitimate whitespace triggered C0 detection" | ||
| exit 1 | ||
| fi | ||
| if [ "$(head -c 3 "${FIXTURES_DIR}/legitimate-whitespace.rs" | od -An -tx1 | tr -d ' ')" = "efbbbf" ]; then | ||
|
Check failure on line 33 in tests/test-invisible-char-detection.sh
|
||
| echo " ❌ FAIL: Legitimate whitespace triggered BOM detection" | ||
| exit 1 | ||
| fi | ||
| echo " ✓ PASS" | ||
| echo | ||
|
|
||
| # Test 3: Leading BOM should trigger blocking | ||
| echo "Test 3: Leading BOM (should BLOCK)" | ||
| if [ "$(head -c 3 "${FIXTURES_DIR}/leading-bom.rs" | od -An -tx1 | tr -d ' ')" = "efbbbf" ]; then | ||
|
Check failure on line 42 in tests/test-invisible-char-detection.sh
|
||
| echo " ✓ PASS: Leading BOM detected" | ||
| else | ||
| echo " ❌ FAIL: Leading BOM not detected" | ||
| exit 1 | ||
| fi | ||
| echo | ||
|
|
||
| # Test 4: NUL byte should trigger blocking | ||
| echo "Test 4: NUL byte (should BLOCK)" | ||
| if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "${FIXTURES_DIR}/nul-byte.rs"; then | ||
| echo " ✓ PASS: NUL byte detected" | ||
| else | ||
| echo " ❌ FAIL: NUL byte not detected" | ||
| exit 1 | ||
| fi | ||
| echo | ||
|
|
||
| # Test 5: Backspace should trigger blocking | ||
| echo "Test 5: Backspace (C0 control, should BLOCK)" | ||
| if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "${FIXTURES_DIR}/backspace.rs"; then | ||
| echo " ✓ PASS: Backspace detected" | ||
| else | ||
| echo " ❌ FAIL: Backspace not detected" | ||
| exit 1 | ||
| fi | ||
| echo | ||
|
|
||
| # Test 6: Vertical tab should trigger blocking | ||
| echo "Test 6: Vertical tab (C0 control, should BLOCK)" | ||
| if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "${FIXTURES_DIR}/vertical-tab.rs"; then | ||
| echo " ✓ PASS: Vertical tab detected" | ||
| else | ||
| echo " ❌ FAIL: Vertical tab not detected" | ||
| exit 1 | ||
| fi | ||
| echo | ||
|
|
||
| # Test 7: Form feed should trigger blocking | ||
| echo "Test 7: Form feed (C0 control, should BLOCK)" | ||
| if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "${FIXTURES_DIR}/form-feed.rs"; then | ||
| echo " ✓ PASS: Form feed detected" | ||
| else | ||
| echo " ❌ FAIL: Form feed not detected" | ||
| exit 1 | ||
| fi | ||
| echo | ||
|
|
||
| # Test 8: Escape character should trigger blocking | ||
| echo "Test 8: Escape character (C0 control, should BLOCK)" | ||
| if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "${FIXTURES_DIR}/escape.rs"; then | ||
|
Check warning on line 92 in tests/test-invisible-char-detection.sh
|
||
| echo " ✓ PASS: Escape character detected" | ||
| else | ||
| echo " ❌ FAIL: Escape character not detected" | ||
| exit 1 | ||
| fi | ||
| echo | ||
|
|
||
| echo "==========================================" | ||
| echo "All tests passed! ✓" | ||
| echo "==========================================" | ||
| echo | ||
| echo "Detection rules verified:" | ||
| echo " • C0 control characters (\\x00-\\x08, \\x0B, \\x0C, \\x0E-\\x1F): BLOCKING" | ||
| echo " • Leading BOM (EF BB BF at position 0): BLOCKING" | ||
| echo " • Legitimate whitespace (tabs, newlines, spaces): ALLOWED" | ||
| echo | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: hyperpolymath/anvomidaviser
Length of output: 8335
🌐 Web query:
GNU findutils manual -exec semicolon exit status child command find exit status💡 Result:
In the GNU findutils implementation of find, the behavior regarding exit statuses when using the -exec action with a semicolon (;) is as follows: The -exec command; action evaluates to true (returns a zero exit status) if the command being executed returns a 0 exit status [1][2][3]. Otherwise, it evaluates to false (returns a non-zero exit status) [3]. Crucially, this evaluation of the -exec action does not directly dictate the overall exit status of the find command itself [4][5][6]. The exit status of the find command follows these rules: 1. Overall find Exit Status: The find command exits with a status of 0 if all files are processed successfully [1][4]. It exits with a non-zero status if an error occurs [1][2]. This is a broad description, and if a non-zero exit status is returned, the results of the find command should not be considered reliable [1]. 2. Impact of -exec command;: The fact that an individual -exec invocation returns a non-zero (false) status does not cause the find command itself to exit with a non-zero status [4][5]. Find continues processing subsequent files as instructed, unless a specific action (like -quit) or a fatal error (such as a system-level failure to access a directory) interrupts the process [1][2][3]. 3. Contrast with -exec... +: In contrast, when using the -exec command {} + variant, if any invocation of the command returns a non-zero exit status, then the find command itself will return a non-zero exit status [2]. In summary, the exit status of a child command run via -exec...; affects the boolean evaluation of that specific -exec predicate within the find expression, but it does not propagate to the main find process's final exit status in the same way that the '+' variant does [2][3].
Citations:
Fail the step on scan errors, not only on findings.
The
EL_EXITbranch only emits a warning. If the scan does not produce complete results,blockingandFINDINGScan remain zero, and the step can exit successfully. The next step can then report a clean result.EL_EXIT=$?capturesfind, not eachgrepinvocation. With-exec ... \;, a per-filegreperror only makes that predicate false whilefindcontinues. Capture per-filegrepstatuses, treat status1as “no match”, and fail the step for statuses greater than1.🤖 Prompt for AI Agents
Source: MCP tools