wip(handoff): restore backend suite to green
Some checks failed
Mirror to GitHub / mirror (push) Successful in 12s
CI / backend (pull_request) Failing after 27m35s
CI / frontend (pull_request) Successful in 2m46s
CI / e2e (pull_request) Failing after 4m9s

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
2026-04-25 06:13:23 -04:00
parent 208ec996d5
commit 49f88569da
26 changed files with 165 additions and 77 deletions

View File

@@ -194,6 +194,7 @@ async def create_folder(
new_folder = UserFolder(
user_id=current_user.id,
account_id=current_user.account_id,
name=folder_data.name,
color=folder_data.color,
icon=folder_data.icon,

View File

@@ -260,6 +260,7 @@ async def save_to_library(
category_id=data.category_id,
share_with_team=data.share_with_team,
user_id=current_user.id,
account_id=current_user.account_id,
team_id=current_user.team_id,
script_body=data.script_body,
parameters_schema=data.parameters_schema,

View File

@@ -20,6 +20,7 @@ from app.core.audit import log_audit
from app.core.rate_limit import limiter
router = APIRouter(tags=["shares"])
public_router = APIRouter(tags=["shares"])
def build_share_response(share: SessionShare) -> ShareResponse:
@@ -206,7 +207,7 @@ async def _get_optional_user(request: Request, db: AsyncSession) -> Optional[Use
return None
@router.get("/share/{share_token}", response_model=SharePublicView)
@public_router.get("/share/{share_token}", response_model=SharePublicView)
@limiter.limit("30/minute")
async def access_share(
share_token: str,

View File

@@ -78,9 +78,11 @@ api_router = APIRouter()
# ---------------------------------------------------------------------------
api_router.include_router(auth.router)
api_router.include_router(shared.router) # Public share links (no auth)
api_router.include_router(shares.public_router) # Public session share links (optional auth)
api_router.include_router(beta_signup.router)
api_router.include_router(webhooks.router) # Stripe webhook receiver
api_router.include_router(public_templates.router) # Public gallery (no auth, rate-limited)
api_router.include_router(survey.router) # Public survey flow (no auth, rate-limited)
# ---------------------------------------------------------------------------
# Admin endpoints — super_admin only
@@ -125,7 +127,6 @@ api_router.include_router(ai_fix.router, dependencies=_tenant_deps)
api_router.include_router(ai_chat.router, dependencies=_tenant_deps)
api_router.include_router(copilot.router, dependencies=_tenant_deps)
api_router.include_router(assistant_chat.router, dependencies=_tenant_deps)
api_router.include_router(survey.router, dependencies=_tenant_deps)
api_router.include_router(tree_transfer.router, dependencies=_tenant_deps)
api_router.include_router(ai_suggestions.router, dependencies=_tenant_deps)
api_router.include_router(kb_accelerator.router, dependencies=_tenant_deps)

View File

@@ -10,7 +10,7 @@ from typing import Optional, Any, TYPE_CHECKING
from sqlalchemy import String, Text, DateTime, ForeignKey, Boolean, Integer, Float, CheckConstraint
import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.dialects.postgresql import UUID, JSONB, TSVECTOR
from app.core.database import Base
@@ -46,6 +46,7 @@ class AISession(Base):
"confidence_tier IN ('guided', 'exploring', 'discovery')",
name="ck_ai_sessions_confidence_tier",
),
sa.Index("idx_ai_sessions_search", "search_vector", postgresql_using="gin"),
)
id: Mapped[uuid.UUID] = mapped_column(
@@ -150,6 +151,18 @@ class AISession(Base):
Text, nullable=True,
comment="Why escalated (set on escalation)",
)
search_vector: Mapped[Optional[str]] = mapped_column(
TSVECTOR,
sa.Computed(
"to_tsvector('english', "
"coalesce(problem_summary, '') || ' ' || "
"coalesce(resolution_summary, '') || ' ' || "
"coalesce(escalation_reason, '') || ' ' || "
"coalesce(problem_domain, ''))",
persisted=True,
),
nullable=True,
)
escalation_package: Mapped[Optional[dict[str, Any]]] = mapped_column(
JSONB, nullable=True,
comment="Context package for receiving engineer: steps_tried, hypotheses, suggestions",

View File

@@ -68,4 +68,6 @@ class RoleUpdate(BaseModel):
class AccountRoleUpdate(BaseModel):
account_role: str = Field(..., pattern="^(owner|admin|engineer|viewer)$")
# Ownership changes must go through the explicit transfer-ownership flow so
# account.owner_id stays consistent with user.account_role.
account_role: str = Field(..., pattern="^(admin|engineer|viewer)$")

View File

@@ -300,13 +300,14 @@ To create a fork, append this marker AFTER your [QUESTIONS]/[ACTIONS] markers:
When you identify a second distinct issue that is clearly separate from the primary topic \
of this session, suggest creating a spin-off ticket using the [ACTIONS] marker below. \
Use this sparingly — only when the issue is genuinely independent, not for every tangential mention.
Use `create_spin_off_ticket` as the command value for this action.
Format:
[ACTIONS]
[
{
"label": "Create ticket: <brief issue title>",
"command": "create_spin_off_ticket",
"command": "<spin-off ticket action command>",
"description": "<one sentence description of the separate issue>"
}
]

View File

@@ -5,6 +5,7 @@ from uuid import UUID
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.ai_session import AISession
from app.models.session_resolution_output import SessionResolutionOutput
@@ -21,7 +22,9 @@ class ResolutionOutputGenerator:
async def generate_all(self, session_id: UUID) -> list[SessionResolutionOutput]:
result = await self.db.execute(
select(AISession).where(AISession.id == session_id)
select(AISession)
.options(selectinload(AISession.steps))
.where(AISession.id == session_id)
)
session = result.scalar_one_or_none()
if not session:

View File

@@ -360,6 +360,7 @@ async def save_to_library(
category_id: UUID | None,
share_with_team: bool,
user_id: UUID,
account_id: UUID,
team_id: UUID | None,
script_body: str | None = None,
parameters_schema: dict | None = None,
@@ -401,6 +402,7 @@ async def save_to_library(
id=uuid_mod.uuid4(),
category_id=resolved_category_id,
created_by=user_id,
account_id=account_id,
team_id=team_id if share_with_team else None,
name=name,
slug=slug,