feat(23-05): wire gated parse->classify->acknowledge chain into webhook
- triggerPhishingDetection now captures groupReportIntoCampaign's result and, when a campaignId exists, calls new runGatedPhishingStages - runGatedPhishingStages reads the per-company automation gate and conditionally runs parseAndStoreMessage, classifyCampaign, and (only for USER_AWARENESS verdicts) generateAndPostAcknowledgment - each stage isolated in its own try/catch (T-23-09); detection + grouping remain unconditional (D-07); auto_report never posts any other action (D-04, T-23-08)
This commit is contained in:
parent
e0f22f27c9
commit
e1193bf476
1 changed files with 73 additions and 1 deletions
|
|
@ -16,6 +16,10 @@ import '../services/workflow-steps'; // Register all workflow step executors
|
|||
import { WorkflowEvent, TicketData } from '../types/workflow';
|
||||
import { detectPhishingTicket, DetectableTicket } from './phishing-detector';
|
||||
import { groupReportIntoCampaign } from './campaign-grouping-service';
|
||||
import { getCompanyAutomationGate } from './phishing-automation-gate';
|
||||
import { parseAndStoreMessage } from './phishing-eml-service';
|
||||
import { classifyCampaign, Verdict } from './campaign-classifier';
|
||||
import { generateAndPostAcknowledgment } from './triage-note-service';
|
||||
|
||||
export class WebhookService {
|
||||
private _autotaskClient: AutotaskClient | null = null;
|
||||
|
|
@ -490,7 +494,75 @@ export class WebhookService {
|
|||
const detection = await detectPhishingTicket(ticket);
|
||||
// D-01/D-08: automatic path short-circuits if already grouped.
|
||||
if (detection.flagged && detection.reportId) {
|
||||
await groupReportIntoCampaign(detection.reportId, { skipIfAlreadyGrouped: true });
|
||||
const grouped = await groupReportIntoCampaign(detection.reportId, { skipIfAlreadyGrouped: true });
|
||||
if (grouped?.campaignId) {
|
||||
await this.runGatedPhishingStages({
|
||||
campaignId: grouped.campaignId,
|
||||
companyId: r.company_id,
|
||||
reportId: detection.reportId,
|
||||
ticketId: Number(r.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 23 D-04/D-06/D-07: runs the opted-in parse -> classify -> report
|
||||
* chain for a company after detection + grouping have already run
|
||||
* unconditionally. Each stage is independently gated by
|
||||
* `phishing_automation_gate` and isolated in its own try/catch so a
|
||||
* failure in one stage never blocks the webhook response or aborts a
|
||||
* later stage (T-23-09).
|
||||
*
|
||||
* auto_report auto-posts EXCLUSIVELY the acknowledge_user thank-you note,
|
||||
* and only when the campaign's current verdict is USER_AWARENESS (D-04).
|
||||
* Every other verdict/action remains proposed-only and manual-approval
|
||||
* gated — this method never calls approve/remediate/block/purge/warn_user.
|
||||
*/
|
||||
private async runGatedPhishingStages(input: {
|
||||
campaignId: string;
|
||||
companyId: number | null;
|
||||
reportId: string;
|
||||
ticketId: number;
|
||||
}): Promise<void> {
|
||||
const { campaignId, companyId, reportId, ticketId } = input;
|
||||
const gate = await getCompanyAutomationGate(companyId);
|
||||
|
||||
if (gate.autoParse) {
|
||||
try {
|
||||
await parseAndStoreMessage({ reportId, ticketId });
|
||||
} catch (err) {
|
||||
console.error('[WEBHOOK] auto_parse stage error', err);
|
||||
}
|
||||
}
|
||||
|
||||
let verdict: Verdict | null = null;
|
||||
|
||||
if (gate.autoClassify) {
|
||||
try {
|
||||
const result = await classifyCampaign(campaignId);
|
||||
verdict = result.verdict;
|
||||
} catch (err) {
|
||||
console.error('[WEBHOOK] auto_classify stage error', err);
|
||||
}
|
||||
}
|
||||
|
||||
if (gate.autoReport) {
|
||||
try {
|
||||
if (verdict === null) {
|
||||
const latest = await postgresClient.query<{ verdict: string | null }>(
|
||||
`SELECT verdict FROM classifications WHERE campaign_id = $1 ORDER BY created_at DESC LIMIT 1`,
|
||||
[campaignId]
|
||||
);
|
||||
verdict = (latest.rows[0]?.verdict as Verdict | undefined) ?? null;
|
||||
}
|
||||
|
||||
if (verdict === 'USER_AWARENESS') {
|
||||
await generateAndPostAcknowledgment(campaignId);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[WEBHOOK] auto_report stage error', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue