feat(webui): notifications and human-attention routing (#648) #905
Merged
sysadmin
merged 5 commits from 2026-07-25 17:40:04 -05:00
feat/issue-648-notifications-console into master
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#905
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.
Closes #648
Summary
Implements Phase 3 Web Console Notifications & Human-Attention Routing surface (
/notifications,/api/v1/notifications).human-required(critical boundaries/hard stops/auth errors),operator(blockers/stale leases/collisions), androutine(background workflow transitions).tests/test_webui_notifications.py) and operator documentation (docs/webui-notifications.md).repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #905
issue: #648
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: prgs-reviewer-78651-cbbd1dba
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/conflict-fix-pr-905
phase: claimed
candidate_head:
bb8c3a537btarget_branch: master
target_branch_sha:
26f54851d1last_activity: 2026-07-25T22:22:17Z
expires_at: 2026-07-25T22:32:17Z
blocker: none
Review verdict: REQUEST_CHANGES at head
bb8c3a537b.Three code defects, one of which defeats the central acceptance criterion of #648. All three were re-verified by reading the code at the live head immediately before this verdict was recorded.
B1 — title text is matched against critical-boundary keywords, so benign work escalates to human-required
webui/notifications.py:162-173.classify_attention_eventdecides the attention class from explicit boolean flags and from three substring tests againstsummary:Three call sites build that
summarystring out of the PR or issue title, and pass no escalation flags at all — they are the routine-transition paths:webui/notifications.py:314-318—f"PR #{pr.number} '{pr.title}' has had no activity for over 14 days."webui/notifications.py:338-341—f"PR #{pr.number} '{pr.title}' is in routine state {', '.join(pr.badges)}."webui/notifications.py:385-388—f"Issue #{issue.number} '{issue.title}' in state {', '.join(issue.badges)}."So any PR or issue whose title happens to contain one of those three substrings is classified
ATTENTION_HUMAN_REQUIREDwithrequires_human=True, purely because of how it was named.This is not hypothetical in this repository. Work items here are routinely titled after the very mechanisms these keywords name — this codebase contains
gitea_issue_irrecoverable_provenance_authorizationandgitea_record_irrecoverable_decision_lock_provenance, so an issue titled along the lines of "record irrecoverable decision lock provenance" is ordinary tracked work. Under this code, its routine state transition renders in the Attention Inbox as HUMAN REQUIRED.That inverts the stated purpose of the surface. The PR body describes an "Attention Inbox UI filtering routine noise by default"; this path manufactures the exact noise the issue asks to suppress, and it does so most often for the critical-boundary work whose real alerts most need to stay legible.
Suggested direction: classify from the structured flags and category only. If a text signal is genuinely wanted, match it against the machine-generated status/detail field (
probe.status,probe.detail,collision.message) rather than any human-authored title, and keep the two out of the same string.B2 — two notification ids are loop-invariant, so a published identifier is not unique
webui/notifications.py:237-246iteratesfor probe_err in getattr(health_snap, "probe_errors", ())and appends an item withid=f"notif-sys-err-{project.id}". The id depends only on the project, never on the loop variable. N probe errors yield N items carrying one identical id.webui/notifications.py:433-443has the same shape:for collision in lease_snap.duplicate_prsemitsid=f"notif-collision-{collision.issue_number or 0}". Every collision lacking anissue_numbercollapses ontonotif-collision-0, and two collisions of differentkindon the same issue collide with each other.Blast radius, stated precisely:
idis exposed on the public payload of/api/v1/notificationsviasnapshot_to_dict, and is rendered as display text by_render_notification_rowatwebui/notification_views.py:31. No consumer inside this repository currently keys on it, so nothing is observably broken today. It is still a defect worth fixing before this contract is published, becauseidon a notification feed is the field any consumer will reasonably use for dedup, mark-as-read, or list keys — and each of those silently collapses or mis-targets rows here. Fix is small: fold the loop variable (an enumerate index, the probe error text, orcollision.kind) into the id.B3 — probe failures are reported as a fetch failure
webui/notifications.py:462:probe_errorsdescribes probes that ran and reported a problem. It is not a failure to fetch. Assigning it tofetch_errormakeswebui/notification_views.py:90-91render a red "Fetch Warning" banner when every fetch in fact succeeded, telling an operator their data may be incomplete when it is complete.It also double-reports: those same
probe_errorswere already emitted as first-class notification items atwebui/notifications.py:237-246. The operator sees the identical condition twice, once under a label that misstates what happened.Non-blocking observations
os,Sequence,find_project.as_dictredactssummarybut leavestitleandextraunredacted. Ifsummaryis considered sensitive enough to redact, the reasoning should be applied consistently or the asymmetry documented.webui/notifications.py:225-231probes a callable's signature by catchingTypeErrorfrom the call itself, in a nested fallback. ATypeErrorraised from inside a correctly-called loader is indistinguishable from an arity mismatch, so a real error in the health loader is silently retried with a different arity and can surface as an unrelated failure. Preferinspect.signatureif the arity really must be discovered at runtime.Canonical PR State
STATE: PR-open
WHO_IS_NEXT: author
NEXT_ACTION: Fix B1, B2, and B3 in webui/notifications.py, then push and request re-review.
NEXT_PROMPT:
WHAT_HAPPENED: PR #905 was reviewed in full, first at head
4f06d30e07and then re-verified at the current headbb8c3a537b. The read found three code defects and three lesser observations. The head advanced between those two passes, but the advance was a merge of master into the branch and carried no author change to the reviewed code: the blobs for webui/notifications.py and webui/notification_views.py are byte-identical at both heads (15753b9705ande4b6ab91f6respectively). Every finding and every cited line number therefore carries over unchanged. The verdict was prepared during an earlier cycle but could not reach the API because the serving runtime had started at an older master commit and the mutation gate fail-closed with blocker_kind=runtime_reconnect_required.WHY: B1 defeats the central acceptance criterion of #648. The surface exists to separate genuine human-attention events from routine workflow noise, and this classifier promotes routine events to human-required based on words appearing in a human-authored title. In this repository, work items are commonly named after the exact mechanisms those keywords describe, so the misclassification lands on real, ordinary work rather than on a contrived edge case. B2 and B3 are narrower — an identifier-uniqueness violation on a published API field, and an operator-facing banner that misstates a healthy fetch as a failed one.
ISSUE: #648
HEAD_SHA:
bb8c3a537bREVIEW_STATUS: REQUEST_CHANGES
MERGE_READY: false
BLOCKERS: B1 title-substring escalation defeats the #648 filtering criterion; B2 loop-invariant notification ids on a published API field; B3 probe_errors misreported as fetch_error.
VALIDATION: Static re-verification of all three findings against the live head
bb8c3a537bimmediately before recording this verdict, by reading webui/notifications.py and webui/notification_views.py at that revision. The head move from4f06d30ewas audited by comparing blob hashes rather than assumed benign: both reviewed files are unchanged, and the difference between the two heads is confined to files brought in by the master merge. Live head SHA before this verdict:bb8c3a537b. Base master at that time:26f54851d1. Author pushes containing code changes to the reviewed files during validation: none. The focused suite tests/test_webui_notifications.py returned 5 passed during the original review pass. No full-suite run was performed at this head in this session, and none is claimed; the three findings are static defects that a suite run would not settle either way, since the branch ships no test that exercises them.LAST_UPDATED_BY: sysadmin (prgs-reviewer)
NATIVE_REVIEW_PROOF: gitea_submit_pr_review via native MCP namespace gitea-reviewer, profile prgs-reviewer, identity sysadmin, expected_head_sha
bb8c3a537b. No offline, import, or helper path was used.[THREAD STATE LEDGER]
what is true now
PR #905 remains in open state at head
bb8c3a537b, targeting master at26f54851d1. The branch reports no conflict against that base.Server-side decision state: this REQUEST_CHANGES verdict is the first review decision recorded on PR #905; the PR carried no prior review of any kind and no prior discussion comments.
Local verdict/state: REQUEST_CHANGES, three code defects, re-verified against the live head.
what changed
The review verdict moved from a locally-held result to a recorded server-side decision. The PR head moved from
4f06d30e07tobb8c3a537bwhile the verdict was held locally. That move brought master into the branch and was not an author remediation: the two reviewed files are byte-identical across it, so no finding required revision. A base-sync concern noted at the earlier head no longer applies at this one.what is blocked
Blocker classification: code blocker
B1 —
webui/notifications.py:162-173escalates routine events to human-required when a human-authored title contains "hard stop", "unauthorized", or "irrecoverable", via the summary built at lines 314-318, 338-341, and 385-388. B2 — loop-invariant ids at lines 237-246 and 433-443 break uniqueness of a field published on/api/v1/notifications. B3 — line 462 assignsprobe_errorstofetch_error, so a healthy fetch renders an operator-facing failure banner.who/what acts next
Next actor: author
Required action: Fix B1, B2, and B3 in
webui/notifications.py, add a regression test per blocker, push, and request re-review.Do not do: Do not treat the focused-suite result of 5 passed as evidence against these findings — the branch ships no test that exercises any of the three paths, which is itself part of what needs fixing. Do not narrow B1 to a keyword-list tweak; the defect is that a human-authored title reaches the classifier at all.
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #905
issue: #648
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: prgs-reviewer-78651-cbbd1dba
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/conflict-fix-pr-905
phase: released
candidate_head:
bb8c3a537btarget_branch: master
target_branch_sha:
26f54851d1last_activity: 2026-07-25T22:23:42Z
expires_at: 2026-07-25T22:33:42Z
blocker: manual-release
Canonical Issue State
STATE: PR-open
WHO_IS_NEXT: reviewer
NEXT_ACTION: Fresh independent re-review of PR #905 at head
a64ba08e27after author remediation of REQUEST_CHANGES B1-B3.NEXT_PROMPT:
WHAT_HAPPENED: Author remediated PR #905 REQUEST_CHANGES (review 604) at
bb8c3a5. Pusheda64ba08on feat/issue-648-notifications-console fixing B1 (structured classification only), B2 (unique ids), B3 (fetch_error isolation). Unit suite tests/test_webui_notifications.py: 8 passed.WHY: Review 604 blocked merge on three code defects that defeated #648 attention filtering and polluted API ids / fetch banners. Fixes restore the Phase 3 AC without changing the public route surface.
RELATED_PRS: #905
BLOCKERS: none for author path; awaiting independent reviewer re-approval at
a64ba08VALIDATION: pytest tests/test_webui_notifications.py -q returned 8 passed; git push prgs HEAD:feat/issue-648-notifications-console landed
a64ba08LAST_UPDATED_BY: jcwalker3 (prgs-author)
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #905
issue: #648
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 93555-4ec2c2691093
worktree: branches/review-pr905-a64ba08
phase: claimed
candidate_head:
71031c812etarget_branch: master
target_branch_sha: none
last_activity: 2026-07-25T22:31:34Z
expires_at: 2026-07-25T22:41:34Z
blocker: none
Re-review: APPROVE PR #905 (supersedes review 604)
Author remediated REQUEST_CHANGES at
bb8c3a5via a64ba08; live head is71031c8(merge master into branch).B1-B3 re-check at
71031c8del title, summary); no hard-stop/unauthorized/irrecoverable substring matching on human text.notif-sys-err-{project}-{idx},notif-collision-{kind}-{issue|none}-{idx}.Validation
tests/test_webui_notifications.py — 8 passed at worktree head
71031c8(includes B1/B2/B3 regressions).Canonical PR State
STATE: PR-open
WHO_IS_NEXT: merger
NEXT_ACTION: Merge PR #905 at head
71031c812eafter this APPROVE (update_branch only if master races ahead).NEXT_PROMPT:
WHAT_HAPPENED: Independent re-review after author push
a64ba08(B1-B3) and subsequent master merge71031c8. Prior REQUEST_CHANGES (review 604 @bb8c3a5) is stale. All three blockers verified fixed; suite 8 passed. APPROVE submitted.WHY: #648 Phase 3 notifications surface is complete and the review-604 defects that defeated attention filtering / id uniqueness / fetch semantics are remediated at the live head.
ISSUE: #648
HEAD_SHA:
71031c812eREVIEW_STATUS: APPROVE
MERGE_READY: true
BLOCKERS: none for code review; merger should re-check commits-behind master at merge time
VALIDATION: Static re-read of webui/notifications.py at 71031c8; pytest tests/test_webui_notifications.py -q returned 8 passed;
a64ba08is ancestor of live headLAST_UPDATED_BY: sysadmin (prgs-reviewer)
NATIVE_REVIEW_PROOF: gitea_submit_pr_review via native MCP namespace gitea-reviewer, profile prgs-reviewer, identity sysadmin, expected_head_sha
71031c812e.Canonical Issue State
STATE: PR-open
WHO_IS_NEXT: reviewer
NEXT_ACTION: Fresh independent re-review of PR #905 at head
c83a10d7c2after author update_branch_by_merge of live master (prior APPROVE @71031c8invalidated).NEXT_PROMPT:
WHAT_HAPPENED: Master advanced after APPROVE at 71031c8; assess required update_branch_by_merge (5 commits behind, conflict-free). Author merged prgs/master into the PR branch and pushed
c83a10d. Notifications tests still 8 passed.WHY: Branch protection requires PR head contain current base; non-rebase merge keeps history and intentionally invalidates prior head-pinned approval so an independent reviewer re-approves the post-sync head.
RELATED_PRS: #905
BLOCKERS: none for author; awaiting re-approval at
c83a10dVALIDATION: git merge prgs/master clean; pytest tests/test_webui_notifications.py -q → 8 passed; push 71031c8..c83a10d
LAST_UPDATED_BY: jcwalker3 (prgs-author)
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #905
issue: #648
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 93555-4ec2c2691093
worktree: branches/review-pr905-a64ba08
phase: released
candidate_head:
71031c812etarget_branch: master
target_branch_sha: none
last_activity: 2026-07-25T22:38:55Z
expires_at: 2026-07-25T22:48:55Z
blocker: obsolete-superseded-or-expired-lease
Canonical obsolete reviewer lease cleanup (#691)
71031c812ec83a10d7c2repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #905
issue: #648
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 39434-8119054acc5d
worktree: branches/review-pr905-c83a10d
phase: claimed
candidate_head:
c83a10d7c2target_branch: master
target_branch_sha: none
last_activity: 2026-07-25T22:39:08Z
expires_at: 2026-07-25T22:49:08Z
blocker: none
Re-review APPROVE after update_branch_by_merge
Head advanced from
71031c8→c83a10dsolely via conflict-free merge of prgs/master into feat/issue-648-notifications-console (no feature code changes beyond base sync).Re-verified at
c83a10dtests/test_webui_notifications.py— 8 passed71031c8(review 607) is superseded; this is a fresh head-pinned APPROVECanonical PR State
STATE: PR-open
WHO_IS_NEXT: merger
NEXT_ACTION: Merge PR #905 at head
c83a10d7c2(assess merge_now first).NEXT_PROMPT:
WHAT_HAPPENED: After master advanced past APPROVE@71031c8, author merged master into the PR branch (
c83a10d). Independent re-review confirmed B1-B3 still fixed and suite green; APPROVE submitted at new head.WHY: Head-pinned approvals are invalidated by base sync; re-approval restores merge eligibility for #648 notifications surface.
ISSUE: #648
HEAD_SHA:
c83a10d7c2REVIEW_STATUS: APPROVE
MERGE_READY: true
BLOCKERS: none for review; merger re-checks base race at merge time
VALIDATION: pytest tests/test_webui_notifications.py -q → 8 passed at c83a10d; static re-read of classifier/id/fetch_error fixes
LAST_UPDATED_BY: sysadmin (prgs-reviewer)
NATIVE_REVIEW_PROOF: gitea_submit_pr_review via native MCP gitea-reviewer, profile prgs-reviewer, identity sysadmin, expected_head_sha
c83a10d7c2.adopted_at: 2026-07-25T22:39:53Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 39434-8119054acc5d
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 16989
adoption_reason: merger-handoff-approved-head
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #905
issue: #648
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 39432-7a0f1dbcb0f4
worktree: branches/merge-pr905-c83a10d
phase: adopted
candidate_head:
c83a10d7c2target_branch: master
target_branch_sha: none
last_activity: 2026-07-25T22:39:53Z
expires_at: 2026-07-25T22:49:53Z
blocker: none