feat(moments): actions map onto moments, with in-session corrections (milestone 458 step 2, #4920)
CI & Build / Plugin hooks (push) Successful in 18s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m23s
CI & Build / Python tests (push) Successful in 2m0s
CI & Build / Build & push image (push) Successful in 36s
CI & Build / Plugin hooks (push) Successful in 18s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m23s
CI & Build / Python tests (push) Successful in 2m0s
CI & Build / Build & push image (push) Successful in 36s
moment_actions.resolve(tool, input) names every moment a call reaches and the action that reached it. One call can reach several: kubectl apply is a run, a deliver and a reach outside the workspace. Command tools match by how each segment of the line starts, with a word boundary; other tools by field=value arguments. The MCP server prefix and case are ignored. 56 shipped defaults cover the harness tools, Scribe tools and common command shapes. moment_mappings (migration 0116) holds what an install adds and the defaults it switches off. A removal is a stored row, so an upgrade does not switch the default back on. Per the operator ruling, corrections happen in the session: map_action and unmap_action (write tools) return now_reaches so the fix can be confirmed in the same reply. list_moments now shows each moment's actions on this install. REST mirrors both doors, recorded as human. Backup v21 carries the mappings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ from scribe.models.note_usage import NoteUsageEvent # noqa: E402, F401
|
||||
from scribe.models.rule_usage import RuleUsageEvent # noqa: E402, F401
|
||||
from scribe.models.system_usage import SystemUsageEvent # noqa: E402, F401
|
||||
from scribe.models.retrieval_judgment import RetrievalJudgment # noqa: E402, F401
|
||||
from scribe.models.moment_mapping import MomentMapping # noqa: E402, F401
|
||||
from scribe.models.project import Project # noqa: E402, F401
|
||||
from scribe.models.milestone import Milestone # noqa: E402, F401
|
||||
from scribe.models.task_log import TaskLog # noqa: E402, F401
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
from sqlalchemy import BigInteger, ForeignKey, Integer, Text, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from scribe.models import Base
|
||||
from scribe.models.base import CreatedAtMixin, iso
|
||||
|
||||
|
||||
class MomentMapping(Base, CreatedAtMixin):
|
||||
"""One install's correction to which actions reach which moment (milestone 458).
|
||||
|
||||
The moments are the product's vocabulary and the shipped defaults say how
|
||||
the common actions reach them (`services/moment_actions.py`). What no
|
||||
default can know is how THIS operator works: one delivers with a push,
|
||||
another with `make ship`, a third by publishing a document. A row here is
|
||||
that local knowledge, written in-session the moment a misfire is noticed.
|
||||
|
||||
`effect` is "add" (this action reaches this moment) or "remove" (a shipped
|
||||
default that misfires here is switched off). Removal is a row rather than
|
||||
an edit to the defaults because the defaults are code: an install can only
|
||||
say "not here", and saying so must survive an upgrade that ships the same
|
||||
default again.
|
||||
|
||||
Text rather than a CHECK on `effect`, for retrieval_tuning_events' reason
|
||||
(rule 36): the service validates it, and a constraint would buy nothing but
|
||||
a migration the day a third effect is wanted.
|
||||
|
||||
ONE ROW PER (user, tool, match, moment), so mapping the same thing twice
|
||||
is an update of the reason rather than a duplicate, and an add and a remove
|
||||
of the same mapping cannot both stand.
|
||||
|
||||
CASCADES with the user, unlike the telemetry tables: this is the user's own
|
||||
configuration, not a history that should outlive them.
|
||||
"""
|
||||
|
||||
__tablename__ = "moment_mappings"
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
|
||||
)
|
||||
# The tool as the harness names it, with any MCP server prefix stripped
|
||||
# (`mcp__plugin_x__update_task` → `update_task`), so a mapping does not
|
||||
# depend on what an install called its server.
|
||||
tool: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
# "" = every call of the tool. For a tool that runs a command, how the
|
||||
# command starts ("make ship"); for any other tool, `field=value` pairs.
|
||||
match: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
moment: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
effect: Mapped[str] = mapped_column(Text, nullable=False, default="add")
|
||||
# Why — what misfired, or what this action is for here. Optional at the
|
||||
# boundary: the correction is usually self-explanatory, and a required
|
||||
# reason would be friction on the exact in-session fix this table exists
|
||||
# to make cheap.
|
||||
reason: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
# "model" | "human", for retrieval_tuning_events' reason: both act as the
|
||||
# same user, and "did I do this or did the session?" is the first question.
|
||||
actor: Mapped[str] = mapped_column(Text, nullable=False, default="model")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"user_id", "tool", "match", "moment", name="uq_moment_mapping",
|
||||
),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self.id,
|
||||
"tool": self.tool,
|
||||
"match": self.match,
|
||||
"moment": self.moment,
|
||||
"effect": self.effect,
|
||||
"reason": self.reason,
|
||||
"actor": self.actor,
|
||||
"created_at": iso(self.created_at),
|
||||
}
|
||||
Reference in New Issue
Block a user