feat(webui): AI-provider connections and evidence-backed insights (Closes #650) #906
Open
jcwalker3
wants to merge 2 commits from
feat/issue-650-providers-insights into master
pull from: feat/issue-650-providers-insights
merge into: :master
:master
:docs/issue-930-remote-mcp-coupling-inventory
:fix/issue-927-pytest-config
:feat/issue-708-mcp-namespace-attachment
:fix/issue-892-author-bootstrap-deadlock
:fix/issue-686-detect-reject-manual-mcp
:feat/issue-707-cross-project-boundaries
:fix/issue-690-review-profile-switch-guard
:fix/issue-704-prevent-env-workspace-bindings
:fix/issue-700-durable-walls
:fix/issue-672-mcp-config-drift
:fix/issue-689-deterministic-mcp-namespace
:fix/issue-678-codex-mcp-reconnect
:feat/issue-649-sentry-console-correlation
:feat/issue-666-concurrent-mcp-restart-tests
:feat/issue-659-maintenance-drain-mode
:feat/issue-648-notifications-console
:fix/issue-670-direct-master-incident
:feat/issue-644-console-recovery
:feat/issue-650-providers-insights
:feat/issue-669-scoped-component-recovery
:docs/issue-668-mcp-ha-rolling-restart
:feat/issue-667-console-restart-controls
:feat/issue-665-restart-audit
:feat/issue-664-break-glass-restart
:feat/issue-645-linkage-console
:feat/issue-643-request-preview-initiate
:fix/issue-897-permission-stale-runtime-classification
:feat/issue-641-runtime-session-view
:feat/issue-663-restart-classes
:feat/issue-661-drain-proof-hard-gate
:fix/issue-854-semantic-container-exclusion
:issue-640
:fix/issue-682-starlette-httpx2
No Reviewers
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#906
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Implements Phase 4 AI-provider connections and evidence-backed operational insights for #650.
Surfaces
GET /providers+GET /api/v1/providers— declared connection status from the worker registry (no API keys, no live probe claims)GET /insights+GET /api/v1/insights— advisory insight cards with mandatory evidence refsInsight kinds (all require evidence; none claim actions completed)
blocked_queue_pressure— traffic blocked itemscontroller_attention— needs_controller itemsstale_runtime_risk— system-health mutation_safe / staleprovider_without_workers— declared-available providers with zero workersanalytics_failure_pressure— failure-status analytics events (when loaded)Safety
advisory_only=true,claims_action_completed=falsealwayssources_unavailableTest plan
pytest tests/test_webui_providers_insights.py— 19 passeddocs/webui-local-dev.mdCloses #650
Canonical handoff
Canonical Issue State
STATE: ready-for-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Review PR #906 for #650 provider connections and evidence-backed insights (read-only advisory surfaces)
NEXT_PROMPT:
WHAT_HAPPENED: Implemented and published Phase 4 providers + insights. Commit
983e8acon feat/issue-650-providers-insights; PR #906 opened; issue #650 labeled status:pr-open. Selected via allocator (controller + author both selected #650; merger had no unclaimed merge work for #904).WHY: Allocator forbids self-select merge of #904; next permissible work was author implement #650.
RELATED_PRS: #906
BLOCKERS: none
VALIDATION: pytest tests/test_webui_providers_insights.py — 19 passed
LAST_UPDATED_BY: author/jcwalker3
pr: #906
branch: feat/issue-650-providers-insights
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/conflict-fix-pr-906-983e8ac
profile: prgs-author
session_id: unknown
phase: claimed
head_before:
983e8ac2c7expires_at: 2026-07-25T23:53:33Z
reviewer_active: no
Canonical Issue State
STATE: ready-for-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Fresh review of PR #906 at head
af70a27b01after conflict-fix merge from master.NEXT_PROMPT:
WHAT_HAPPENED: Author conflict-fix on PR #906. Live mergeable was false at
983e8ac. Merged prgs/master into feat/issue-650-providers-insights; resolved conflicts in webui/app.py, webui/nav.py, docs/webui-local-dev.md by keeping both #650 providers/insights and #645 linkage. Pushedaf70a27(no force). Gitea now reports mergeable=true.WHY: Master had landed #645 linkage console which touched the same app/nav/docs surfaces as #650.
RELATED_PRS: #906
BLOCKERS: none remaining for mergeability; formal review still required at new head
VALIDATION: WEBUI_TEST_OFFLINE=1 venv/bin/pytest tests/test_webui_providers_insights.py tests/test_webui_gitea_linkage.py -q from branches/conflict-fix-pr-906-983e8ac → 68 passed, 6 subtests passed; local merge-tree clean; master is ancestor of head
LAST_UPDATED_BY: jcwalker3 (prgs-author)
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #906
issue: #650
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: prgs-reviewer-78651-cbbd1dba
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/conflict-fix-pr-906-983e8ac
phase: claimed
candidate_head:
af70a27b01target_branch: master
target_branch_sha:
26f54851d1last_activity: 2026-07-25T22:25:11Z
expires_at: 2026-07-25T22:35:11Z
blocker: none
Reviewer findings for PR #906 at head
af70a27b01. Verdict held: REQUEST_CHANGES, one blocker.This is posted as a discussion comment rather than a formal review verdict because
gitea_mark_final_review_decisionfail-closed on an unreleased author conflict-fix lease (comment 16866, profile prgs-author, phase=claimed, head_before983e8ac2, expires_at 2026-07-25T23:53:33Z). The conflict fix it covers already succeeded — this head is the merge result and Gitea reports the PR mergeable — the claiming session simply never posted its release comment. A reviewer cannot clear a comment-only author lease. The formal REQUEST_CHANGES verdict will be recorded once that lease is released or its TTL expires. Until then, treat this comment as the review of record: REVIEW_STATUS on this PR is REQUEST_CHANGES, and it must not land at this head.One blocker. The rest of this change is in good shape, and the conflict resolution that produced this head is correct — that was audited first and is reported below as a positive finding rather than left implicit.
What was audited and is sound
The conflict-fix merge kept both sides. This head is a merge commit with parents
983e8ac2c7d7752753c17b29139371b320bbab7c(the #650 work) and9c69bfcd80f62b112d89acc875ec13c0af714c49(master carrying #645). I diffed the merge result against each parent for all three hand-resolved files. Against the master parent the only deletions arewebui/nav.py's/insightsstubNavItemand itsSTUB_PAGESplaceholder entry, plus the matchingdocs/webui-local-dev.mdstub row — which is exactly what #650 is supposed to replace. Against the author parent the only deletion is one docstring line superseded by the combined wording. #645's linkage surface survives intact:NavItem("/gitea", "Issue/PR linkage"), thelinkage_loader/linkage_viewsimports, and both/giteaand/api/v1/gitea/linkageroutes are all present. Nothing was dropped by the resolution.HTML escaping is thorough. Every interpolation in
webui/insights_views.pythat carries text passes throughhtml.escape. The handful that do not are integers (worker_count,enabled_worker_count) or already-escaped fragments assembled upstream. I found no injection path.Read-only posture holds. All four routes are registered
methods=["GET"], so non-GET yields 405 structurally rather than by convention. Nothing ininsights_loader.pywrites to Gitea, the control plane, or the registry. Provider records come from the credential-free worker registry andto_dicthard-codessecrets_exposed: False; no key material is read or rendered.Evidence discipline holds.
_require_evidenceraises rather than emitting an insight with no evidence refs, and every generator routes through it.B1 —
InsightsSnapshot.okcan never be False, so a total evidence blackout renders as healthywebui/insights_loader.py:692:In
generate_insights, each of the four sources (traffic, system_health, providers, analytics) appends to exactly one ofusedorunavailableon every path — there is no branch where a source contributes to neither. Solen(used) + len(unavailable) == 4invariantly, andokis unconditionally True. It is not reachable as False for any input.Demonstrated at this head:
The snapshot simultaneously reports
ok: trueandfetch_error: "no evidence sources produced a usable snapshot". Those two statements contradict each other.This leaves three deliberately-authored branches unreachable:
webui/app.py—status_code=200 if snapshot.ok else 502on/api/v1/insights. The 502 can never be returned.webui/insights_views.py:130—if not snapshot.ok and not snapshot.insights:guards the prominenthealth-stale"Insights unavailable" banner. It can never render.fetch_errorvalue itself, which is only populated whenusedis empty — i.e. only in the state thatokrefuses to report.Why this is a blocker rather than a tidy-up: the module's own docstring states the rule this violates — "Qualified absence. When a source could not run, the insight list says so rather than inventing an empty-and-healthy fleet or zero blocked items." A consumer polling
/api/v1/insightsduring a full observability blackout receives HTTP 200,ok: true, andinsights: []. That is precisely an empty-and-healthy fleet. The existence of the unreachable 502 branch is itself evidence that a failure signal was intended here and is simply not wired up; this is unmet intent, not a deliberate contract choice. It also makesokmean two different things across the two endpoints this PR adds, sinceProviderSnapshot.okis correctly False on registry failure and does drive a real 502.Stated fairly, the mitigations: the payload still carries
sources_unavailableandfetch_error, and the HTML page still lists unavailable sources in its meta line, so a careful consumer can recover the truth. The defect is that the two headline signals — theokfield and the HTTP status — both say "fine" when nothing was measured.Fix is small:
ok = bool(used), plus a test asserting that a snapshot with no usable source reportsok Falseand that/api/v1/insightsanswers 502 in that state.Non-blocking observations
webui/insights_loader.py:367—severity=SEVERITY_WARN if count else SEVERITY_INFO. The function returns early whenneedsis empty, socountis always at least 1 and theSEVERITY_INFObranch is dead.insight_blocked_queue,insight_controller_attention, andinsight_analytics_failures, but the cap is not disclosed in the emitted payload. The title still reports the full count, so a reader comparing "43 blocked items" against 20 evidence refs cannot tell truncation from missing evidence. Consider an explicitevidence_truncatedflag.webui/insights_loader.py:551-553and the equivalent guards catchValueErroraround each builder to absorb_require_evidence. That same handler also swallows aValueErrorraised by theint(...)coercions onnumber,pr, andissue, so malformed upstream data silently drops an insight rather than surfacing. Narrow the guard, or coerce defensively before building.EvidenceRef.to_dictredactsdetailbut leaveskindandrefunredacted, andProviderConnection.provider_idis unredacted while every other string field on that record is redacted. Low risk given the current sources, but the asymmetry looks unintentional.insight_blocked_queuekeysinsight_idon the count alone. Unique within a snapshot, so nothing is broken today, but the id is not stable across snapshots in a way a consumer could rely on.Base state, for the record
This head took its master merge at
9c69bfcd80f62b112d89acc875ec13c0af714c49. Master has since advanced to26f54851d1d20eff92c339e778125f31c09fa95f, so master is no longer an ancestor of this head. Gitea still reports the PR as mergeable at the time of this review. That is not a finding against the change — it is stated so the next actor does not mistake it for one.Canonical PR State
STATE: PR-open
WHO_IS_NEXT: author
NEXT_ACTION: Make InsightsSnapshot.ok reachable as False when no evidence source contributed, add a regression test, then push and request re-review.
NEXT_PROMPT:
WHAT_HAPPENED: PR #906 was reviewed in full at head
af70a27b01, in a clean branches/ worktree pinned to that exact sha. The conflict-fix merge was audited against both parents and found correct. Escaping, read-only posture, secret handling, and evidence discipline were checked and found sound. One blocker was found and reproduced by execution: InsightsSnapshot.ok is unconditionally True, so a total evidence blackout presents as a healthy empty result and three authored failure branches are unreachable. Recording of the formal verdict was refused by the reviewer gate on account of an unreleased author conflict-fix lease, so the findings are placed here instead.WHY: The blocker matters because this console surface exists to distinguish "measured, and there is nothing to report" from "nothing could be measured". The module docstring commits to that distinction explicitly. As written, the two signals a consumer reads first — the ok field and the HTTP status — collapse those two states into one, and the unreachable 502 branch shows a failure signal was intended rather than deliberately omitted. The remaining observations are genuine but narrow, and none of them alone would hold up the change.
ISSUE: #650
RELATED_PRS: #906
HEAD_SHA:
af70a27b01REVIEW_STATUS: REQUEST_CHANGES
MERGE_READY: false
BLOCKERS: B1 InsightsSnapshot.ok is unconditionally True, so a total evidence blackout returns ok true with HTTP 200 and leaves the 502 branch, the unavailable banner, and fetch_error all unreachable. Separately, the unreleased author conflict-fix lease at comment 16866 blocks recording of the formal verdict.
VALIDATION: Reviewed in the worktree /Users/jasonwalker/Development/Gitea-Tools/branches/conflict-fix-pr-906-983e8ac, verified clean and pinned at
af70a27b01. Live head SHA before these findings:af70a27b01. Pushes occurred during validation: no. Merge audit: git diff against each parent for webui/app.py, webui/nav.py, and docs/webui-local-dev.md; master-side deletions limited to the /insights stub NavItem, its STUB_PAGES entry, and the matching docs row; author-side deletions limited to one superseded docstring line. B1 was reproduced by execution, not inferred: load_insights_snapshot(load_live=False) under WEBUI_TEST_OFFLINE=1 returned ok True, zero insights, empty sources_used, four entries in sources_unavailable, and fetch_error "no evidence sources produced a usable snapshot", which maps to HTTP 200. Focused suites: WEBUI_TEST_OFFLINE=1 pytest tests/test_webui_providers_insights.py tests/test_webui_gitea_linkage.py — 68 passed, 6 subtests passed, matching the author's reported figure and confirming the merge left #645 green. No full-suite run was performed at this head and none is claimed. Immediately before posting, the head was re-confirmed as unmoved ataf70a27b01, the worktree re-confirmed clean, and B1 re-read at webui/insights_loader.py:692 whereok = bool(used) or bool(merged_unavailable)still stands unchanged. The one assertion that had gone stale between the review pass and this posting was the base-master sha, corrected above from7bb5ff47to 26f54851; no finding depended on it.LAST_UPDATED_BY: sysadmin (prgs-reviewer)
NATIVE_REVIEW_PROOF: findings posted via gitea_create_issue_comment on the native MCP namespace gitea-reviewer, profile prgs-reviewer, identity sysadmin, at head
af70a27b01. No offline, import, or helper path was used. No formal review verdict has been recorded on PR #906 at this head; this comment does not claim one.[THREAD STATE LEDGER]
what is true now
PR #906 remains in open state at head
af70a27b01. It carries the #650 providers and insights surfaces plus a conflict-fix merge of master taken at9c69bfcd80.Server-side decision state: no formal review verdict of any kind is recorded on PR #906. The reviewer verdict is held locally and stated in this comment; the gate refused to record it while the author conflict-fix lease at comment 16866 remains at phase=claimed.
Local verdict/state: REQUEST_CHANGES on one blocker, with the conflict resolution separately audited and found correct.
what changed
The review moved from unstarted to a recorded set of findings in the discussion thread, short of a formal verdict. The PR head did not move during validation, and has not moved since the review pass: it is still
af70a27b01. Master advanced to26f54851d1after this head took its merge at9c69bfcd, so master is no longer an ancestor of the head; Gitea still reported the PR mergeable during the review. A reviewer lease was acquired for this PR at comment 16936 and is released immediately after this comment.what is blocked
Blocker classification: code blocker
B1 —
webui/insights_loader.py:692computesok = bool(used) or bool(merged_unavailable), which is unconditionally True because each of the four evidence sources appends to exactly one ofusedorunavailable. A total evidence blackout therefore reportsok: truewith HTTP 200 and an empty insights list, contradicting the module docstring's qualified-absence rule, and leaving the/api/v1/insights502 branch, the "Insights unavailable" banner atwebui/insights_views.py:130, and thefetch_errorvalue all unreachable.Separately, recording of the formal verdict is fenced by the unreleased author conflict-fix lease at comment 16866, which expires 2026-07-25T23:53:33Z.
who/what acts next
Next actor: author
Required action: Change
okto depend only on whether a source contributed, add a regression test covering the no-usable-source state and its 502, release the stale conflict-fix lease at comment 16866, and optionally clear the five non-blocking observations in the same push.Do not do: Do not redo or revert the conflict-fix merge — it was audited against both parents and preserves both #650 and #645, including the linkage routes and nav entry. Do not treat the 68 passing focused tests as evidence against B1; no test in the branch exercises the no-usable-source state, which is why the defect survived. Do not read the absence of a formal verdict as a positive decision on this PR.
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #906
issue: #650
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: prgs-reviewer-78651-cbbd1dba
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/conflict-fix-pr-906-983e8ac
phase: released
candidate_head:
af70a27b01target_branch: master
target_branch_sha:
26f54851d1last_activity: 2026-07-25T22:28:28Z
expires_at: 2026-07-25T22:38:28Z
blocker: manual-release
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.