docs(6206): the remaining models, routes, mcp/tools/repos and config comments drop citations
A shipped tool docstring (mcp/tools/repos bind_repo) cited this instance's "rule 1: dev is home"; it now says what a dev-branch project should do. routes/knowledge, routes/tasks, routes/systems and models/retrieval_log state the property each guards rather than the bug that once broke it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,9 +60,9 @@ class Config:
|
||||
# the MCP layer doesn't proxy web search (Claude has its own).
|
||||
SEARXNG_URL: str = os.environ.get("SEARXNG_URL", "")
|
||||
|
||||
# Git forge integration (#2689) — optional read access to a git forge so
|
||||
# Git forge integration — optional read access to a git forge so
|
||||
# snippet bodies can be fetched/verified server-side. Connections are
|
||||
# per-user keyring rows (#2778, Settings → Git forges); these env values
|
||||
# per-user keyring rows (Settings → Git forges); these env values
|
||||
# survive as an implicit keyring entry for ADMIN users' projects only, so
|
||||
# a deployment can keep the operator's token in a Docker secret instead
|
||||
# of the database. A stored row for the same host wins over the env entry.
|
||||
|
||||
@@ -22,8 +22,8 @@ async def bind_repo(repo_url: str, project_id: int, ref: str = "") -> dict:
|
||||
|
||||
The binding is also what the shape ledger reads (refresh_pattern_coverage):
|
||||
`ref` names the branch it follows. Default (""): the repo's default branch
|
||||
— which means the ledger only sees work after a merge. A dev-first project
|
||||
(rule 1: dev is home) should bind with ref="dev" so classification follows
|
||||
— which means the ledger only sees work after a merge. A project that
|
||||
works on a dev branch should bind with ref="dev" so classification follows
|
||||
the push, not the merge. Re-binding with ref="" keeps the standing ref;
|
||||
pass ref="-" to clear it back to the default branch.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from scribe.models.base import SoftDeleteMixin, TimestampMixin, iso
|
||||
|
||||
class CanonicalSystem(Base, TimestampMixin, SoftDeleteMixin):
|
||||
"""A GLOBAL area name — the shared vocabulary every project's Systems
|
||||
can point at (milestone 307, decision note 3026).
|
||||
can point at.
|
||||
|
||||
A `System` is per-project and NOT NULL on project_id, so nothing outside a
|
||||
project can reference one: a rule that applies across projects has no way
|
||||
|
||||
@@ -135,7 +135,7 @@ class DesignToken(Base, TimestampMixin, SoftDeleteMixin):
|
||||
# and the token's own value are usually different colours entirely. No
|
||||
# value-matching rule could ever connect them.
|
||||
#
|
||||
# Consumed by the source lint (#2277), not by the drift panel: these
|
||||
# Consumed by the source lint, not by the drift panel: these
|
||||
# literals live in component CSS, which the panel cannot see and says so.
|
||||
supersedes: Mapped[list] = mapped_column(
|
||||
JSONB, nullable=False, default=list, server_default=text("'[]'::jsonb")
|
||||
|
||||
@@ -6,7 +6,7 @@ from scribe.models.base import TimestampMixin, iso
|
||||
|
||||
|
||||
class ForgeConnection(Base, TimestampMixin):
|
||||
"""One user's read-only credential for one git forge host (#2778).
|
||||
"""One user's read-only credential for one git forge host.
|
||||
|
||||
The keyring model: a user owns a set of connections and every server-side
|
||||
forge read for a project runs on the PROJECT OWNER's set, resolved by the
|
||||
|
||||
@@ -6,7 +6,7 @@ from scribe.models.base import CreatedAtMixin, iso
|
||||
|
||||
|
||||
class MomentMapping(Base, CreatedAtMixin):
|
||||
"""One install's correction to which actions reach which moment (milestone 458).
|
||||
"""One install's correction to which actions reach which moment.
|
||||
|
||||
The moments are the product's vocabulary and the shipped defaults say how
|
||||
the common actions reach them (`services/moment_actions.py`). What no
|
||||
@@ -20,8 +20,8 @@ class MomentMapping(Base, CreatedAtMixin):
|
||||
say "not here", and saying so must survive an upgrade that ships the same
|
||||
default again.
|
||||
|
||||
Text rather than a CHECK on `effect`, for retrieval_tuning_events' reason
|
||||
(rule 36): the service validates it, and a constraint would buy nothing but
|
||||
Text rather than a CHECK on `effect`, for retrieval_tuning_events' reason:
|
||||
the service validates it, and a constraint would buy nothing but
|
||||
a migration the day a third effect is wanted.
|
||||
|
||||
ONE ROW PER (user, tool, match, moment), so mapping the same thing twice
|
||||
|
||||
@@ -54,23 +54,22 @@ class NoteUsageEvent(Base, CreatedAtMixin):
|
||||
# A CONVENTION, not a fixed vocabulary: `mcp_<tool>` for an agent call,
|
||||
# `rest_<kind>` for a human opening a detail view, and a bare name for a
|
||||
# hook or background surface ('auto_inject', 'write_path_place',
|
||||
# 'write_path_semantic'). This comment deliberately no longer lists the
|
||||
# members — the previous list had gone stale, naming 'rest_note' that
|
||||
# nothing wrote while omitting sources that existed, and a half-true
|
||||
# enumeration reads as authoritative in exactly the way that misleads
|
||||
# (#2476). `grep -rn record_pulled\\\|record_surfaced src/` is the
|
||||
# 'write_path_semantic'). This comment deliberately does not list every
|
||||
# member — such a list goes stale, naming sources nothing writes while
|
||||
# omitting ones that exist, and a half-true enumeration reads as
|
||||
# authoritative in exactly the way that misleads. `grep -rn record_pulled\\\|record_surfaced src/` is the
|
||||
# authoritative list, and unlike a comment it cannot drift.
|
||||
#
|
||||
# The mcp_/rest_ split is load-bearing. "Is this dead weight?" is served by
|
||||
# any pull; "was that injected line useful?" is served by AGENT pulls only,
|
||||
# so never aggregate across the prefix without saying why (#1038, #2085).
|
||||
# so never aggregate across the prefix without saying why.
|
||||
source: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
|
||||
# The project the READER was in when this happened — not the project the
|
||||
# record belongs to, which is already on `notes.project_id`. The whole
|
||||
# point is the comparison between the two: a record opened somewhere other
|
||||
# than where it was written is the evidence that it TRANSFERRED, which is
|
||||
# the claim the lesson kind rests on (milestone 385) and the one thing
|
||||
# the claim the lesson kind rests on and the one thing
|
||||
# RetrievalLog could half-answer and this table could not answer at all.
|
||||
#
|
||||
# Nullable, and null is ordinary rather than historical. A surfacing arm
|
||||
|
||||
@@ -28,7 +28,7 @@ class Project(Base, TimestampMixin, SoftDeleteMixin):
|
||||
design_system_id: Mapped[int | None] = mapped_column(
|
||||
BigInteger, ForeignKey("design_systems.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
# The per-project forge pin (#2778). NULL is the ordinary state: forge
|
||||
# The per-project forge pin. NULL is the ordinary state: forge
|
||||
# reads resolve against the owner's keyring by repo host. When set, the
|
||||
# project's forge reads use ONLY this connection — an explicit, auditable
|
||||
# choice, constrained by the service layer to a connection the project
|
||||
@@ -37,7 +37,7 @@ class Project(Base, TimestampMixin, SoftDeleteMixin):
|
||||
BigInteger, ForeignKey("forge_connections.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
)
|
||||
# The inception record (milestone 297): what this project was decided to
|
||||
# The inception record: what this project was decided to
|
||||
# inherit, when, and through which door — {decided_at, decided_by, via,
|
||||
# choices: {design_system_id, seed_systems}}. NULL means nobody has
|
||||
# decided yet, and enter_project asks; the effects themselves live in
|
||||
|
||||
@@ -28,7 +28,7 @@ class RepoBinding(Base, TimestampMixin):
|
||||
Integer, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
repo_key: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
# The branch the coverage refresh reads for this binding (#2873); NULL =
|
||||
# The branch the coverage refresh reads for this binding; NULL =
|
||||
# the forge's default branch. Chosen at bind time so a dev-first project
|
||||
# can have its ledger follow dev instead of waiting for the merge.
|
||||
ref: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
@@ -11,12 +11,11 @@ VERDICTS = (ON_POINT, ADJACENT, UNRELATED)
|
||||
|
||||
|
||||
class RetrievalJudgment(Base, CreatedAtMixin):
|
||||
"""One reviewer's verdict on one candidate of one logged retrieval call
|
||||
(#4772).
|
||||
"""One reviewer's verdict on one candidate of one logged retrieval call.
|
||||
|
||||
The relevance half the usage tables cannot hold. `note_usage_events` says
|
||||
whether a line was OPENED, and since every menu line carries its matched
|
||||
passage (#4364) "not opened" covers an unrelated line, a line whose passage
|
||||
whether a line was OPENED, and because every menu line carries its matched
|
||||
passage, "not opened" covers an unrelated line, a line whose passage
|
||||
was enough, and a line the reader already had. Only someone reading the
|
||||
query beside the line can tell those apart, so this records that reading —
|
||||
with its reason, because a verdict nobody can re-read is a threshold.
|
||||
|
||||
@@ -48,8 +48,8 @@ class RetrievalLog(Base):
|
||||
# suppressed". `result_count == 0` alone conflates two different events —
|
||||
# the ranker found nothing above threshold, and the ranker found something
|
||||
# the reader already had — and only the first says a threshold is too high.
|
||||
# Reading a zero as a ranker decline is how #3311 mis-scoped a milestone;
|
||||
# an unmeasured value that renders as 0 is the same mistake with a nicer
|
||||
# Reading a zero as a ranker decline sends a reader to move a floor that
|
||||
# was never involved; an unmeasured value that renders as 0 is the same mistake with a nicer
|
||||
# face, so surfaces that filter INSIDE the search leave this null.
|
||||
suppressed_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
top_score: Mapped[float | None] = mapped_column(Float, nullable=True)
|
||||
@@ -58,12 +58,12 @@ class RetrievalLog(Base):
|
||||
# against `top_score`, which is the best it DID offer. They are equal on
|
||||
# any call that returned something, and only this one exists on a call
|
||||
# that returned nothing, which is the only place a bar can be judged from
|
||||
# (#3670). Null means the caller did not measure it, never "nothing was
|
||||
# Null means the caller did not measure it, never "nothing was
|
||||
# close": a 0.0 there would read as a corpus with no relevant records at
|
||||
# all, which is an artifact standing in for a measurement.
|
||||
best_available_score: Mapped[float | None] = mapped_column(Float, nullable=True)
|
||||
# WHICH record scored that, so a reader can judge what the bar refused
|
||||
# rather than only how close it came (#3807). Written from the same ranked
|
||||
# rather than only how close it came. Written from the same ranked
|
||||
# candidate as the score above — the two describing different records would
|
||||
# be worse than no id at all, because it invites judging the wrong one.
|
||||
#
|
||||
|
||||
@@ -10,7 +10,7 @@ from scribe.models.lesson_rule_link import CONFIRMED, LINK_STATES, REJECTED, SUG
|
||||
|
||||
# The same three states as a lesson's link to a rule, for the same reasons —
|
||||
# re-exported so a reader of this table need not know where they were first
|
||||
# named. CHECK ck_rule_moment_judgments_state (migration 0118, rule 36).
|
||||
# named. CHECK ck_rule_moment_judgments_state (migration 0118).
|
||||
JUDGMENT_STATES = LINK_STATES
|
||||
__all__ = [
|
||||
"CONFIRMED", "JUDGMENT_STATES", "NO_MOMENT", "REJECTED", "SUGGESTED",
|
||||
@@ -31,7 +31,7 @@ SOURCES = ("pass", "signal", "edit")
|
||||
|
||||
|
||||
class RuleMomentJudgment(Base, CreatedAtMixin):
|
||||
"""Whether a rule belongs on a moment, and who said so (milestone 458 step 7).
|
||||
"""Whether a rule belongs on a moment, and who said so.
|
||||
|
||||
A mount (`rule_moments`) is the current answer; this is the reasoning
|
||||
behind it and the answers that are NOT mounts. One row per (rule, moment):
|
||||
@@ -60,7 +60,7 @@ class RuleMomentJudgment(Base, CreatedAtMixin):
|
||||
note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
# The co-occurrence evidence (signal source), in lesson_rules' shape.
|
||||
evidence: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
# The other direction (step 7b, migration 0119): sessions saying this
|
||||
# The other direction (migration 0119): sessions saying this
|
||||
# MOUNTED rule arrived at the moment and did not apply. lesson_rules'
|
||||
# evidence shape keyed per day, plus the reasons given and the actions
|
||||
# that reached the moment; `kept_at` once the operator kept the mount.
|
||||
|
||||
@@ -25,7 +25,7 @@ class System(Base, TimestampMixin, SoftDeleteMixin):
|
||||
Integer, ForeignKey("projects.id", ondelete="CASCADE")
|
||||
)
|
||||
name: Mapped[str] = mapped_column(Text, default="", server_default="")
|
||||
# The GLOBAL area this local System is an instance of (milestone 307).
|
||||
# The GLOBAL area this local System is an instance of.
|
||||
# Nullable and SET NULL on purpose: the catalog is a convergence aid, not a
|
||||
# gate — an unmapped System is fully usable, and retiring a canonical entry
|
||||
# must never take a project's System with it. The local `name` is NEVER
|
||||
@@ -40,7 +40,7 @@ class System(Base, TimestampMixin, SoftDeleteMixin):
|
||||
status: Mapped[str] = mapped_column(Text, default="active", server_default="active")
|
||||
order_index: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
||||
# The files that ARE this area, as globs relative to the repo root
|
||||
# (milestone 444). The description says what the area is FOR; these say
|
||||
# The description says what the area is FOR; these say
|
||||
# where it lives, so a command or edit touching those files can be
|
||||
# resolved to the System — and its rulings — without a similarity search.
|
||||
# NOT NULL with a `[]` default for the reason DesignToken.supersedes gives:
|
||||
|
||||
@@ -10,7 +10,7 @@ PULLED = "pulled"
|
||||
|
||||
class SystemUsageEvent(Base, CreatedAtMixin):
|
||||
"""One row per time a System's rulings were SHOWN to the agent because its
|
||||
files were touched, or the System was PULLED in full (milestone 444).
|
||||
files were touched, or the System was PULLED in full.
|
||||
|
||||
The third of the usage tables, after `note_usage_events` and
|
||||
`rule_usage_events`, and separate from both for the reason the rule twin
|
||||
|
||||
@@ -172,8 +172,8 @@ async def test_smtp():
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
# The forge CONFIG moved to per-user keyring rows (#2778, Settings → Git
|
||||
# forges); what stays admin is the webhook secret, because the push endpoint
|
||||
# The forge CONFIG is per-user keyring rows (Settings → Git forges); what
|
||||
# stays admin is the webhook secret, because the push endpoint
|
||||
# is one URL per instance and authenticates deliveries, not users.
|
||||
|
||||
|
||||
@@ -329,13 +329,13 @@ async def run_db_maintenance_now():
|
||||
return jsonify(summary)
|
||||
|
||||
|
||||
# ── Scheduled backups (milestone 523) ────────────────────────────────────────
|
||||
# ── Scheduled backups ────────────────────────────────────────────────────────
|
||||
# One dated, uncompressed tar per run to a folder or an S3 bucket — see
|
||||
# services/backup_file.py for why it is shaped that way, backup_destination.py
|
||||
# for where it goes, backup_job.py for when.
|
||||
|
||||
# How long "Test" waits on the destination. A folder on a hung NFS mount has
|
||||
# no timeout of its own (rule 156), and the request must come back either way.
|
||||
# no timeout of its own, and the request must come back either way.
|
||||
_BACKUP_CHECK_TIMEOUT = 45.0
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Canonical-system routes — the GLOBAL area vocabulary, and the mapping of a
|
||||
project's Systems onto it (milestone 307, decision note 3026).
|
||||
project's Systems onto it.
|
||||
|
||||
Two shapes live here because they are two halves of one idea:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Design system + token REST endpoints (milestone #254 step 4).
|
||||
"""Design system + token REST endpoints.
|
||||
|
||||
Wraps `services/design_systems.py`, which owns the ACL and the cycle guard.
|
||||
Design systems are owner-scoped top-level records rather than project-scoped
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Family canon routes — the web door to the promotion engine (milestone 463).
|
||||
"""Family canon routes — the web door to the promotion engine.
|
||||
|
||||
The agent decides promotions and each project's answers through the MCP
|
||||
tools; this door exists so a person can READ every decision and its reasons
|
||||
— the ideas, the adoption matrix, the log — and retire an idea or undo a
|
||||
decision when they disagree. The promote and propose endpoints are here for
|
||||
parity with the agent's door (rule 33), and are recorded as the operator's.
|
||||
Every write is gated on the idea's note in the service (rule 78).
|
||||
parity with the agent's door, and are recorded as the operator's.
|
||||
Every write is gated on the idea's note in the service.
|
||||
"""
|
||||
import logging
|
||||
|
||||
|
||||
@@ -14,10 +14,9 @@ logger = logging.getLogger(__name__)
|
||||
knowledge_bp = Blueprint("knowledge", __name__, url_prefix="/api/knowledge")
|
||||
|
||||
# Derived from the service's facet table, never re-listed here. This set was a
|
||||
# hand-kept copy and had drifted three kinds behind it: it admitted `plan`
|
||||
# (retired in 0066) and rejected `issue` (shipped in 0065, 435 rows) and
|
||||
# `snippet` — so the browse surface could not filter to the kinds it was
|
||||
# already rendering badges for (#3128).
|
||||
# hand-kept copy drifts behind it — admitting retired kinds and rejecting new
|
||||
# ones — until the browse surface cannot filter to the kinds it renders
|
||||
# badges for.
|
||||
_VALID_TYPES = FACET_TYPES
|
||||
_VALID_SORTS = {"modified", "created", "alpha", "type"}
|
||||
|
||||
@@ -64,13 +63,10 @@ async def list_knowledge():
|
||||
)
|
||||
|
||||
items = await label_shared_items(uid, items)
|
||||
# The surfaced-vs-opened counts, on the list a person actually browses
|
||||
# (#4230). `usage_for_notes` always worked on every note row, but only the
|
||||
# snippet and rule lists ever attached it — and this is the ONLY lesson and
|
||||
# note list in the UI, so those two kinds had the counter collected and
|
||||
# shown nowhere. Attaching here rather than teaching `/api/lessons` a
|
||||
# second time is what closes both holes at once: `/knowledge` is how notes,
|
||||
# lessons and processes are all browsed.
|
||||
# The surfaced-vs-opened counts, on the list a person actually browses.
|
||||
# This is the ONLY lesson and note list in the UI — `/knowledge` is how
|
||||
# notes, lessons and processes are all browsed — so attaching here is what
|
||||
# shows those kinds' counters at all.
|
||||
#
|
||||
# Mixed kinds is not a problem for this: usage keys on the note row, which
|
||||
# every facet of this feed is.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Platform routes — the GLOBAL platform catalog, and which platforms a
|
||||
project is (milestone 463 step 2).
|
||||
project is.
|
||||
|
||||
Two shapes, as with the canonical areas:
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ async def list_systems_route(project_id: int):
|
||||
uid, project_id, include_archived=_truthy(request.args.get("include_archived")),
|
||||
)
|
||||
counts = await systems_svc.open_issue_counts_by_system(uid, project_id)
|
||||
# How often each area's rulings reached a session (#4769) — one GROUP BY
|
||||
# How often each area's rulings reached a session — one GROUP BY
|
||||
# for the page, and a zero shape for an area nothing has touched.
|
||||
usage = await system_usage_svc.usage_for_systems([s.id for s in systems])
|
||||
out = []
|
||||
@@ -67,9 +67,8 @@ async def create_system_route(project_id: int):
|
||||
data = await request.get_json() or {}
|
||||
if not (data.get("name") or "").strip():
|
||||
return jsonify({"error": "name is required"}), 400
|
||||
# The same gate the MCP door enforces. It lived only in the tool layer
|
||||
# until now, which is exactly how the web UI shipped without gates the
|
||||
# agent surface had (#2482) — one service call, one answer (rule 33).
|
||||
# The same gate the MCP door enforces — one service call, one answer, so
|
||||
# neither door ships without a gate the other has.
|
||||
assessment = await systems_svc.assess_system_name(uid, project_id, data["name"])
|
||||
duplicate = assessment["duplicate"]
|
||||
if duplicate and not data.get("force"):
|
||||
|
||||
@@ -242,9 +242,8 @@ async def update_task_route(task_id: int):
|
||||
if "tags" in data:
|
||||
fields["tags"] = data["tags"]
|
||||
|
||||
# Re-filing a task's kind. The editor has always SENT this field and the
|
||||
# route silently dropped it — reporting "Task saved" and reverting on the
|
||||
# next load (#3129). Validated at the same layer as status and priority so
|
||||
# Re-filing a task's kind. The editor sends this field; dropping it would
|
||||
# report "Task saved" and revert on the next load. Validated at the same layer as status and priority so
|
||||
# an unrecognised value is a 400 rather than a database CHECK violation.
|
||||
if "kind" in data:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user