scripts/sync_c2pa_soft_bindings.py --check hits api.github.com to resolve
the latest upstream commit SHA, unauthenticated. That endpoint's 60/hour
rate limit is per source IP, which GitHub Actions runners exhaust fast
since many workflows share the runner IP pool -- this was failing the
lint job with "403: rate limit exceeded" on every push regardless of
whether the change touched anything related.
Send an Authorization: Bearer header built from GITHUB_TOKEN/GH_TOKEN
when set, scoped to api.github.com only (raw.githubusercontent.com is a
separate, much higher-limit CDN and doesn't need it). Wire the workflow's
own GITHUB_TOKEN secret into that step's env so CI actually has one to
send, raising the limit to 5000/hour.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The OpenArt visible-mark engine (commit 9d29d2c) was registered without
a data/fixtures/visible/openart/example.png gallery canary and without
an _ALTERNATE_SIZE entry in test_visible_fill_quality.py, breaking
TestGallery::test_every_registered_mark_has_an_example,
TestGallery::test_engine_detects_its_own_example[openart], and
test_cv2_mask_quality_smoke[alternate-openart] on every CI platform.
Generate the fixture via render_visible_examples.py (self-detects at
conf=0.968) and add the missing size entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every existing text-mark engine (Doubao, Jimeng, Qwen, Baidu, Kling,
Yuanbao) is tuned to one vendor's exact wordmark silhouette, so none of
them match a bare "AI生成" strip with no brand name in front of it.
Confirmed on raiw-app production: a free `visible` run left a bottom-right
"AI生成" watermark completely unremoved because no detector fired.
Research found the wording is not tied to one dominant generator: vivo's
own support documentation confirms its Gallery stamps a bare "AI生成"
label after AI消除/AI扩图/大片模式 editing, and independent reporting
documents the same watermark on Xiaomi Gallery's AI-edit output. Since
the label is a GB 45438-2025/TC260 compliance stamp rather than one
vendor's brand mark, this adds one generic fallback (`generic_ai_label`)
instead of another per-OEM tuned config.
The detection silhouette is font-rendered (no logo element to capture)
via the shared scripts/render_vendor_silhouettes.py pipeline already
used for Qwen/Baidu/Kling/Yuanbao's own text-only marks. Calibration is
synthetic-only (no real captured generator screenshot was available);
the module docstring and DETECT_NCC_THRESHOLD comment carry that caveat
explicitly and this should be recalibrated once real captures exist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
OpenArt.ai stamps free-plan generations with a semi-transparent white
"OpenArt" wordmark (bowtie/infinity icon + brand name) placed near the
CENTER of the frame rather than in a corner. Confirmed from a customer-
reported raiw-app production case where the mark went completely
undetected and unremoved, with no C2PA/EXIF/TC260 signal for identify
to fall back on.
- Add a new "cc" (frame-center) corner mode to the shared
TextMarkConfig/TextMarkEngine base, alongside the existing
corner/edge anchors.
- Add openart_engine.py, following the existing text-mark engine
pattern. No real capture exists (OpenArt requires an account, out of
scope to create), so the bundled alpha asset is a procedural
reconstruction of the public logo and the NCC threshold is an
explicitly-labeled provisional guess, not a measured calibration.
- Register the mark in watermark_registry.py, which auto-derives
identify's platform attribution.
- Add tests/test_openart_engine.py (synthetic composites only) plus
registry/identify test coverage, and document the mark and its
calibration caveats in README.md, docs/supported-signals.md,
docs/module-internals.md, and the skill's commands.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A decorative sparkle/glint glyph in stylized typography (e.g. a book-cover
title) can clear the visible Gemini sparkle's false-positive gate at
confidence just under _SPARKLE_FP_CONF, since it shares the same bright,
near-white, sharp four-point signature the gate uses to recognize a genuine
sparkle. Confirmed against a raiw-app production incident (2026-08-27) where
this caused the removal step to permanently smudge unrelated cover art.
Investigated tightening _SPARKLE_FP_CONF and the margin/gradient/saturation
checks (plus a prototyped local edge-density signal); each was disproved by
a constructed counter-example showing a genuine weak Gemini sparkle over a
noisy/textured photo background lands in the same confidence band with the
same signature, so no safe targeted fix exists without a real labeled
corpus. No detection/removal behavior changed.
Adds DEBUG logging at the exact FP-gate bypass path for future calibration,
a regression test (TestDecorativeGlyphFalsePositive) pinning the confirmed
false positive and the blocking counter-example, and a docs record of the
finding in the Gemini sparkle section of module-internals.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The video full-clip job needs ffmpeg from Ubuntu's own repositories, but its
apt-get update refreshed every source on the runner image, so a broken
third-party index failed the job twice on 2026-09-09: google-chrome.list
served a Hash Sum mismatch (bc1428ab... against 233e56de...) and both the run
and its rerun died at Install ffmpeg with ten otherwise-green jobs around
them. Remove every non-Ubuntu source definition from
/etc/apt/sources.list.d before updating, so no vendor's broken index can
fail a step that never needed it. The only apt-get in the workflows.
pre-commit: 1) maintain.sh - exit 2 at the uv-secure gate, the three triaged
no-fix advisories in docs/development.md (rechecked 2026-09-09 against PyPI);
ruff check+format clean, pyright src/ 0 errors, 2368 passed 3 skipped run
separately, same gate run as the preceding commit with no post-gate edit
between them; 2) /simplify - single pass, no fan-out available, no findings;
3) docs sync - grepped apt-get/google-chrome/Install ffmpeg across docs/,
README, .claude/rules/, tests/: no other references, the rationale lives in
the step's comment; 4) CLAUDE.md - audited against the structure rule, no
additions, revise-claude-md: no additions
The listing's MIT-0 next to the repository's Apache-2.0 was documented as a
drift to repair in the ClawHub listing itself. It is not repairable and not
drift: ClawHub's own client pins the platform skill license
(dist/schema/license.js sets PLATFORM_SKILL_LICENSE = "MIT-0", and the
version schema accepts only "MIT-0" or null), so every skill published there
is offered under MIT No Attribution. Publishing to ClawHub means offering
this skill on those terms in addition to Apache-2.0; the field is not
editable and the mismatch must not be read as drift.
pre-commit: 1) maintain.sh - exit 2 at the uv-secure gate, the three triaged
no-fix advisories in docs/development.md (rechecked 2026-09-09 against PyPI);
ruff check+format clean, pyright src/ 0 errors, 2368 passed 3 skipped run
separately; 2) /simplify - single pass, no fan-out available, prose-only
change, no findings; 3) docs sync - MIT-0/ClawHub grepped across docs/,
README, .claude/rules/, skills/: only release-and-distribution.md describes
the publish flow and stays accurate, the audit doc row is a dated snapshot,
no refs to update; 4) CLAUDE.md - audited against the structure rule, no
additions, revise-claude-md: no additions
pillow-heif's opener rewrites the EXIF orientation to 1 and stashes the real
value in info["original_orientation"], while libheif turns the raster on decode
exactly when the container declares an effective irot/imir transform. A camera
HEIC (stored raster, EXIF-only rotation) and a re-encoded one (rotation baked
as irot) both decode with Image.size matching the raster and the tag blanked,
so _read_display_tags dropped the turn for both (issue #105): a camera HEIC
went through erase and came out sideways with nothing on it saying so.
For HEIF the caller's orientation_applied claim is now ignored the way it is
for TIFF, and the container decides: isobmff.heif_transform_applied scans
meta/iprp/ipco for an irot with a non-zero angle or any imir, the orientation
comes from original_orientation, and a transform-bearing file drops the tag.
AVIF keeps the generic path: this plugin build neither blanks its tag nor
applies the transform there, and a camera-style AVIF's orientation stays
unreachable from Pillow (recorded in known-limitations).
Tests build the camera fixture without exiftool: a pillow-heif-written file
with its irot angle neutralized to the identity behaves like the reporter's
exiftool-built camera file. Mutation-checked in both directions: forcing the
detector either way fails exactly the tests whose property broke.
pre-commit: 1) maintain.sh - exit 2 at the uv-secure gate, the three triaged
no-fix advisories in docs/development.md (rechecked 2026-09-09: accelerate
1.15.0 and lightning 2.6.5 still carry no fix); ruff check+format clean,
pyright src/ 0 errors, 2368 passed 3 skipped run separately; 2) /simplify -
single pass, no fan-out available, one duplicate test helper inlined, no
other findings; 3) docs sync - heif_transform_applied, original_orientation,
irot, imir live only in module-internals.md and known-limitations.md, both
updated, no other refs; 4) CLAUDE.md - audited against the structure rule,
no additions; revise-claude-md: no CLAUDE.md changes
Both readers in the imread stack apply the TIFF orientation tag (cv2's
libtiff path under IMREAD_UNCHANGED too, and Pillow's TIFF plugin,
identically), so the visible path's blanket orientation_applied=False
carried the tag onto an already-turned raster. _read_display_tags now
ignores the explicit claim for TIFF and decides on the IFD's stored size,
which also keeps a hypothetical non-turning build correct: a stored-shape
raster still carries the tag. Guarded at the api seam, not just imwrite.
pre-commit: 1) maintain.sh - exit 2 at the uv-secure gate, the three
triaged no-fix advisories in docs/development.md (rechecked 2026-09-08);
ruff check+format clean, pyright src/ 0 errors, 2363 passed 3 skipped
run separately; 2) /simplify - single pass, no fan-out available, nothing
to fix (api-state plumbing rejected as disproportionate); 3) docs sync -
identifiers live only in docs/module-internals.md, updated, no other
refs; 4) CLAUDE.md - audited against the structure rule, no additions,
revise-claude-md: no additions
Resolve image_io.py and test_image_io.py the same way the rebased
adaptation branch does, so the branch carries the author's original
commit 1cfdf19 on top of current main.