Restore rolls back to a backup; a session can only file issues in another project #220
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "grimoire",
|
||||
"description": "Grimoire for Claude Code: connects the grimoire MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Grimoire skills (using-grimoire, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting, family-canon), and syncs your saved Grimoire Processes as skills (/grimoire:sync).",
|
||||
"version": "2026.10.26.2029",
|
||||
"version": "2026.11.05.0242",
|
||||
"author": {
|
||||
"name": "Bryan Van Deusen"
|
||||
},
|
||||
|
||||
@@ -143,12 +143,43 @@ WRITE_TARGETS: dict[str, dict[str, str]] = {
|
||||
|
||||
# Argument names that would locate a write wherever they appear. The guard test
|
||||
# requires every write tool parameter with one of these names to be declared
|
||||
# above, so a tool that gains one can't write past the fence unnoticed.
|
||||
# above, or listed in REFERENCES below, so a tool that gains one can't write
|
||||
# past the fence unnoticed.
|
||||
LOCATOR_NAMES = frozenset({
|
||||
"project_id", "task_id", "note_id", "lesson_id", "snippet_id", "process_id",
|
||||
"milestone_id", "system_id", "rule_id", "deleted_batch_id",
|
||||
})
|
||||
|
||||
# Arguments with a locating name that only point at a record, with why each one
|
||||
# doesn't say where the write lands.
|
||||
REFERENCES: dict[str, dict[str, str]] = {
|
||||
"classify_shapes_by_rule": {
|
||||
"snippet_id": "the canon the shapes are judged against; the rows written are project_id's",
|
||||
},
|
||||
"confirm_shape_proposals": {
|
||||
"snippet_id": "narrows which proposals to confirm; the rows written are project_id's",
|
||||
},
|
||||
"judge_lesson_link": {
|
||||
"lesson_id": "a judgment on a lesson-to-rule pairing the system asked for",
|
||||
"rule_id": "a judgment on a lesson-to-rule pairing the system asked for",
|
||||
},
|
||||
"rule_outcome": {
|
||||
"rule_id": "telemetry on what a rule changed in this session's own work",
|
||||
},
|
||||
"rule_misfired": {
|
||||
"rule_id": "telemetry on a moment that fired a rule in this session",
|
||||
"project_id": "the project the misfire happened in, recorded with the report",
|
||||
},
|
||||
}
|
||||
|
||||
# Tool-name prefixes the plugin's fence answers without asking the server: every
|
||||
# tool named this way reads. tests/test_write_scope.py holds the hook's copy of
|
||||
# this list equal to it, and holds every write tool to a name outside it.
|
||||
READ_PREFIXES = (
|
||||
"get_", "list_", "search", "find_", "enter_project", "what_might_apply",
|
||||
"resolve_design_system",
|
||||
)
|
||||
|
||||
# The one write a session may make in another project: filing an issue there,
|
||||
# as a new open task outside any plan of theirs.
|
||||
ISSUE_STATUSES = frozenset({"", "todo"})
|
||||
|
||||
@@ -13,7 +13,7 @@ from quart import g
|
||||
|
||||
from grimoire.services import write_scope
|
||||
from grimoire.services.write_scope import (
|
||||
LOCATOR_NAMES, LOCATORS, WRITE_TARGETS, check_act, is_issue_filing,
|
||||
LOCATOR_NAMES, LOCATORS, REFERENCES, WRITE_TARGETS, check_act, is_issue_filing,
|
||||
)
|
||||
|
||||
|
||||
@@ -53,14 +53,31 @@ def test_an_argument_that_would_locate_a_write_cannot_be_left_out():
|
||||
f"{tool}.{arg}"
|
||||
for tool, names in params.items()
|
||||
for arg in names & LOCATOR_NAMES
|
||||
if arg not in WRITE_TARGETS[tool]
|
||||
if arg not in WRITE_TARGETS[tool] and arg not in REFERENCES.get(tool, {})
|
||||
)
|
||||
assert not undeclared, (
|
||||
f"write tools take these locating arguments without declaring them in "
|
||||
f"WRITE_TARGETS: {undeclared}"
|
||||
f"WRITE_TARGETS, or saying in REFERENCES why they only point: {undeclared}"
|
||||
)
|
||||
|
||||
|
||||
def test_a_reference_is_an_argument_the_tool_takes_and_not_also_a_locator():
|
||||
params = _write_tool_params()
|
||||
for tool, refs in REFERENCES.items():
|
||||
for arg in refs:
|
||||
assert arg in params[tool], f"REFERENCES names {tool}.{arg}, which it doesn't take"
|
||||
assert arg not in WRITE_TARGETS[tool], f"{tool}.{arg} is both a locator and a reference"
|
||||
|
||||
|
||||
def test_no_write_tool_carries_a_read_prefix():
|
||||
# The fence hook skips the server for these prefixes, so a write named
|
||||
# like a read would pass unchecked.
|
||||
from grimoire.mcp.server import _WRITE_TOOLS
|
||||
|
||||
read_named = sorted(t for t in _WRITE_TOOLS if t.startswith(write_scope.READ_PREFIXES))
|
||||
assert not read_named, f"write tools named like reads: {read_named}"
|
||||
|
||||
|
||||
def test_the_locator_scan_sees_the_arguments_it_guards():
|
||||
# The guard above passes vacuously if the scan finds no parameters at all.
|
||||
params = _write_tool_params()
|
||||
|
||||
Reference in New Issue
Block a user