feat: add tree forking, custom step tracking, and session sharing
Implement three foundational schema features from the design doc: - Tree forking with lineage tracking (migration 022): parent_tree_id, root_tree_id, fork_depth columns with self-referential FKs and composite analytics index - Custom step enhancement: CustomStepSchema with source tracking (ad-hoc, step-library, forked-tree) for backward-compatible JSONB - Session sharing (migration 023): session_shares and session_share_views tables with account-scoped visibility, cryptographic tokens, view tracking, and allow_public_shares account policy Includes 21 new integration tests (9 forking, 12 sharing), SaaS consultant-recommended denormalizations, rate limiting on public share access, and test fixture fix for invite code requirement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -79,10 +79,62 @@ class Tree(Base):
|
||||
)
|
||||
usage_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||
|
||||
# Fork tracking
|
||||
parent_tree_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("trees.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True
|
||||
)
|
||||
fork_reason: Mapped[Optional[str]] = mapped_column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
comment="Brief reason: 'Added Cisco Meraki steps for our network'"
|
||||
)
|
||||
parent_updated_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
comment="Snapshot of parent's updated_at when fork created. Compare to detect parent updates."
|
||||
)
|
||||
|
||||
# Fork lineage tracking
|
||||
root_tree_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("trees.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
comment="Original tree at root of fork chain (NULL for non-forked trees)"
|
||||
)
|
||||
fork_depth: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
server_default="0",
|
||||
comment="Fork depth: 0 = original, 1 = direct fork, 2 = fork of fork, etc."
|
||||
)
|
||||
|
||||
# Relationships
|
||||
author: Mapped[Optional["User"]] = relationship("User", foreign_keys=[author_id], back_populates="trees")
|
||||
team: Mapped[Optional["Team"]] = relationship("Team", back_populates="trees")
|
||||
account: Mapped[Optional["Account"]] = relationship("Account", foreign_keys=[account_id], back_populates="trees")
|
||||
|
||||
# Fork relationships (self-referential)
|
||||
parent: Mapped[Optional["Tree"]] = relationship(
|
||||
"Tree",
|
||||
remote_side="Tree.id",
|
||||
foreign_keys=[parent_tree_id],
|
||||
back_populates="forks"
|
||||
)
|
||||
forks: Mapped[list["Tree"]] = relationship(
|
||||
"Tree",
|
||||
foreign_keys=[parent_tree_id],
|
||||
back_populates="parent"
|
||||
)
|
||||
root: Mapped[Optional["Tree"]] = relationship(
|
||||
"Tree",
|
||||
remote_side="Tree.id",
|
||||
foreign_keys=[root_tree_id]
|
||||
)
|
||||
sessions: Mapped[list["Session"]] = relationship("Session", back_populates="tree")
|
||||
|
||||
# New organization relationships
|
||||
|
||||
Reference in New Issue
Block a user