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>
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>
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>
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>
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>
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>
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>
DATABASE_URL is set via the environment block in docker-compose.yml
and does not need to be present in backend/.env.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The script previously only checked that backend/.env existed, allowing
deployments with unset or placeholder values. This meant GEMINI_API_KEY
could be missing, causing every analysis to fail at 80% with a
PERMISSION_DENIED error from the Gemini API.
Now checks GEMINI_API_KEY, AZURE_TENANT_ID, AZURE_CLIENT_ID, and
DATABASE_URL are set to real values before any build step runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
UserManagement now calls refresh() on the global UserContext when the
current user's agency or role is changed, so downstream consumers
(e.g. CreateCampaignModal) immediately reflect the update.
CreateProjectModal now reads the Agency and Agency Lead fields from
the current user's profile instead of hardcoding "OLIVER Agency" and
"Steve O'Donoghue".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generate a professional 22-page A4 PDF covering the full ModComms system
architecture including: system overview, multi-agent AI pipeline, WebSocket
analysis flow, database schema (15 tables), frontend component hierarchy,
Azure AD authentication & RBAC, knowledge base processing pipeline,
deployment architecture, REST API reference, and appendices.
Includes 8 Mermaid diagrams rendered to high-res PNGs, styled tables,
and consistent Barclays design tokens throughout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each line in a bullet group (Issue, Recommendation, etc.) now renders as
its own top-level <li> at the same bullet level. Groups are visually
separated with top margin on the first item of each group.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Recommendation lines are now displayed as an indented nested bullet
beneath their parent Issue bullet, keeping them visually grouped together
while giving each Recommendation its own bullet marker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Recommendation lines were rendered as continuation text within the same
bullet as their Issue, appearing without a bullet marker. Now lines
starting with "Recommendation:" are treated as new bullet groups so they
each get their own bullet point in the list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemini sometimes returns the literal two-character sequence \n instead of
a real newline in agent feedback text. This caused "Recommendation:" to
appear on the same line as "Issue:" with visible \n characters. Adding a
normalization step at the start of formatFeedbackText converts literal \n
sequences to real newlines so the existing line-splitting logic handles
them correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Display the brandGuidelines field as a sortable, filterable "Brand"
column between Owning Agency and Last Modified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Change user-facing strings from American to British English: analyze→analyse,
analyzing→analysing, optimized→optimised, color→colour, analyzes→analyses,
synthesizes→synthesises, optimization→optimisation. Code identifiers, status
enums, and developer-facing messages are intentionally unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Create a comprehensive 25-slide PowerPoint presentation showcasing all
Mod Comms features, including multi-agent AI system, campaign management,
real-time analysis, feedback reports, knowledge base, analytics, auditing,
user roles, and technical architecture. Includes a Python generator script
for reproducible builds and a companion features markdown document.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
Reorder user details from Role/First/Last/Email to First/Last/Email/Role/Agency
so the 2-column grid shows names together at the top. Add Agency as its own
field instead of appending it to the Role display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Replace hardcoded "OLIVER Agency" with the logged-in user's agency name
from UserContext. The field remains disabled/non-editable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Prevents accidental Super Admin privilege grants by requiring users to
type "make this user super admin" before the role change is applied.
Modal blocks paste/drag input and reverts the dropdown on cancel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Role and agency changes now show an inline green checkmark + "Saved" indicator
on the affected row that auto-clears after 2 seconds. Agency creation shows a
green success banner that auto-dismisses after 3 seconds. Successful actions
also clear any stale error banners.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handleNavigateToAuditedItem silently failed because campaignProofs are
loaded lazily (only when viewing a campaign), and errors went to a
setError() state that was never rendered. Now the function fetches and
caches proofs on demand when not already loaded, and uses alert() for
visible error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Switch canWrite from blacklist (role !== 'oversight_admin') to explicit
whitelist (super_admin, agency_admin, basic_user) for clearer permission
logic. Propagate readOnly prop to CampaignDetail and ProofDetailView
subcomponents so upload/delete buttons are properly hidden for read-only
roles at all navigation levels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Replace the hardcoded "Account Type" field with a "Role" field that
shows the user's role label and agency name from the backend /api/me
endpoint (e.g. "Super Admin (Oliver)", "Standard User (Barclays)").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
Replace the repetitive "Actionable Issues" label with the more
constructive "Key Actions" in the feedback report and PDF export.
Remove the uppercase CSS class so the heading renders in sentence case.
Update empty-state text to "No key actions identified."
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
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>
- Add UserRole type and AppUser interface to types.ts
- Create UserContext with useUser() hook providing role-based permission booleans
- Split App into App (auth wrapper) + AppContent (uses UserContext)
- Update Sidebar to filter nav items by UserRole instead of boolean isAdmin
- Add User Management nav item (super_admin only)
- Add AgencyFilterBar component for oversight_admin/super_admin session-level filtering
- Pass agencyId to getCampaigns, getAnalytics, audit endpoints in apiService
- Add getMe, getUsers, updateUser, createAgency to apiService
- Build UserManagement page with user table (role/agency dropdowns) and agency CRUD
- Add readOnly prop to Campaigns (hides create/delete/status-toggle for oversight_admin)
- Add readOnly prop to Settings (disables all ManagementCards, shows view-only banner)
- Pass agencyId to Analytics component for filtered data
- Update urlState with Knowledge Base and User Management views
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
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>
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>
Thread resolvedItems from App → Campaigns → ProofDetailView → FeedbackReport
→ SubReviewCard so that issues previously marked as resolved are restored
from the database on page load instead of resetting to actionable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>