Skip to content
Open
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
2 changes: 1 addition & 1 deletion .audittraining/path-reference-drift/TYPOLOGY.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ tree, prose, table |lexical
|*TP_self_drift* |the repo’s OWN manifest/doc asserts a layout that
contradicts its tree |TRUE positive |fix the doc

|*TP_real* |a real non-path defect (e.g. workflow missing
|*TP_real* |a real non-path defect (e.g. workflow missing
`+timeout-minutes+`) |TRUE positive |fix the source

|*FP_relative* |path is real but unanchored —
Expand Down
2 changes: 1 addition & 1 deletion .audittraining/release-candidates/REPORT.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ file in subdirectory)
* Consider publishing to crates.io (for Rust)
. *For bunsenite (existing releases):*
* Check commits since v1.0.2
* Review for breaking changes vs. patches
* Review for breaking changes vs. patches
* Follow semver for version bump
. *Manual verification needed:*
* supernorma - check deno.json
Expand Down
70 changes: 43 additions & 27 deletions .github/workflows/dogfood-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ jobs:

# ---------------------------------------------------------------------------
# Job 3: Empty-linter — invisible character detection
# Canonical scanner ported from rsr-template-repo (rsr#49): byte-safe two-tier
# design — typographic invisibles warn, C0/NUL corruption blocks. Toolset map:
# hyperpolymath/empty-linter docs/INTEGRATION.adoc
# ---------------------------------------------------------------------------
empty-lint:
name: Empty-linter (invisible characters)
Expand All @@ -142,39 +145,52 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v7.0.1

- name: Prove the scanner trips on known-bad fixtures
run: tests/invisible-characters-test.sh

- name: Scan for invisible characters
id: lint
run: |
# 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)
RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin"
BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin"
if ! scripts/check-invisible-characters.sh \
"$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then
echo "::error::Invisible-character scanner failed; refusing a partial pass"
exit 2
fi

FINDINGS=0
while IFS= read -r -d '' filepath; do
FINDINGS=$((FINDINGS + 1))
REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}"
SAFE_PATH="${REL_PATH//'%'/'%25'}"
SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}"
SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}"
SAFE_PATH="${SAFE_PATH//':'/'%3A'}"
SAFE_PATH="${SAFE_PATH//','/'%2C'}"
echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected"
done < "$RESULTS_FILE"

BLOCKING=0
while IFS= read -r -d '' filepath; do
BLOCKING=$((BLOCKING + 1))
REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}"
SAFE_PATH="${REL_PATH//'%'/'%25'}"
SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}"
SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}"
SAFE_PATH="${SAFE_PATH//':'/'%3A'}"
SAFE_PATH="${SAFE_PATH//','/'%2C'}"
echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected"
done < "$BLOCKING_FILE"

echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT"
echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT"
echo "blocking=$BLOCKING" >> "$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
if [ "$BLOCKING" -gt 0 ]; then
echo "## Empty-linter: BLOCKED — $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY"
exit 1
fi

- name: Write summary
run: |
Expand Down
2 changes: 1 addition & 1 deletion docs/proof-debt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ is the worst-case local-tree value the seed should accept without
flagging.

*Marker count (canonical / tracked):* 5. *Marker count (local-tree max,
incl. agent worktrees):* 15.
incl. agent worktrees):* 15.

This file is the *initial seed* — every marker starts in §(d) DEBT and
the maintainer triages each into §(a) / §(b) / §(c) / §(d) as
Expand Down
2 changes: 1 addition & 1 deletion docs/proofs/HANDOVER-neural-convergence.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ assertion; Agda retired) |✅
|parser totality |`+verification/proofs/lean4/ParserTotality.lean+` |✅

|ABI package + verify package |`+src/abi/*.idr+`
(incl. `+RuleEngine.idr+`), `+verify/src/*.idr+` |✅
(incl. `+RuleEngine.idr+`), `+verify/src/*.idr+` |✅

|*Neural convergence — PageRank*
|`+verification/proofs/lean4/PageRankInvariants.lean+` |⛔ preconditions
Expand Down
2 changes: 1 addition & 1 deletion docs/status/handover-2026-06-20.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ backlog, 71-alert code-scanning backlog.
4 low). Needs the repo’s Dependabot security tab or a
`+security_events+`-scoped token; no MCP tool exposes Dependabot
vulnerability alerts in-session. 0 open Dependabot PRs currently;
action-group bumps (e.g. #294) have merged since the issue was filed, so
action-group bumps (e.g. #294) have merged since the issue was filed, so
some lows may already be cleared.

=== Fresh-thread items
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
70 changes: 70 additions & 0 deletions scripts/check-invisible-characters.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# Byte-safe scanner for invisible Unicode encodings and forbidden C0 controls.
set -u

scan_root="${1:-}"
results_file="${2:-}"
blocking_results_file="${3:-}"
grep_bin="${INVISIBLE_GREP_BIN:-grep}"
find_bin="${INVISIBLE_FIND_BIN:-find}"

if [[ -z "$scan_root" || ! -d "$scan_root" || -z "$results_file" ]]; then
echo "usage: $0 SCAN_ROOT RESULTS_FILE" >&2
exit 2
fi

# Scan bytes under the C locale. This detects UTF-8 encodings even when another
# byte in the file is invalid UTF-8, while excluding permitted TAB/LF/CR bytes.
pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]|\xC2(?:\xA0|\xAD)|\xE2\x80[\x8B-\x8F\xAA-\xAF]|\xE2\x81(?:\xA0|[\xA6-\xA9])|\xEF\xBB\xBF'
blocking_pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]'
: > "$results_file" || exit 2
if [[ -n "$blocking_results_file" ]]; then
: > "$blocking_results_file" || exit 2
fi
scan_error=0
enumeration_file="$(mktemp /tmp/rsr-invisible-files.XXXXXX)" || exit 2
# Remove the temporary NUL-delimited file enumeration when the scanner exits.
# shellcheck disable=SC2329
cleanup() {
rm -f -- "$enumeration_file"
}
trap cleanup EXIT

if ! "$find_bin" "$scan_root" \
-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' \) \
-print0 > "$enumeration_file"; then
echo "file enumeration failed: $scan_root" >&2
exit 1
fi

while IFS= read -r -d '' filepath; do
LC_ALL=C "$grep_bin" -aPq "$pattern" "$filepath"
status=$?
case "$status" in
0)
printf '%s\0' "$filepath" >> "$results_file" || scan_error=1
if [[ -n "$blocking_results_file" ]]; then
LC_ALL=C "$grep_bin" -aPq "$blocking_pattern" "$filepath"
blocking_status=$?
case "$blocking_status" in
0) printf '%s\0' "$filepath" >> "$blocking_results_file" || scan_error=1 ;;
1) ;;
*) echo "blocking-classifier error ($blocking_status): $filepath" >&2; scan_error=1 ;;
esac
fi
;;
1) ;;
*) echo "scanner error ($status): $filepath" >&2; scan_error=1 ;;
esac
done < "$enumeration_file"

exit "$scan_error"
2 changes: 1 addition & 1 deletion src/ui/gossamer/BURBLE-DEFERRAL.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ lands:
dashboard expects Hypatia to emit findings/dispatches via a Burble
session rather than via a direct HTTP read of the harness endpoints.
. *Multi-operator session.* Two or more operators need to share Hypatia
state (e.g. a review seat watching the safety triangle live while
state (e.g. a review seat watching the safety triangle live while
another operator drives dispatches).
. *Voice control reaches the GUI.* Burble’s voice-control plane wants to
fire `+Msg.Navigate(Department.Verification)+` or similar from outside
Expand Down
93 changes: 93 additions & 0 deletions tests/invisible-characters-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
fixture_root="$(mktemp -d /tmp/rsr-invisible-test.XXXXXX)"
# cleanup removes the temporary fixture directory when its path matches the expected safe pattern.
cleanup() {
case "$fixture_root" in
/tmp/rsr-invisible-test.*) rm -rf -- "$fixture_root" ;;
*) echo "refusing unsafe cleanup target: $fixture_root" >&2 ;;
esac
}
trap cleanup EXIT

scanner="$repo_root/scripts/check-invisible-characters.sh"
results="$fixture_root/results.bin"
blocking_results="$fixture_root/blocking-results.bin"
fixtures="$fixture_root/fixtures"
mkdir -p "$fixtures"

printf 'tab\tline\ncarriage\rreturn\n' > "$fixtures/safe.md"
printf 'nbsp:\302\240\n' > "$fixtures/nbsp.md"
printf 'soft-hyphen:\302\255\n' > "$fixtures/soft-hyphen.adoc"
printf 'zero-width:\342\200\213\n' > "$fixtures/zero-width.json"
printf 'bidi:\342\200\256\n' > "$fixtures/bidi.toml"
printf 'word-joiner:\342\201\240\n' > "$fixtures/word-joiner.yml"
printf '\357\273\277leading bom\n' > "$fixtures/bom.sh"
printf 'nul:\000byte\n' > "$fixtures/nul.rs"
printf 'backspace:\010byte\n' > "$fixtures/backspace.rs"
printf 'invalid:\377 then nbsp:\302\240\n' > "$fixtures/invalid-utf8.md"
printf 'newline name:\302\240\n' > "$fixtures/with
newline.md"

"$scanner" "$fixtures" "$results" "$blocking_results"

count=0
safe_seen=false
newline_seen=false
while IFS= read -r -d '' filepath; do
count=$((count + 1))
[[ "$filepath" == "$fixtures/safe.md" ]] && safe_seen=true
[[ "$filepath" == "$fixtures/with"$'\n'"newline.md" ]] && newline_seen=true
done < "$results"

[[ "$count" -eq 10 ]] || {
echo "expected 10 findings, got $count" >&2
exit 1
}
[[ "$safe_seen" == false ]] || {
echo "TAB/LF/CR-only safe fixture was incorrectly reported" >&2
exit 1
}
[[ "$newline_seen" == true ]] || {
echo "newline-containing filename was not preserved as one record" >&2
exit 1
}

blocking_count=0
nul_blocked=false
backspace_blocked=false
while IFS= read -r -d '' filepath; do
blocking_count=$((blocking_count + 1))
[[ "$filepath" == "$fixtures/nul.rs" ]] && nul_blocked=true
[[ "$filepath" == "$fixtures/backspace.rs" ]] && backspace_blocked=true
done < "$blocking_results"
[[ "$blocking_count" -eq 2 && "$nul_blocked" == true && "$backspace_blocked" == true ]] || {
echo "expected only NUL and backspace fixtures in the blocking set" >&2
exit 1
}

if "$scanner" "$fixture_root/missing" "$results"; then
echo "missing scan root did not fail closed" >&2
exit 1
fi

failing_grep="$fixture_root/failing-grep"
printf '#!/usr/bin/env sh\nexit 2\n' > "$failing_grep"
chmod +x "$failing_grep"
if INVISIBLE_GREP_BIN="$failing_grep" "$scanner" "$fixtures" "$results"; then
echo "grep execution errors did not fail closed" >&2
exit 1
fi

failing_find="$fixture_root/failing-find"
printf '#!/usr/bin/env sh\nexit 2\n' > "$failing_find"
chmod +x "$failing_find"
if INVISIBLE_FIND_BIN="$failing_find" "$scanner" "$fixtures" "$results"; then
echo "find execution errors did not fail closed" >&2
exit 1
fi

echo "invisible-character scanner positive and negative controls passed"
Loading