fix(dedup): a rule or preference create surfaces what it overlaps by meaning (#4134)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Failing after 1m16s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Failing after 1m16s
CI & Build / Build & push image (push) Skipped
find_duplicate_rule was title-only, on the stated premise that rules are not a semantic-retrieval surface - false since rules were embedded. A preference restating a rule under another title passed untouched, and since both kinds share one ranking, the weaker label could arrive alone. find_overlapping_rules queries semantic_search_rules with the rule_document shape, both kinds, in the scope the new record ranks in (global: every rule the caller owns; project: global + that project). All three MCP create doors call it before creating and return overlaps + overlap_note on the reply. It advises rather than blocks, on measurement: across 16 sampled records the nearest DISTINCT neighbour reached 0.853, while a true rewording scored 0.850. No threshold separates the bands, so the floor (0.80) sits below the restatement and the author judges. The stale docstring is corrected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
@@ -489,13 +489,20 @@ async def create_rule(
|
||||
order_index: Display order within the topic (default 0).
|
||||
force: Bypass the near-duplicate gate. By default, a title-identical rule
|
||||
already in this topic BLOCKS creation and returns its id so you update
|
||||
it instead. Set true only for a genuinely distinct rule.
|
||||
it instead. Set true only for a genuinely distinct rule. A rule or
|
||||
preference that answers the same MOMENT under another title does
|
||||
not block: the create goes through and the reply carries
|
||||
`overlaps` and `overlap_note` — read the top one and decide.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
if not force:
|
||||
dup = await dedup_svc.find_duplicate_rule(title, topic_id=topic_id)
|
||||
if dup is not None:
|
||||
return dedup_svc.duplicate_response(dup, "rule")
|
||||
# Before the create, so the new rule cannot find itself (#4134).
|
||||
overlaps = await dedup_svc.find_overlapping_rules(
|
||||
uid, title, statement, when_to_apply,
|
||||
)
|
||||
rule = await rulebooks_svc.create_rule(
|
||||
topic_id=topic_id, user_id=uid,
|
||||
title=title, statement=statement, when_to_apply=when_to_apply,
|
||||
@@ -503,7 +510,9 @@ async def create_rule(
|
||||
why=why, how_to_apply=how_to_apply, order_index=order_index,
|
||||
verify_with=verify_with, expires_when=expires_when,
|
||||
)
|
||||
return await rulebooks_svc.rule_detail(uid, rule, system_ids)
|
||||
data = await rulebooks_svc.rule_detail(uid, rule, system_ids)
|
||||
data.update(dedup_svc.overlap_response(overlaps, "rule"))
|
||||
return data
|
||||
|
||||
|
||||
async def create_project_rule(
|
||||
@@ -582,7 +591,9 @@ async def create_project_rule(
|
||||
order_index: Display order within the project's rule list (default 0).
|
||||
force: Bypass the near-duplicate gate. By default, a title-identical rule
|
||||
already on this project BLOCKS creation and returns its id so you
|
||||
update it instead. Set true only for a genuinely distinct rule.
|
||||
update it instead. Set true only for a genuinely distinct rule. An
|
||||
overlap by meaning never blocks; it arrives as `overlaps` and
|
||||
`overlap_note` on the reply — see create_rule.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
derived_title = title.strip() or statement.strip().split(".")[0][:50]
|
||||
@@ -590,6 +601,9 @@ async def create_project_rule(
|
||||
dup = await dedup_svc.find_duplicate_rule(derived_title, project_id=project_id)
|
||||
if dup is not None:
|
||||
return dedup_svc.duplicate_response(dup, "rule")
|
||||
overlaps = await dedup_svc.find_overlapping_rules(
|
||||
uid, derived_title, statement, when_to_apply, project_id=project_id,
|
||||
)
|
||||
rule = await rulebooks_svc.create_project_rule(
|
||||
project_id=project_id, user_id=uid,
|
||||
title=derived_title, statement=statement, when_to_apply=when_to_apply,
|
||||
@@ -597,7 +611,9 @@ async def create_project_rule(
|
||||
why=why, how_to_apply=how_to_apply, order_index=order_index,
|
||||
verify_with=verify_with, expires_when=expires_when,
|
||||
)
|
||||
return await rulebooks_svc.rule_detail(uid, rule, system_ids)
|
||||
data = await rulebooks_svc.rule_detail(uid, rule, system_ids)
|
||||
data.update(dedup_svc.overlap_response(overlaps, "rule"))
|
||||
return data
|
||||
|
||||
|
||||
async def update_rule(
|
||||
@@ -784,7 +800,10 @@ async def create_preference(
|
||||
a preference could only be filed after the fact (#4249).
|
||||
force: Bypass the near-duplicate gate. For a genuinely distinct
|
||||
preference, not for one that is "mostly" different — a mostly
|
||||
different preference is an update.
|
||||
different preference is an update. A RULE that already answers
|
||||
this moment comes back as `overlaps` / `overlap_note` on the reply
|
||||
rather than blocking; if it says the same thing, the preference is
|
||||
the weaker copy of it and should go.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
if not when_to_apply.strip():
|
||||
@@ -803,13 +822,18 @@ async def create_preference(
|
||||
dup = await dedup_svc.find_duplicate_rule(title, topic_id=topic_id)
|
||||
if dup is not None:
|
||||
return dedup_svc.duplicate_response(dup, "rule")
|
||||
overlaps = await dedup_svc.find_overlapping_rules(
|
||||
uid, title, statement, when_to_apply,
|
||||
)
|
||||
rule = await rulebooks_svc.create_rule(
|
||||
topic_id=topic_id, user_id=uid,
|
||||
title=title, statement=statement, when_to_apply=when_to_apply,
|
||||
kind="preference", arose_from_id=arose_from_id,
|
||||
why=why, how_to_apply=how_to_apply, order_index=order_index,
|
||||
)
|
||||
return await rulebooks_svc.rule_detail(uid, rule, system_ids)
|
||||
data = await rulebooks_svc.rule_detail(uid, rule, system_ids)
|
||||
data.update(dedup_svc.overlap_response(overlaps, "preference"))
|
||||
return data
|
||||
|
||||
|
||||
async def update_preference(
|
||||
|
||||
Reference in New Issue
Block a user