From 74670fbe04f7b8ddc149451d466652daa17c9571 Mon Sep 17 00:00:00 2001 From: Theater-ahyeon <1347507191@qq.com> Date: Mon, 21 Sep 2026 20:42:59 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(automation):=20=E4=B8=BA=E6=AF=8F?= =?UTF-8?q?=E6=97=A5=E6=83=85=E6=8A=A5=E7=AE=80=E6=8A=A5=E9=A1=B9=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E7=A8=B3=E5=AE=9A=E8=AF=81=E6=8D=AE=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E4=B8=8E=E5=BF=AB=E7=85=A7=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/shared/src/automation/brief.test.ts | 41 ++++++--- packages/shared/src/automation/brief.ts | 91 ++++++++++++-------- 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/packages/shared/src/automation/brief.test.ts b/packages/shared/src/automation/brief.test.ts index 19623825..19f9e58f 100644 --- a/packages/shared/src/automation/brief.test.ts +++ b/packages/shared/src/automation/brief.test.ts @@ -78,22 +78,41 @@ describe('buildBrief', () => { expect(brief.quiet).toEqual({ count: 0, message: 'No monitored securities.' }) }) - it('every item carries an explainable source', () => { + it('every item carries an explainable source, evidence references, and context version when applicable', () => { const brief = buildBrief( inputs({ - runs: [run('r1', 2, true)], - alerts: [alert('a1', 'AAPL.US', 'AAPL price alert')], - diffs: [diff('NVDA.US', true, 'strengthened')], - portfolio: [{ label: 'AAPL.US ยท 12.4% of portfolio', detail: 'Single-stock exposure 12.4%' }], - movers: [mover('TSLA.US', 7.2)], + diffs: [ + { + ...diff('NVDA.US', true, 'strengthened'), + changes: [ + { + category: 'valuation', + label: 'PE ratio', + before: 50, + after: 40, + direction: 'improved', + material: true, + evidence: ['capability:company.valuation run:run-1 fetchedAt:1700000000'], + }, + ], + }, + ], + runs: [run('run-auto', 1, true)], }), 1_700_000_000_000 ) - // portfolio(1) + watchlist mover(1) + watchlist diff(1) + thesis(1) + alert(1) + automation(1) - expect(brief.items.length).toBe(6) - for (const item of brief.items) { - expect(['Portfolio', 'Watchlist', 'Thesis', 'Alert', 'Automation']).toContain(item.source) - } + expect(brief.items.length).toBeGreaterThan(0) + const diffItem = brief.items.find((item) => item.id === 'diff-NVDA.US') + expect(diffItem?.evidenceRefs).toContain('capability:company.valuation run:run-1 fetchedAt:1700000000') + expect(diffItem?.contextVersion).toBe('diff-diff-NVDA.US') + + const thesisItem = brief.items.find((item) => item.id === 'thesis-NVDA.US') + expect(thesisItem?.evidenceRefs).toContain('capability:company.valuation run:run-1 fetchedAt:1700000000') + expect(thesisItem?.contextVersion).toBe('diff-diff-NVDA.US') + + const autoItem = brief.items.find((item) => item.id === 'automation-run-auto') + expect(autoItem?.evidenceRefs).toContain('automation-run:run-auto:material') + expect(autoItem?.contextVersion).toBe('run-run-auto') }) it('flags only material movers as watchlist changes', () => { diff --git a/packages/shared/src/automation/brief.ts b/packages/shared/src/automation/brief.ts index 01375bee..7bcf7fa7 100644 --- a/packages/shared/src/automation/brief.ts +++ b/packages/shared/src/automation/brief.ts @@ -26,6 +26,10 @@ export interface BriefItem { severity: BriefSeverity /** Structured explainability payload for "Why am I seeing this?". */ payload?: Record + /** Stable evidence / source references backing this brief conclusion (Issue #103 / #100). */ + evidenceRefs?: string[] + /** Versioned context / snapshot id this item was derived from. */ + contextVersion?: string } export interface BriefQuietState { @@ -151,15 +155,20 @@ function watchlistItems(inputs: BriefInputs): BriefItem[] { const diffItems: BriefItem[] = inputs.diffs .filter((diff) => diff.material) - .map((diff) => ({ - id: `watchlist-${diff.symbol}-diff`, - symbol: diff.symbol, - title: `${diff.symbol} material research change`, - message: diff.summary ?? `${diff.changes.length} material change(s) vs the previous report.`, - source: 'Watchlist', - severity: 'warning', - payload: { diffId: diff.id, changes: diff.changes.length }, - })) + .map((diff) => { + const evidenceRefs = diff.changes.flatMap((c) => c.evidence) + return { + id: `diff-${diff.symbol}`, + symbol: diff.symbol, + title: `${diff.symbol} research changed`, + message: diff.summary ?? 'Material research update.', + source: 'Watchlist', + severity: 'warning', + payload: { diffId: diff.id, changes: diff.changes.length }, + ...(evidenceRefs.length > 0 ? { evidenceRefs } : {}), + contextVersion: `diff-${diff.id}`, + } + }) return [...moverItems, ...diffItems] } @@ -167,15 +176,20 @@ function watchlistItems(inputs: BriefInputs): BriefItem[] { function thesisItems(inputs: BriefInputs): BriefItem[] { return inputs.diffs .filter((diff) => diff.thesisImpact !== undefined && diff.thesisImpact.direction !== 'unchanged') - .map((diff) => ({ - id: `thesis-${diff.symbol}`, - symbol: diff.symbol, - title: `${diff.symbol} thesis ${diff.thesisImpact?.direction ?? 'unchanged'}`, - message: diff.thesisImpact?.summary ?? 'Thesis impact detected.', - source: 'Thesis', - severity: diff.thesisImpact?.direction === 'invalidated' ? 'critical' : 'warning', - payload: { diffId: diff.id, direction: diff.thesisImpact?.direction }, - })) + .map((diff) => { + const evidenceRefs = diff.changes.flatMap((c) => c.evidence) + return { + id: `thesis-${diff.symbol}`, + symbol: diff.symbol, + title: `${diff.symbol} thesis ${diff.thesisImpact?.direction ?? 'unchanged'}`, + message: diff.thesisImpact?.summary ?? 'Thesis impact detected.', + source: 'Thesis', + severity: diff.thesisImpact?.direction === 'invalidated' ? 'critical' : 'warning', + payload: { diffId: diff.id, direction: diff.thesisImpact?.direction }, + ...(evidenceRefs.length > 0 ? { evidenceRefs } : {}), + contextVersion: `diff-${diff.id}`, + } + }) } function alertItems(inputs: BriefInputs): BriefItem[] { @@ -193,22 +207,31 @@ function alertItems(inputs: BriefInputs): BriefItem[] { function automationItems(inputs: BriefInputs): BriefItem[] { return inputs.runs .filter((run) => run.materialChanges > 0 || run.notified) - .map((run) => ({ - id: `automation-${run.id}`, - title: - run.materialChanges > 0 - ? `Automation: ${run.materialChanges} material change${run.materialChanges === 1 ? '' : 's'}` - : `Automation: ${run.ruleId} completed`, - message: `Evaluated ${run.evaluated} securities, analyzed ${run.analyzed}.`, - source: 'Automation', - severity: run.materialChanges > 0 ? 'warning' : 'info', - payload: { - ruleId: run.ruleId, - evaluated: run.evaluated, - analyzed: run.analyzed, - failures: run.failures, - }, - })) + .map((run) => { + const isMaterial = run.materialChanges > 0 + const evidenceRefs: string[] = [] + if (isMaterial) { + evidenceRefs.push(`automation-run:${run.id}:material`) + } + return { + id: `automation-${run.id}`, + title: + isMaterial + ? `Automation: ${run.materialChanges} material change${run.materialChanges === 1 ? '' : 's'}` + : `Automation: ${run.ruleId} completed`, + message: `Evaluated ${run.evaluated} securities, analyzed ${run.analyzed}.`, + source: 'Automation', + severity: isMaterial ? 'warning' : 'info', + payload: { + ruleId: run.ruleId, + evaluated: run.evaluated, + analyzed: run.analyzed, + failures: run.failures, + }, + ...(evidenceRefs.length > 0 ? { evidenceRefs } : {}), + ...(run.id ? { contextVersion: `run-${run.id}` } : {}), + } + }) } function compareItems(a: BriefItem, b: BriefItem): number { From b6987067e864c3897ac0c281749c2202d82e933e Mon Sep 17 00:00:00 2001 From: Theater-ahyeon <1347507191@qq.com> Date: Wed, 23 Sep 2026 09:28:27 +0800 Subject: [PATCH 2/2] fix(automation): rename brief evidence references to provenance anchors to avoid canonical identity confusion --- packages/shared/src/automation/brief.test.ts | 8 ++++---- packages/shared/src/automation/brief.ts | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/automation/brief.test.ts b/packages/shared/src/automation/brief.test.ts index 19f9e58f..06275e1d 100644 --- a/packages/shared/src/automation/brief.test.ts +++ b/packages/shared/src/automation/brief.test.ts @@ -78,7 +78,7 @@ describe('buildBrief', () => { expect(brief.quiet).toEqual({ count: 0, message: 'No monitored securities.' }) }) - it('every item carries an explainable source, evidence references, and context version when applicable', () => { + it('every item carries an explainable source, provenance references, and context version when applicable', () => { const brief = buildBrief( inputs({ diffs: [ @@ -103,15 +103,15 @@ describe('buildBrief', () => { ) expect(brief.items.length).toBeGreaterThan(0) const diffItem = brief.items.find((item) => item.id === 'diff-NVDA.US') - expect(diffItem?.evidenceRefs).toContain('capability:company.valuation run:run-1 fetchedAt:1700000000') + expect(diffItem?.provenanceRefs).toContain('capability:company.valuation run:run-1 fetchedAt:1700000000') expect(diffItem?.contextVersion).toBe('diff-diff-NVDA.US') const thesisItem = brief.items.find((item) => item.id === 'thesis-NVDA.US') - expect(thesisItem?.evidenceRefs).toContain('capability:company.valuation run:run-1 fetchedAt:1700000000') + expect(thesisItem?.provenanceRefs).toContain('capability:company.valuation run:run-1 fetchedAt:1700000000') expect(thesisItem?.contextVersion).toBe('diff-diff-NVDA.US') const autoItem = brief.items.find((item) => item.id === 'automation-run-auto') - expect(autoItem?.evidenceRefs).toContain('automation-run:run-auto:material') + expect(autoItem?.provenanceRefs).toContain('automation-run:run-auto:material') expect(autoItem?.contextVersion).toBe('run-run-auto') }) diff --git a/packages/shared/src/automation/brief.ts b/packages/shared/src/automation/brief.ts index 7bcf7fa7..d2d0fa95 100644 --- a/packages/shared/src/automation/brief.ts +++ b/packages/shared/src/automation/brief.ts @@ -26,8 +26,8 @@ export interface BriefItem { severity: BriefSeverity /** Structured explainability payload for "Why am I seeing this?". */ payload?: Record - /** Stable evidence / source references backing this brief conclusion (Issue #103 / #100). */ - evidenceRefs?: string[] + /** Provenance and correlation anchors backing this brief conclusion (Issue #103). */ + provenanceRefs?: string[] /** Versioned context / snapshot id this item was derived from. */ contextVersion?: string } @@ -156,7 +156,7 @@ function watchlistItems(inputs: BriefInputs): BriefItem[] { const diffItems: BriefItem[] = inputs.diffs .filter((diff) => diff.material) .map((diff) => { - const evidenceRefs = diff.changes.flatMap((c) => c.evidence) + const provenanceRefs = diff.changes.flatMap((c) => c.evidence) return { id: `diff-${diff.symbol}`, symbol: diff.symbol, @@ -165,7 +165,7 @@ function watchlistItems(inputs: BriefInputs): BriefItem[] { source: 'Watchlist', severity: 'warning', payload: { diffId: diff.id, changes: diff.changes.length }, - ...(evidenceRefs.length > 0 ? { evidenceRefs } : {}), + ...(provenanceRefs.length > 0 ? { provenanceRefs } : {}), contextVersion: `diff-${diff.id}`, } }) @@ -177,7 +177,7 @@ function thesisItems(inputs: BriefInputs): BriefItem[] { return inputs.diffs .filter((diff) => diff.thesisImpact !== undefined && diff.thesisImpact.direction !== 'unchanged') .map((diff) => { - const evidenceRefs = diff.changes.flatMap((c) => c.evidence) + const provenanceRefs = diff.changes.flatMap((c) => c.evidence) return { id: `thesis-${diff.symbol}`, symbol: diff.symbol, @@ -186,7 +186,7 @@ function thesisItems(inputs: BriefInputs): BriefItem[] { source: 'Thesis', severity: diff.thesisImpact?.direction === 'invalidated' ? 'critical' : 'warning', payload: { diffId: diff.id, direction: diff.thesisImpact?.direction }, - ...(evidenceRefs.length > 0 ? { evidenceRefs } : {}), + ...(provenanceRefs.length > 0 ? { provenanceRefs } : {}), contextVersion: `diff-${diff.id}`, } }) @@ -209,9 +209,9 @@ function automationItems(inputs: BriefInputs): BriefItem[] { .filter((run) => run.materialChanges > 0 || run.notified) .map((run) => { const isMaterial = run.materialChanges > 0 - const evidenceRefs: string[] = [] + const provenanceRefs: string[] = [] if (isMaterial) { - evidenceRefs.push(`automation-run:${run.id}:material`) + provenanceRefs.push(`automation-run:${run.id}:material`) } return { id: `automation-${run.id}`, @@ -228,7 +228,7 @@ function automationItems(inputs: BriefInputs): BriefItem[] { analyzed: run.analyzed, failures: run.failures, }, - ...(evidenceRefs.length > 0 ? { evidenceRefs } : {}), + ...(provenanceRefs.length > 0 ? { provenanceRefs } : {}), ...(run.id ? { contextVersion: `run-${run.id}` } : {}), } })