fix(6253): the moment-proposal queue lists a signal pair only once it has proposed

pending() listed every suggested signal row, including pairs still gathering
evidence below the three-situation bar, so the queue held 200 "proposals"
where about 25 had crossed it. A signal pair now needs proposed_at in its
evidence, the same test the misfire half already applies; proposals from a
pass are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 20:03:52 -04:00
co-authored by Claude Opus 5.5
parent 67d3c68fbf
commit d51ccd9b69
2 changed files with 15 additions and 2 deletions
+13 -2
View File
@@ -43,7 +43,7 @@ from __future__ import annotations
import logging
from datetime import datetime, timezone
from sqlalchemy import exists, func, not_, select
from sqlalchemy import exists, func, not_, or_, select
from sqlalchemy.exc import IntegrityError
from grimoire.models import async_session
@@ -274,6 +274,11 @@ async def pending(user_id: int, rule_id: int | None = None, limit: int = 200) ->
Each rule carries what it is mounted on now, so a proposal is read beside
the mounts it would add to.
A signal pair is a proposal only once it has crossed the bar and asked
(`proposed_at` in its evidence). Below the bar it is a row gathering
evidence, and listing it would bury the few pairs the signal stands
behind under every rule that was once opened after a moment.
"""
from grimoire.services.rulebooks import _owned_rules_clause
@@ -282,7 +287,13 @@ async def pending(user_id: int, rule_id: int | None = None, limit: int = 200) ->
stmt = (
select(RuleMomentJudgment, Rule)
.join(Rule, Rule.id == RuleMomentJudgment.rule_id)
.where(RuleMomentJudgment.state == SUGGESTED, _owned_rules_clause(user_id))
.where(
RuleMomentJudgment.state == SUGGESTED, _owned_rules_clause(user_id),
or_(
RuleMomentJudgment.source != "signal",
RuleMomentJudgment.evidence["proposed_at"].astext.isnot(None),
),
)
)
if rule_id:
stmt = stmt.where(Rule.id == int(rule_id))