File size: 4,247 Bytes
46d5e00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// Versioned policy (docs/ARCHITECTURE.md §3.5): first matching rule wins. Pure function; no I/O.

import type { Evidence } from './jev.ts';
import type { Path, Stage } from './types.ts';

export const POLICY_VERSION = 'p2';
export const TAU = 0.97;
/** Jev "same script" probability needed to treat a reworded call as a known campaign. */
export const SEMANTIC_MIN = 0.9;

export type Action = 'deliver' | 'quarantine' | 'ignore';

// Pushover priorities: -1 silent, 0 normal, 1 high, 2 emergency (repeats until acknowledged).
export interface Decision {
  rule: string;
  action: Action;
  priority: -1 | 0 | 1 | 2;
  tag: string;
}

type CampaignLabel = { label: 'junk' | 'legit' | null; labelSource: 'you' | 'auto' | null };

export interface PolicyInput {
  path: Path;
  transcript: string;
  trusted: boolean;
  /** Near-verbatim match (word-shingle Jaccard ≥ 0.6). */
  campaign: CampaignLabel | null;
  /** Reworded match: Jev's "which known script?" answer, p = probability of the chosen campaign. */
  semantic?: (CampaignLabel & { p: number }) | null;
  evidence: Evidence | null; // null when Jev failed
  pJunk: number | null;
  tau?: number;
}

const FRAUD_TOPIC = /\b(fraud|unusual|suspicious|unauthori[sz]ed|charge|transaction|purchase|security)\b/i;

export function decide(input: PolicyInput): Decision {
  const { path, transcript, evidence: e } = input;
  const tau = input.tau ?? TAU;
  const semanticJunk = !!input.semantic && input.semantic.label === 'junk' && input.semantic.p >= SEMANTIC_MIN;

  if (path === 'robot' && transcript.trim().length < 5) return { rule: 'dead-air', action: 'ignore', priority: -1, tag: 'Dead air' };
  if (path === 'human') return { rule: '1-human', action: 'deliver', priority: 0, tag: 'Voicemail' };
  if (input.trusted) return { rule: '2-trusted', action: 'deliver', priority: 0, tag: 'Trusted caller' };
  // Level 1: near-verbatim repeat of a script you labeled Scam outranks every model signal.
  if (input.campaign?.label === 'junk' && input.campaign.labelSource === 'you')
    return { rule: '3-your-scam-campaign', action: 'quarantine', priority: -1, tag: 'Known scam script' };
  if (!e || input.pJunk === null) return { rule: '0-unanalyzed', action: 'deliver', priority: -1, tag: 'Unanalyzed robot message' };

  if (e.protected >= 0.3) {
    // Level 2: a reworded match can warn about a bank/pharmacy/school message, never hold it back.
    const impersonation =
      ((e.pressure >= 0.7 || e.agent >= 0.7) && e.specific <= 0.2 && e.official <= 0.2) ||
      // Real notices also say "this is an informational/official notice"; self-assertion only counts without real specifics.
      (e.selfAssert >= 0.9 && e.specific < 0.5 && e.official < 0.5) ||
      semanticJunk;
    if (impersonation)
      return { rule: '4-protected-impersonation', action: 'deliver', priority: 0, tag: "⚠️ Likely impersonation — don't press anything; call the number on your card" };
    if (e.specific >= 0.7 || e.official >= 0.7)
      return FRAUD_TOPIC.test(transcript)
        ? { rule: '4-protected-fraud-alert', action: 'deliver', priority: 2, tag: '🚨 Fraud/security alert — verify via the number on your card' }
        : { rule: '4-protected-genuine', action: 'deliver', priority: 1, tag: 'Account/care/school alert' };
    return { rule: '4-protected', action: 'deliver', priority: 0, tag: 'Bank/pharmacy/school message' };
  }

  if (input.campaign?.label === 'junk' || semanticJunk)
    return { rule: '5-junk-campaign', action: 'quarantine', priority: -1, tag: 'Known robocall script' };
  if (input.pJunk >= tau && e.protected <= 0.1 && e.specific <= 0.1 && e.official <= 0.1)
    return { rule: '6-junk-score', action: 'quarantine', priority: -1, tag: 'Junk robocall' };
  return { rule: '7-review', action: 'deliver', priority: -1, tag: 'Robot message — review' };
}

/** Which quarantine rules the current rollout stage is allowed to enforce. */
export function enforce(d: Decision, stage: Stage): Action {
  if (d.action !== 'quarantine') return d.action;
  if (d.rule.startsWith('3-') || d.rule.startsWith('5-')) return stage === 'S0' ? 'deliver' : 'quarantine';
  if (d.rule.startsWith('6-')) return stage === 'S2' ? 'quarantine' : 'deliver';
  return 'deliver';
}