feat: the matcher gets an identity route, so a name can link what time cannot (4392)
CI and images / lint (push) Successful in 4s
CI and images / extension-version (push) Successful in 4s
CI and images / frontend-build (push) Successful in 26s
CI and images / backend-lint-and-test (push) Successful in 34s
CI and images / integration (push) Successful in 2m52s
CI and images / sign-extension (push) Successful in 3s
CI and images / build-agent (push) Successful in 6s
CI and images / build-web (push) Successful in 2m6s
CI and images / smoke-web (push) Successful in 55s
CI and images / promote (push) Skipped

#4392 measured three compounding reasons the announcement matcher could not
fire for this creator. This is the first two.

**Vocabulary.** `discord` is not how these creators write once the audience
already knows where the server is. Across 20,558 Patreon bodies `discord`
appears in 486 and `the server` in 37 — but the distribution is the point, not
the totals: for artist 8, 21 of 42 posts say `discord`, 7 say `the server`,
and it is the RECENT ones that say the latter. A vocabulary list written from
old posts silently stops matching the posts that still need it.

**Two routes.** Circumstance and identity answer different questions.
Proximity and a declaration say two things happened near each other; a working
name the creator uses on these two posts and nowhere else says they are the
same piece. Averaging those makes the threshold uninterpretable and costs
both ways — adding identity as a fourth weight dilutes the others enough that
measured teaser/drop pairs an hour apart stop proposing, while capping
identity at a weight means the strongest evidence available can never carry a
pair alone. So identity may OVERRIDE, never dilute; below IDENTITY_FLOOR it is
recorded for the operator and moves nothing, which is the conservative
direction, since a wrong link asserts two different pieces are one.

The circumstantial bundle is now proximity/declared/marker at .45/.35/.20.
Rebalanced rather than extended: the existing guard still holds arithmetically
(no single weight reaches the threshold) and the existing pairs still score
where they did — a teaser and its drop an hour apart with an invite link go
from 0.815 to 0.781, both well over.

Measured against the live instance, artist 8: 17 candidate pairs, 14 carried
by a shared name, no false positives, and no drop claimed by two posts. The
operator's own example — `Anya -- 🍈🍈` and the drop 75 minutes before it —
links on the token `0-k`, which is what its two files are called on either
platform. Two pairs 21h apart that proximity scores 0.10 are reached, and two
more (`680lc`, `cnni18x`, four posts each) are the stated cost of the floor.

Both rare-token signals are artist-scoped and loaded once per artist per
sweep, not per candidate pair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
This commit is contained in:
2026-09-24 08:07:25 -04:00
co-authored by Claude Opus 5
parent fb7563e263
commit fd214f3a08
2 changed files with 382 additions and 26 deletions
+237 -10
View File
@@ -22,6 +22,7 @@ from backend.app.models import (
)
from backend.app.services.discord_grouping import DROP_GROUPER
from backend.app.services.post_association_service import (
DECLARED_MENTION,
WEIGHTS,
PostAssociationService,
declared_signal,
@@ -130,30 +131,42 @@ async def _artist_with_channels(db, name: str):
return artist, patreon, discord
async def _teaser(db, artist, source, *, at, body, ext="teaser"):
async def _images(db, artist, post, ext, names):
"""Attach named files to a post. The NAME is the point — the working-name
signal reads it, so a test that cares about identity supplies one."""
for i, name in enumerate(names):
db.add(ImageRecord(
path=f"/images/{artist.id}/{ext}_{i}_{name}.jpg",
sha256=f"{ext}{i}{name}".ljust(64, "0")[:64],
size_bytes=10, mime="image/jpeg", width=10, height=10,
origin="downloaded", primary_post_id=post.id, artist_id=artist.id,
))
await db.flush()
async def _teaser(db, artist, source, *, at, body, ext="teaser", names=None,
title="New piece"):
post = Post(
source_id=source.id, artist_id=artist.id, external_post_id=ext,
post_date=at, post_title="New piece", description=body,
post_date=at, post_title=title, description=body,
)
db.add(post)
await db.flush()
db.add(ImageRecord(
path=f"/images/{source.id}-{ext}.jpg", sha256=f"{ext:0>64}"[:64],
size_bytes=10, mime="image/jpeg", width=10, height=10,
origin="downloaded", primary_post_id=post.id, artist_id=artist.id,
))
await db.flush()
await _images(db, artist, post, ext, names or [ext])
return post
async def _drop(db, artist, source, *, at, ext="fc-drop:1"):
async def _drop(db, artist, source, *, at, ext="fc-drop:1", body=None,
names=()):
post = Post(
source_id=source.id, artist_id=artist.id, external_post_id=ext,
post_date=at, synthesized_by=DROP_GROUPER,
post_date=at, description=body, synthesized_by=DROP_GROUPER,
synthesis_details={"message_count": 4, "images_since_surface": 0},
)
db.add(post)
await db.flush()
if names:
await _images(db, artist, post, ext.replace(":", "-"), names)
return post
@@ -372,3 +385,217 @@ async def test_the_rescan_is_a_no_op_when_the_switch_is_off(db):
settings.discord_link_enabled = False
await db.commit()
assert await rescan(db) == {"enabled": False, "scanned": 0, "proposed": 0}
# --- the identity route ----------------------------------------------------
#
# Milestone 388. Everything below was calibrated against the operator's real
# library (artist 8: 520 images, 15 same-artist pairs that share a name, no
# false positives) rather than invented, so the numbers in these docstrings are
# measurements.
@pytest.mark.asyncio
async def test_a_shared_working_name_proposes_a_pair_time_cannot_reach(db):
"""The reason identity is a route and not a fourth weight.
These two are 20 hours apart and the teaser says nothing about Discord, so
every circumstantial signal is near zero — proximity scores 0.17 and the
weighted total 0.075, a long way under the bar. What links them is that
the creator exported both from one file and the name came along.
Measured equivalents on the live instance: `ConnFront` ↔ `ConnFront` and
`Thicc Tomboy scene2` ↔ `Thicc_Tomboy_scene2`, the latter 23.8 hours apart
at a proximity of 0.005.
"""
artist, patreon, discord = await _artist_with_channels(db, "namedartist")
now = datetime.now(UTC)
teaser = await _teaser(
db, artist, patreon, at=now - timedelta(hours=20),
body="a little preview", names=["ConnFront"],
)
drop = await _drop(db, artist, discord, at=now, names=["01_ConnFront"])
await db.commit()
made = await PostAssociationService(db).match_post(
teaser.id, threshold=DEFAULT_THRESHOLD, window_hours=WINDOW,
)
await db.commit()
assert made == 1
assoc = (await db.execute(select(PostAssociation))).scalar_one()
assert assoc.payload_post_id == drop.id
assert assoc.signals["identity"] == 1.0
assert weighted_score(assoc.signals) < DEFAULT_THRESHOLD, (
"the circumstantial bundle alone must NOT reach the bar here — if it "
"does, this test has stopped proving what it claims to"
)
@pytest.mark.asyncio
async def test_the_proposal_says_which_name_it_matched_on(db):
"""A review queue that cannot explain itself is one the operator learns to
click through without reading."""
artist, patreon, discord = await _artist_with_channels(db, "explainartist")
now = datetime.now(UTC)
teaser = await _teaser(
db, artist, patreon, at=now - timedelta(hours=20),
body="a little preview", names=["LoisLaneTB2"],
)
await _drop(db, artist, discord, at=now, names=["01_LoisLaneTB2"])
await db.commit()
await PostAssociationService(db).match_post(
teaser.id, threshold=DEFAULT_THRESHOLD, window_hours=WINDOW,
)
await db.commit()
assoc = (await db.execute(select(PostAssociation))).scalar_one()
assert assoc.signals["identity_token"] == "loislanetb2"
@pytest.mark.asyncio
async def test_a_name_the_creator_reuses_everywhere_links_nothing(db):
"""THE false-positive guard for this route.
A character name is not an identity. Measured on artist 8: `anya` is on
images in 8 posts, `riju` 9, `bea` 16 — ungated, every Anya post would
match every Anya drop, and the matcher would confidently assert that a
year of unrelated pieces are all the same piece.
"""
artist, patreon, discord = await _artist_with_channels(db, "habitartist")
now = datetime.now(UTC)
teaser = await _teaser(
db, artist, patreon, at=now - timedelta(hours=20),
body="a little preview", ext="t0", names=["Anya"],
)
await _drop(db, artist, discord, at=now, names=["01_Anya"])
# The same name across enough of this artist's OTHER posts to make it a
# habit. Nothing about the pair above changes; only its context does.
for i in range(7):
await _teaser(
db, artist, patreon, at=now - timedelta(days=30 + i),
body="older", ext=f"other{i}", names=["Anya"],
)
await db.commit()
made = await PostAssociationService(db).match_post(
teaser.id, threshold=DEFAULT_THRESHOLD, window_hours=WINDOW,
)
await db.commit()
assert made == 0
assert (await db.execute(select(PostAssociation))).scalars().all() == []
@pytest.mark.asyncio
async def test_a_name_below_the_floor_is_recorded_but_carries_nothing(db):
"""Identity may override, never dilute. Below the floor it is real
evidence that is not strong enough to assert sameness on its own, so it is
written down for the operator and moves no score.
The measured cost of the floor sitting at 0.75: artist 8's `680lc` and
`cnni18x` pairs each span four posts and are genuine, and neither will
propose on this signal alone.
"""
artist, patreon, discord = await _artist_with_channels(db, "floorartist")
now = datetime.now(UTC)
teaser = await _teaser(
db, artist, patreon, at=now - timedelta(hours=20),
body="a little preview", ext="t0", names=["cnni18x"],
)
await _drop(db, artist, discord, at=now, names=["01_cnni18x"])
for i in range(2):
await _teaser(
db, artist, patreon, at=now - timedelta(days=30 + i),
body="older", ext=f"other{i}", names=["cnni18x"],
)
await db.commit()
svc = PostAssociationService(db)
made = await svc.match_post(
teaser.id, threshold=DEFAULT_THRESHOLD, window_hours=WINDOW,
)
await db.commit()
assert made == 0
corpus = await svc._corpus(artist.id)
assert corpus.token_posts["cnni18x"] == 4, "four posts carry the name"
# --- the marker, and the vocabulary ----------------------------------------
@pytest.mark.asyncio
async def test_a_marker_the_creator_uses_once_lifts_a_pair_over_the_line(db):
"""The operator's own tie-back: `🍈🍈` in the Patreon title and
`@everyone 🍈 🍈` in the Discord message. No vocabulary list would predict
it, and no filename carries it.
Measured here: 0.446 on proximity alone, 0.646 once the marker counts —
the bar is 0.60. The mirror case is the guard below it.
"""
artist, patreon, discord = await _artist_with_channels(db, "markerartist")
now = datetime.now(UTC)
teaser = await _teaser(
db, artist, patreon, at=now - timedelta(minutes=12),
body="it's up", title="Anya -- \U0001F348\U0001F348",
)
await _drop(db, artist, discord, at=now, body="@everyone \U0001F348 \U0001F348")
await db.commit()
made = await PostAssociationService(db).match_post(
teaser.id, threshold=DEFAULT_THRESHOLD, window_hours=WINDOW,
)
await db.commit()
assert made == 1
assoc = (await db.execute(select(PostAssociation))).scalar_one()
assert assoc.signals["marker"] == 1.0
@pytest.mark.asyncio
async def test_a_marker_the_creator_uses_constantly_lifts_nothing(db):
"""The same arrangement, with the marker turned into punctuation.
This is a real measured proposal, not a hypothetical: 💦 is in 13 of artist
8's 300 posts and it was the DECIDING term for a pair that proximity alone
scored 0.441. A habitual marker riding along with proximity is just
proximity wearing a hat, which the threshold sits above 0.45 to prevent.
"""
artist, patreon, discord = await _artist_with_channels(db, "punctartist")
now = datetime.now(UTC)
teaser = await _teaser(
db, artist, patreon, at=now - timedelta(minutes=12),
body="it's up", title="Drizzle \U0001F4A6", ext="t0",
)
await _drop(db, artist, discord, at=now, body="@everyone \U0001F4A6")
for i in range(5):
await _teaser(
db, artist, patreon, at=now - timedelta(days=30 + i),
body="\U0001F4A6", title=f"older {i}", ext=f"other{i}",
)
await db.commit()
made = await PostAssociationService(db).match_post(
teaser.id, threshold=DEFAULT_THRESHOLD, window_hours=WINDOW,
)
await db.commit()
assert made == 0
def test_the_creators_own_phrasing_counts_as_a_declaration():
"""`discord` is not how these creators actually write once the audience
already knows where the server is. Measured on artist 8: 21 of 42 posts
say `discord`, 7 say `the server`, and it is the RECENT ones that say the
latter — so a vocabulary list written from old posts silently stops
matching the posts that still need it."""
assert declared_signal("<p>Full res is on the server</p>") == DECLARED_MENTION
assert declared_signal("<p>up on our server now</p>") == DECLARED_MENTION
assert declared_signal("<p>posted to my server</p>") == DECLARED_MENTION
def test_an_unrelated_server_is_still_not_a_declaration():
"""The widened vocabulary must not widen into ordinary prose."""
assert declared_signal("<p>the servers were down all morning</p>") == 0.0