fix(cli): stop silent passthrough when visible finds no known mark

When `visible --mark auto` (or an explicit `--mark` with detection on) found
no registered mark, it exited 0 without writing output -- which a wrapping
service reads as success and re-serves the unchanged input. ~74% of real
uploads carry no registered visible mark, so this was the dominant "it didn't
work" / NPS score-0 failure mode.

Now it runs a cheap metadata-only identify, prints actionable guidance (route
to `all` for an invisible/metadata mark, or `erase` for an arbitrary logo),
writes no output file, and exits EXIT_NO_VISIBLE_MARK (2) -- distinct from
success (0) and a hard error (1) so the caller can surface the message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-06-12 21:36:56 -07:00
co-authored by Claude Opus 4.8
parent b08405bece
commit 30b56f0ea3
4 changed files with 74 additions and 7 deletions
+26
View File
@@ -125,6 +125,32 @@ class TestVisibleCommand:
assert "visible AI watermark" in result.output
assert "--mark" in result.output
def test_visible_auto_no_mark_exits_two_with_eraser_hint(self, runner, sample_png, tmp_path):
# No known visible mark and no AI provenance signal: the command must not
# re-serve the input as a finished result. It exits EXIT_NO_VISIBLE_MARK
# (2) -- distinct from success (0) and a hard error (1) -- writes no
# output file, and points the user at the region eraser.
output = tmp_path / "clean.png"
result = runner.invoke(main, ["visible", str(sample_png), "-o", str(output)])
assert result.exit_code == 2, result.output
assert not output.exists()
assert "erase" in result.output
def test_visible_auto_no_mark_routes_to_all_when_metadata(self, runner, tmp_path):
# An image whose only signal is an invisible/metadata watermark (here SD
# generation parameters) has no visible mark to remove; the command must
# exit 2 and upsell the full 'all' pipeline rather than the eraser.
img = Image.fromarray(np.random.default_rng(0).integers(0, 255, (200, 200, 3), dtype=np.uint8))
pnginfo = PngInfo()
pnginfo.add_text("parameters", "Steps: 20, Sampler: Euler, a test landscape")
src = tmp_path / "ai.png"
img.save(src, pnginfo=pnginfo)
output = tmp_path / "clean.png"
result = runner.invoke(main, ["visible", str(src), "-o", str(output)])
assert result.exit_code == 2, result.output
assert not output.exists()
assert "all" in result.output
def test_visible_basic(self, runner, sample_png, tmp_path):
output = tmp_path / "clean.png"
result = runner.invoke(