File size: 4,133 Bytes
7f6dd09
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import axios from "axios";
import config from "../config.js";
import supabase from "../lib/supabase.js";
import { getJob, updateJobStatus } from "./jobService.js";

const INJECTION_KEYWORDS = [
  "ignore",
  "override",
  "set score",
  "approve this",
  "reject this",
  "system prompt",
  "you are now",
  "disregard",
  "forget previous",
  "new instructions",
  "jailbreak",
  "ignore all",
  "ignore previous",
];

function computeDecision(score) {
  if (score >= 75) return "APPROVE";
  if (score >= 55) return "CONDITIONAL";
  return "REJECT";
}

async function processOfficerNotes(jobId, rawNotes, officerId) {
  // Step A β€” Sanitize
  let sanitizedNotes = rawNotes.trim();
  if (sanitizedNotes.length > 2000) {
    throw new Error("Notes too long. Maximum 2000 characters.");
  }
  // Strip any HTML tags
  sanitizedNotes = sanitizedNotes.replace(/<[^>]*>/g, "");

  // Step B β€” Injection detection
  const lower = sanitizedNotes.toLowerCase();
  const injectionFound = INJECTION_KEYWORDS.some((kw) => lower.includes(kw));

  if (injectionFound) {
    // Log to audit_log
    await supabase.from("audit_log").insert({
      job_id: jobId,
      event_type: "INJECTION_ATTEMPT",
      event_data: { officer_id: officerId, raw_text: rawNotes },
    });

    // Log to officer_notes
    await supabase.from("officer_notes").insert({
      job_id: jobId,
      raw_text: rawNotes,
      injection_detected: true,
      score_delta: -50,
      officer_id: officerId,
    });

    // Fetch current score so we can report before/after
    const job = await getJob(jobId);
    const currentScore = job?.result?.score_breakdown?.final_score ?? 0;
    const currentDecision = job?.result?.score_breakdown?.decision ?? "REJECT";
    const newScore = Math.max(0, currentScore - 50);

    return {
      injection_detected: true,
      injection_message:
        "Prompt injection attempt detected. Penalty: -50 points. Incident logged to compliance.",
      score_before: currentScore,
      score_delta: -50,
      score_after: newScore,
      decision_before: currentDecision,
      decision_after: computeDecision(newScore),
      adjustments: [],
      interpretation: "Security violation detected.",
    };
  }

  // Step C β€” Fetch current score from job
  const job = await getJob(jobId);
  if (!job) throw new Error("Job not found");
  const currentScore = job.result?.score_breakdown?.final_score ?? 0;
  const currentDecision = job.result?.score_breakdown?.decision ?? "REJECT";

  // Step D β€” Call Python AI service
  const aiRes = await axios.post(
    `${config.aiServiceUrl}/api/v1/cam/officer-notes`,
    {
      job_id: jobId,
      notes_text: sanitizedNotes,
      officer_id: officerId,
    },
    { timeout: 60000 },
  );
  const aiResponse = aiRes.data;
  const scoreDelta = aiResponse.score_delta ?? 0;

  // Step E β€” Update job result in Supabase
  const newScore = Math.max(0, Math.min(100, currentScore + scoreDelta));
  const newDecision = computeDecision(newScore);

  const updatedResult = {
    ...job.result,
    score_breakdown: {
      ...job.result.score_breakdown,
      final_score: newScore,
      decision: newDecision,
    },
    officer_notes_applied: true,
    officer_score_delta: scoreDelta,
  };

  await updateJobStatus(jobId, "completed", updatedResult);

  // Step F β€” Insert records
  await supabase.from("officer_notes").insert({
    job_id: jobId,
    raw_text: sanitizedNotes,
    injection_detected: false,
    score_delta: scoreDelta,
    officer_id: officerId,
  });

  await supabase.from("audit_log").insert({
    job_id: jobId,
    event_type: "OFFICER_NOTES_SUBMITTED",
    event_data: {
      officer_id: officerId,
      score_delta: scoreDelta,
      injection_detected: false,
    },
  });

  // Step G β€” Return response
  return {
    injection_detected: false,
    score_before: currentScore,
    score_delta: scoreDelta,
    score_after: newScore,
    decision_before: currentDecision,
    decision_after: newDecision,
    adjustments: aiResponse.adjustments || [],
    interpretation: aiResponse.interpretation || "",
  };
}

export { processOfficerNotes };