* feat(frameworks): add settings section and import modal - Add Analysis Frameworks group to preferences-content.ts between Intelligence and Media sections - Per-panel active framework display (read-only, 4 panels) - Skill library list with built-in badge, Rename and Delete actions for imported frameworks - Import modal with two tabs: From agentskills.io (fetch + preview) and Paste JSON - All error cases handled inline: network, domain validation, missing instructions, invalid JSON, duplicate name, instructions too long, rate limit - Add api/skills/fetch-agentskills.ts edge function (proxy to agentskills.io) - Add analysis-framework-store.ts (loadFrameworkLibrary, saveImportedFramework, deleteImportedFramework, renameImportedFramework, getActiveFrameworkForPanel) - Add fw-* CSS classes to main.css matching dark panel aesthetic * feat(panels): wire analytical framework store into InsightsPanel, CountryDeepDive, DailyMarketBrief, DeductionPanel - InsightsPanel: append active framework to geoContext in updateFromClient(); subscribe in constructor, unsubscribe in destroy() - CountryIntelManager: pass framework as query param to fetchCountryIntelBrief(); subscribe to re-open brief on framework change; unsubscribe in destroy() - DataLoaderManager: add dailyBriefGeneration counter for stale-result guard; pass frameworkAppend to buildDailyMarketBrief(); subscribe to framework changes to force refresh; unsubscribe in destroy() - daily-market-brief service: add frameworkAppend? field to BuildDailyMarketBriefOptions; append to extendedContext before summarize call - DeductionPanel: append active framework to geoContext in handleSubmit() before RPC call * feat(frameworks): add FrameworkSelector UI component - Create FrameworkSelector component with premium/locked states - Premium: select dropdown with all framework options, change triggers setActiveFrameworkForPanel - Locked: disabled select + PRO badge, click calls showGatedCta(FREE_TIER) - InsightsPanel: adds asterisk note (client-generated analysis hint) - Wire into InsightsPanel, DailyMarketBriefPanel, DeductionPanel (via this.header) - Wire into CountryDeepDivePanel header right-side (no Panel base, panel=null) - Add framework-selector CSS to main.css * fix(frameworks): make new proto fields optional in generated types * fix(frameworks): extract firstMsg to satisfy strict null checks in tsconfig.api.json * fix(docs): add blank lines around lists/headings to pass markdownlint * fix(frameworks): add required proto string fields to call sites after make generate * chore(review): add code review todos 041-057 for PR #2380 7 review agents (TypeScript, Security, Architecture, Performance, Simplicity, Agent-Native, Learnings) identified 17 findings across 5 P1, 8 P2, and 4 P3 categories.
2.9 KiB
status, priority, issue_id, tags, dependencies
| status | priority | issue_id | tags | dependencies | ||||
|---|---|---|---|---|---|---|---|---|
| pending | p1 | 041 |
|
deduct-situation.ts framework excluded from cache key — cross-user cache poisoning
Problem Statement
deduct-situation.ts extracts the framework field and passes it to callLlm as systemAppend, but the Redis cache key does NOT include the framework. User A submits query X with no framework; result cached under key K. User B submits same query X with the PMESII-PT framework; gets key K hit and receives the frameworkless response. User B's framework is silently ignored. Meanwhile get-country-intel-brief.ts correctly hashes frameworkRaw into its cache key on line 40 — the inconsistency was introduced in this PR.
Findings
server/worldmonitor/intelligence/v1/deduct-situation.ts:29— cache key:deduct:situation:v2:${sha256(query + '|' + geoContext)}— framework absentserver/worldmonitor/intelligence/v1/get-country-intel-brief.ts:38-40— correct pattern:const frameworkHash = frameworkRaw ? (await sha256Hex(frameworkRaw)).slice(0, 8) : ''; const cacheKey = \ci-sebuf:v3:...${frameworkHash ? ':' + frameworkHash : ''}``- Flagged independently by: kieran-typescript-reviewer, performance-oracle, code-simplicity-reviewer, agent-native-reviewer, architecture-strategist
Proposed Solutions
Option A: Mirror get-country-intel-brief.ts pattern (Recommended)
Add frameworkHash to the cache key, identical to get-country-intel-brief.ts:
const frameworkRaw = typeof req.framework === 'string' ? req.framework.slice(0, 2000) : '';
const frameworkHash = frameworkRaw ? (await sha256Hex(frameworkRaw)).slice(0, 8) : '';
const queryHash = (await sha256Hex(query.toLowerCase() + '|' + geoContext.toLowerCase())).slice(0, 16);
const cacheKey = `deduct:situation:v2:${queryHash}${frameworkHash ? ':' + frameworkHash : ''}`;
Pros: Consistent with existing pattern, one-line extension | Effort: Small | Risk: Low
Option B: Bump cache key version to v3 and include framework
Same as A but bump version to v3 to invalidate all existing deduction cache entries (avoids old frameworkless entries being served after deploy).
Pros: Clean slate for caches | Cons: All existing cached deductions invalidated — extra LLM cost on redeploy | Effort: Small | Risk: Low
Technical Details
- File:
server/worldmonitor/intelligence/v1/deduct-situation.ts - PR: koala73/worldmonitor#2380
- Related:
server/worldmonitor/intelligence/v1/get-country-intel-brief.ts:38-40(reference implementation)
Acceptance Criteria
deduct-situation.tscache key includes a hash offrameworkRawwhen non-empty- Pattern mirrors
get-country-intel-brief.tsframeworkHash approach - Two requests with same query/geoContext but different frameworks produce different cache keys
Work Log
- 2026-03-27: Identified during PR #2380 review by 5 independent agents