Cache proposal approve/reject/edit + apply dispatcher#137
Merged
jamby77 merged 9 commits intoApr 29, 2026
Merged
Conversation
jamby77
force-pushed
the
feature/cache-mcp-readonly-tools
branch
from
April 27, 2026 13:08
720786d to
54794d1
Compare
jamby77
force-pushed
the
feature/cache-apply-logic-and-approval-api
branch
from
April 27, 2026 13:09
eee5420 to
db14ecd
Compare
jamby77
force-pushed
the
feature/cache-mcp-readonly-tools
branch
from
April 27, 2026 13:25
54794d1 to
85cd223
Compare
jamby77
force-pushed
the
feature/cache-apply-logic-and-approval-api
branch
from
April 27, 2026 13:25
fc837be to
caaa3b7
Compare
jamby77
force-pushed
the
feature/cache-mcp-readonly-tools
branch
from
April 27, 2026 13:46
85cd223 to
af032f6
Compare
- Add approve/reject/editAndApprove/expireProposals on CacheProposalService
sharing logic between HTTP controller (actor_source='ui') and MCP tools
(actor_source='mcp')
- Add CacheApplyService that runs Valkey work and transitions
approved -> applied|failed with idempotency on re-entry
- Add CacheApplyDispatcher with 4 handlers:
- semantic threshold_adjust: HSET {cache_name}:__config (gated on
threshold_adjust capability advertised in marker)
- agent tool_ttl_adjust: HSET {cache_name}:__tool_policies
- semantic invalidate: FT.SEARCH + DEL
- agent invalidate: SCAN + DEL on tool/key_prefix/session pattern
- Add CacheExpirationCron (5 min cadence, setInterval pattern)
marking past-due pending proposals as expired with system audit
- Add /cache-proposals HTTP controller with 6 endpoints
- Add 5 MCP approval endpoints in mcp.controller.ts
- Extract shared mapCacheProposalErrorToHttp helper used by both
controllers and add typed errors for proposal lifecycle
- Tests: 18 new specs covering approve/reject/edit/expire flows and
dispatcher behaviour
- HIGH: parseFtSearchKeys was iterating with i+=2, missing every other key when FT.SEARCH was called with RETURN 0 (which behaves like NOCONTENT and returns just [count, key1, key2, ...]). Step by 1. - LOW: extract optionalString, optionalFiniteNumber, formatApprovalResult to controller-helpers.ts, share between cache-proposal.controller and mcp.controller - Add regression test exercising RETURN 0 response shape
bugbot HIGH: applyAgentInvalidate's key_prefix branch built a SCAN pattern as `<filter_value>*` without prepending `<cache.name>:`, unlike the tool/session branches. This let a key_prefix invalidation match keys belonging to other caches or unrelated application data on the same Valkey instance. Scope the pattern to the cache namespace and add a regression test.
…lob cache name
bugbot MEDIUM: applySemanticThresholdAdjust, applyAgentToolTtlAdjust,
applySemanticInvalidate were passing cache.name as the first arg to
ApplyFailedError, which the constructor labels as proposalId — that
wrong value was landing in applied_result.details. Plus, the constructor
spread '{ proposalId, ...details }' allowed a stale proposalId in
details to overwrite the explicit one. Pass proposalId through to all
dispatcher methods, attach cacheName separately in details, and put the
explicit proposalId last in the spread so it always wins.
bugbot LOW: tool and session SCAN patterns interpolated cache.name
without escaping while key_prefix did. Apply escapeGlob uniformly to
cache.name across all three branches.
jamby77
force-pushed
the
feature/cache-apply-logic-and-approval-api
branch
from
April 27, 2026 13:46
c4ae2d2 to
af5cf8b
Compare
Add /cache-proposals route with Pending and History tabs. Pending cards render four (cache_type, proposal_type) variants with approve, reject, and edit-and-approve flows. Edit hidden on invalidate cards. History table filters by status/cache_name and opens a detail panel with full reasoning, payload, and audit trail. Sidebar shows an unread badge for new pending proposals.
4 tasks
Use a module-level subscription store with useSyncExternalStore so the sidebar badge clears when CacheProposals.markAllRead() runs. Wraps markAllRead in useCallback to avoid firing the page-level effect every render. Drops the unused cacheProposalQueryKeys export.
- proposalSource now reads proposed_by only (was falling through to reviewed_by, mislabelling UI-proposed/MCP-reviewed entries as 'mcp'). - Track unread by last-seen timestamp instead of id, so when the marker proposal is approved/rejected/expired it doesn't inflate the unread count to all remaining (already-seen) pending proposals.
…sion Math.max guards setLastSeenAt so a markAllRead call where the newest pending proposal has a smaller timestamp (e.g. the previous newest was approved by another user) cannot move the marker backward and resurrect already-seen proposals as unread.
## Summary
- Add the **Cache Proposals** tab to Monitor (route `/cache-proposals`)
with Pending and History sub-views.
- **Pending view**: cards render the 4 `(cache_type, proposal_type)`
variants — semantic threshold adjust, agent tool TTL adjust, semantic
invalidate, agent invalidate. Approve is optimistic ("Applying…"),
Reject opens an inline reason input, Edit swaps the proposed value for
an inline numeric input that posts via `/edit-and-approve`. Edit is
hidden on invalidate cards. `estimated_affected > 10000` triggers a warn
visual.
- **History view**: table with status + cache_name filters, source
column, clickable rows opening a detail panel with reasoning, payload,
apply result, and full audit trail.
- **Unread indicator**: sidebar badge on the Cache Proposals nav item,
cleared when the user opens the Pending tab. Persisted in localStorage.
- New helpers: `lib/formatters.ts` (`formatTtlSeconds`, `formatTimeAgo`,
`formatExpiresIn`).
- Component tests (Vitest + RTL) cover all 4 card variants, edit hidden
on invalidate, warn visual at >10000, optimistic Applying state, reject
flow with/without reason, edit-and-approve, and direct approve.
Stacked on #137 — merge after the apply-dispatcher PR lands.
Closes the UI portion of issue-179753285 (Days 7–9). Spec:
`docs/plans/specs/spec-cache-proposals-ui.md`.
## Test plan
- [ ] `pnpm --filter @betterdb/web test` — all web tests pass (175
tests, 27 files).
- [ ] `pnpm --filter @betterdb/web build` — typecheck + Vite build
clean.
- [ ] Manual: visit `/cache-proposals`, verify all 4 card variants
render correctly, approve/reject/edit flows POST to the right endpoint,
history filters work, detail panel opens with audit trail, unread badge
clears on tab visit.
- [ ] Verify no hard-coded hex colours — all status colours use
`var(--chart-*)` / shadcn theme variables.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Medium risk because it introduces new approve/reject/edit-and-approve
mutation flows that trigger operational changes via new API endpoints,
plus polling/unread state persisted in localStorage.
>
> **Overview**
> Introduces a new `/cache-proposals` page with **Pending** and
**History** views for reviewing agent-submitted cache proposals.
>
> Pending proposals render as actionable cards (approve, reject with
optional reason, and edit+approve for supported proposal types) with
basic validation and “high impact” highlighting; history adds filtering
and a row-click detail sheet that shows payload, apply result, and audit
events.
>
> Adds a `cacheProposalsApi` client plus React Query hooks (including
pending polling and query invalidation) and a sidebar **unread badge**
backed by `localStorage`, along with new time/TTL formatting utilities
and targeted component/unit tests.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1bef37b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the cache proposal approval and apply pipeline for issue
179753285 (cache intelligence v1, advisory mode):
approve,reject,editAndApprove,expireProposals,getProposalWithAuditmethods, sharing logic betweenUI (HTTP) and MCP callers
approved → applied | failed,writes
applied/failedauditHSET {cache_name}:__config threshold[:category] <value>(gated on
threshold_adjustcapability in discovery marker)HSET {cache_name}:__tool_policies <tool> <json>FT.SEARCH+DELSCAN+DELon tool/key_prefix/session patternssetInterval, drivesexpireCacheProposalsBefore, writesactor_source='system'audit/cache-proposals: 6 endpoints (pending, history,get, approve, reject, edit-and-approve)
(
actor_source='mcp')mapCacheProposalErrorToHttphelperStacked on PR #136 (
feature/cache-mcp-readonly-tools).Test plan
tsc --noEmitcleaneslintcleanintegration-tests spec)
Note
Medium Risk
Adds new write/delete operations against Valkey (including bulk invalidation) and new state transitions/auditing paths; mistakes could lead to unintended cache data loss or stuck proposals, though changes are guarded by approval flow and covered by unit tests.
Overview
Implements an end-to-end cache proposal review workflow: new API endpoints to list pending/history, fetch proposal+audit, and
approve/reject/edit-and-approve, with shared error-to-HTTP mapping and controller input validation.Adds an apply pipeline that transitions approved proposals to
appliedorfailedwith audit events, plus aCacheApplyDispatcherthat executes Valkey-side changes (semantic thresholdHSET, agent tool TTL policyHSET, semantic invalidate viaFT.SEARCH+DEL, agent invalidate viaSCAN+DEL) and a background cron that periodically expires overdue proposals.Web UI gains a new Cache Proposals page (pending + history + detail/audit drawer), sidebar unread badge (localStorage last-seen), and hooks/API client for fetching and mutating proposals; comprehensive unit tests cover dispatcher behavior and proposal state transitions.
Reviewed by Cursor Bugbot for commit 6901271. Bugbot is set up for automated code reviews on this repo. Configure here.