-
-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathbehavioralVerifierHandler.ts
More file actions
117 lines (105 loc) · 3.99 KB
/
Copy pathbehavioralVerifierHandler.ts
File metadata and controls
117 lines (105 loc) · 3.99 KB
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
/**
* Behavioral Verifier — thin client to Synalux portal API.
*
* Calls POST /api/v1/prism/verify-behavior with the file path
* and returns a domain-specific scenario the agent must answer
* before editing the file.
*
* FAIL-CLOSED: if the portal is unreachable, returns a generic
* verification challenge rather than skipping verification.
*/
import { PRISM_SYNALUX_BASE_URL, SYNALUX_CONFIGURED } from "../config.js";
import { getSynaluxJwt } from "../utils/synaluxJwt.js";
import { debugLog } from "../utils/logger.js";
interface VerifyBehaviorArgs {
file_path: string;
change_summary: string;
project?: string;
workspace_id?: string;
}
interface VerifyBehaviorResult {
requires_verification: boolean;
domain?: string;
scenario?: string;
rules?: string[];
reason?: string;
}
const FALLBACK_SCENARIO = [
"⚠️ BEHAVIORAL VERIFICATION (OFFLINE MODE)",
"",
"Portal unreachable — using generic verification.",
"Before editing this file, answer ALL of these:",
"",
"1. What does the end user experience BEFORE vs AFTER this change?",
"2. Does this endpoint verify the caller owns/belongs-to the resource?",
"3. Can a user from workspace A access workspace B's data by guessing an ID?",
"4. If this is a revert, was the original change actually correct?",
"",
"Answer concretely. If you cannot, READ THE FILE FIRST.",
].join("\n");
/**
* MCP tool entrypoint. Every tool handler MUST return a CallToolResult
* object ({ content: [{ type: "text", text }] }) — returning a bare string
* makes the MCP SDK reject the result ("expected object, received string",
* -32602). See the dispatch contract in server.ts (result.content usage).
*/
export async function verifyBehaviorHandler(
args: VerifyBehaviorArgs,
): Promise<{ content: { type: "text"; text: string }[] }> {
return { content: [{ type: "text", text: await buildScenarioText(args) }] };
}
async function buildScenarioText(
args: VerifyBehaviorArgs,
): Promise<string> {
if (!SYNALUX_CONFIGURED || !PRISM_SYNALUX_BASE_URL) {
return FALLBACK_SCENARIO;
}
const jwt = await getSynaluxJwt();
if (!jwt) {
console.error("[verify-behavior] ⚠️ JWT unavailable — fail-closed with generic scenario");
return FALLBACK_SCENARIO;
}
try {
const url = `${PRISM_SYNALUX_BASE_URL}/api/v1/prism/verify-behavior`;
const res = await fetch(url, {
method: "POST",
headers: {
"Authorization": `Bearer ${jwt}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
file_path: args.file_path,
change_summary: args.change_summary,
workspace_id: args.workspace_id,
}),
signal: AbortSignal.timeout(5_000),
});
if (!res.ok) {
console.error(`[verify-behavior] ⚠️ portal returned ${res.status} — fail-closed. URL: ${url}`);
return FALLBACK_SCENARIO;
}
const data = (await res.json()) as VerifyBehaviorResult;
return formatResult(data);
} catch (err) {
console.error(`[verify-behavior] ⚠️ VERIFICATION FAILED: ${(err as Error).message} — using generic fallback`);
return FALLBACK_SCENARIO;
}
}
function formatResult(data: VerifyBehaviorResult): string {
if (!data.requires_verification) {
return JSON.stringify({ requires_verification: false, reason: data.reason || "non-behavioral file" });
}
return [
`⚠️ BEHAVIORAL VERIFICATION REQUIRED`,
`Domain: ${data.domain}`,
``,
`Before making this edit, answer this scenario:`,
``,
data.scenario || "(generic) Describe what the end user experiences BEFORE vs AFTER this change.",
``,
`RULES:`,
...(data.rules || []).map((r, i) => `${i + 1}. ${r}`),
``,
`Answer the scenario in your next message before proceeding with the edit.`,
].join("\n");
}