Commit graph

86 commits

Author SHA1 Message Date
Vadym Samoilenko
c6f7b0d5f1 Populate email on login for users with empty email field
When a user already exists in the DB, get_or_create_from_azure was
returning early without updating their email from Azure AD claims.
Users created before email sync was in place would permanently show
empty emails in User Management.

Now syncs email from Azure AD claims on each login if the stored
email is empty.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:45:48 +00:00
Vadym Samoilenko
0432635153 Grant oversight_admin write access to campaigns and proofs
Oversight admins can now create campaigns, upload proofs, and
flag/resolve issues when they have an agency assigned. They retain
all existing cross-agency read access for analytics, auditing, and
user management. Oversight admins without an agency see a read-only
campaigns view.

Changes:
- Add oversight_admin to canWrite permission in UserContext
- Guard readOnly for oversight_admin without agency in App.tsx
- Remove oversight_admin block from require_write_access dependency
- Remove WebSocket oversight_admin upload block in main.py
- Require agency for oversight_admin campaign creation in routes.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:08:54 +00:00
Vadym Samoilenko
05e1628086 Add debug logging to model_fallback callback in handlers.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 13:08:25 +00:00
Vadym Samoilenko
efa6e772e0 Add toast notification when primary Gemini model falls back to backup
Backend: thread on_fallback callback through analysis chain
(gemini_service → agents → analysis_service → handlers). The handler
sends a 'model_fallback' WebSocket message exactly once per analysis
when the primary model is unavailable.

Frontend: handle 'model_fallback' WS message and show a dismissible
yellow toast at the bottom of the screen with an 8-second auto-dismiss.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 13:00:12 +00:00
Vadym Samoilenko
9ecabafa2b Fix Gemini http_options timeout unit: seconds → milliseconds
google-genai SDK expects http_options 'timeout' in milliseconds.
Passing 45 (seconds) was interpreted as 45ms → ~1s deadline,
which Google API rejected with 400 INVALID_ARGUMENT
'Manually set deadline 1s is too short. Minimum allowed deadline is 10s.'

Primary: 45_000ms (45s), Fallback: 150_000ms (150s)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:52:25 +00:00
Vadym Samoilenko
74585c5c18 Fix Gemini timeout by using HTTP-level timeout on separate clients
asyncio.wait_for cannot reliably cancel SDK-internal HTTP connections.
Replace with two genai.Client instances — one per model — each configured
with http_options={'timeout': N} so the TCP connection is actually torn
down when the deadline is reached.

Primary model: 45s, Fallback model: 150s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:49:11 +00:00
Vadym Samoilenko
a9bd6a2775 Increase fallback Gemini timeout from 60s to 150s
Log analysis showed fallback model responses up to 154s under parallel
load. 60s was too aggressive and would cause false timeouts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:43:39 +00:00
Vadym Samoilenko
e8c0257ea6 Add timeouts to Gemini API calls to prevent 10+ minute hangs
Primary model (gemini-3.1-pro-preview): 45s timeout
Fallback model (gemini-3-flash-preview): 60s timeout

Without timeouts, the fallback model under high load would wait
indefinitely, causing analysis to hang for 10+ minutes per file.
asyncio.TimeoutError from the primary model is now handled the same
as other exceptions (falls through to fallback).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:42:04 +00:00
Vadym Samoilenko
84d5b533f7 Handle WebSocket disconnect gracefully during analysis
When a client disconnects (navigates away, closes tab) while analysis is
still running, the result send raises RuntimeError "WebSocket is not
connected". Catch this specifically as INFO rather than ERROR, and guard
the fallback send_message in the general Exception handler so it doesn't
raise a second uncaught error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:30:51 +00:00
Vadym Samoilenko
46e7de5695 Fix MissingGreenlet 500 error on GET /api/campaigns
Add selectinload(Campaign.agency) to get_with_proof_counts query so the
agency relationship is eagerly loaded. Without it, accessing campaign.agency.name
in the route triggered a lazy load in an async context, raising
sqlalchemy.exc.MissingGreenlet and returning HTTP 500.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:17:12 +00:00
Vadym Samoilenko
82e38e8853 Add gemini-3-flash-preview fallback and Cloud Run service config
gemini_service.py: if the primary model (gemini-3.1-pro-preview) is
unavailable or returns a permission error, all three call sites now
automatically retry with gemini-3-flash-preview before propagating failure.

cloudrun.yaml: new Cloud Run service definition that ensures stable
WebSocket operation — 10-minute request timeout (vs 60s default),
2 vCPU / 4Gi RAM for PDF rasterisation, min 1 warm instance to prevent
cold-start disconnects, and GEMINI_API_KEY sourced from Secret Manager
so the service can actually reach the Gemini API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:18:57 +00:00
Vadym Samoilenko
138fa0fcdf Fix missing Submitter/Agency on Auditing Errors tab
The authenticated user's DB ID was fetched in main.py for a role check
but never forwarded to handle_analyze_message, so Proof.created_by was
always NULL. This caused submitter_name and submitter_agency to resolve
to None on the Errors tab.

Fix: capture current_user_id from the role-check session in main.py,
pass it to handle_analyze_message, and forward it to
add_version_with_review as created_by. Newly submitted proofs will now
have their submitter recorded and visible in all three Auditing tabs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 18:21:10 +00:00
michael
a25c7a9d31 Grant oversight_admin read-only access to User Management
Allow oversight_admin users to view the User Management screen with
read-only access. They can see users, roles, agencies, and change
history but cannot edit roles, assign agencies, or create agencies.

Backend: open GET /users and GET /users/{id}/change-history to
oversight_admin (PUT /users stays super_admin only).
Frontend: add oversight_admin to sidebar nav and context permission,
render static text instead of dropdowns and hide the add-agency form
for non-super-admin users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:35:07 -06:00
michael
81431d9aa9 Update Gemini model to gemini-3.1-pro-preview
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:04:59 -06:00
michael
e3575052ee Add per-agency analytics breakdown table for admin users
New GET /analytics/by-agency endpoint groups review metrics by agency.
The Analytics page now shows a sortable agency performance table with
pass rates, failures, errors, and legal review counts for each agency.
Only visible to super_admin and oversight_admin users. Selected agency
row is highlighted when the AgencyFilterBar is active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:33:48 -06:00
michael
7f1df8c508 Fix user audit trail logging stale agency value for old and new
Expire the SQLAlchemy cached user object after flush() so the
subsequent get_by_id() reloads the agency relationship with fresh
data. Previously the identity map returned the same Python object
with the old .agency, causing audit logs to record identical old
and new values on agency changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:53:04 -06:00
michael
bcc20260de Add sortable, filterable campaign list with "My Campaigns Only" toggle
- Backend: Expose created_by field on CampaignResponse schema and all
  response constructors in routes.py
- Frontend API layer: Add created_by to CampaignResponse interface and
  createdBy to the frontend campaign converter
- Campaign list: Add column sorting (click headers to toggle asc/desc),
  per-column text filter inputs below headers, and a "My Campaigns Only"
  toggle that filters to campaigns created by the current user
- Default sort is lastModified descending to match existing behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:43:07 -06:00
michael
99fa956c74 Fix submitter agency not loading in flagged/resolved audit queries
The selectinload for FlaggedItem.submitter and ResolvedItem.submitter
was not chaining .selectinload(User.agency), so the submitter's agency
was always None in the API response. This caused the "Submit Agency"
column to be empty in the Flags and Resolutions tabs of the Auditing
page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:15:35 -06:00
michael
407f11c003 Add user change history audit trail for compliance
Adds a user_change_logs table to track all role and agency changes made
to users by super admins. Includes a change history modal in the User
Management screen (clock icon per row) showing timestamped, human-readable
change descriptions with the actor who made each change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 08:41:16 -06:00
michael
ebfcd60c71 Fix campaign visibility bug for unassigned users after agency reassignment
Unassigned (no agency) non-admin users previously saw ALL campaigns due to
a truthiness check that treated None agency_id as "no filter". This was a
security bug — they should see NO campaigns and be blocked from creating them.

Backend: Add _NO_AGENCY sentinel to distinguish "no filter" from "no agency",
add early-returns at all 5 list/analytics endpoints, fix _check_campaign_access
to explicitly reject unassigned users, and block campaign creation with 403.

Frontend: Add isUnassigned boolean to UserContext, show informational empty
state on Campaigns view, and reinforce readOnly for defense-in-depth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:42:42 -06:00
michael
3207ec301c Standardise Issue/Recommendation formatting across all agents
Replace single-line bullet format with a structured two-part format
(**Issue:** / **Recommendation:**) in all specialist and lead agent
prompts. Update Gemini response schema description to match. Update
frontend formatFeedbackText and formatFeedbackTextForPDF to parse
**bold** markdown and preserve line breaks within multi-line bullets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 10:17:08 -06:00
michael
a2f52c0960 Add example-correction formatting rule to all agent prompts
Agents now show example corrections in the format they're recommending
(e.g. sentence case examples when recommending sentence case) to avoid
contradictions between advice and examples.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:55:01 -06:00
michael
4cae1e4c78 Remove "Verdict:" prefix inconsistency from lead agent summaries
Replace all "verdict" language in the lead agent prompt with "status/summary"
and add prescriptive opening-line templates so the LLM produces consistent
output without a "Verdict:" prefix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:50:26 -06:00
michael
37f364ec0f Add acronym expansion guidance to all agent prompts
Agents now spell out acronyms in full on first use (e.g. "Web Content
Accessibility Guidelines (WCAG)") for clarity. The instruction covers
common acronyms like WCAG, FSCS, GDE, APR, CTA, FCA, PRA, and T&Cs,
and applies to any acronym encountered in output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:35:45 -06:00
michael
f92e76b333 Add punctuation and capitalisation consistency rules to all agent prompts
Adds an IMPORTANT instruction block to all 5 agent prompt templates
(legal, brand, channel best practices, channel tech specs, lead) that
enforces: capitalisation after full stops and in labels, consistent
bullet-point ending style, and "e.g." without a trailing comma.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:31:37 -06:00
michael
6504511fb6 Add Plain English language instructions to all agent prompts
Instructs all five agents (legal, brand, channel best practices,
channel tech specs, lead) to prefer simple vocabulary over complex
alternatives (e.g. "add" over "incorporate", "about" over "regarding").
Also fixes "constitute" → "qualify as" in the legal agent prompt itself.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:26:39 -06:00
michael
265bf08470 Replace punitive "violation" language with constructive alternatives in agent prompts
UAT feedback flagged the use of "violations", "violates", etc. as feeling
accusatory. Replaced all instances with constructive terms ("issues",
"doesn't align with", "doesn't meet") and added an explicit instruction
to all 5 agent prompt templates to avoid this language in output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:18:52 -06:00
michael
4a684e7757 Enforce British English spelling in all agent prompt templates
Added explicit UK English instruction to the Response Format section of
all five agents (legal, brand, channel best practices, channel tech specs,
lead) so output uses spellings like "authorised", "colour", "capitalise".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:53:51 -06:00
michael
d21036a0de Add 4-tier RBAC backend: auth dependencies, role enforcement, agency filtering
- Add CHECK constraint migration for users.role (super_admin, oversight_admin, agency_admin, basic_user)
- Add get_current_db_user dependency resolving Azure claims to User ORM with agency
- Add require_role() factory and require_write_access() dependency
- Auto-promote dev user to super_admin when DISABLE_AUTH=true
- Add /api/me, PUT /api/users/{id}, POST /api/agencies endpoints
- Apply agency-based data filtering on campaigns, analytics, audit routes
- Block oversight_admin from all mutation routes (campaigns, proofs, flags, resolves)
- Restrict dropdown option mutations to super_admin only
- Add role check in WebSocket handler to block oversight_admin from analysis
- Add CurrentUserResponse, UserUpdate, AgencyCreate schemas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:28:23 -06:00
michael
6bd8a03a15 Remove page number and document name references from agent feedback
Add explicit no-citations instructions to all 5 agent prompts to prevent
Gemini from including page numbers, document names, or source citations
in analysis feedback. These references were unhelpful since the system
doesn't use RAG and users cannot action them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:39:41 -06:00
michael
60ac3ab22e Rewrite distillation prompts to preserve all source document details
The previous prompts instructed Gemini to "remove redundancy, marketing
fluff, or content not relevant to..." which caused salient details —
especially unusual, granular, or edge-case instructions — to be lost
from spec output. Rewritten all 5 agent prompts (legal, brand_barclays,
brand_barclaycard, channel_best_practices, channel_tech_specs) to:

- Reframe the task as "restructure and organise" rather than "distil
  and filter"
- Add a zero-tolerance detail-loss instruction with concrete examples
  of unconventional rules that must be preserved
- Explicitly forbid omitting, summarising away, or paraphrasing
  specific rules/values/conditions
- Allow merging only exact duplicates while keeping all unique content

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:21:03 -06:00
michael
00207be5f0 Add metadata specifications summary to Channel Tech Specs agent output
Instruct Gemini to begin its feedback with a "Specifications checked"
line recapping the channel, sub-channel, and proof type metadata so
reviewers can confirm the correct specs were applied.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:59:41 -06:00
michael
3e548bc949 Fix flags and resolves not persisting to database
Remove proof_version_id from FlaggedItemCreate and ResolvedItemCreate
request schemas — the backend already derives it from URL path params.
The frontend was sending an empty string which caused Pydantic to reject
the request with 422, silently preventing flags/resolves from saving.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:26:11 -06:00
michael
1800e71229 Fix cache invalidation falling back to static files after reprocessing
After processing a new knowledge base spec, invalidate_cache() was
clearing the DB spec from the cache without replacing it. The next
analysis would then fall back to static prompts/*.md files instead of
using the newly generated DB spec.

Now invalidate_cache() accepts optional new_spec_content to immediately
populate the DB cache, and knowledge_base_service passes the freshly
distilled spec content so it's available for the next analysis without
a server restart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:56:11 -06:00
michael
de62fa1f87 Show partial parse status in UI when some pages fail
- LlamaParse service now returns a ParseResult dataclass with markdown,
  total page count, and a list of failed pages (page number + error)
- Knowledge base service sets status to "partial" (instead of "parsed")
  when some pages failed, with a descriptive error listing which pages
  failed and why
- Frontend StatusBadge shows "partial parse" in orange for partial status
- Error details are shown inline below the document row for both partial
  and error statuses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:51:52 -06:00
michael
49facbe713 Log full details of failed LlamaParse pages for troubleshooting
Handle MarkdownPageFailedMarkdownPage objects gracefully by checking for
the markdown attribute with hasattr instead of assuming all pages have
it. Failed pages now log their type and all attributes so the actual
LlamaParse error is visible in logs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:45:58 -06:00
michael
8a9a24ebe6 Parallelize LlamaParse document processing with asyncio.gather
Parse documents concurrently (up to 10 at a time via semaphore) instead
of serially. Each coroutine uses its own DB session for per-document
status updates, while a shared lock serializes job progress increments
on the main session to avoid session-sharing issues.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:39:01 -06:00
michael
ba9c0ebde3 Reduce auth logging verbosity: INFO → DEBUG
All routine MSAL token verification logs now use DEBUG level so they
don't flood the console on every polling request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:13:25 -06:00
michael
7835f557aa Fix processing job not visible to background task (FK violation)
The background task runs in its own DB session but the job row hadn't been
committed yet by the request session. The background task couldn't find
the job, causing FK violations when trying to create spec_versions.

Fix: explicitly commit the request session after creating the job and
before adding the background task, ensuring the job row is visible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:09:50 -06:00
michael
42bf5ad003 Fix stuck processing state: auto-fail stale jobs, improve active job detection
- Frontend: only treat parsing_documents/distilling as actively running;
  pending jobs older than 2 minutes are ignored as stale
- Backend: add fail_stale_jobs() that marks pending/active jobs older than
  5 minutes as failed before checking for active jobs in trigger_processing
- Prevents UI from getting stuck on old jobs that never completed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:03:25 -06:00
michael
0b7213af01 Fix doc ID mismatch in KB document upload
Create DB record first to get the auto-generated UUID, then use that ID
for the storage key. Previously a separate UUID was generated for storage
but the DB record got a different one, causing file retrieval to fail
during processing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:50:35 -06:00
michael
1601622e07 Fix SpecVersion/ProcessingJob circular relationship direction error
Remove bidirectional back_populates between SpecVersion and ProcessingJob
since both sides have FKs to each other (circular), causing SQLAlchemy to
see both as MANYTOONE. ProcessingJob.spec_version is now a standalone
relationship with explicit foreign_keys. SpecVersion no longer has a
reverse relationship to ProcessingJob (not needed for any queries).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:42:35 -06:00
michael
4833fa127d Fix SQLAlchemy ambiguous FK error between SpecVersion and ProcessingJob
Add explicit foreign_keys argument to both sides of the bidirectional
relationship to resolve the multiple FK paths (SpecVersion.processing_job_id
and ProcessingJob.spec_version_id).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:40:36 -06:00
michael
2c6f3d0686 Switch LlamaParse to llama-cloud SDK with agentic_plus tier
Replace deprecated llama-cloud-services package with llama-cloud>=1.0 (API v2).
Use AsyncLlamaCloud client with tier="agentic_plus" for maximum parsing accuracy
on complex layouts, tables, and visual structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:29:23 -06:00
michael
9e2473c3e9 Add Knowledge Base management system for AI agent specs
Full-stack implementation enabling UI-driven management of the 5 AI agent knowledge bases
(Legal, Brand Barclays, Brand Barclaycard, Channel Best Practices, Channel Tech Specs).

Backend:
- 4 new DB models: KnowledgeBase, SourceDocument, SpecVersion, ProcessingJob
- Migration 006: creates tables, seeds 5 KB rows, imports existing prompts/*.md as v1 specs
- KnowledgeBaseRepository with full CRUD for all 4 tables
- LlamaParseService for document parsing, KnowledgeBaseService for pipeline orchestration
- ReferenceDocsService updated with DB-backed spec loading + cache invalidation
- 11 REST endpoints under /api/knowledge-base (list, detail, upload, delete, process, job status, versions, diff, activate)
- StorageService extended with KB document storage

Frontend:
- TypeScript types for all KB entities (KnowledgeBaseListItem, SourceDocument, ProcessingJob, SpecVersion, DiffResult)
- ApiService methods for all KB endpoints including multipart file upload
- KnowledgeBase component with 3-level UI: agent grid, detail view (documents + versions tabs), diff viewer
- Drag-and-drop file upload, processing progress bar with 3s polling, version comparison
- KnowledgeBaseIcon + Sidebar nav item with adminOnly filtering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-12 15:00:36 -06:00
michael
220a97ab57 Wire up Errors tab in Auditing: auto-create ErrorItem on Analysis Error
- Create ErrorItem record when proof analysis results in "Analysis Error" status
- Add submitter_name/submitter_agency fields to ErrorItemResponse schema
- Eager-load proof creator and agency in error items query to avoid N+1
- Populate submitter fields from proof creator in the API route
- Update frontend ErrorItemResponse type and conversion to map submitter fields
- Fix ErrorsTable proof name styling to blue link (text-active-blue) matching Flags tab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 13:55:16 -06:00
michael
a957cf0276 Pass proof metadata (channel, sub-channel, proof type) to AI agents during analysis
Previously, proof metadata collected during upload was only used for database
persistence. Now it flows through the entire analysis pipeline so agents can
tailor their feedback to the specific channel and format being reviewed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 11:30:38 -06:00
michael
5629d18df4 Fix email service crash when Mailgun not configured
Add validation to check MAILGUN_API_URL has a valid protocol prefix
and MAILGUN_API_KEY is set before attempting to make HTTP request.
Returns False gracefully with warning log instead of crashing with
httpx.UnsupportedProtocol error.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 14:40:42 -06:00
michael
bf1d689f42 Fix migration down_revision to correct revision ID
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 10:18:11 -06:00
michael
2f547dc494 Detect identical file uploads via MD5 hashing
- Add file_hash and is_identical_file columns to proof_versions table
- Compute MD5 hash on file upload and compare with previous version
- Display warning banner when uploading identical file as revision
- Return is_identical_file in WebSocket response and API endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 10:15:48 -06:00