feat(ledger): code_shapes — the shape ledger schema (#2787, milestone 294 step 1)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 13s
CI & Build / integration (push) Successful in 25s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 45s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 13s
CI & Build / integration (push) Successful in 25s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 45s
The accounting half of the pattern system (governing note 2786): the snippet library records canon (small), this table accounts for EVERY extracted shape (total). Identity is (project, repo_key, path, symbol, kind) — kind included because one file can define '.foo' (css) and 'foo' (sym) as distinct shapes. Status vocabulary: canonical / instance / variant / exempt / unclassified, with unclassified as the default and THE todo state; classifications carry who judged (agent|audit|hook|mechanical|import), when, and the why for variants/exemptions. first/last-seen commits + vanished_at keep history instead of deleting it; a rename reads as vanish+new (accepted for v1). snippet_id is SET NULL on snippet deletion so accounting rows outlive their target and rejoin the todo via the step-2 sync, never dangle silently. Backups: v7 carries code_shapes (judgment data, worth moving) — full and per-user export sections, and a restore that keeps a judgment only when its snippet survives the id re-mapping, downgrading to unclassified otherwise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,5 +44,6 @@ from scribe.models.rulebook import ( # noqa: E402, F401
|
||||
)
|
||||
from scribe.models.repo_binding import RepoBinding # noqa: E402, F401
|
||||
from scribe.models.forge_connection import ForgeConnection # noqa: E402, F401
|
||||
from scribe.models.code_shape import CodeShape # noqa: E402, F401
|
||||
from scribe.models.system import System, RecordSystem # noqa: E402, F401
|
||||
from scribe.models.design_system import DesignSystem, DesignToken # noqa: E402, F401
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from scribe.models import Base
|
||||
from scribe.models.base import TimestampMixin
|
||||
|
||||
# The classification vocabulary (note 2786). `unclassified` is the default and
|
||||
# THE todo state; every other status is a judgment, stamped with who made it.
|
||||
SHAPE_STATUSES = ("canonical", "instance", "variant", "exempt", "unclassified")
|
||||
SHAPE_CLASSIFIERS = ("agent", "audit", "hook", "mechanical", "import")
|
||||
|
||||
|
||||
class CodeShape(Base, TimestampMixin):
|
||||
"""One extracted code shape and its classification against canon (#2787).
|
||||
|
||||
The accounting half of the pattern system (governing note 2786): the
|
||||
snippet library records CANON (small); this ledger accounts for EVERY
|
||||
shape the coverage extractor finds in a bound repo (total). A row's
|
||||
status says how the shape relates to canon — it IS a snippet's reference
|
||||
(`canonical`), conforms to one (`instance` — snippet_id may point at
|
||||
another project's snippet, so family canon counts), departs deliberately
|
||||
(`variant`, with the why in `reason`), was judged one-off (`exempt`,
|
||||
a recorded judgment rather than silence), or awaits judgment
|
||||
(`unclassified` — the todo).
|
||||
|
||||
Identity is (project, repo_key, path, symbol, kind) — kind is part of it
|
||||
because one file can define `.foo` (css) and `foo` (sym) as distinct
|
||||
shapes. A rename therefore reads as vanish + new row: accepted for v1,
|
||||
because chasing renames needs content identity the extractor doesn't
|
||||
have. `vanished_at` keeps the history instead of deleting it.
|
||||
|
||||
snippet_id is SET NULL on snippet deletion: the classification's target
|
||||
is gone but the judgment happened; the sync pass (step 2) re-files such
|
||||
rows as unclassified so they rejoin the todo instead of dangling.
|
||||
"""
|
||||
|
||||
__tablename__ = "code_shapes"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"project_id", "repo_key", "path", "symbol", "kind",
|
||||
name="uq_code_shapes_identity",
|
||||
),
|
||||
Index("ix_code_shapes_project_status", "project_id", "status"),
|
||||
Index("ix_code_shapes_snippet", "snippet_id"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
project_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
repo_key: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
path: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
symbol: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
kind: Mapped[str] = mapped_column(Text, nullable=False) # "css" | "sym"
|
||||
status: Mapped[str] = mapped_column(Text, nullable=False, default="unclassified")
|
||||
snippet_id: Mapped[int | None] = mapped_column(
|
||||
BigInteger, ForeignKey("notes.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
classified_by: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
classified_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True
|
||||
)
|
||||
first_seen_commit: Mapped[str] = mapped_column(Text, default="")
|
||||
last_seen_commit: Mapped[str] = mapped_column(Text, default="")
|
||||
vanished_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self.id,
|
||||
"project_id": self.project_id,
|
||||
"repo_key": self.repo_key,
|
||||
"path": self.path,
|
||||
"symbol": self.symbol,
|
||||
"kind": self.kind,
|
||||
"status": self.status,
|
||||
"snippet_id": self.snippet_id,
|
||||
"reason": self.reason,
|
||||
"classified_by": self.classified_by,
|
||||
"classified_at": self.classified_at.isoformat() if self.classified_at else None,
|
||||
"first_seen_commit": self.first_seen_commit,
|
||||
"last_seen_commit": self.last_seen_commit,
|
||||
"vanished_at": self.vanished_at.isoformat() if self.vanished_at else None,
|
||||
"created_at": self.created_at.isoformat(),
|
||||
"updated_at": self.updated_at.isoformat(),
|
||||
}
|
||||
Reference in New Issue
Block a user