docs(6206): webhooks, search, rulebooks routes and retrieval_tuning, note, models/__init__ drop citations

routes/search states the derived-facets property rather than the silent
whole-corpus bug that motivated it; routes/rulebooks gives the preference
restore exception its reasoning without the milestone numbers it cited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 20:43:32 -04:00
co-authored by Claude Opus 5.5
parent 0c88e1b0b0
commit 6ec78b530e
6 changed files with 32 additions and 34 deletions
+6 -7
View File
@@ -280,19 +280,18 @@ async def get_rule_version(rule_id: int, version_id: int):
return jsonify(version.to_dict(include_text=True))
# NO restore route FOR A RULE, deliberately (milestone 323). A note version
# NO restore route FOR A RULE, deliberately. A note version
# can be restored; a binding instruction should not be revertible in one
# click. Putting a rewrite back goes through update_rule, which takes its own
# snapshot and leaves the undo in the history like any other edit — a silent
# revert would erase the only record of why the rewrite happened.
#
# A PREFERENCE IS THE EXCEPTION, and the route below refuses anything else.
# 323's reasoning turns on the rewrite being the operator's own decision.
# The reasoning above turns on the rewrite being the operator's own decision.
# A preference's rewrite is not: the agent makes it mid-work without asking,
# which is what the kind is for. Reverting one is a veto over someone else's
# edit rather than an undo of your own, and milestone 399 named the cost of
# making that veto expensive — drift supervised in name only. The safeguard
# 323 actually wanted survives intact, because the restore goes through
# edit rather than an undo of your own, and making that veto expensive leaves
# drift supervised in name only. The safeguard survives intact, because the restore goes through
# update_rule too: the rewrite stays in the history with the revert recorded
# after it, so the history gains an entry rather than losing one.
@@ -369,7 +368,7 @@ async def unrelate_rules(relation_id: int):
@login_required
async def move_rule(rule_id: int):
"""Give a rule a new home: {topic_id} makes it global, {project_id} makes
it that project's. The MCP twin is move_rule (rule 33: same names)."""
it that project's. The MCP twin is move_rule, under the same names."""
data = await request.get_json() or {}
uid = get_current_user_id()
try:
@@ -452,7 +451,7 @@ async def create_project_rule(project_id: int):
)), 201
# ── The staleness sweep (milestone 312) ────────────────────────────────
# ── The staleness sweep ────────────────────────────────────────────────
@rulebooks_bp.get("/rules-due-for-verification")
@login_required
+8 -9
View File
@@ -11,8 +11,8 @@ from scribe.services.embeddings import semantic_search_notes
from scribe.services.knowledge import content_type_filters
from scribe.services.retrieval_telemetry import record_retrieval
# The interactive floor lives in embeddings.py now, shared with Browse search
# and the list views' semantic `q` — one number, one rationale (#2463).
# The interactive floor lives in embeddings.py, shared with Browse search and
# the list views' semantic `q` — one number, one rationale.
search_bp = Blueprint("search", __name__, url_prefix="/api/search")
@@ -26,10 +26,9 @@ async def search_route():
return jsonify({"error": "q is required"}), 400
limit = min(request.args.get("limit", 10, type=int), 50)
# Every kind the facet table declares, derived rather than mapped here —
# this route used to know exactly two and read anything else as "no
# filter", so `?content_type=snippets` silently returned the whole corpus
# (#4250). An unknown kind is now a 400 naming the ones that exist: a
# Every kind the facet table declares, derived rather than mapped here, so
# no kind can fall through to "no filter" and silently return the whole
# corpus. An unknown kind is a 400 naming the ones that exist: a
# result set is an answer, and it should not be able to answer a question
# nobody asked.
try:
@@ -37,9 +36,9 @@ async def search_route():
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
is_task = filters.get("is_task")
# Same association filters the MCP tool takes (#33). Optional, default
# global: this route has NO frontend consumer today (measured 2026-08-08 —
# the web UI searches through /api/knowledge), so it serves API callers,
# Same association filters the MCP tool takes. Optional, default global:
# the web UI searches through /api/knowledge, so this route serves API
# callers,
# and an API caller states its scope explicitly.
system_id = request.args.get("system_id", type=int)
project_id = request.args.get("project_id", type=int)
+4 -4
View File
@@ -1,4 +1,4 @@
"""Forge push webhook — drift flagging at the moment the repo moves (#2691).
"""Forge push webhook — drift flagging at the moment the repo moves.
The forge POSTs here on every push; changed paths are matched against recorded
snippet locations (through repo bindings) and matched verdicts get an
@@ -14,7 +14,7 @@ Contract with the forge's delivery loop:
- No secret configured → 404: the endpoint doesn't exist until an operator
creates it. Bad signature → 401: that's a caller problem worth signaling.
- A PROCESSING failure returns 200 with ``{"ok": false}`` and drops a
WARNING + AppLog row (the #2663 canary pattern): repeated 5xx responses
WARNING + AppLog row (the telemetry canary pattern): repeated 5xx responses
make forges mark deliveries failed and operators disable the hook, which
would silently turn the feature off — the exact failure mode this
milestone exists to prevent.
@@ -55,7 +55,7 @@ def signature_ok(secret: str, body: bytes, signature: str) -> bool:
def delivered_signature(headers) -> str:
"""The HMAC hex a delivery carries, whichever forge sent it: Gitea's
X-Gitea-Signature verbatim, or GitHub's X-Hub-Signature-256 minus its
"sha256=" scheme prefix (#2693). Empty when neither header is present."""
"sha256=" scheme prefix. Empty when neither header is present."""
return headers.get("X-Gitea-Signature", "") or headers.get(
"X-Hub-Signature-256", ""
).removeprefix("sha256=")
@@ -104,7 +104,7 @@ async def forge_push():
"forge push %s flagged %d snippet(s) for recheck", head[:12], flagged
)
# A push is exactly when the coverage number goes stale — recompute it
# off the delivery path (#2692). Fire-and-forget: the forge's delivery
# off the delivery path. Fire-and-forget: the forge's delivery
# loop must not wait on an archive download, and a failure is a
# WARNING from spawn(), never a failed delivery. This also SEEDS the
# cache on a webhook-configured instance — no manual first refresh.