Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
508af11
feature(api): add cache proposal data model
jamby77 Apr 27, 2026
79e978c
fix(api): apply bugbot findings on cache proposal data model
jamby77 Apr 27, 2026
131bb42
fix(api): apply round-2 bugbot findings on cache proposal data model
jamby77 Apr 27, 2026
3f21579
feature(api): add cache proposal service and 3 MCP propose tools
jamby77 Apr 27, 2026
ed05f42
fix(api): apply bugbot findings on cache proposal service
jamby77 Apr 27, 2026
d8c0351
fix(api): apply round-2 bugbot findings on cache proposal service
jamby77 Apr 27, 2026
646bb88
fix(api): rate limiter reserve() returns post-record remaining
jamby77 Apr 27, 2026
f3a24dc
fix(api): release rate-limit slot when storage write fails
jamby77 Apr 27, 2026
5bade0c
fix(api): apply round-3 bugbot findings on combined cache proposals PR
jamby77 Apr 27, 2026
ccb4f97
chore(api): consolidate discovery-marker key constants in shared
jamby77 Apr 27, 2026
542e92b
refactor(api): introduce SEMANTIC_CACHE / AGENT_CACHE constants
jamby77 Apr 27, 2026
a4c1d1e
fix(api): apply round-4 bugbot findings on cache proposal service
jamby77 Apr 27, 2026
2b74f2e
chore(api): disable explicit return type rule in ESLint config
jamby77 Apr 27, 2026
c499e64
fix(api): make pending unique indexes treat NULL category/tool_name a…
jamby77 Apr 27, 2026
1c00b6c
fix(api): align proposal_payload validation order with expected_statu…
jamby77 Apr 27, 2026
e1e8096
fix(api): rename pending unique indexes to _v2 + keep rate-limit slot…
jamby77 Apr 27, 2026
9a8b19e
feature(api): add 6 read-only cache MCP tools
jamby77 Apr 27, 2026
073407f
refactor(api): use shared discovery-protocol keys + extract readonly …
jamby77 Apr 27, 2026
e4accd0
refactor(api): extract readHashInt helper and simplify CacheReadonlyS…
jamby77 Apr 27, 2026
725fb0d
refactor(api): use SEMANTIC_CACHE / AGENT_CACHE in CacheReadonlyService
jamby77 Apr 27, 2026
41751b3
refactor(api): pull recommendation strings + reasonings into named co…
jamby77 Apr 27, 2026
23f5f1a
refactor(api): rename valkey-fields → record-fields
jamby77 Apr 27, 2026
df4049b
fix(api): listCaches reads __stats from prefix, not name
jamby77 Apr 27, 2026
af032f6
fix(api): drop redundant avgNearMissDelta guard in loosen-threshold b…
jamby77 Apr 27, 2026
8e7044d
feature(api): add cache proposal approve/reject/edit + apply dispatcher
jamby77 Apr 27, 2026
16844a4
fix(api): address bugbot findings on apply dispatcher PR
jamby77 Apr 27, 2026
ea47b86
fix(api): scope agent_cache key_prefix invalidate to cache namespace
jamby77 Apr 27, 2026
af5cf8b
fix(api): plumb proposalId into all apply-dispatcher errors + escapeG…
jamby77 Apr 27, 2026
ba932ec
feature(web): cache proposals UI with pending/history views
jamby77 Apr 28, 2026
68b1dcd
fix(web): share cache-proposals unread state across hook instances
jamby77 Apr 28, 2026
88beada
fix(web): correct cache-proposals source column and unread count
jamby77 Apr 28, 2026
1bef37b
fix(web): enforce monotonic lastSeenAt to prevent unread badge regres…
jamby77 Apr 28, 2026
6901271
feature(web): cache proposals UI (#138)
jamby77 Apr 29, 2026
1f6442e
Cache proposal approve/reject/edit + apply dispatcher (#137)
jamby77 Apr 29, 2026
bbedf2d
feature(api): read-only cache MCP tools (#136)
jamby77 Apr 29, 2026
80d9951
fix(api): apply dispatcher uses cache.prefix, not cache.name, for Val…
jamby77 Apr 29, 2026
71db792
fix(api): semantic invalidate FT.SEARCH index uses cache.prefix
jamby77 Apr 29, 2026
9a6017f
fix(api): wrap history endpoint in mapCacheProposalErrorToHttp
jamby77 Apr 29, 2026
fd24114
fix(api): reject unsupported cache_type/proposal_type combos in dispatch
jamby77 Apr 29, 2026
3680fd7
fix(api): approve idempotency on failed proposal returns prior result
jamby77 Apr 29, 2026
ab114e4
fix(api): address bugbot findings on cache-proposals
jamby77 Apr 29, 2026
31afa1f
fix(api): memory adapter dup check matches SQL partial-index sub-key
jamby77 Apr 29, 2026
8923d4a
fix(api): read live current threshold/TTL from Valkey when proposing
jamby77 Apr 29, 2026
7dff00f
fix(api): cache-proposals storage tests use distinct categories
jamby77 Apr 29, 2026
b1a1e77
fix(api): simplify dead-code branch in readCurrentThreshold
jamby77 Apr 29, 2026
8d4d4ff
fix(api): read dispatcher-written threshold override before SDK baseline
jamby77 Apr 29, 2026
913ee95
fix(api): deep-clone expired proposal in memory adapter
jamby77 Apr 29, 2026
6279323
fix(api): editAndApprove fails fast on unsupported proposal_type
jamby77 Apr 29, 2026
8e10355
fix(api): unify expiry boundary to <= across service and storage
jamby77 Apr 29, 2026
7edd19b
Merge remote-tracking branch 'origin/master' into feature/cache-propo…
jamby77 May 4, 2026
3016e7f
feature(semantic-cache): runtime threshold overrides via {prefix}:__c…
jamby77 May 4, 2026
ce24103
feature(mcp): wire 5 cache-proposal approval tools
jamby77 May 4, 2026
b92dbe8
docs(mcp): document cache intelligence tools + sync server.json version
jamby77 May 4, 2026
5e5d971
refactor(cache-proposals): move entire feature to proprietary as Pro …
jamby77 May 5, 2026
7783576
test(web): cover HistoryTable, DetailPanel, useCacheProposalsUnread +…
jamby77 May 5, 2026
54d899a
feat(cache-proposals): runtime config refresh for agent-cache and sem…
KIvanow May 5, 2026
39d5660
refactor(semantic-cache): drop redundant B3 read-time threshold layer…
jamby77 May 5, 2026
9249a97
fix(cache-proposals): use @app alias for ConnectionRegistry import in…
jamby77 May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default tseslint.config(
},
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/explicit-function-return-type': 'off',
Comment thread
jamby77 marked this conversation as resolved.
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
Expand Down
10 changes: 10 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ let KeyAnalyticsModule: any = null;
let AnomalyModule: any = null;
let WebhookProModule: any = null;
let InferenceLatencyProModule: any = null;
let CacheProposalsModule: any = null;
let AgentModule: any = null;
let DataRetentionModule: any = null;

Expand Down Expand Up @@ -81,6 +82,14 @@ try {
// Proprietary module not available
}

try {
const cacheProposalsModule = require('../../../proprietary/cache-proposals/cache-proposals.module');
CacheProposalsModule = cacheProposalsModule.CacheProposalsModule;
console.log('[CacheProposals] Proprietary module loaded');
} catch {
// Proprietary module not available
}

if (process.env.CLOUD_MODE) {
try {
const agentModule = require('../../../proprietary/agent/agent.module');
Expand Down Expand Up @@ -144,6 +153,7 @@ const proprietaryImports = [
AnomalyModule,
WebhookProModule,
InferenceLatencyProModule,
CacheProposalsModule,
AiModule,
AgentModule,
DataRetentionModule,
Expand Down
30 changes: 30 additions & 0 deletions apps/api/src/common/interfaces/storage-port.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ export type {
VectorIndexSnapshotQueryOptions,
} from '@betterdb/shared';
export type { MetricForecastSettings, MetricKind } from '@betterdb/shared';
export type {
CacheType,
ProposalType,
ProposalStatus,
ProposalAuditEvent,
ActorSource,
ProposalPayload,
StoredCacheProposal,
StoredCacheProposalAudit,
CreateCacheProposalInput,
ListCacheProposalsOptions,
UpdateProposalStatusInput,
AppendProposalAuditInput,
AppliedResult,
} from '@betterdb/shared';
import type {
AppSettings,
AuditQueryOptions,
Expand All @@ -53,6 +68,12 @@ import type {
Webhook,
WebhookDelivery,
WebhookEventType,
StoredCacheProposal,
StoredCacheProposalAudit,
CreateCacheProposalInput,
ListCacheProposalsOptions,
UpdateProposalStatusInput,
AppendProposalAuditInput,
} from '@betterdb/shared';

// Anomaly Event Types
Expand Down Expand Up @@ -480,4 +501,13 @@ export interface StoragePort {
saveMetricForecastSettings(settings: MetricForecastSettings): Promise<MetricForecastSettings>;
deleteMetricForecastSettings(connectionId: string, metricKind: MetricKind): Promise<boolean>;
getActiveMetricForecastSettings(): Promise<MetricForecastSettings[]>;

// Cache Proposal Methods
createCacheProposal(input: CreateCacheProposalInput): Promise<StoredCacheProposal>;
getCacheProposal(id: string): Promise<StoredCacheProposal | null>;
listCacheProposals(options: ListCacheProposalsOptions): Promise<StoredCacheProposal[]>;
updateCacheProposalStatus(input: UpdateProposalStatusInput): Promise<StoredCacheProposal | null>;
expireCacheProposalsBefore(now: number): Promise<StoredCacheProposal[]>;
appendCacheProposalAudit(input: AppendProposalAuditInput): Promise<StoredCacheProposalAudit>;
getCacheProposalAudit(proposalId: string): Promise<StoredCacheProposalAudit[]>;
}
8 changes: 8 additions & 0 deletions apps/api/src/common/utils/record-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function readIntField(record: Record<string, string>, field: string): number {
const value = record[field];
if (value === undefined || value === '') {
return 0;
}
const parsed = parseInt(value, 10);
return Number.isNaN(parsed) ? 0 : parsed;
}
41 changes: 41 additions & 0 deletions apps/api/src/mcp/mcp-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common';

export const INSTANCE_ID_RE = /^[a-zA-Z0-9_-]+$/;
export const MAX_LIMIT = 10000;

@Injectable()
export class ValidateInstanceIdPipe implements PipeTransform<string, string> {
transform(value: string): string {
if (!INSTANCE_ID_RE.test(value)) {
throw new BadRequestException('Invalid instance ID');
}
return value;
}
}

export function safeParseInt(value: string | undefined, defaultValue: number): number;
export function safeParseInt(value: string | undefined, defaultValue?: undefined): number | undefined;
export function safeParseInt(value: string | undefined, defaultValue?: number): number | undefined {
if (value === undefined) {
return defaultValue;
}
const parsed = parseInt(value, 10);
if (isNaN(parsed)) {
return defaultValue;
}
return parsed;
}

/** Parse and cap a limit/count query param */
export function safeLimit(value: string | undefined, defaultValue: number): number {
return Math.max(1, Math.min(safeParseInt(value, defaultValue), MAX_LIMIT));
}

/** Convert ms timestamp query param to seconds. */
export function msToSeconds(value: string | undefined): number | undefined {
const ms = safeParseInt(value);
if (ms === undefined || ms < 0) {
return undefined;
}
return Math.floor(ms / 1000);
}
39 changes: 5 additions & 34 deletions apps/api/src/mcp/mcp.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller, Get, Post, Body, Param, Query, HttpException, HttpStatus, UseGuards, Optional, Inject, BadRequestException, PipeTransform, Injectable, Logger } from '@nestjs/common';
import { Controller, Get, Post, Body, Param, Query, HttpException, HttpStatus, UseGuards, Optional, Inject, BadRequestException, Logger } from '@nestjs/common';
import { ANOMALY_SERVICE } from '@betterdb/shared';
import { UsageTelemetryService } from '../telemetry/usage-telemetry.service';
import { ConnectionRegistry } from '../connections/connection-registry.service';
Expand All @@ -9,47 +9,16 @@ import { ClientAnalyticsAnalysisService } from '../client-analytics/client-analy
import { ClusterDiscoveryService } from '../cluster/cluster-discovery.service';
import { ClusterMetricsService } from '../cluster/cluster-metrics.service';
import { StoragePort } from '../common/interfaces/storage-port.interface';
import { MAX_LIMIT, ValidateInstanceIdPipe, msToSeconds, safeLimit, safeParseInt } from './mcp-helpers';

const INSTANCE_ID_RE = /^[a-zA-Z0-9_-]+$/;
const EVENT_NAME_RE = /^[a-zA-Z0-9_.-]+$/;
const VALID_ORDER_BY = new Set(['key-count', 'cpu-usec']);
const MAX_LIMIT = 10000;

@Injectable()
class ValidateInstanceIdPipe implements PipeTransform<string, string> {
transform(value: string): string {
if (!INSTANCE_ID_RE.test(value)) {
throw new BadRequestException('Invalid instance ID');
}
return value;
}
}

function safeParseInt(value: string | undefined, defaultValue: number): number;
function safeParseInt(value: string | undefined, defaultValue?: undefined): number | undefined;
function safeParseInt(value: string | undefined, defaultValue?: number): number | undefined {
if (value === undefined) return defaultValue;
const parsed = parseInt(value, 10);
if (isNaN(parsed)) return defaultValue;
return parsed;
}

/** Parse and cap a limit/count query param */
function safeLimit(value: string | undefined, defaultValue: number): number {
return Math.max(1, Math.min(safeParseInt(value, defaultValue), MAX_LIMIT));
}

/** Convert ms timestamp query param to seconds for commandlog service */
function msToSeconds(value: string | undefined): number | undefined {
const ms = safeParseInt(value);
if (ms === undefined || ms < 0) return undefined;
return Math.floor(ms / 1000);
}

@Controller('mcp')
@UseGuards(AgentTokenGuard)
export class McpController {
private readonly logger = new Logger(McpController.name);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private readonly anomalyService: any;

private readonly telemetryService: UsageTelemetryService | null;
Expand All @@ -62,6 +31,7 @@ export class McpController {
private readonly clusterDiscoveryService: ClusterDiscoveryService,
private readonly clusterMetricsService: ClusterMetricsService,
@Inject('STORAGE_CLIENT') private readonly storageClient: StoragePort,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@Optional() @Inject(ANOMALY_SERVICE) anomalyService?: any,
@Optional() telemetryService?: UsageTelemetryService,
) {
Expand Down Expand Up @@ -449,4 +419,5 @@ export class McpController {
));
return { ok: true };
}

}
Loading
Loading