From d51ccd9b6947b57589f156187a89deeaa718a00c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 22 Oct 2026 20:03:52 -0400 Subject: [PATCH] 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 --- src/grimoire/services/rule_moment_judgments.py | 15 +++++++++++++-- tests/test_integration_rule_moment_judgments.py | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/grimoire/services/rule_moment_judgments.py b/src/grimoire/services/rule_moment_judgments.py index 05d717a2..b461e10b 100644 --- a/src/grimoire/services/rule_moment_judgments.py +++ b/src/grimoire/services/rule_moment_judgments.py @@ -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)) diff --git a/tests/test_integration_rule_moment_judgments.py b/tests/test_integration_rule_moment_judgments.py index cd361b82..f3cebb14 100644 --- a/tests/test_integration_rule_moment_judgments.py +++ b/tests/test_integration_rule_moment_judgments.py @@ -143,6 +143,8 @@ async def test_the_signal_counts_sessions_and_asks_once_the_bar_is_crossed(world for situation in ("s1", "s1", "s2"): assert await judgments_svc.co_occurred(uid, done.id, ["work.verify"], situation=situation) == "" + # Below the bar the pair is gathering evidence, not waiting on anyone. + assert (await judgments_svc.pending(uid, rule_id=done.id))["rules"] == [] line = await judgments_svc.co_occurred(uid, done.id, ["work.verify"], situation="s3") assert f"#{done.id}" in line and "`work.verify`" in line and "3 distinct sessions" in line assert "judge_rule_moments" in line