fix(memory): scope InsightsRepo operations to userId — M2-001/002 (#290)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #290.
This commit is contained in:
@@ -137,7 +137,7 @@ export class SummarizationService {
|
||||
|
||||
const promoted = await this.logService.logs.promoteToCold(warmCutoff);
|
||||
const purged = await this.logService.logs.purge(coldCutoff);
|
||||
const decayed = await this.memory.insights.decayOldInsights(decayCutoff);
|
||||
const decayed = await this.memory.insights.decayAllInsights(decayCutoff);
|
||||
|
||||
this.logger.log(
|
||||
`Tier management: ${promoted} logs→cold, ${purged} purged, ${decayed} insights decayed`,
|
||||
|
||||
@@ -73,8 +73,8 @@ export class MemoryController {
|
||||
}
|
||||
|
||||
@Get('insights/:id')
|
||||
async getInsight(@Param('id') id: string) {
|
||||
const insight = await this.memory.insights.findById(id);
|
||||
async getInsight(@CurrentUser() user: { id: string }, @Param('id') id: string) {
|
||||
const insight = await this.memory.insights.findById(id, user.id);
|
||||
if (!insight) throw new NotFoundException('Insight not found');
|
||||
return insight;
|
||||
}
|
||||
@@ -97,8 +97,8 @@ export class MemoryController {
|
||||
|
||||
@Delete('insights/:id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async removeInsight(@Param('id') id: string) {
|
||||
const deleted = await this.memory.insights.remove(id);
|
||||
async removeInsight(@CurrentUser() user: { id: string }, @Param('id') id: string) {
|
||||
const deleted = await this.memory.insights.remove(id, user.id);
|
||||
if (!deleted) throw new NotFoundException('Insight not found');
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,11 @@ export function createInsightsRepo(db: Db) {
|
||||
.limit(limit);
|
||||
},
|
||||
|
||||
async findById(id: string): Promise<Insight | undefined> {
|
||||
const rows = await db.select().from(insights).where(eq(insights.id, id));
|
||||
async findById(id: string, userId: string): Promise<Insight | undefined> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(insights)
|
||||
.where(and(eq(insights.id, id), eq(insights.userId, userId)));
|
||||
return rows[0];
|
||||
},
|
||||
|
||||
@@ -29,17 +32,24 @@ export function createInsightsRepo(db: Db) {
|
||||
return rows[0]!;
|
||||
},
|
||||
|
||||
async update(id: string, data: Partial<NewInsight>): Promise<Insight | undefined> {
|
||||
async update(
|
||||
id: string,
|
||||
userId: string,
|
||||
data: Partial<NewInsight>,
|
||||
): Promise<Insight | undefined> {
|
||||
const rows = await db
|
||||
.update(insights)
|
||||
.set({ ...data, updatedAt: new Date() })
|
||||
.where(eq(insights.id, id))
|
||||
.where(and(eq(insights.id, id), eq(insights.userId, userId)))
|
||||
.returning();
|
||||
return rows[0];
|
||||
},
|
||||
|
||||
async remove(id: string): Promise<boolean> {
|
||||
const rows = await db.delete(insights).where(eq(insights.id, id)).returning();
|
||||
async remove(id: string, userId: string): Promise<boolean> {
|
||||
const rows = await db
|
||||
.delete(insights)
|
||||
.where(and(eq(insights.id, id), eq(insights.userId, userId)))
|
||||
.returning();
|
||||
return rows.length > 0;
|
||||
},
|
||||
|
||||
@@ -70,8 +80,33 @@ export function createInsightsRepo(db: Db) {
|
||||
|
||||
/**
|
||||
* Decay relevance scores for old insights that haven't been accessed recently.
|
||||
* Scoped to a specific user to prevent cross-user data mutation.
|
||||
*/
|
||||
async decayOldInsights(olderThan: Date, decayFactor = 0.95): Promise<number> {
|
||||
async decayOldInsights(userId: string, olderThan: Date, decayFactor = 0.95): Promise<number> {
|
||||
const result = await db
|
||||
.update(insights)
|
||||
.set({
|
||||
relevanceScore: sql`${insights.relevanceScore} * ${decayFactor}`,
|
||||
decayedAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(insights.userId, userId),
|
||||
lt(insights.updatedAt, olderThan),
|
||||
sql`${insights.relevanceScore} > 0.1`,
|
||||
),
|
||||
)
|
||||
.returning();
|
||||
return result.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decay relevance scores for all users' old insights.
|
||||
* This is a system-level maintenance operation intended for scheduled cron jobs only.
|
||||
* Do NOT expose this through user-facing API endpoints.
|
||||
*/
|
||||
async decayAllInsights(olderThan: Date, decayFactor = 0.95): Promise<number> {
|
||||
const result = await db
|
||||
.update(insights)
|
||||
.set({
|
||||
|
||||
Reference in New Issue
Block a user