Files
resolutionflow/backend/app/schemas/session_suggested_fix.py
Michael Chihlas 66e592096c feat(pilot): Phase 3 — Suggested fix tracking + Resolve preview with state_version cache
Adds the AI-proposed resolution path and the inline preview of the
markdown that will be posted to the customer ticket on Resolve. The
preview is keyed on (session_id, ai_sessions.state_version) so back-to-
back fetches against unchanged state hit an in-process cache instead
of paying for a Sonnet call.

Backend:
- preview_cache: in-process LRU keyed on (kind, session_id, state_version).
  No TTL — state_version is the source of truth. Soft-cap 5000 entries.
- unified_chat_service: [SUGGEST_FIX] parser (last-block-wins, JSON
  payload, confidence clamped 0-100), supersession persistence (sets
  superseded_at on prior active row), atomic state_version bump.
- ResolutionNoteGeneratorService: pulls session, facts, active fix, and
  redacted script_generations into a structured input bundle for Sonnet;
  produces the four-section markdown (Problem / What we confirmed /
  Root cause / Resolution). Sensitive script parameters redacted via
  ScriptTemplateEngine.redact_sensitive driven by the template's
  parameters_schema.
- /api/v1/ai-sessions/{id}/suggested-fixes/active — 200 with the active
  fix or 404.
- /api/v1/ai-sessions/{id}/suggested-fixes/{fix_id}/decision — records
  one_off / draft_template / build_template / dismissed; dismiss
  supersedes; bumps state_version. 409 on dismissing an already-
  superseded fix.
- /api/v1/ai-sessions/{id}/resolution-note/preview — generates or returns
  cached markdown; from_cache flag in payload signals cache hit.
- scripts.py POST /generate now bumps state_version on the linked
  ai_session_id when present (third source of preview-cache invalidation
  per Section 5.5).
- ASSISTANT_SYSTEM_PROMPT documents [SUGGEST_FIX] (when to/not to emit,
  format, supersession semantics).
- 12 tests covering the parser (well-formed, last-wins, malformed,
  confidence clamping), supersession + state_version invariant, all
  decision branches, preview cache hit-on-no-change + miss-after-write.

Frontend:
- src/components/pilot/sections/SuggestedFix.tsx — amber-accented card
  with confidence badge; dismiss action wired to the decision endpoint.
- src/components/pilot/ResolutionNotePreview.tsx — popover with refresh,
  loading state, cached/fresh indicator, ticket-ref display.
- src/api/sessionSuggestedFixes.ts — typed client; getActive normalizes
  404 to null so callers don't have to special-case.
- TaskLane gains suggestedFixSlot + bottomSlot props (rendered after
  Diagnostic Checks; bottomSlot anchors the Resolve action).
- AssistantChatPage: refreshSessionDerived helper batches fact + fix
  refresh; fact mutations and chat sends both schedule a 500ms-debounced
  preview refresh per the Section 5.5 spec.

Verified end-to-end against the dev stack with a real Sonnet call:
- /active 404 → fact create → preview generates four-section markdown
  grounded only in provided facts → second preview call hits cache
  (from_cache=true, no LLM call) → fact write 2 → cache miss, regenerates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:45:52 -04:00

64 lines
2.0 KiB
Python

"""Pydantic schemas for session suggested fixes (Phase 3).
See FLOWPILOT-MIGRATION.md Section 5.2.
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from uuid import UUID
from pydantic import BaseModel, Field
UserDecision = Literal["one_off", "draft_template", "build_template", "dismissed"]
class SessionSuggestedFixResponse(BaseModel):
id: UUID
session_id: UUID
title: str
description: str
confidence_pct: int
script_template_id: UUID | None
ai_drafted_script: str | None
ai_drafted_parameters: dict[str, Any] | None
user_decision: UserDecision | None
superseded_at: datetime | None
created_at: datetime
model_config = {"from_attributes": True}
class SessionSuggestedFixDecisionRequest(BaseModel):
"""Engineer's path choice on a suggested fix.
Server-side side effects per Section 5.2:
- one_off: render the script (Phase 5), no template created.
- draft_template: render + queue a draft_templates row (Phase 5/6).
- build_template: redirect to full template creation (Phase 5).
- dismissed: mark the fix superseded so a fresh suggestion can take over.
"""
decision: UserDecision
class SessionSuggestedFixDecisionResponse(BaseModel):
"""Returned after recording a decision; richer payloads land in Phase 5."""
id: UUID
user_decision: UserDecision
# Set when the decision triggered side effects (e.g. a script generation).
# Phase 3 only records the choice; this stays None until Phase 5 wires it.
rendered_script: str | None = None
redirect_path: str | None = Field(
None,
description="Where to send the engineer next (e.g. /scripts/builder?... for build_template)",
)
# ── Resolution note preview ────────────────────────────────────────────────
class ResolutionNotePreviewResponse(BaseModel):
markdown: str
target_ticket_ref: str | None
state_version: int
from_cache: bool