feat(moments): a mount that keeps arriving where it does not apply proposes its own removal (milestone 458 step 7b, #4955)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m19s
CI & Build / Python tests (push) Successful in 2m3s
CI & Build / Build & push image (push) Successful in 18s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m19s
CI & Build / Python tests (push) Successful in 2m3s
CI & Build / Build & push image (push) Successful in 18s
The open-after-moment signal proposes a mount; nothing proposed taking one off, so a wrong mount was noise at every occurrence until someone happened to notice. rule_misfired(rule_id, moment, why, reached_by) records a report against a MOUNTED pair, counted per distinct day (the MCP door carries no session id) on a new rule_moment_judgments.misfire column (migration 0119, backup v24). At three days the response carries a line asking the agent to offer the operator the fix - reject takes the rule off, unmap_action stops the action reaching the moment, confirm keeps the mount and stops the asking - and Settings > Moments lists it as an unmount proposal with the reasons and the actions that reached it. A re-mount clears the count. Taught in moments.md, missed-retrieval.md and the reply hold's wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,14 @@ async def test_the_detail_carries_the_mounts_and_omits_an_empty_set(world):
|
||||
async def test_a_backup_carries_the_mounts_to_the_restored_rule(world):
|
||||
uid, rule = world["uid"], world["rule"]
|
||||
await rulebooks_svc.set_rule_moments(rule.id, uid, ["work.finish", "reply.report"])
|
||||
# A misfire report on one of them (v24) — the operator's evidence for
|
||||
# taking a mount off must not be lost to a restore.
|
||||
from scribe.services import rule_moment_judgments as judgments_svc
|
||||
reported = await judgments_svc.misfired(
|
||||
uid, rule.id, "reply.report", why="the reply only asked a question",
|
||||
reached_by="the reply that ends this turn",
|
||||
)
|
||||
assert reported["recorded"]
|
||||
|
||||
async with async_session() as s:
|
||||
payload = {
|
||||
@@ -172,9 +180,14 @@ async def test_a_backup_carries_the_mounts_to_the_restored_rule(world):
|
||||
]
|
||||
async with async_session() as s:
|
||||
judged = (await s.execute(
|
||||
select(RuleMomentJudgment.moment).where(RuleMomentJudgment.rule_id == restored_rule.id)
|
||||
select(RuleMomentJudgment).where(RuleMomentJudgment.rule_id == restored_rule.id)
|
||||
)).scalars().all()
|
||||
assert sorted(judged) == ["reply.report", "work.finish"]
|
||||
assert sorted(j.moment for j in judged) == ["reply.report", "work.finish"]
|
||||
misfire = {j.moment: j.misfire for j in judged}
|
||||
assert misfire["work.finish"] is None
|
||||
assert [r["why"] for r in misfire["reply.report"]["reasons"]] == [
|
||||
"the reply only asked a question",
|
||||
]
|
||||
|
||||
|
||||
async def _purge_projects(username: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user