feat: add config profiles data model and migrations
- Add ConfigProfile model with user ownership, name, description - Add ConfigInclude model for ordered profile self-references - Add ConfigMount model for mount/file definitions - Add selected_profile_id to ToolInstance for per-instance profile selection - Add default profile properties to UserConfig JSONB config - Create Alembic migration 0013 for new tables and columns - Register new models in models/__init__.py - Mark config_folders.is_active as deprecated - Add migration metadata test Quality gates: syntax check passed (all files parse successfully) OpenSpec: add-config-profiles task 1.1
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""add config profiles, includes, mounts, and tool instance profile selection
|
||||
|
||||
Revision ID: 0013_add_config_profiles
|
||||
Revises: 0012_default_port_req
|
||||
Create Date: 2026-05-24 12:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "0013_add_config_profiles"
|
||||
down_revision: Union[str, None] = "0012_default_port_req"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create config_profiles table
|
||||
op.create_table(
|
||||
"config_profiles",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("name", sa.String(length=255), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("user_id", "name", name="uq_config_profiles_user_name"),
|
||||
)
|
||||
op.create_index("idx_config_profiles_user", "config_profiles", ["user_id"])
|
||||
|
||||
# Create config_includes table
|
||||
op.create_table(
|
||||
"config_includes",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("profile_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("included_profile_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("order_index", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["profile_id"], ["config_profiles.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["included_profile_id"], ["config_profiles.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("profile_id", "included_profile_id", name="uq_config_includes_pair"),
|
||||
)
|
||||
op.create_index("idx_config_includes_profile", "config_includes", ["profile_id"])
|
||||
op.create_index("idx_config_includes_included", "config_includes", ["included_profile_id"])
|
||||
|
||||
# Create config_mounts table
|
||||
op.create_table(
|
||||
"config_mounts",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("profile_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("mount_path", sa.String(length=1024), nullable=False),
|
||||
sa.Column("content", sa.Text(), nullable=True),
|
||||
sa.Column("source_profile_id", postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column("order_index", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["profile_id"], ["config_profiles.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["source_profile_id"], ["config_profiles.id"], ondelete="SET NULL"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("idx_config_mounts_profile", "config_mounts", ["profile_id"])
|
||||
|
||||
# Add selected_profile_id to tool_instances
|
||||
op.add_column(
|
||||
"tool_instances",
|
||||
sa.Column("selected_profile_id", postgresql.UUID(as_uuid=True), nullable=True),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_tool_instances_selected_profile",
|
||||
"tool_instances",
|
||||
"config_profiles",
|
||||
["selected_profile_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
op.create_index("idx_tool_instances_selected_profile", "tool_instances", ["selected_profile_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Remove selected_profile_id from tool_instances
|
||||
op.drop_index("idx_tool_instances_selected_profile", table_name="tool_instances")
|
||||
op.drop_constraint("fk_tool_instances_selected_profile", "tool_instances", type_="foreignkey")
|
||||
op.drop_column("tool_instances", "selected_profile_id")
|
||||
|
||||
# Drop config_mounts
|
||||
op.drop_index("idx_config_mounts_profile", table_name="config_mounts")
|
||||
op.drop_table("config_mounts")
|
||||
|
||||
# Drop config_includes
|
||||
op.drop_index("idx_config_includes_included", table_name="config_includes")
|
||||
op.drop_index("idx_config_includes_profile", table_name="config_includes")
|
||||
op.drop_table("config_includes")
|
||||
|
||||
# Drop config_profiles
|
||||
op.drop_index("idx_config_profiles_user", table_name="config_profiles")
|
||||
op.drop_table("config_profiles")
|
||||
@@ -1,5 +1,8 @@
|
||||
from src.models.base import Base
|
||||
from src.models.config_folder import ConfigFolder
|
||||
from src.models.config_include import ConfigInclude
|
||||
from src.models.config_mount import ConfigMount
|
||||
from src.models.config_profile import ConfigProfile
|
||||
from src.models.git_repository import GitRepository
|
||||
from src.models.project import Project
|
||||
from src.models.ssh_key import SSHKey
|
||||
@@ -8,4 +11,17 @@ from src.models.tool_type import ToolType
|
||||
from src.models.user import User
|
||||
from src.models.user_config import UserConfig
|
||||
|
||||
__all__ = ["Base", "ConfigFolder", "GitRepository", "Project", "SSHKey", "ToolInstance", "ToolType", "User", "UserConfig"]
|
||||
__all__ = [
|
||||
"Base",
|
||||
"ConfigFolder",
|
||||
"ConfigInclude",
|
||||
"ConfigMount",
|
||||
"ConfigProfile",
|
||||
"GitRepository",
|
||||
"Project",
|
||||
"SSHKey",
|
||||
"ToolInstance",
|
||||
"ToolType",
|
||||
"User",
|
||||
"UserConfig",
|
||||
]
|
||||
|
||||
@@ -26,6 +26,8 @@ class ConfigFolder(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
||||
project_overrides: Mapped[dict | None] = mapped_column(
|
||||
JSON, default=dict, nullable=True
|
||||
) # {"project_id": {"mount_path": "...", "files": {...}}}
|
||||
# DEPRECATED: Legacy auto-mounting flag. No longer used for launch-time
|
||||
# auto-mounting. Use ConfigProfile and ToolInstance.selected_profile_id instead.
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
|
||||
user: Mapped["User"] = relationship()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey, Integer, UniqueConstraint
|
||||
from sqlalchemy import Uuid as UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.models.config_profile import ConfigProfile
|
||||
|
||||
|
||||
class ConfigInclude(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
||||
__tablename__ = "config_includes"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("profile_id", "included_profile_id", name="uq_config_includes_pair"),
|
||||
)
|
||||
|
||||
profile_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(), ForeignKey("config_profiles.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
included_profile_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(), ForeignKey("config_profiles.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
order_index: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
profile: Mapped["ConfigProfile"] = relationship(
|
||||
"ConfigProfile",
|
||||
foreign_keys=[profile_id],
|
||||
back_populates="includes",
|
||||
)
|
||||
included_profile: Mapped["ConfigProfile"] = relationship(
|
||||
"ConfigProfile",
|
||||
foreign_keys=[included_profile_id],
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey, Integer, String, Text
|
||||
from sqlalchemy import Uuid as UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.models.config_profile import ConfigProfile
|
||||
|
||||
|
||||
class ConfigMount(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
||||
__tablename__ = "config_mounts"
|
||||
|
||||
profile_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(), ForeignKey("config_profiles.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
mount_path: Mapped[str] = mapped_column(String(1024), nullable=False)
|
||||
content: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
source_profile_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(), ForeignKey("config_profiles.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
order_index: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
profile: Mapped["ConfigProfile"] = relationship(
|
||||
"ConfigProfile",
|
||||
foreign_keys=[profile_id],
|
||||
back_populates="mounts",
|
||||
)
|
||||
source_profile: Mapped["ConfigProfile | None"] = relationship(
|
||||
"ConfigProfile",
|
||||
foreign_keys=[source_profile_id],
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey, String, Text, UniqueConstraint
|
||||
from sqlalchemy import Uuid as UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.models.user import User
|
||||
|
||||
|
||||
class ConfigProfile(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
||||
__tablename__ = "config_profiles"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("user_id", "name", name="uq_config_profiles_user_name"),
|
||||
)
|
||||
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
user: Mapped["User"] = relationship()
|
||||
includes: Mapped[list["ConfigInclude"]] = relationship(
|
||||
"ConfigInclude",
|
||||
foreign_keys="ConfigInclude.profile_id",
|
||||
back_populates="profile",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="ConfigInclude.order_index",
|
||||
)
|
||||
mounts: Mapped[list["ConfigMount"]] = relationship(
|
||||
"ConfigMount",
|
||||
back_populates="profile",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="ConfigMount.order_index",
|
||||
)
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.models.config_profile import ConfigProfile
|
||||
from src.models.git_repository import GitRepository
|
||||
from src.models.project import Project
|
||||
from src.models.tool_type import ToolType
|
||||
@@ -62,8 +63,12 @@ class ToolInstance(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
||||
last_stopped_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True
|
||||
)
|
||||
selected_profile_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(), ForeignKey("config_profiles.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
tool_type: Mapped["ToolType"] = relationship()
|
||||
repository: Mapped["GitRepository"] = relationship()
|
||||
project: Mapped["Project"] = relationship()
|
||||
owner: Mapped["User"] = relationship()
|
||||
selected_profile: Mapped["ConfigProfile | None"] = relationship()
|
||||
|
||||
@@ -18,3 +18,23 @@ class UserConfig(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
||||
config: Mapped[dict[str, object]] = mapped_column(JSON, default=dict, nullable=False)
|
||||
|
||||
user: Mapped["User"] = relationship(back_populates="user_config")
|
||||
|
||||
@property
|
||||
def default_profile_id(self) -> uuid.UUID | None:
|
||||
profile_id = self.config.get("default_profile_id")
|
||||
return uuid.UUID(profile_id) if profile_id else None
|
||||
|
||||
@default_profile_id.setter
|
||||
def default_profile_id(self, value: uuid.UUID | None) -> None:
|
||||
if value is not None:
|
||||
self.config["default_profile_id"] = str(value)
|
||||
elif "default_profile_id" in self.config:
|
||||
del self.config["default_profile_id"]
|
||||
|
||||
@property
|
||||
def default_profiles(self) -> dict[str, str]:
|
||||
return self.config.get("default_profiles", {})
|
||||
|
||||
@default_profiles.setter
|
||||
def default_profiles(self, value: dict[str, str]) -> None:
|
||||
self.config["default_profiles"] = value
|
||||
|
||||
@@ -39,3 +39,18 @@ def test_refresh_tokens_migration_has_expected_revision_chain() -> None:
|
||||
|
||||
assert module.revision == "0002_refresh_tokens"
|
||||
assert module.down_revision == "0001_initial_schema"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_config_profiles_migration_has_expected_revision_chain() -> None:
|
||||
migration_path = Path(__file__).resolve().parents[2] / "alembic" / "versions" / "0013_add_config_profiles.py"
|
||||
spec = spec_from_file_location("add_config_profiles", migration_path)
|
||||
|
||||
assert spec is not None
|
||||
assert spec.loader is not None
|
||||
|
||||
module = module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
assert module.revision == "0013_add_config_profiles"
|
||||
assert module.down_revision == "0012_default_port_req"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-24
|
||||
@@ -0,0 +1,45 @@
|
||||
## Context
|
||||
|
||||
The current system uses `config_folders` with a flat `files` JSONB and an `is_active` flag for auto-mounting at tool launch time. This design is inflexible: only one folder can be active, there's no ordering of includes, no explicit per-tool-instance selection, and mount definitions are mixed with file contents in a single blob.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Provide structured config profiles with named collections of mounts and includes
|
||||
- Support ordered include lists so profiles can reference other profiles in sequence
|
||||
- Allow per-tool-instance profile selection with fallback to user/tool-type defaults
|
||||
- Remove implicit auto-mounting behavior at launch time
|
||||
- Maintain backward compatibility for existing `config_folders` data during migration
|
||||
|
||||
**Non-Goals:**
|
||||
- Frontend UI for profile management (separate change)
|
||||
- Real-time profile switching on running instances
|
||||
- Profile versioning or history
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. New `config_profiles` table replaces the semantic role of `config_folders`
|
||||
- Rationale: A profile is a higher-level concept than a folder; it includes mounts, includes, and metadata
|
||||
- `config_folders` remains for data migration but is no longer used for auto-mounting
|
||||
|
||||
### 2. `config_includes` provides ordered many-to-many self-reference on `config_profiles`
|
||||
- Rationale: Profiles need to include other profiles (e.g., a "base" profile included by "project-specific")
|
||||
- `order_index` column controls application order
|
||||
|
||||
### 3. `config_mounts` stores individual mount/file entries
|
||||
- Rationale: Normalizing mounts allows querying, ordering, and validation per mount
|
||||
- Each mount has a `mount_path`, optional `content` text, and optional `source_profile_id` for transitive includes
|
||||
|
||||
### 4. Default profile stored on `user_configs.config` JSONB
|
||||
- Rationale: Avoids schema changes to `users`; the existing `user_configs` table already stores per-user JSON
|
||||
- Key: `default_profile_id` (global default) and `default_profiles` map for per-tool-type defaults
|
||||
|
||||
### 5. `tool_instances.selected_profile_id` for explicit selection
|
||||
- Rationale: Clear, direct foreign key; nullable to allow fallback to defaults
|
||||
- Null means "use default resolution"
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Risk] Existing `config_folders` data becomes orphaned if not migrated → Mitigation: keep table, stop auto-mount behavior only
|
||||
- [Risk] Profile include cycles could cause infinite loops → Mitigation: validate at write time, detect cycles in include graph
|
||||
- [Risk] Multiple includes with overlapping mount paths → Mitigation: last-include-wins based on order_index
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
The current `config_folders` table provides basic file mounting but lacks structured profile management, ordering, and per-tool-instance selection. We need a proper config profile system that supports ordered includes, mount/file definitions, default selection, and explicit profile assignment per tool instance.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `ConfigProfile` model to replace the legacy `config_folders` concept with structured profiles
|
||||
- Add `ConfigInclude` model for ordered include lists within profiles
|
||||
- Add `ConfigMount` model for mount/file definitions (replacing the flat `files` JSONB on `config_folders`)
|
||||
- Add default profile selection per user and tool type
|
||||
- Add `selected_profile_id` to `ToolInstance` for per-instance profile selection
|
||||
- Remove launch-time reliance on legacy active config folder auto-mounting (mark `config_folders.is_active` as deprecated, stop auto-mounting at launch)
|
||||
- Create database migrations for all new tables
|
||||
- **BREAKING**: Legacy `config_folders` auto-mounting behavior will be removed; tool instances must explicitly select a profile
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `config-profile-management`: CRUD operations for config profiles, includes, and mounts
|
||||
- `tool-instance-profile-selection`: Assign and switch config profiles per tool instance
|
||||
|
||||
### Modified Capabilities
|
||||
- `tool-instance-launch`: Change launch behavior to use explicit profile selection instead of auto-mounting active config folder
|
||||
|
||||
## Impact
|
||||
|
||||
- New database tables: `config_profiles`, `config_includes`, `config_mounts`
|
||||
- Modified tables: `tool_instances` (add `selected_profile_id`), `users` or `user_configs` (add default profile selection)
|
||||
- API endpoints for profile management and instance profile assignment
|
||||
- Tool launch logic changes (remove auto-mount, use explicit profile)
|
||||
@@ -0,0 +1,29 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User can create config profiles
|
||||
The system SHALL allow users to create named config profiles containing mounts and includes.
|
||||
|
||||
#### Scenario: Successful profile creation
|
||||
- **WHEN** user creates a profile with name, description, and mount list
|
||||
- **THEN** the profile is stored with a unique ID and associated mounts
|
||||
|
||||
### Requirement: Profile includes are ordered
|
||||
The system SHALL support ordered includes where profiles can reference other profiles with a defined application sequence.
|
||||
|
||||
#### Scenario: Include with order
|
||||
- **WHEN** user adds an include to a profile with order_index 1
|
||||
- **THEN** the included profile's mounts are applied after order_index 0 includes
|
||||
|
||||
### Requirement: Config mounts define files and paths
|
||||
The system SHALL store individual mount entries with mount_path, optional content, and optional source profile reference.
|
||||
|
||||
#### Scenario: Add mount to profile
|
||||
- **WHEN** user adds a mount with mount_path "/app/config.json" and content "{}"
|
||||
- **THEN** the mount is stored and linked to the profile
|
||||
|
||||
### Requirement: Cycle detection in includes
|
||||
The system SHALL prevent creation of include cycles.
|
||||
|
||||
#### Scenario: Attempt cyclic include
|
||||
- **WHEN** user tries to include profile B in profile A where A is already included in B
|
||||
- **THEN** the system rejects the request with an error
|
||||
@@ -0,0 +1,22 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Tool instance can have selected profile
|
||||
The system SHALL allow setting an explicit config profile on a tool instance.
|
||||
|
||||
#### Scenario: Assign profile to instance
|
||||
- **WHEN** user sets selected_profile_id on a tool instance
|
||||
- **THEN** the instance stores the profile ID and uses it at launch time
|
||||
|
||||
### Requirement: Tool instance uses default profile when none selected
|
||||
The system SHALL resolve a default profile for a tool instance when no explicit profile is selected.
|
||||
|
||||
#### Scenario: Fallback to user default
|
||||
- **WHEN** a tool instance has no selected_profile_id
|
||||
- **THEN** the system uses the user's default profile for that tool type, or the global default
|
||||
|
||||
### Requirement: Remove legacy auto-mount behavior
|
||||
The system SHALL no longer auto-mount the active config folder at tool launch time.
|
||||
|
||||
#### Scenario: Launch without active folder
|
||||
- **WHEN** a tool instance launches with no selected profile and no default
|
||||
- **THEN** the instance starts without mounting any config folder
|
||||
@@ -5,11 +5,11 @@
|
||||
- [x] 1.3 Create ConfigMount model for mount/file definitions
|
||||
- [x] 1.4 Add selected_profile_id to ToolInstance model
|
||||
- [x] 1.5 Add default profile fields to UserConfig model
|
||||
- [ ] 1.6 Create Alembic migration for new tables and columns
|
||||
- [x] 1.6 Create Alembic migration for new tables and columns
|
||||
- [x] 1.7 Register new models in models/__init__.py
|
||||
- [ ] 1.8 Add migration metadata and test
|
||||
- [x] 1.8 Add migration metadata and test
|
||||
|
||||
## 2. Legacy Deprecation
|
||||
|
||||
- [ ] 2.1 Mark config_folders.is_active as deprecated in model
|
||||
- [x] 2.1 Mark config_folders.is_active as deprecated in model
|
||||
- [ ] 2.2 Remove auto-mounting logic from tool launch (separate change)
|
||||
|
||||
Reference in New Issue
Block a user