feat(dashboard): expose suppress + unsuppress HTTP endpoints + memories UI button

Fixes the biggest UI gap flagged by the v2.0.7 audit: the `suppress`
tool has shipped since v2.0.5 with full graph event handlers
(MemorySuppressed, MemoryUnsuppressed, Rac1CascadeSwept) and violet
implosion animations — but zero trigger from anywhere except the MCP
layer. Dashboard users literally could not forget a memory without
dropping to raw MCP calls.

Backend (Axum):
- `POST /api/memories/{id}/suppress` optionally accepts `{"reason": "..."}`
  and returns the full response payload (suppression_count, prior_count,
  retrieval_penalty, reversible_until, estimated_cascade_neighbors,
  labile_window_hours). Emits `MemorySuppressed` so the 3D graph plays
  the compounding violet implosion per the v2.0.6 event handlers.
- `POST /api/memories/{id}/unsuppress` reverses within the 24h labile
  window. Returns `stillSuppressed: bool` so the UI can distinguish a
  full reversal from a compounded-down state. Emits `MemoryUnsuppressed`
  for the rainbow burst reversal animation.

Frontend:
- `api.memories.suppress(id, reason?)` and `api.memories.unsuppress(id)`
  wired through `apps/dashboard/src/lib/stores/api.ts`.
- Two new TypeScript response types (`SuppressResult`, `UnsuppressResult`)
  in `lib/types/index.ts` mirroring the backend JSON shapes.
- Memories page gets a third action button alongside Promote / Demote /
  Delete: violet "Suppress" with a hover-tooltip explaining "Top-down
  inhibition (Anderson 2025). Compounds. Reversible for 24h." The button
  ships `reason: "dashboard trigger"` so the audit log carries source
  attribution.

Tests: 425 mcp + 366 core all pass, svelte-check 580 files 0 errors.
This commit is contained in:
Sam Valladares 2026-04-19 20:31:44 -05:00
parent f0dd9c2c83
commit fc6dca6338
5 changed files with 191 additions and 2 deletions

View file

@ -10,7 +10,9 @@ import type {
ImportanceScore,
RetentionDistribution,
ConsolidationResult,
IntentionItem
IntentionItem,
SuppressResult,
UnsuppressResult
} from '$types';
const BASE = '/api';
@ -34,7 +36,18 @@ export const api = {
get: (id: string) => fetcher<Memory>(`/memories/${id}`),
delete: (id: string) => fetcher<{ deleted: boolean }>(`/memories/${id}`, { method: 'DELETE' }),
promote: (id: string) => fetcher<Memory>(`/memories/${id}/promote`, { method: 'POST' }),
demote: (id: string) => fetcher<Memory>(`/memories/${id}/demote`, { method: 'POST' })
demote: (id: string) => fetcher<Memory>(`/memories/${id}/demote`, { method: 'POST' }),
// v2.0.7: suppress + unsuppress. Anderson 2025 top-down inhibitory
// control. Each suppress call compounds; reversible within 24h. The
// backend emits MemorySuppressed / MemoryUnsuppressed so the 3D graph
// plays the violet implosion / rainbow reversal.
suppress: (id: string, reason?: string) =>
fetcher<SuppressResult>(`/memories/${id}/suppress`, {
method: 'POST',
body: reason ? JSON.stringify({ reason }) : undefined
}),
unsuppress: (id: string) =>
fetcher<UnsuppressResult>(`/memories/${id}/unsuppress`, { method: 'POST' })
},
// Search

View file

@ -173,6 +173,34 @@ export interface VestigeEvent {
data: Record<string, unknown>;
}
// v2.0.7: active-forgetting response shapes. Each suppress call COMPOUNDS;
// `suppressionCount` is the lifetime total. `reversibleUntil` is the ISO
// timestamp after which the labile window closes and the suppression locks in.
export interface SuppressResult {
suppressed: true;
id: string;
suppressionCount: number;
priorCount: number;
retrievalPenalty: number;
retentionStrength: number;
retrievalStrength: number;
stability: number;
estimatedCascadeNeighbors: number;
reversibleUntil: string;
labileWindowHours: number;
reason: string | null;
}
export interface UnsuppressResult {
unsuppressed: true;
id: string;
suppressionCount: number;
stillSuppressed: boolean;
retentionStrength: number;
retrievalStrength: number;
stability: number;
}
// Intentions (prospective memory)
export interface IntentionItem {
id: string;