Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 94 additions & 27 deletions .github/workflows/dogfood-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,42 +124,109 @@ jobs:
# Inline invisible character detection (from empty-linter's core patterns).
# Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens,
# non-breaking spaces, null bytes, and other invisible Unicode in source files.
set +e
PATTERNS='\xc2\xa0|\xe2\x80\x8b|\xe2\x80\x8c|\xe2\x80\x8d|\xef\xbb\xbf|\xc2\xad|\xe2\x80\x8e|\xe2\x80\x8f|\xe2\x80\xaa|\xe2\x80\xab|\xe2\x80\xac|\xe2\x80\xad|\xe2\x80\xae|\x00'
find "$GITHUB_WORKSPACE" \
-not -path '*/.git/*' -not -path '*/node_modules/*' \
-not -path '*/.deno/*' -not -path '*/target/*' \
-not -path '*/_build/*' -not -path '*/deps/*' \
-not -path '*/external_corpora/*' -not -path '*/.lake/*' \
-type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \
-o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \
-o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \
-o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \
-o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \
-exec grep -Prl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null
EL_EXIT=$?
set -e

FINDINGS=$(wc -l < /tmp/empty-lint-results.txt 2>/dev/null || echo 0)
echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT"
echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT"
echo "ready=true" >> "$GITHUB_OUTPUT"

# Emit annotations for each file with invisible chars
while IFS= read -r filepath; do
[ -z "$filepath" ] && continue
REL_PATH="${filepath#$GITHUB_WORKSPACE/}"
echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)"
done < /tmp/empty-lint-results.txt
python3 - <<'PY'
import os
from pathlib import Path

root = Path(os.environ["GITHUB_WORKSPACE"])
skipped_dirs = {
".cache", ".deno", ".elixir_ls", ".git", ".lake", ".zig-cache",
"_build", "build", "coverage", "deps", "dist", "external_corpora",
"node_modules", "out", "target", "vendor", "zig-cache", "zig-out",
}
intentional_fixture_dirs = {
("tests", "fixtures", "bom-detection"),
("tests", "fixtures", "empty-linter"),
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
source_suffixes = {
".adoc", ".adb", ".ads", ".agda", ".c", ".cc", ".clj", ".cljs",
".cpp", ".erl", ".ex", ".exs", ".fs", ".fsi", ".fsx", ".gleam",
".h", ".hh", ".hpp", ".hrl", ".hs", ".idr", ".java", ".jl",
".js", ".json", ".kt", ".kts", ".lean", ".lua", ".md", ".ml",
".php", ".r", ".rb", ".res", ".rs", ".scala", ".sh", ".swift",
".toml", ".ts", ".v", ".yaml", ".yml", ".zig",
}
invisible_codepoints = {
0x00A0, 0x00AD, 0x2060, 0xFEFF,
*range(0x200B, 0x2010),
*range(0x202A, 0x2030),
*range(0x2066, 0x206A),
}

def command_escape(value):
return str(value).replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")

def property_escape(value):
return command_escape(value).replace(":", "%3A").replace(",", "%2C")

# Runtime regression for GitHub workflow-command property delimiters.
assert property_escape("docs/a,b::c.md") == "docs/a%2Cb%3A%3Ac.md"

def intentionally_invalid_fixture(relative):
return any(relative.parts[:len(prefix)] == prefix for prefix in intentional_fixture_dirs)

findings = []
errors = []
for directory, dirnames, filenames in os.walk(root, topdown=True):
dirnames[:] = [name for name in dirnames if name not in skipped_dirs]
directory_path = Path(directory)
for filename in filenames:
path = directory_path / filename
relative = path.relative_to(root)
if (
path.is_symlink()
or path.suffix.lower() not in source_suffixes
or intentionally_invalid_fixture(relative)
):
continue
try:
data = path.read_bytes()
except OSError as error:
errors.append((relative, f"could not read file: {error}"))
continue

reasons = set()
if data.startswith(b"\xef\xbb\xbf"):
reasons.add("leading UTF-8 BOM")
if any(byte <= 0x08 or byte in (0x0B, 0x0C) or 0x0E <= byte <= 0x1F for byte in data):
reasons.add("C0 control character")
try:
text_content = data.decode("utf-8", errors="strict")
except UnicodeDecodeError as error:
errors.append((relative, f"invalid UTF-8 at byte {error.start}"))
continue
if any(ord(character) in invisible_codepoints for character in text_content):
reasons.add("invisible Unicode code point")
if reasons:
findings.append((relative, ", ".join(sorted(reasons))))

with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
output.write(f"findings={len(findings)}\n")
output.write(f"exit_code={2 if errors else 0}\n")
output.write("ready=true\n")

for relative, reasons in findings:
print(f"::warning file={property_escape(relative)}::Invisible characters detected: {command_escape(reasons)}")
for relative, reason in errors:
print(f"::error file={property_escape(relative)}::Invisible-character scan failed: {command_escape(reason)}")
PY

- name: Write summary
run: |
if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then
FINDINGS="${{ steps.lint.outputs.findings }}"
EXIT_CODE="${{ steps.lint.outputs.exit_code }}"
if [ "$EXIT_CODE" -ne 0 ] 2>/dev/null; then
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo ":x: Scanner execution failed; see error annotations above." >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY"
exit 1
else
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ permissions:

jobs:
governance:
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@84355587cb2a1f86e6882de83514a32db2646e7a
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@6b38eb50104901e2fec80f9455a972bc3eced813
1 change: 0 additions & 1 deletion aletheia/.github/workflows/governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,3 @@ permissions:
jobs:
governance:
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@861b5e911d9e5dcfb3c0ab3dd2a9a3c8fd0a1613
timeout-minutes: 10
2 changes: 1 addition & 1 deletion docs/proof-debt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ the canonical `+hyperpolymath/absolute-zero+` repo upstream and bump the
submodule pin here (`+git submodule update --remote absolute-zero+` then
commit).
. If a *new* proof-bearing file appears *outside* `+absolute-zero/+`
(e.g. under `+aletheia/+` for the Rust verification pipeline, or under
(e.g. under `+aletheia/+` for the Rust verification pipeline, or under
`+contractiles/+`), classify it into §(a)/(b)/(c)/(d) per the canonical
schema. That would be load-bearing maa-framework debt.

Expand Down
2 changes: 1 addition & 1 deletion docs/tech-debt-2026-05-26.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ soundness-relevant escape hatches in Haskell/Rust source.

*Recommended next move:* triage each finding into one of: (a) discharge
by proof, (b) cover with property-tests + a documented refutation
budget, or (c) annotate as a known/necessary axiom (e.g. `+funExt+`) in
budget, or (c) annotate as a known/necessary axiom (e.g. `+funExt+`) in
`+docs/proof-debt.md+`.

=== 2. Licence debt
Expand Down
Loading