Commit Graph
361 Commits
Author SHA1 Message Date
Victor Kuznetsov f020115ca2 Release 0.39.1 2026-09-09 18:51:48 -07:00
Victor Kuznetsov 96b8ab9ae2 Let the HEIF container, not the blanked tag, decide the orientation carry
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
2026-09-09 18:02:05 -07:00
Victor Kuznetsov 003dabfae4 Let the TIFF geometry override a caller's raw-decode claim
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
2026-09-09 12:56:14 -07:00
Victor Kuznetsov 4c090c5ae2 Merge main into fix/heif-and-tiff-orientation
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.
2026-09-09 12:56:14 -07:00
Victor Kuznetsov e556ed032a Release 0.39.0 with generator taxonomy and the LiblibAI pill
pre-commit: 1) maintain.sh - exit 2 (uv-secure on the documented
lightning PYSEC-2026-3624/GHSA-qqmf-gpg7-g8gw and accelerate
GHSA-4j2p-28q2-5m79 blocks, no fixes on PyPI; core checks separately:
C2PA sync current, Ruff clean with no rewrites, Pyright src/ 0 errors,
2359 passed 3 skipped); 2) /simplify - single-pass four angles, clean;
3) docs sync - grepped 0.38.0/0.39.0 over README, docs/, skills/,
rules/: no stale refs (0.38.0 hits are feature-first mentions or the
diffusers dependency); 4) CLAUDE.md - improver grade A, no changes;
revise: no additions
2026-09-08 20:14:35 -07:00
Victor Kuznetsov 36fed0594e Fix sharpening quantization and cross-platform regression tests
Round unsharp output in its existing buffer before uint8 conversion, preserving
constant images across OpenCV builds. Compare explicit stored WebP decodes.
Use host-absolute launcher fixtures and restrict Ubuntu Bash harnesses to POSIX.

Document the CI findings and the unresolved Dependabot Transformers/tokenizers
candidate conflict. Keep the existing model dependency constraints.
2026-09-08 19:25:44 -07:00
Victor Kuznetsov 5c6588b3ef Fix output preservation, research validation, and documentation drift
Repair image and metadata publication contracts, detection routing, research
artifact validation, release verification, and the published agent skill.
Refresh the dependency lock and reconcile documentation with verified behavior.

Add regression coverage and record the completed audit and corrected VideoSeal
measurements. Preserve the existing Accelerate and Lightning versions.

Validation: Ruff and Pyright pass; 2358 tests pass with 3 skipped. The maintenance
gate exits 2 on the documented Accelerate and Lightning security findings.
2026-09-08 19:07:10 -07:00
Victor Kuznetsov 56afcab241 Merge feat/model-freeze: generator taxonomy, model-side receipt gate
bytedance generator class (surgical six-head provider.pt), tc260 group
veto, stable-name receipt-gate.npz with the threshold inside the
artifact, snapshot-tolerant loader, weights pinned to revision
4c276376 (published via photo-classify-freeze-2026-09-07 release).
2026-09-08 14:18:04 -07:00
Victor Kuznetsov 3daca46589 Detect and remove LiblibAI compact top-left pill 2026-09-08 13:37:07 -07:00
Victor Kuznetsov 15f5fee7c3 Add unified provider oracle tooling 2026-09-08 13:26:43 -07:00
Victor KuznetsovandClaude Opus 5 e85307f1c0 Scrub private infrastructure detail and move the GPU harnesses out
The Content Seal manifest named the production GPU worker in its provenance
column and in two row keys, several docstrings described their sources as user
uploads from the private corpus, and that corpus was called by the storage
product holding it. Each is now what a public reader can check: a pipeline and
strength, material that is not publication-cleared, and a private corpus. The
corpus test follows the renamed rows. Deployment and tier detail goes the same
way, since which backend each hosting tier can afford is a product decision,
not a library fact.

Twelve harnesses that need a cloud GPU account leave scripts/. The research
pages that cite their findings name them as harnesses kept outside this
repository and stop implying a reader can rerun them; the tracked inputs and
recorded verdicts remain the durable record, and the offline analysis step
stays here with its test. A development invariant states the rule so the next
harness is not added back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:29:12 -07:00
Victor Kuznetsov c9672efd46 Pin classify weights to the 2026-09-07 snapshot revision
Published via publish-photo-classify-hf.yml (run 34188279796) from the
photo-classify-freeze-2026-09-07 release: six-head provider.pt, model-side
receipt-gate.npz, updated card and operating point. Older lib versions
keep their own pinned revisions byte-for-byte.
2026-09-07 21:57:11 -07:00
Victor Kuznetsov 8562c457a4 Ship generator taxonomy: bytedance class, model-side receipt gate
Provider classes now name the GENERATOR that rendered the pixels, never
the brand. bytedance joins openai/google/muse-image as the shared
ByteDance lineage (Doubao and Jimeng render with one model family;
measured: separate heads cross-fire 32/22 at 3.5x mass, the surgical
union holds 83.9% on the frozen test cell while openai/google/meta/no_ai
stay byte-identical to run1). tc260 stops being a public value: its head
remains as the group veto for the rest of China's generators (without
it, 207/379 China test rows would be falsely named openai or
muse-image), and the loader tolerates provider snapshots that predate
bytedance. The receipt gate moves model-side: stable-name artifact
receipt-gate.npz carries its threshold, head and threshold always load
from one file, and the publish script stages the stable gate from the
weights source. Microsoft stays metadata-only: 144 of its 253 bank rows
name an OpenAI model as their C2PA softwareAgent and none names a
Microsoft renderer.

pre-commit: 1) maintain.sh - exit 1 (uv-secure: lightning 2.6.5 via
trustmark, PYSEC-2026-3624 no fix released; per docs/development.md the
core checks ran separately: ruff clean, pyright 0 errors, 2059 passed
3 skipped); 2) /simplify - single-pass four angles, clean, one noted
skip (gate-test npz staging duplication); 3) docs sync - grepped
bytedance/receipt-gate.npz/provider values over README, docs, skill,
rules: no stale references; 4) CLAUDE.md - improver: no changes,
revise: no additions (learnings live in canonical docs)
2026-09-07 21:13:48 -07:00
Victor KuznetsovandClaude Opus 5 5b12f61db8 Record why the OpenAI chroma floor stays flat
Three withheld zero-face carriers, all confirmed zero-face with the same YuNet
detector the adaptive arms gate on, first cleared at 0.045 / 0.06 / 0.11. The
zero-face operating point is 0.11 + (0.11 - 0.045) = 0.175, which the shipped
0.20 already covers, so that floor now rests on both content classes rather than
only the face-bearing carriers it was derived from.

The content split this cohort kept suggesting does not survive the measurement:
the two classes differ by 0.025 while the spread WITHIN each is 0.065 and
0.0625, so a face split would key on a variable explaining about a quarter of
the variation it leaves behind.

The methodological point is the durable half. The fixture-only view of zero-face
OpenAI content spans 0.015 of boundary; the real carriers span 0.065, four times
wider. A narrow fixture spread yields a small worst-clean-plus-one-spread margin
and a false impression of homogeneity, and that impression is what made a split
look justified twice.

Every verdict is the public verifier's separate SynthID line with Content
Credentials reported not detected alongside, over RGB-identical metadata-stripped
positive controls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 19:08:08 -07:00
Diego.F 1cfdf192d8 Read a TIFF source's stored size from its IFD, not from Image.size
`_read_display_tags` drops the orientation when the raster no longer has the
source's stored dimensions, which is what stops an upright decode from being
tagged into a second rotation. It read those dimensions off `Image.size`.

That is not the stored size for TIFF: Pillow's TIFF reader applies the EXIF
orientation itself and reports the upright one. For a transposing orientation
the comparison was upright-against-upright, so it matched and the tag rode
along onto pixels that were already turned. `erase` on a 96x64 TIFF tagged 6
returned a 64x96 PNG carrying Orientation 6, and a viewer turned it again.

The TIFF IFD keeps the real geometry in ImageWidth/ImageLength, and no other
Pillow plugin exposes `tag_v2`, so every other container falls back to
`Image.size` and is byte-for-byte unaffected.

Also corrects `_pil_read`'s "No EXIF auto-rotation, matching cv2": Pillow's
TIFF reader rotates and cannot be told not to. Coming back upright is the
useful outcome; carrying the tag on top of it was the defect.
2026-09-08 00:54:22 +02:00
Victor Kuznetsov b2496134c2 Add margin to the explicit Chroma OpenAI floor 2026-09-07 14:15:57 -07:00
Victor Kuznetsov 723f2949ec Route OpenAI auto removal through Qwen 2026-09-07 14:01:17 -07:00
Victor Kuznetsov 7ddd2097bd Export WEIGHTS_ALLOW_PATTERNS for offline pre-caches
The runtime's _weights_dir resolves the Hub snapshot offline through an
inline allow_patterns list, so a deployment that pre-caches the snapshot
for HF_HUB_OFFLINE=1 had to re-list the files by hand and silently
drifted when a release added one: the offline resolution then failed on
a four-fifths-present cache even though every missing piece had its own
fallback. Export the exact runtime request list as one public name, use
it in _weights_dir, and pin the seam with a test that captures the
snapshot_download call. Document the contract in photo-classify.md and
module-internals.md.
2026-09-07 11:31:08 -07:00
Victor Kuznetsov c0fec5de19 Release 0.38.0 with the receipt gate for classify 2026-09-06 21:16:37 -07:00
Victor Kuznetsov 68b56ece37 Add stratified watermark benchmark and resource profiling 2026-09-06 20:18:48 -07:00
Victor Kuznetsov 0d6667cf22 Separate runtime actions from development provenance checks 2026-09-06 20:18:47 -07:00
Victor Kuznetsov 646f72534b Carry the ICC profile and EXIF orientation across re-encodes
Issue #98. cv2's encoders write no container metadata, so every pixel-path
output (all, visible, invisible, erase) came back without its colour profile
and orientation tag, even with --keep-metadata.

imwrite now takes display_tags_from, the file whose decode produced the
pixels, and restores the two display tags into the re-encoded output: PNG and
JPEG by splicing iCCP/eXIf chunks and APP1/APP2 segments into the encoded
bytes, WebP by a lossless Pillow re-save (libwebp hides hand-spliced chunks
from readers without a VP8X header), HEIF through the save call itself.
Orientation is re-tagged only when the raster still has the source's stored
dimensions: cv2 rotates JPEG and (build-dependent) PNG decodes under
IMREAD_COLOR but never IMREAD_UNCHANGED, which is what the pixel paths read
with, so the decision is made against the raster's own shape and cannot
double-rotate.

The default metadata strip now preserves what the restore put back: the PIL
path forwards the profile and a scrubbed EXIF (orientation included) for PNG
and WebP outputs, the 16-bit chunk walker re-emits eXIf scrubbed instead of
dropping it wholesale, and --remove-all actually drops the ICC profile on
every format (Pillow's saver fallback kept it alive, contradicting the
documented "removed by --remove-all").

Mirror and 180-degree orientations (2, 3, 4) stay untagged: no cheap signal
distinguishes a rotated decode from a stored one for them.
2026-09-06 14:41:19 -07:00
Victor Kuznetsov c1bf835e2f Fix Kling video detection across decoders 2026-09-05 13:43:00 -07:00
Victor Kuznetsov efdbfa0598 Add real watermark fixtures and expand provider coverage 2026-09-05 13:12:58 -07:00
Diego 9f7b707016 Preserve 16-bit images through fill and metadata stripping (#101)
Keep alpha bit-exact, make remove-all drop iCCP, and bound MI-GAN conversion and PNG chunk-copy overhead.
2026-09-05 13:11:16 -07:00
Diego 697e11f2ab Report unsupported global CPU offload profiles (#104)
Handle unresolved auto preload through its Qwen fallback and avoid a false warning.
2026-09-05 12:30:00 -07:00
Diego 507d7c43a3 Run adaptive polish before the humanizer (#102)
Correct the postprocessing order documentation and simplify its regression coverage.
2026-09-05 12:10:17 -07:00
Victor Kuznetsov a1022d33c3 Pin the photo-classify snapshot at the gate revision
ffc46db5 carries receipt-gate-2026-09-02.npz and the receipt_gate
operating-point entry, so fresh classify installs load the gate from the
model snapshot; the package asset remains the fallback for pre-gate
weights directories.
2026-09-05 12:08:08 -07:00
Victor Kuznetsov 5b52782f01 Move the receipt gate to the model side
The gate head is fitted on the freeze CLIP-L-ft embedding space, so it
belongs with the model: _load_receipt_gate now prefers
receipt-gate-2026-09-02.npz from the Hub snapshot or the
RAIW_CLASSIFY_WEIGHTS directory, with the package asset kept as the
fallback for weights frozen before the gate existed. Card mode of the
publish workflow now uploads the gate alongside the README and the
operating point, and the snapshot allow-pattern includes it.
2026-09-05 11:51:02 -07:00
Victor Kuznetsov 0761937d60 Improve Samsung detector recall on textured images 2026-09-05 11:51:02 -07:00
Victor Kuznetsov b8a8c3d3be Improve visible-mark evaluation and Samsung masking 2026-09-05 11:51:02 -07:00
Victor Kuznetsov 0fd139aac4 Fix agent skill dependency checks and metadata guidance 2026-09-05 11:32:22 -07:00
Victor KuznetsovandClaude Opus 5 637395b8a6 Name the missing pixel stack instead of tracebacking on it
The default package installs without `pixels`, and the Homebrew formula ships
exactly that build, so a first run that followed this project's own install
instructions died mid-command on a bare `ModuleNotFoundError: No module named
'cv2'` -- the one moment the user needed the extra named, and the only moment
they were not told it. `visible`, `erase`, `all` and `batch` now stop with the
install hint; `remove_batch` lets the same error out instead of counting one
absent package as an error per image, which turned a directory of 84 into 84
failures with no cause named.

Ask `optional_deps.pixels_available()` rather than matching the exception's
module name. An ImportError raised inside `cv2/__init__.py` carries `cv2.cv2`
and a numpy ABI mismatch carries `None`, so a name match let exactly the
tracebacks this guard exists for through.

`identify` keeps its historical no-op when the visible arm cannot decode, but
the report no longer stays silent about it: an install lacking the extra dropped
the visible Gemini mark from a marked image and read exactly like a clean scan.
The caveat names which silence happened, because an absent extra and an
unreadable file need different fixes.

`metadata --remove` reports surviving markers instead of crashing when the
raster-normalization recovery cannot import cv2, and the video runtime's install
hint is now shell-quoted like the others -- bare brackets are a glob in zsh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 11:27:16 -07:00
Victor Kuznetsov e41bdbc9c8 Add receipt-document gate to classify
A DEFINITELY verdict now runs a linear receipt gate on the CLIP-L-ft
vector Model 1 already computes: a hit publishes unknown and skips the
124-d provider pass. Trained on CORD-v2 train (800, CC BY 4.0) plus 200
synthetic capture-style receipts against 2,400 ai_train negatives; the
threshold is certified on CORD validation and a synthetic holdout with
no field-receipt pixels. Field receipts false-AI 19/59 -> 2/59, CORD
89/99 -> 0/99, ai_test DEFINITELY cost 7/1,847.
2026-09-05 11:14:07 -07:00
Victor Kuznetsov cbb5155784 Expose visible validation in pipeline results 2026-09-04 20:59:09 -07:00
Victor Kuznetsov 11026ec0c6 Improve Kling visible watermark masking 2026-09-04 20:05:07 -07:00
Victor Kuznetsov d391cc42d1 Add post-fill validation for visible watermarks 2026-09-03 11:05:59 -07:00
Victor Kuznetsov 7ab38a85b6 Sync the C2PA soft-binding registry 2026-09-03 09:44:24 -07:00
Victor Kuznetsov 09a4b20ac6 Release 0.37.1 with the Chroma tokenizer dependency 2026-09-02 10:03:03 -07:00
Victor KuznetsovandClaude Opus 5 5052dbe63c Release 0.37.0
Two new commands since 0.36.0, so a minor bump: `classify` (photograph
AI-versus-camera from pixels, with a gated provider head, behind the new
`classify` extra) and `verify-openai-synthid` (the official remote verifier,
behind the new `verify` extra). Also measured auto engine selection and a
Doubao edge-mask artifact fix.

`classify` downloads its freeze from wiltodelta/raiw-photo-classify. That is
the first release to carry the Hub id at all, which is why the rename landed
before the bump rather than after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 17:47:57 -07:00
Victor KuznetsovandClaude Opus 5 29de21256b Point the classify weights at wiltodelta/raiw-photo-classify
The Hub model and the private training repository were both called
raiw-models, so the training repo's README had to spend a paragraph telling
the two apart. Renamed on both sides: the Hub model to raiw-photo-classify
and the GitHub repository to raiw-classifier-training.

Done now because it is free now. classify.py landed after v0.36.0 and is in
no release, and the Hub model had zero all-time downloads, so no installed
package and no user carries the old id. Hugging Face redirects the old URL
and preserves counts, and the pinned freeze revision c0ac82b6 resolves on
the new name -- verified by downloading through the library's own constants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 15:33:01 -07:00
Victor Kuznetsov 07b07634cf Merge the docs/arxiv-paper-review research line into main (#95)
* Add rigorous SynthID research and evaluation harness

* Record SynthID cross-epoch validation results

* Reset SynthID detector hypotheses to paired training

* Add paired wavelet and spectral SynthID probes

* Record closed-corpus SynthID detector ablations

* Add registered SynthID phase probing

* Add periodic SynthID tile probing

* Add calibrated SynthID pixel detector

* Harden scale-registered SynthID detection

* Record SynthID phase and channel rescue findings

* Add production SynthID routing and OpenAI verification

* Reframe periodic pixel route as pipeline lattice; confirm and harden detection

The frozen periodic experts read an origin-anchored generation-pipeline lattice destroyed by a crop off the tile grid, not the crop-robust SynthID mark. Route the pixel result as an experimental pipeline_lattice signal kept out of the watermark inventory, and carry the crop sensitivity in every verdict envelope.

Add split-patch phase/amplitude/codeword confirmation for registered-v3, affine-lattice and cyclostationary research probes, and timeout/retry/error-taxonomy hardening for the official OpenAI verification path.

* Record pipeline-lattice reframing and registered-v3 phase lock

Document that the pixel route detects an origin-anchored lattice rather than the watermark, and record the registered-v3 two-pixel phase-lock measurement: a 2px diagonal crop killed all 36 tested detections (28 foreign-generator, 8 Google) with recovery only at offsets that are multiples of four. Update README, CLI, Python API, supported-signals, known-limitations, module-internals, the SynthID reference, and the detector research plan.

* Split SynthID campaign docs by detector, classifiers, and removal

Keep Model 1 as a research AI-versus-camera result outside the library.
Retire the mixed oracle-ladders page to a routing hub and mark the plan
file as a chronological archive.

* Keep 2026-08-22 SynthID campaign results in research docs only

Drop Bayer, Model 1, and lattice re-check numbers from user guides and
module internals. The library surface is unchanged.

* Unplug lattice detector from package, expand research docs

* Remove lattice detector sources from the public package

* Document SynthID GitHub sweep findings

* Document general AI classifier sweep

* Document modern-negative taxonomy audit and classifier doc split

Split general AI-classifier research into ai-generated-image-classifiers.md
and record the modern-negative campaign: Unsplash/Openverse date-clean cells,
contamination controls, quarter-hard paired result, closed linear veto and
taxonomy-continuation arms.

pre-commit: 1) maintain.sh - exit 1, known uv-secure lightning advisory with no upstream fix (same triage as main); core checks separately green (ruff, format, pyright, 1564 tests); 2) /simplify - docs-only single pass, no findings; 3) docs sync - split references updated everywhere, none stale; 4) CLAUDE.md - routing line already updated, compliant

* Record wild-AI audit, Meta provider class, and stock-negative expansion

The wild vendor-flagged AI cell (300 stratified rows) puts Model 1 recall at
69.7% on unknown-renderer stock AI; the stock-negative harvest triples the
modern fashion/product cells and confirms the combined-pool veto control;
the Meta muse-image corpus doubles to 132 rows with its margin sweep; a
per-channel cv2 reference fixes the latent fold test under cv2 4.10.0.

pre-commit: 1) maintain.sh - exit 1, known uv-secure lightning advisory with no upstream fix; core checks separately green (ruff, format, pyright, 1665 tests); 2) /simplify - docs-only single pass, no findings; 3) docs sync - new run references point at the gitignored research store, none stale; 4) CLAUDE.md - compact, no changes needed

* Close representation-change campaign: both candidates falsified

Pre-registered gates (AI-test >=1772, fresh OI <=50, Kodak 0, FLUX >=288,
EvalGEN >=95) rejected DINOv2-L ridge head (1427/1847, 162/3000, 1/24, 29/300,
5/100) and the paired-reconstruction FFT tower (14/1847, 12/3000, 0/24, 0/300,
0/100; contract learned at 0.949 pairwise separation but no transfer to
production generators). Campaign verdict: general classifier beyond local
scale as pursued; product stays on documented remainder.

pre-commit: 1) maintain.sh - exit 1, known uv-secure lightning advisory with no upstream fix; core checks green minutes ago (ruff, format, pyright, 1665 tests); 2) /simplify - docs-only single pass, no findings; 3) docs sync - verdict mirrored in data/research/REPRESENTATION-CAMPAIGN.md, no stale refs; 4) CLAUDE.md - no changes

* Record full failure map, H2 router falsification, and H3 process detector closure

20 measured negative domains with batch-2 (memes, whiteboards, tickets, book
pages). H2 domain router falsified: 97.9% of AI-test looks structured, every
family 94-100%. H3 process detector on 369 contrastive pairs from three
generators (OpenAI gpt-image-1-mini, Meta muse-image-1.0, Gemini flash-image)
achieves 0.909 pairwise AUC on training pairs but transfers at AUC 0.55-0.60
to production AI; human receipts score higher than any AI family. The
generator-specific spectral trace does not generalize across three generators,
closing the process-trace family in its current form.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (ruff, format, pyright, 1665 tests); 2) /simplify - docs + research scripts, single pass, no findings; 3) docs sync - all artifacts in data/research/, no stale refs; 4) CLAUDE.md - no changes

* Record H4-H6 research hypotheses: MLP probe, spectral invariant, TTA voting

H4 (MLP on frozen CLIP): 98.6% AI-test recall but 15.8% photo FPR — same
trade-off curve as quarter-hard. H5 (spectral invariant from CVPR 2025):
2.4% recall, the 1/f^alpha profile does not discriminate current generators.
H6 (TTA voting): fixes museum FP but not documents/UI/fashion, loses 9% recall.
All three falsified against frozen gates; ten total closed hypotheses now
document the CLIP-L representation limit from every angle.

Also records the provider cascade across all 20 domains (69.8% stray on
documents mirrors the detector failure map) and the full Y-phase campaign
PSNR data awaiting oracle rate-limit reset.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (ruff, format, pyright, 1665 tests); 2) /simplify - docs-only single pass, no findings; 3) docs sync - all artifacts in data/research/, no stale refs; 4) CLAUDE.md - no changes

* Fix Y-phase frequency convention, record H4-H6 and campaign results

The initial Y-phase campaign targeted FFT bins 16-32 instead of spatial
periods 16-32 px (1/32 to 1/16 cycles/px). After the fix, the full-octave
Y-phase scramble reproduces the known kill (not_detected at 20.5 dB on s1
at native 1122x1402). Partial sub-bands are visually invisible (42.5 dB)
but stay detected: the mark is distributed across the full octave, not
concentrated. Point intervention is not viable within this frequency band.

H4 MLP probe: 98.6% recall, 15.8% FPR (same trade-off wall).
H5 spectral invariant: 2.4% recall (1/f^alpha does not discriminate).
H6 TTA voting: fixes museum FP but not documents/UI/fashion.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (1665 tests, verified minutes ago); 2) /simplify - docs-only; 3) docs sync - all artifacts synced; 4) CLAUDE.md - no changes

* Add TC260 fifth class and per-class margin calibration to provider cascade

Five-head cascade (openai/google/tc260/meta/no_ai): openai 68.8%, google
76.6%, tc260 63.8%, meta 71.8% mean recall at uniform margin 0.50.
Per-class margins calibrated against 1.67% photo budget: openai 0.31,
google 0.29, tc260 0.39, meta 0.55. TC260 test recall 71.4% at calibrated
margins on 196 held-out images. Chinese-AIGC is a viable pixel class.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (verified within the hour); 2) /simplify - research script, single pass; 3) docs sync - artifacts in data/research/; 4) CLAUDE.md - no changes

* Record universal detector v3-v8: diverse training shifts the curve

Fresh MLP on frozen CLIP embeddings with 49 negative domains (12,258 images)
and 5,043 AI positives achieves 93.0% AI-test recall with 39/49 domains at
zero false positives. Digital art FP dropped from 99.3% (Model 1) to 24.8%,
fashion from 62-71% to 12%, UI from 99.3% to 12%. Photo FPR 0.7%. The
improvement comes from training data diversity on frozen embeddings, not
representation change (v5 fine-tune proved this by undoing the gains).
Also adds .local-eval to ruff exclude so the gate covers only tracked files.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (ruff, format, pyright, 1731 tests); 2) /simplify - docs + research; 3) docs sync - all artifacts in data/research/; 4) CLAUDE.md - no changes

* Add three-tier ensemble detector: DEFINITELY/POSSIBLY/LIKELY_HUMAN

Combines Model 1 and v8 into calibrated confidence tiers. DEFINITELY_AI
(both models agree) achieves 79.6% recall at 0.50% photo FPR with 46/49
domains at zero FP. The union reaches 93.3% recall at 1.80% photo FPR
with Kodak 0/24. First detector working across ALL content types with
per-tier calibrated error rates.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (ruff, format, pyright, 1731 tests); 2) /simplify - docs + ensemble; 3) docs sync - artifacts in data/research/; 4) CLAUDE.md - no changes

* Add provider v2: diversity training eliminates document leakage

Same diversity-training insight applied to the provider cascade. Adding
3,769 structured negative features to training drops document/receipt
leakage from 69.8% to 5.1%, UI from 10% to 4.3%, fashion from 5-15% to
0-3%. AI recall improves: OpenAI 84% (was 75%), Google 88.8% (was 79%).
Photo leakage 1.4%. Worst regression: memes 47.9% (was 27%).

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (ruff, format, pyright, 1731 tests); 2) /simplify - provider v2; 3) docs sync - artifacts in data/research/; 4) CLAUDE.md - no changes

* Add universal v11: full taxonomy, 136 domains, 124 at zero FP

Master data plan mapped 148 visual domains. Massive collection (3,343
images, 73 domains from Openverse) plus gap-filler (724 images from
Safebooru anime/manga) expanded training to 19,768 images across 136
negative domains. Universal v11 (MLP-512 + Model 1 ensemble):
DEFINITELY 90.3% recall at 0.50% photo FPR, ANY 95.6% recall,
124/134 domains at exactly 0% FP, worst FP 13.8% (digital art).

New domains opened: anime, manga, chibi, mecha, vintage photos,
banknotes, newspapers, magazines, movie posters, antique maps,
watercolor/oil originals, patterns, cosmetics, real estate, 3D
renders, and 60+ more.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (ruff, format, pyright, 1731 tests); 2) /simplify - docs + research; 3) docs sync - artifacts in data/research/; 4) CLAUDE.md - no changes

* Confirm representation limit: v12 fine-tune destroys structured FP despite 6x data

Fine-tuning CLIP tower on 19,518 images achieves 95.1% standalone recall
but returns to 100% FP on art/UI and 14/59 zero-FP domains (vs 124/134
for frozen v11). This is a representation property, not a data issue.
Frozen CLIP + MLP (v11) is the only working architecture. Also records
provider v3 trained on all negative domains.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (ruff, format, pyright, 1731 tests); 2) /simplify - docs + research; 3) docs sync - artifacts in data/research/; 4) CLAUDE.md - no changes

* Document v13 plateau: 128/136 zero FP, frozen-CLIP ceiling confirmed

v13 with top-plan expansion (1,169 new images) improves domain coverage
to 128/136 at zero FP and reduces fashion FP to 5.3%, but DEFINITELY
recall plateaus at 89.6% and art FP at 15.9%. The frozen CLIP + MLP
architecture has reached its ceiling.

pre-commit: 1) maintain.sh - exit 1, known lightning advisory; core green (verified minutes ago, 1731 tests); 2) /simplify - docs-only; 3) docs sync - v13 recorded; 4) CLAUDE.md - no changes

* Document the 2026-08-31 photo-classifier freeze.

Record the AI-versus-camera contract, Meta v3 hold-out, Flickr UI
replacement, and that receipts stay out of the supported domain.

* Scrub Spaces catalog details from classifier research docs

* Record receipt layout-gate probe for the universal detector
2026-09-01 11:37:32 -07:00
Victor Kuznetsov 5e85f3ac6f Call tc260 a label standard, not a provider. (#93)
The head was trained on mixed Chinese AIGC producers that share the
TC260 mark. openai and google stay provider classes. muse-image stays
a model class.
2026-09-01 11:00:21 -07:00
Victor Kuznetsov 36c36212f0 Name classify provider classes by training scope. (#92)
openai, google, and tc260 stay provider classes. The Muse Image head
is public as muse-image, not meta. The freeze file still keys that
head meta_muse_image.
2026-09-01 10:50:30 -07:00
Victor Kuznetsov 84ea8ede2e Add measured auto engine selection 2026-09-01 10:19:59 -07:00
Victor Kuznetsov 925401c905 Add the classify extra for the 2026-08-31 photo freeze. (#90)
Ship Model 1 then gated Model 2 as an explicit CLI and library call.
identify, has_invisible_target, and all do not import it. Weights stay
on Hugging Face wiltodelta/raiw-models at a pinned revision.
2026-08-31 22:45:18 -07:00
Victor Kuznetsov 2ff06d4c5a Fix Doubao edge-mask artifacts 2026-08-31 19:49:31 -07:00
Victor Kuznetsov 5991894ca5 Release 0.36.0 2026-08-31 12:26:42 -07:00
Victor Kuznetsov f7e0a18e6c Add --pipeline auto: per-cohort engine router from provenance
Routes OpenAI/Microsoft provenance to chroma-zimage (lower floors, better
matched-strength fidelity) and Google/Meta/unknown to qwen-zimage, from
the 2026-08-29/30 four-cohort calibration. The resolution happens in
remove_watermark after the vendor is known and before the strength
resolution, so each engine's own floors apply.
2026-08-30 20:02:14 -07:00
Victor Kuznetsov bc5922451f Add prompt-embedding cache for Chroma1: skip T5 inference and free the 24B encoder after first encode 2026-08-30 19:50:49 -07:00