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>
59 lines
2.5 KiB
Python
59 lines
2.5 KiB
Python
from sqlalchemy import Index, Integer, Text, text
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from scribe.models import Base
|
|
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.
|
|
|
|
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
|
|
to say "this is about CI" without chaining itself to one project's row.
|
|
This table is that join key. It carries no `user_id` on purpose — a shared
|
|
project must INHERIT the vocabulary rather than re-earn it, so the catalog
|
|
is global and the same word means the same thing in every install.
|
|
|
|
It is a convergence aid, never a gate: `systems.canonical_id` is nullable,
|
|
an unmapped System stays fully usable, and `record_systems` never sees this
|
|
table at all — the local name is a legitimate local label and is never
|
|
rewritten to match.
|
|
|
|
`slug` is the match key, not a display value. It folds the spelling
|
|
differences that produced four names for one area on the author's own
|
|
instance ("CI & Release" / "CI and Release" / "CI & release"): an exact slug
|
|
hit maps automatically, and anything short of that becomes a proposal for a
|
|
human to confirm. See services/canonical_systems.canonical_slug.
|
|
"""
|
|
|
|
__tablename__ = "canonical_systems"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
name: Mapped[str] = mapped_column(Text, nullable=False)
|
|
# Normalized match key — unique among LIVE rows, so a soft-deleted entry
|
|
# doesn't block recreating the same area (the partial-unique convention
|
|
# rules/topics already use).
|
|
slug: Mapped[str] = mapped_column(Text, nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
order_index: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
|
|
__table_args__ = (
|
|
Index(
|
|
"uq_canonical_systems_slug", "slug",
|
|
unique=True, postgresql_where=text("deleted_at IS NULL"),
|
|
),
|
|
)
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"slug": self.slug,
|
|
"description": self.description,
|
|
"order_index": self.order_index,
|
|
"created_at": iso(self.created_at),
|
|
"updated_at": iso(self.updated_at),
|
|
}
|