M8: GUI polish, n8n workflow-3 fixes, RAGcore retrieval root-cause and fix
GUI: dashboard Attention Queue presents a curated severity mix instead of pure severity-sort (grouped Now/Today/Later headers); Today's Movements seed data curated so a fresh reset shows a credible day (2+ departures, 2+ returns), with a new seed-integrity test; About Demo restructured into a compact grid with progressive disclosure for technical sections; Duplicate Merge shows match/conflict counts, hides matching fields by default, and previews the final merged record before confirmation. Repo hygiene: removed a stray empty `backend;C` directory and an untracked 31MB zip export; `.gitignore` now excludes future archive exports. n8n: fixed invalid JSON (a missing `},` between two node objects) in the committed `fleet-ops-vehicle-return.json` -- the file could not be parsed. Live-validated workflow 3 (RAGcore Procedure Sync): found and fixed a real defect (three body parameters had a stray trailing `}}`) and a missing Error Workflow wiring, both via the safe `n8n import:workflow` CLI path; exported the corrected, still- inactive workflow as the new source of truth and updated MANIFEST.md/check_drift.py. Publishing it (starts real daily unattended runs) remains a separate decision. RAGcore: root-caused and fixed (live, approved) the "zero retrieval candidates" bug -- a filesystem permission bug (`embedding_profiles.json` unreadable by the app's own runtime user) that broke every retrieval call before it reached Qdrant. Every other suspect (grants, scope resolution, Qdrant filters, embeddings) was verified healthy first. Found a second, deeper gap: the reranker adapter calls an Ollama HTTP route that does not exist on the deployed Ollama version, so `/v1/answers` still returns `not_answerable`. `KNOWLEDGE_PROVIDER` stays `demo` until that is resolved on the RAGcore side. Evidence-based MCP Hub integration status (real tool-call audit history, not just a boolean flag) replaces the old `configured`/`not_configured` guess. Full findings in `docs/final-integrations/current-state-audit.md`. Backend: 172 tests passing, ruff clean, mypy clean (50 files). Frontend: tsc clean, production build clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3ebca9e9b7
commit
34df66d28c
@@ -72,8 +72,14 @@ def get_dashboard(
|
||||
issue_ref=issue.public_ref,
|
||||
)
|
||||
)
|
||||
attention_items.sort(key=lambda item: _SEVERITY_ORDER.get(item.severity, 3))
|
||||
attention_items = attention_items[:8]
|
||||
# Curate a credible severity mix instead of letting `high` dominate every slot:
|
||||
# each item's real severity is unchanged, only the display selection is capped per
|
||||
# tier (a handful of "now", then "today", then "later") so a heavy day of high-severity
|
||||
# issues doesn't crowd out medium/low ones the operator should still see.
|
||||
high_items = [i for i in attention_items if i.severity == "high"]
|
||||
medium_items = [i for i in attention_items if i.severity == "medium"]
|
||||
low_items = [i for i in attention_items if i.severity == "low"]
|
||||
attention_items = (high_items[:3] + medium_items[:3] + low_items[:2])[:8]
|
||||
|
||||
today = _today()
|
||||
bookings = db.scalars(select(Booking)).all()
|
||||
|
||||
@@ -4,16 +4,10 @@ from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_db, require_operations_manager
|
||||
from app.core.config import get_settings
|
||||
from app.schemas import (
|
||||
CurrentUser,
|
||||
IntegrationStatusOut,
|
||||
McpHubIntegrationStatus,
|
||||
)
|
||||
from app.services.integration_status import derive_n8n_status
|
||||
from app.schemas import CurrentUser, IntegrationStatusOut
|
||||
from app.services.integration_status import derive_mcp_hub_status, derive_n8n_status
|
||||
|
||||
router = APIRouter(prefix="/api/v1/integrations", tags=["integrations"])
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
@router.get("/status", response_model=IntegrationStatusOut)
|
||||
@@ -23,8 +17,5 @@ def integration_status(
|
||||
) -> IntegrationStatusOut:
|
||||
return IntegrationStatusOut(
|
||||
n8n=derive_n8n_status(db),
|
||||
mcp_hub=McpHubIntegrationStatus(
|
||||
registration_enabled=settings.mcp_hub_registration_enabled,
|
||||
state="configured" if settings.mcp_hub_registration_enabled else "not_configured",
|
||||
),
|
||||
mcp_hub=derive_mcp_hub_status(db),
|
||||
)
|
||||
|
||||
@@ -279,7 +279,11 @@ class N8nIntegrationStatus(BaseModel):
|
||||
|
||||
class McpHubIntegrationStatus(BaseModel):
|
||||
registration_enabled: bool
|
||||
state: Literal["not_configured", "configured"]
|
||||
state: Literal["not_configured", "no_evidence", "operational"]
|
||||
total_calls: int
|
||||
last_tool: str | None = None
|
||||
last_client: str | None = None
|
||||
last_called_at: datetime | None = None
|
||||
|
||||
|
||||
class IntegrationStatusOut(BaseModel):
|
||||
|
||||
@@ -8,12 +8,18 @@ from sqlalchemy.orm import Session
|
||||
from app.core.config import get_settings
|
||||
from app.models.audit import AuditEvent
|
||||
from app.models.outbox import OutboxEvent
|
||||
from app.schemas import N8nErrorHandlerStatus, N8nIntegrationStatus, N8nWorkflowEvidence
|
||||
from app.schemas import (
|
||||
McpHubIntegrationStatus,
|
||||
N8nErrorHandlerStatus,
|
||||
N8nIntegrationStatus,
|
||||
N8nWorkflowEvidence,
|
||||
)
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
# The 4 canonical Fleet Ops n8n workflows (see n8n/workflows/MANIFEST.md). Workflow 3
|
||||
# (RAGcore Procedure Sync) is not built yet, so it always reports no evidence.
|
||||
# The 4 canonical Fleet Ops n8n workflows (see n8n/workflows/MANIFEST.md). All 4 are
|
||||
# built (workflow 3, RAGcore Procedure Sync, has all 6 nodes saved); workflow 3 is not
|
||||
# yet published/active, so it will show no *run* evidence until it is.
|
||||
_CANONICAL_WORKFLOWS = (
|
||||
"Fleet Ops — Vehicle Return Orchestration",
|
||||
"Fleet Ops — Scheduled Data Quality Scan",
|
||||
@@ -92,7 +98,7 @@ def derive_n8n_status(db: Session) -> N8nIntegrationStatus:
|
||||
workflows = [
|
||||
N8nWorkflowEvidence(
|
||||
name=name,
|
||||
built=name != "Fleet Ops — RAGcore Procedure Sync",
|
||||
built=True,
|
||||
last_seen_at=evidence_by_workflow[name],
|
||||
)
|
||||
for name in _CANONICAL_WORKFLOWS
|
||||
@@ -117,3 +123,41 @@ def derive_n8n_status(db: Session) -> N8nIntegrationStatus:
|
||||
latest_failure_workflow=latest_handler_failure_workflow,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def derive_mcp_hub_status(db: Session) -> McpHubIntegrationStatus:
|
||||
"""Evidence-based MCP Hub status: real tool-call audit history, not just the
|
||||
`MCP_HUB_REGISTRATION_ENABLED` flag flipped on. Every `mcp_tool_request` call
|
||||
already writes an `AuditEvent` (see `app/api/routers/mcp_integrations.py`)."""
|
||||
total_calls = (
|
||||
db.scalar(
|
||||
select(func.count(AuditEvent.id)).where(AuditEvent.action == "mcp_tool_request")
|
||||
)
|
||||
or 0
|
||||
)
|
||||
latest_call_row = db.execute(
|
||||
select(AuditEvent.occurred_at, AuditEvent.actor_label, AuditEvent.metadata_json)
|
||||
.where(AuditEvent.action == "mcp_tool_request")
|
||||
.order_by(AuditEvent.occurred_at.desc())
|
||||
.limit(1)
|
||||
).first()
|
||||
last_called_at = latest_call_row[0] if latest_call_row else None
|
||||
last_client = latest_call_row[1] if latest_call_row else None
|
||||
last_tool = (latest_call_row[2] or {}).get("tool") if latest_call_row else None
|
||||
|
||||
state: Literal["not_configured", "no_evidence", "operational"]
|
||||
if not settings.mcp_hub_registration_enabled:
|
||||
state = "not_configured"
|
||||
elif total_calls > 0:
|
||||
state = "operational"
|
||||
else:
|
||||
state = "no_evidence"
|
||||
|
||||
return McpHubIntegrationStatus(
|
||||
registration_enabled=settings.mcp_hub_registration_enabled,
|
||||
state=state,
|
||||
total_calls=total_calls,
|
||||
last_tool=last_tool,
|
||||
last_client=last_client,
|
||||
last_called_at=last_called_at,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user