Four defects found while standing the core service up and auditing two real trees with it: a 320-file manuscript folder (French and English prose plus 295 audio files) and a 439-file Next.js repo. Each one has a reproduction below. Fixes follow in three focused PRs.
Version: main at 474be13, core image built from service/Dockerfile, no optional profiles.
1. /capabilities reports ffmpeg: false on an image where ffmpeg works
_tool_usable() probes with _VERSION_FLAG.get(cmd, "--version"), and _VERSION_FLAG has no entry for ffmpeg. ffmpeg has no --version; it exits 8.
$ docker compose exec -T wr-core sh -lc 'command -v ffmpeg; ffmpeg -version >/dev/null 2>&1; echo "-version exit=$?"; ffmpeg --version >/dev/null 2>&1; echo "--version exit=$?"'
/usr/bin/ffmpeg
-version exit=0
--version exit=8
$ curl -s http://127.0.0.1:8765/capabilities | grep ffmpeg
"ffmpeg": false,
Only the report is wrong: clean_audio.py and clean_video.py resolve the binary with which(). But a capability report is what a caller reads before deciding a job is impossible, so a false negative there reads as a clean verdict about a tool nobody ran.
2. ghostscript is advertised but never installed in the core image
/capabilities lists it and _ghostscript_usable() probes for it, yet service/Dockerfile does not install it.
$ docker compose exec -T wr-core sh -lc 'command -v gs || echo ABSENT'
ABSENT
Deep PDF work is therefore unavailable on an image that claims to support it.
3. The audit counts source files it never reads
TEXT_EXTS carries .js but none of .ts, .tsx, .jsx, .mjs, .cjs, and no .gd/.gdshader. classify() returns unknown, the per-file note says "unrecognized format; not scanned", and the summary still counts the file under Files scanned.
Witness, seven files each carrying exactly one U+200B:
$ audit_dir.py /data
Files scanned: 7
By kind: {'text': 3, 'unknown': 3, 'markdown': 1}
Actionable files: 4
[probable] /data/sample.css: layer-a [zwj_family] U+200B ZERO WIDTH SPACE (Cf) x1
[probable] /data/sample.md: layer-a: U+200B ... (zwj_family)
[probable] /data/sample.py: layer-a [zwj_family] U+200B ZERO WIDTH SPACE (Cf) x1
[probable] /data/sample.txt: layer-a [zwj_family] U+200B ZERO WIDTH SPACE (Cf) x1
sample.ts, sample.tsx and sample.gd carry the same character and are silent.
On a real Next.js repo: Files scanned: 439, Files skipped: 0, of which 184 were unknown and never read. The headline overstates coverage by 42%, and a zero-width carrier in a .tsx string passes the audit as clean.
4. A Layer A space changes confidence with the file extension
text_hit_confidence() returns informational for kind == "space", with the comment that a space homoglyph is weaker context than an invisible carrier. Container formats (markdown, HTML) are classified by classify_finding_confidence() instead, which has no such rule.
Identical bytes, two names:
$ audit_dir.py /data
Actionable files: 1
[probable] /data/Nova.md: layer-a: U+00A0 NO-BREAK SPACE (Zs) x150 (space)
[informational] /data/Nova.txt: layer-a [space] U+00A0 NO-BREAK SPACE (Zs) x150
is_actionable() is what check_staged.py reads to fail a commit, so the extension alone decides whether the gate fires.
Measured on a French manuscript: 430 correct non-breaking spaces across four chapters, every one reported as a probable AI mark because the chapters are .md. Those are French typography, the space before ; : ! ? and inside « ». The watermarks-remover-check hook would fail every commit touching them, and watermarks-remover-clean would strip all 430 in place, since clean_file.py exposes no space option and clean_staged.py invokes it with a fixed argv.
Related, and reported separately in the third PR: ALLOWED_CLEAN_OPTIONS has no normalize_spaces, so a caller going through /clean cannot ask for what clean_text.py --no-normalize-spaces already does.
Happy to split, reshape or drop any of these if you would rather handle them differently.
Four defects found while standing the core service up and auditing two real trees with it: a 320-file manuscript folder (French and English prose plus 295 audio files) and a 439-file Next.js repo. Each one has a reproduction below. Fixes follow in three focused PRs.
Version:
mainat 474be13, core image built fromservice/Dockerfile, no optional profiles.1.
/capabilitiesreportsffmpeg: falseon an image where ffmpeg works_tool_usable()probes with_VERSION_FLAG.get(cmd, "--version"), and_VERSION_FLAGhas no entry for ffmpeg. ffmpeg has no--version; it exits 8.Only the report is wrong:
clean_audio.pyandclean_video.pyresolve the binary withwhich(). But a capability report is what a caller reads before deciding a job is impossible, so a false negative there reads as a clean verdict about a tool nobody ran.2. ghostscript is advertised but never installed in the core image
/capabilitieslists it and_ghostscript_usable()probes for it, yetservice/Dockerfiledoes not install it.Deep PDF work is therefore unavailable on an image that claims to support it.
3. The audit counts source files it never reads
TEXT_EXTScarries.jsbut none of.ts,.tsx,.jsx,.mjs,.cjs, and no.gd/.gdshader.classify()returnsunknown, the per-file note says "unrecognized format; not scanned", and the summary still counts the file underFiles scanned.Witness, seven files each carrying exactly one U+200B:
sample.ts,sample.tsxandsample.gdcarry the same character and are silent.On a real Next.js repo:
Files scanned: 439,Files skipped: 0, of which 184 wereunknownand never read. The headline overstates coverage by 42%, and a zero-width carrier in a.tsxstring passes the audit as clean.4. A Layer A space changes confidence with the file extension
text_hit_confidence()returnsinformationalforkind == "space", with the comment that a space homoglyph is weaker context than an invisible carrier. Container formats (markdown, HTML) are classified byclassify_finding_confidence()instead, which has no such rule.Identical bytes, two names:
is_actionable()is whatcheck_staged.pyreads to fail a commit, so the extension alone decides whether the gate fires.Measured on a French manuscript: 430 correct non-breaking spaces across four chapters, every one reported as a probable AI mark because the chapters are
.md. Those are French typography, the space before; : ! ?and inside« ». Thewatermarks-remover-checkhook would fail every commit touching them, andwatermarks-remover-cleanwould strip all 430 in place, sinceclean_file.pyexposes no space option andclean_staged.pyinvokes it with a fixed argv.Related, and reported separately in the third PR:
ALLOWED_CLEAN_OPTIONShas nonormalize_spaces, so a caller going through/cleancannot ask for whatclean_text.py --no-normalize-spacesalready does.Happy to split, reshape or drop any of these if you would rather handle them differently.