Milestone 414 "A rule is global or belongs to one project — and retrieval honours which", steps 1–3, plus #3191.
What ships
#4074, rule retrieval honours scope (188e78b): the prompt, pre-tool, write-path and preference-slot rule searches surface global rules plus the bound project's own, never another project's. An unbound session gets global rules only. search(content_type="rule") scopes to its project_id, or searches the whole rulebook without one. A shared project's rules reach its collaborators through access.can_read_project.
Schema and data: migration 0101 drops project_rulebook_subscriptions, project_rule_suppressions and project_topic_suppressions, and strips the retired choices from stored inception records.
Removed everywhere: the subscribe and suppress operations are gone from services, MCP tools, REST routes and the UI.
New-project setup asks two questions (design system, starter Systems).
Backup v15. Older archives still restore.
Rule listings:get_applicable_rules returns a project's own rules plus the global rules tagged to an area it works in.
Guidance: the using-scribe skill and docs say global vs project.
#4063, move a rule between scopes (4e4020c): move_rule (MCP), POST /api/rules/<id>/move (REST), and a "Where this rule applies" picker in the rule editor and the project rules tab. It keeps the rule's id, history, areas and relations, and refuses a title clash in the target topic with a clear message.
#3191, read-scope fix (0bf7406): rules_due_for_verification and rule_history now work with a read-only key. Every registered MCP tool must now be declared read or write, checked by a test that can be shown to fail.
Plugin 2026.09.15.1626.
CI
Green on dev: runs 6897, 6899, 6900 and 6901 (typecheck, plugin hooks, lint, unit, integration).
After deploy
/plugin and /reload-plugins.
Check in the browser: a project's rules tab, the new-project setup card, and the move picker.
Step 4 (#4075): move rule 94 to a global topic, then check in a live session that other projects' rules no longer arrive.
Milestone 414 "A rule is global or belongs to one project — and retrieval honours which", steps 1–3, plus #3191.
## What ships
- **#4074, rule retrieval honours scope** (188e78b): the prompt, pre-tool, write-path and preference-slot rule searches surface global rules plus the bound project's own, never another project's. An unbound session gets global rules only. `search(content_type="rule")` scopes to its `project_id`, or searches the whole rulebook without one. A shared project's rules reach its collaborators through `access.can_read_project`.
- **#4052, retire subscriptions and suppressions** (0bcd4b5, b6751e4). **Breaking.**
- **Schema and data:** migration 0101 drops `project_rulebook_subscriptions`, `project_rule_suppressions` and `project_topic_suppressions`, and strips the retired choices from stored inception records.
- **Removed everywhere:** the subscribe and suppress operations are gone from services, MCP tools, REST routes and the UI.
- **New-project setup** asks two questions (design system, starter Systems).
- **Backup v15.** Older archives still restore.
- **Rule listings:** `get_applicable_rules` returns a project's own rules plus the global rules tagged to an area it works in.
- **Guidance:** the using-scribe skill and docs say global vs project.
- **#4063, move a rule between scopes** (4e4020c): `move_rule` (MCP), `POST /api/rules/<id>/move` (REST), and a "Where this rule applies" picker in the rule editor and the project rules tab. It keeps the rule's id, history, areas and relations, and refuses a title clash in the target topic with a clear message.
- **#3191, read-scope fix** (0bf7406): `rules_due_for_verification` and `rule_history` now work with a read-only key. Every registered MCP tool must now be declared read or write, checked by a test that can be shown to fail.
Plugin 2026.09.15.1626.
## CI
Green on dev: runs 6897, 6899, 6900 and 6901 (typecheck, plugin hooks, lint, unit, integration).
## After deploy
- `/plugin` and `/reload-plugins`.
- Check in the browser: a project's rules tab, the new-project setup card, and the move picker.
- Step 4 (#4075): move rule 94 to a global topic, then check in a live session that other projects' rules no longer arrive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
semantic_search_rules searched every rule the user owned, and every hook arm
called it without a project, so each project's rules were injected into every
other project's sessions and a project rule meant nothing a session could feel.
The search now takes a scope: global rules by default (an unbound session, or a
caller that forgets to say), global plus project N when given project_id (N's
rules only if the caller can read that project, through access.can_read_project),
and every owned rule with everywhere=True. The four hook arms and the report
preference lookup pass the session's project; an explicit
search(content_type="rule") scopes to its project_id, or asks the whole rulebook
without one.
Milestone 414 step 1. Guarded by an AST walk that every hook call site passes
project_id, and an integration test on real Postgres that a rule is reached only
from its home.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
A rule's home is its scope now: a rule in a rulebook topic is global, a rule on
a project applies to that project, and retrieval reads that directly (#4074).
A subscription had stopped changing anything a session received; a suppression
muted rules from a subscription. Operator, 2026-09-15: "we have global and
project scoped rules, we don't need the subscriptions now."
What goes, whole (rule 22):
- Migration 0101 drops project_rulebook_subscriptions, project_rule_suppressions
and project_topic_suppressions, and strips subscribe_rulebooks (and 394's
leftover exclude_always_on_rulebooks) from stored inception choices.
- Service, MCP and REST: subscribe/unsubscribe and the four suppress/unsuppress
operations. The Subscribers checklist, the subscribe chips, the skip buttons
and the Suppressed section in the rules UI.
- Inception asks two questions (design system, seed Systems). create_project and
decide_project_inception lose subscribe_rulebooks.
- Backup v15 stops exporting the three sections; older archives still restore,
the keys simply unread. Trash no longer hard-deletes suppression rows.
What changes meaning:
- get_applicable_rules is a project's LISTING: its own rules, plus the global
rules tagged to an area it works in. Untagged global rules apply everywhere
and arrive by retrieval, so they are not listed. A co_surfaces partner on a
different project is not dragged in.
- list_rules(project_id) lists that project's own rules.
- rules_payload drops subscribed_rulebooks and suppressed_*; the handshake's
brief form is project_rules alone.
- using-scribe's "Where a new rule goes" and inception sections, tool
docstrings and docs say global vs project. Plugin 2026.09.15.1620.
Milestone 414 step 2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
A rule's home is its reach: a rulebook topic makes it global, a project makes it
that project's. There was no way to change one, so a project rule decided to be
global could only be recreated and the original trashed — losing the id every
record cites, its edit history, its area tags and its relations.
- services.rulebooks.move_rule(rule_id, user_id, topic_id= | project_id=):
exactly one destination (the model's CHECK), owned by the caller, not the
rule's current home. A topic already holding a live rule with the same title
is refused with a message naming that rule, instead of uq_rule_per_topic
failing the commit. Someone else's rule reads as not found.
- Deliberately NOT done, and said in the docstring: no version (a version is
what a rule said, milestone 323 decision 4), no duplicate gate (nothing new
enters the corpus), no re-embed (retrieval reads the home at query time).
- Both doors: MCP move_rule, REST POST /api/rules/<id>/move (rule 33).
- UI: RuleHomePicker, one component in the rule editor (a global rule) and a
project's rules tab (a project rule), so the two cannot drift on what a
destination is.
- using-scribe names move_rule under "Where a new rule goes". Plugin
2026.09.15.1626.
Milestone 414 step 3.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
rules_due_for_verification (the rule staleness sweep) and rule_history (what a
rule used to say) are pure reads, and a read-scoped API key was refused both:
neither is in _READ_ONLY_TOOLS, and the completeness test that should have
caught it only looked at tools whose NAMES start like a read (get_, list_,
search…). Neither does.
- Both join _READ_ONLY_TOOLS; the comment that pointed at this issue now says
why they sat unlisted.
- _WRITE_TOOLS declares every writing tool by name. Nothing reads it at
runtime — default-deny already refuses an unlisted tool — it exists so the
classification is total.
- test_every_registered_tool_is_classified_exactly_once takes its candidates
from what build_mcp_server() actually mounts, requires each in exactly one
of the three sets, and still flags a classified name that is no tool. The
decision stays explicit; only the candidate set widened.
- test_the_completeness_check_can_fail drops a real tool from its set and
asserts it is noticed (rule 167).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Milestone 414 "A rule is global or belongs to one project — and retrieval honours which", steps 1–3, plus #3191.
What ships
188e78b): the prompt, pre-tool, write-path and preference-slot rule searches surface global rules plus the bound project's own, never another project's. An unbound session gets global rules only.search(content_type="rule")scopes to itsproject_id, or searches the whole rulebook without one. A shared project's rules reach its collaborators throughaccess.can_read_project.0bcd4b5,b6751e4). Breaking.project_rulebook_subscriptions,project_rule_suppressionsandproject_topic_suppressions, and strips the retired choices from stored inception records.get_applicable_rulesreturns a project's own rules plus the global rules tagged to an area it works in.4e4020c):move_rule(MCP),POST /api/rules/<id>/move(REST), and a "Where this rule applies" picker in the rule editor and the project rules tab. It keeps the rule's id, history, areas and relations, and refuses a title clash in the target topic with a clear message.0bf7406):rules_due_for_verificationandrule_historynow work with a read-only key. Every registered MCP tool must now be declared read or write, checked by a test that can be shown to fail.Plugin 2026.09.15.1626.
CI
Green on dev: runs 6897, 6899, 6900 and 6901 (typecheck, plugin hooks, lint, unit, integration).
After deploy
/pluginand/reload-plugins.🤖 Generated with Claude Code
https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy