feat(inception): projects.inception record + project_rulebook_exclusions — migration 0085 with legacy backfill; backup v10 (#2879, milestone 297 step 1)
CI & Build / Python lint (push) Successful in 6s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 42s
CI & Build / integration (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m36s
CI & Build / Build & push image (push) Successful in 36s
CI & Build / Python lint (push) Successful in 6s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 42s
CI & Build / integration (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m36s
CI & Build / Build & push image (push) Successful in 36s
A project's inheritance becomes a decision, not a default (milestone 297).
- projects.inception (JSONB, NULL = undecided): {decided_at, decided_by, via
mcp|ui|legacy, choices {exclude_always_on_rulebooks, subscribe_rulebooks,
design_system_id, seed_systems}}; on to_dict.
- project_rulebook_exclusions: a project's opt-out of a whole always-on
rulebook — the sibling of the rule/topic suppressions, CASCADE both ways.
- services/inception.py (first cut): the vocabulary, validate_inception
(pure, all-or-nothing), normalize_choices, is_decided. Effects come in
step 3.
- Migration 0085 backfills every existing project via="legacy" with its
current standing (no exclusions, its subscriptions, its design_system_id,
no seed) so the ask fires only for projects created after this ships.
- Backup v10: rulebook_exclusions section; project rows carry inception and
design_system_id, restored in a post-pass once rulebooks/design systems are
mapped (design_system_id was not restored before — fixed in passing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import enum
|
||||
from sqlalchemy import BigInteger, ForeignKey, Integer, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from scribe.models import Base
|
||||
from scribe.models.base import SoftDeleteMixin, TimestampMixin, iso
|
||||
@@ -36,6 +37,14 @@ 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
|
||||
# inherit, when, and through which door — {decided_at, decided_by, via,
|
||||
# choices: {exclude_always_on_rulebooks, subscribe_rulebooks,
|
||||
# design_system_id, seed_systems}}. NULL means nobody has decided yet,
|
||||
# and enter_project asks; the effects themselves live in the subscription
|
||||
# / exclusion tables, design_system_id and the project's Systems — this is
|
||||
# the WHY, kept so later surfaces can say it. See services/inception.py.
|
||||
inception: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
@@ -48,6 +57,7 @@ class Project(Base, TimestampMixin, SoftDeleteMixin):
|
||||
"color": self.color,
|
||||
"design_system_id": self.design_system_id,
|
||||
"forge_connection_id": self.forge_connection_id,
|
||||
"inception": self.inception,
|
||||
"created_at": iso(self.created_at),
|
||||
"updated_at": iso(self.updated_at),
|
||||
}
|
||||
|
||||
@@ -129,6 +129,19 @@ project_rule_suppressions = Table(
|
||||
Column("created_at", DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)),
|
||||
)
|
||||
|
||||
# A project's opt-out of a whole ALWAYS-ON rulebook (milestone 297): the
|
||||
# sibling of the two suppression tables below, one level up. Always-on
|
||||
# rulebooks bind every project implicitly; an inception decision can exclude
|
||||
# specific ones for this project, and get_applicable_rules /
|
||||
# list_always_on_rules(project_id) skip them. FKs CASCADE like the others.
|
||||
project_rulebook_exclusions = Table(
|
||||
"project_rulebook_exclusions",
|
||||
Base.metadata,
|
||||
Column("project_id", BigInteger, ForeignKey("projects.id", ondelete="CASCADE"), primary_key=True),
|
||||
Column("rulebook_id", BigInteger, ForeignKey("rulebooks.id", ondelete="CASCADE"), primary_key=True),
|
||||
Column("created_at", DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)),
|
||||
)
|
||||
|
||||
project_topic_suppressions = Table(
|
||||
"project_topic_suppressions",
|
||||
Base.metadata,
|
||||
|
||||
Reference in New Issue
Block a user