feat(inception): always-on exclusions reach every rule surface — list_always_on_rules(project_id), get_applicable_rules, rules_payload.excluded_always_on, session context, exclude/include tools (#2880, milestone 297 step 2)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Failing after 1m12s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Failing after 1m12s
CI & Build / Build & push image (push) Skipped
A project that opted out of an always-on rulebook at inception must not see
it anywhere: list_always_on_rules(project_id=) and the subscription-derived
set skip it (an exclusion is total), get_applicable_rules / rules_payload
carry `excluded_always_on` as the seventh key so the departure is visible
wherever the rules are, and the SessionStart block built for a bound project
names it ("Excluded for this project by its inception decision …"). MCP:
list_always_on_rules takes project_id; exclude_always_on_rulebook /
include_always_on_rulebook mirror suppress/unsuppress (owner-only; the
rulebook must be always_on — subscribed rulebooks are left by unsubscribing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -222,16 +222,22 @@ async def list_rules(
|
||||
return {"rules": [_rule_summary(r) for r in rows], "total": len(rows)}
|
||||
|
||||
|
||||
async def list_always_on_rules() -> dict:
|
||||
async def list_always_on_rules(project_id: int = 0) -> dict:
|
||||
"""Return all rules from rulebooks flagged always_on for the current user.
|
||||
|
||||
Call this at session start. Treat the returned rules as binding for the
|
||||
session — they apply regardless of which project (if any) is in scope.
|
||||
Pair with get_project(id).applicable_rules when working on a specific
|
||||
project to also load that project's subscription-derived rules.
|
||||
|
||||
Args:
|
||||
project_id: 0 (default) = the user-wide set. Inside a project, pass
|
||||
its id: an always-on rulebook the project EXCLUDED at inception
|
||||
(see enter_project's `excluded_always_on`) is left out — the
|
||||
project decided not to inherit it.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
rules = await rulebooks_svc.list_always_on_rules(uid)
|
||||
rules = await rulebooks_svc.list_always_on_rules(uid, project_id=project_id)
|
||||
return {"rules": [_rule_summary(r) for r in rules], "total": len(rules)}
|
||||
|
||||
|
||||
@@ -407,6 +413,35 @@ async def unsubscribe_project_from_rulebook(
|
||||
|
||||
# ── Suppressions — project-level mute of rulebook rules / topics ────────
|
||||
|
||||
async def exclude_always_on_rulebook(project_id: int, rulebook_id: int) -> dict:
|
||||
"""Opt a project OUT of a whole always-on rulebook (milestone 297).
|
||||
|
||||
Always-on rulebooks bind every project implicitly; an inception decision
|
||||
can say "not this one, not here". The exclusion is total for that project
|
||||
— list_always_on_rules(project_id), enter_project/get_project rules and
|
||||
the session-start context all leave it out and name it under
|
||||
`excluded_always_on`. Owner-only; the rulebook must be always_on (a
|
||||
subscribed rulebook is left with unsubscribe_project_from_rulebook).
|
||||
Idempotent; include_always_on_rulebook reverses it. Normally reached via
|
||||
decide_project_inception, not by hand.
|
||||
"""
|
||||
uid = current_user_id()
|
||||
await rulebooks_svc.exclude_always_on_rulebook_for_project(
|
||||
project_id=project_id, rulebook_id=rulebook_id, user_id=uid,
|
||||
)
|
||||
return {"project_id": project_id, "rulebook_id": rulebook_id, "excluded": True}
|
||||
|
||||
|
||||
async def include_always_on_rulebook(project_id: int, rulebook_id: int) -> dict:
|
||||
"""Reverse exclude_always_on_rulebook: the always-on rulebook binds this
|
||||
project again. Idempotent."""
|
||||
uid = current_user_id()
|
||||
await rulebooks_svc.include_always_on_rulebook_for_project(
|
||||
project_id=project_id, rulebook_id=rulebook_id, user_id=uid,
|
||||
)
|
||||
return {"project_id": project_id, "rulebook_id": rulebook_id, "excluded": False}
|
||||
|
||||
|
||||
async def suppress_rule_for_project(
|
||||
project_id: int, rule_id: int,
|
||||
) -> dict:
|
||||
@@ -470,5 +505,6 @@ def register(mcp) -> None:
|
||||
subscribe_project_to_rulebook, unsubscribe_project_from_rulebook,
|
||||
suppress_rule_for_project, unsuppress_rule_for_project,
|
||||
suppress_topic_for_project, unsuppress_topic_for_project,
|
||||
exclude_always_on_rulebook, include_always_on_rulebook,
|
||||
):
|
||||
mcp.tool(name=fn.__name__)(fn)
|
||||
|
||||
Reference in New Issue
Block a user