feat(moments): mount the corpus by proposal - a pass and an open-after-moment signal, both stopping at the operator (milestone 458 step 7, #4925)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / integration (push) Failing after 43s
CI & Build / Python tests (push) Failing after 46s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / integration (push) Failing after 43s
CI & Build / Python tests (push) Failing after 46s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Build & push image (push) Skipped
A rule written before moments existed is mounted on nothing. Step 7 records, per (rule, moment), whether it belongs there and who said so: - rule_moment_judgments (migration 0118, backup v23): suggested / confirmed / rejected, from a pass, the signal, or an edit. Moment "" is "no moment fits". - The pass: rules_to_mount lists unjudged rules; propose_rule_moments records suggestions that mount nothing; rule_moment_proposals and judge_rule_moments put them to the operator. A confirm mounts, a reject is kept so the pair is never proposed again. Same service behind REST and a "Waiting on you" panel in Settings > Moments. - Edits are judgments: set_rule_moments, the one mount write path, confirms what was added and rejects what was removed in the same transaction. - The signal: scribe_moment.sh keeps a per-session acts ledger; when a rule is opened, scribe_record_opened.sh sends the last three minutes of it to /api/plugin/rule-opened. The acts resolve through the install's mappings; work.run and work.change are not evidence. Counted per distinct session with lesson_rules' evidence model, and once due the open returns one line asking the reader to offer the mount. - scribe_session_end.sh removes the session's scribe-moment files. Plugin 2026.10.05.2003. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""Moment proposals and judgments — what a mock can see (milestone 458 step 7).
|
||||
|
||||
The behaviour against real tables is tests/test_integration_rule_moment_judgments.py.
|
||||
These pin the pieces that need no database: that the migration's CHECKs and
|
||||
the model agree (rule 36), which moments count as evidence, and that the line
|
||||
the signal hands a session names the tool that answers it.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from scribe.models.rule_moment_judgment import JUDGMENT_STATES, NO_MOMENT, SOURCES
|
||||
from scribe.services import moments as moments_svc
|
||||
from scribe.services import rule_moment_judgments as svc
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _migration():
|
||||
path = ROOT / "alembic" / "versions" / "0118_rule_moment_judgments.py"
|
||||
spec = importlib.util.spec_from_file_location("m0118", path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_the_migration_checks_and_the_model_agree():
|
||||
m = _migration()
|
||||
assert tuple(m._STATES) == tuple(JUDGMENT_STATES)
|
||||
assert tuple(m._SOURCES) == tuple(SOURCES)
|
||||
|
||||
|
||||
def test_verdicts_name_real_states():
|
||||
assert set(svc.VERDICTS.values()) <= set(JUDGMENT_STATES)
|
||||
|
||||
|
||||
def test_no_moment_is_not_a_mountable_name():
|
||||
""""" is the "none fits" answer; if it ever became mountable, a pass's
|
||||
"no moment" would read as a mount on nothing."""
|
||||
assert not moments_svc.is_moment(NO_MOMENT)
|
||||
|
||||
|
||||
def test_the_ubiquitous_moments_are_not_evidence():
|
||||
assert svc.signal_moments(["work.run", "work.verify", "work.change", "work.verify",
|
||||
"not.a.moment", "skill.brainstorming"]) == [
|
||||
"work.verify", "skill.brainstorming",
|
||||
]
|
||||
# Every skipped name is a real moment — a typo here would skip nothing.
|
||||
assert all(moments_svc.is_moment(m) for m in svc.SIGNAL_SKIP)
|
||||
|
||||
|
||||
def test_the_proposal_line_names_the_tool_that_answers_it():
|
||||
rule = SimpleNamespace(id=11, title="Definition of done", kind="rule")
|
||||
line = svc._proposal_line(rule, "work.verify", {"situations": ["a", "b", "c"]})
|
||||
assert line.startswith("> Rule #11")
|
||||
assert "`work.verify`" in line and "3 distinct sessions" in line
|
||||
assert '"rule_id": 11' in line and '"moment": "work.verify"' in line
|
||||
assert "judge_rule_moments" in line and '"reject"' in line
|
||||
Reference in New Issue
Block a user