feat(ml): CCIP references exclude unconfirmed auto character tags + confirm trips detectors (m139)
CI / lint (push) Failing after 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Failing after 42s
CI / integration (push) Successful in 3m46s

Completes "no self-training": unconfirmed auto-applied character tags no longer
seed CCIP references — character_references + the prototype builder
(_current_fingerprints/_rebuild_one) gain a shared _positive_char_tag filter
(human-applied OR operator-confirmed), mirroring the head-positive exclusion.

Confirming a tag also has to move the change-detectors, or an incremental
refresh/Retrain right after a confirm wouldn't fold the tag in (only the nightly
full pass would): the CCIP global gate now counts character confirmations, and
the head training fingerprint counts confirmations. Test for the CCIP path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-06 18:59:00 -04:00
parent 775941609d
commit bae077e323
4 changed files with 60 additions and 3 deletions
+22
View File
@@ -12,6 +12,7 @@ from backend.app.models import (
MLSettings,
Tag,
TagKind,
TagPositiveConfirmation,
)
from backend.app.models.tag import image_tag
from backend.app.services.ml.character_prototypes import (
@@ -141,6 +142,27 @@ def test_multi_character_image_not_referenced(db_sync):
assert _proto_count(db_sync, daphne.id) == 0
def test_unconfirmed_auto_char_tag_not_referenced(db_sync):
# A single-character image whose ONLY character tag was AUTO-applied
# (unconfirmed) must NOT seed a prototype — else CCIP self-trains on its own
# guess (milestone 139). Confirming it (which trips the global gate) makes it
# a reference.
raven = _char(db_sync, "Raven")
img = _img(db_sync, "z" * 64)
_figure(db_sync, img.id)
db_sync.execute(image_tag.insert().values(
image_record_id=img.id, tag_id=raven.id, source="ccip_auto",
))
db_sync.commit()
refresh_character_prototypes(db_sync)
assert _proto_count(db_sync, raven.id) == 0
db_sync.add(TagPositiveConfirmation(image_record_id=img.id, tag_id=raven.id))
db_sync.commit()
refresh_character_prototypes(db_sync)
assert _proto_count(db_sync, raven.id) == 1
def test_lost_references_are_removed(db_sync):
raven = _char(db_sync, "Raven")
img = _img(db_sync, "e" * 64)