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:
@@ -8,7 +8,7 @@ from scribe.config import Config
|
||||
# inline dict cannot be recovered from the engine — and a guard that cannot
|
||||
# read the value it guards is a guard that passes forever.
|
||||
_CONNECT_ARGS: dict = {
|
||||
# A DEADLINE ON ESTABLISHING A CONNECTION (#4181, rule 156).
|
||||
# A DEADLINE ON ESTABLISHING A CONNECTION.
|
||||
#
|
||||
# asyncpg's `timeout` bounds the CONNECT — the TCP handshake plus session
|
||||
# setup — and nothing else. Against a host whose storage has wedged, that
|
||||
@@ -80,9 +80,9 @@ from scribe.models.canonical_system import CanonicalSystem # noqa: E402, F401
|
||||
from scribe.models.rulebook import ( # noqa: E402, F401
|
||||
Rulebook, RulebookTopic, Rule, RuleRelation, rule_moments, rule_systems,
|
||||
)
|
||||
# After notes and rules: it foreign-keys both (milestone 440).
|
||||
# After notes and rules: it foreign-keys both.
|
||||
from scribe.models.lesson_rule_link import LessonNoRule, LessonRuleLink # noqa: E402, F401
|
||||
# After rules: it foreign-keys them (milestone 458 step 7).
|
||||
# After rules: it foreign-keys them.
|
||||
from scribe.models.rule_moment_judgment import RuleMomentJudgment # noqa: E402, F401
|
||||
from scribe.models.repo_binding import RepoBinding # noqa: E402, F401
|
||||
from scribe.models.forge_connection import ForgeConnection # noqa: E402, F401
|
||||
@@ -90,7 +90,7 @@ from scribe.models.code_shape import CodeShape, CodeShapeConsumer, CodeShapeEven
|
||||
from scribe.models.system import System, RecordSystem # noqa: E402, F401
|
||||
from scribe.models.design_system import DesignSystem, DesignToken # noqa: E402, F401
|
||||
# After notes, projects and rulebook topics: family canon foreign-keys all
|
||||
# three (milestone 463).
|
||||
# three.
|
||||
from scribe.models.family import ( # noqa: E402, F401
|
||||
FamilyAdoption, FamilyDecision, FamilyIdea, FamilyIdeaPlatform,
|
||||
FamilyIdeaReference, Platform, ProjectPlatform,
|
||||
|
||||
@@ -76,7 +76,7 @@ class Note(Base, TimestampMixin, SoftDeleteMixin):
|
||||
recurrence_next_spawn_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True
|
||||
)
|
||||
# The claim — which session is working this task NOW (milestone 381).
|
||||
# The claim — which session is working this task NOW.
|
||||
# Orthogonal to `status`: status is the work's state and durable, the claim
|
||||
# is a session's attention and dies on read once `claim_touched_at` is past
|
||||
# the lease. Semantics in services/task_claims.py.
|
||||
@@ -93,7 +93,7 @@ class Note(Base, TimestampMixin, SoftDeleteMixin):
|
||||
# snippet — a reusable shape, with its structured fields mirrored in
|
||||
# `data` and its trigger composed into the title (0070)
|
||||
# lesson — a transferable insight, findable by WHEN IT APPLIES rather
|
||||
# than by topic (milestone 385). Same mirror discipline as a
|
||||
# than by topic. Same mirror discipline as a
|
||||
# snippet: the trigger lives in `data.when_to_apply` and is
|
||||
# composed into the title and the head of the body, which is
|
||||
# what puts it in the embedded document. A lesson is never a
|
||||
@@ -102,8 +102,8 @@ class Note(Base, TimestampMixin, SoftDeleteMixin):
|
||||
#
|
||||
# DELIBERATELY UNGATED. Unlike `task_kind` there is no CHECK on this column:
|
||||
# migration 0036 added it as plain Text with a server default and nothing
|
||||
# has constrained it since, so rule 36 has no whitelist to expand when a
|
||||
# kind is added. The vocabulary that actually decides what a reader can
|
||||
# has constrained it since, so there is no whitelist to expand when a kind
|
||||
# is added. The vocabulary that actually decides what a reader can
|
||||
# reach is `services.knowledge._FACETS`; an unrecognised value there
|
||||
# resolves to a filter matching nothing, which is the intended answer to a
|
||||
# typo.
|
||||
@@ -117,7 +117,7 @@ class Note(Base, TimestampMixin, SoftDeleteMixin):
|
||||
# so historical plan-tasks stay writable
|
||||
# Only meaningful when the note is a task (status is not None); ordinary
|
||||
# notes keep the 'work' default and ignore it. Orthogonal to note_type
|
||||
# (which is the note/entity axis). CHECK notes_task_kind_check (rule 36).
|
||||
# (which is the note/entity axis). CHECK notes_task_kind_check.
|
||||
task_kind: Mapped[str] = mapped_column(Text, default="work", server_default="work")
|
||||
# Queryable structured fields for typed records — currently snippets, whose
|
||||
# name/language/signature/locations live here so they can be INDEXED. The
|
||||
|
||||
@@ -7,7 +7,7 @@ from scribe.models import Base
|
||||
|
||||
|
||||
class RetrievalTuningEvent(Base):
|
||||
"""One row per change to a retrieval surface's floor or budget (#4102).
|
||||
"""One row per change to a retrieval surface's floor or budget.
|
||||
|
||||
WHY A TABLE AND NOT JUST THE SETTING
|
||||
|
||||
@@ -53,8 +53,8 @@ class RetrievalTuningEvent(Base):
|
||||
# did; `tests/test_retrieval_surfaces.py` asserts it rather than trusting it.
|
||||
surface: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
|
||||
# "floor" or "budget". Text rather than an enum because rule 36 makes a new
|
||||
# CHECK value a migration, and this is a young surface that may well grow a
|
||||
# "floor" or "budget". Text rather than an enum because a new CHECK value is
|
||||
# a migration, and this is a young surface that may well grow a
|
||||
# third tunable — a constraint bought nothing here except a future migration.
|
||||
dial: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
|
||||
@@ -75,7 +75,7 @@ class RetrievalTuningEvent(Base):
|
||||
# becomes a formality; the service refuses a blank one.
|
||||
reason: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
|
||||
# WHAT SPACE THIS NUMBER WAS MEASURED IN (#4104). A floor is a cosine
|
||||
# WHAT SPACE THIS NUMBER WAS MEASURED IN. A floor is a cosine
|
||||
# distance in one embedding model's geometry, over documents cut one
|
||||
# particular way — change either and the number describes something that
|
||||
# no longer exists.
|
||||
@@ -111,7 +111,7 @@ class RetrievalTuningEvent(Base):
|
||||
"new_value": self.new_value,
|
||||
"actor": self.actor,
|
||||
"reason": self.reason,
|
||||
# Null for every row written before #4104, and rendered as
|
||||
# Null on rows written before the stamp existed, and rendered as
|
||||
# "unstamped" rather than guessed at — see the column comments.
|
||||
"embedding_model": self.embedding_model,
|
||||
"shape_version": self.shape_version,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user