truthguard-ai 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of truthguard-ai might be problematic. Click here for more details.
- package/dist-npm/Claims/index.d.ts +73 -0
- package/dist-npm/Claims/index.d.ts.map +1 -0
- package/dist-npm/Claims/index.js +1669 -0
- package/dist-npm/Claims/index.js.map +1 -0
- package/dist-npm/Config/index.d.ts +41 -0
- package/dist-npm/Config/index.d.ts.map +1 -0
- package/dist-npm/Config/index.js +129 -0
- package/dist-npm/Config/index.js.map +1 -0
- package/dist-npm/Grounding/index.d.ts +40 -0
- package/dist-npm/Grounding/index.d.ts.map +1 -0
- package/dist-npm/Grounding/index.js +1433 -0
- package/dist-npm/Grounding/index.js.map +1 -0
- package/dist-npm/L2/index.d.ts +93 -0
- package/dist-npm/L2/index.d.ts.map +1 -0
- package/dist-npm/L2/index.js +1773 -0
- package/dist-npm/L2/index.js.map +1 -0
- package/dist-npm/Matchers/index.d.ts +101 -0
- package/dist-npm/Matchers/index.d.ts.map +1 -0
- package/dist-npm/Matchers/index.js +690 -0
- package/dist-npm/Matchers/index.js.map +1 -0
- package/dist-npm/Mode/index.d.ts +87 -0
- package/dist-npm/Mode/index.d.ts.map +1 -0
- package/dist-npm/Mode/index.js +117 -0
- package/dist-npm/Mode/index.js.map +1 -0
- package/dist-npm/Policy/index.d.ts +89 -0
- package/dist-npm/Policy/index.d.ts.map +1 -0
- package/dist-npm/Policy/index.js +143 -0
- package/dist-npm/Policy/index.js.map +1 -0
- package/dist-npm/Registry/index.d.ts +93 -0
- package/dist-npm/Registry/index.d.ts.map +1 -0
- package/dist-npm/Registry/index.js +818 -0
- package/dist-npm/Registry/index.js.map +1 -0
- package/dist-npm/Rules/index.d.ts +587 -0
- package/dist-npm/Rules/index.d.ts.map +1 -0
- package/dist-npm/Rules/index.js +6236 -0
- package/dist-npm/Rules/index.js.map +1 -0
- package/dist-npm/Rules/intents.d.ts +22 -0
- package/dist-npm/Rules/intents.d.ts.map +1 -0
- package/dist-npm/Rules/intents.js +242 -0
- package/dist-npm/Rules/intents.js.map +1 -0
- package/dist-npm/TraceReadiness/index.d.ts +42 -0
- package/dist-npm/TraceReadiness/index.d.ts.map +1 -0
- package/dist-npm/TraceReadiness/index.js +169 -0
- package/dist-npm/TraceReadiness/index.js.map +1 -0
- package/dist-npm/i18n/index.d.ts +44 -0
- package/dist-npm/i18n/index.d.ts.map +1 -0
- package/dist-npm/i18n/index.js +124 -0
- package/dist-npm/i18n/index.js.map +1 -0
- package/package.json +5 -17
- package/dist/cli/index.d.ts +0 -15
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -807
- package/dist/cli/index.js.map +0 -1
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TruthGuard Failure Registry — Machine-Readable Source of Truth
|
|
4
|
+
*
|
|
5
|
+
* Central catalog of all failure types with metadata:
|
|
6
|
+
* severity, category, blockability, suppression rules, version status.
|
|
7
|
+
*
|
|
8
|
+
* This is the SINGLE source of truth — docs and UI derive from this.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.FAILURE_REGISTRY = void 0;
|
|
12
|
+
exports.getRegistryEntry = getRegistryEntry;
|
|
13
|
+
exports.getDefaultSeverity = getDefaultSeverity;
|
|
14
|
+
exports.getDefaultAction = getDefaultAction;
|
|
15
|
+
exports.getByCategory = getByCategory;
|
|
16
|
+
exports.computeSeverity = computeSeverity;
|
|
17
|
+
exports.getSuppressedTypes = getSuppressedTypes;
|
|
18
|
+
exports.compareSeverity = compareSeverity;
|
|
19
|
+
exports.getRepairOrder = getRepairOrder;
|
|
20
|
+
exports.getTierMaxAction = getTierMaxAction;
|
|
21
|
+
exports.capActionToTier = capActionToTier;
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// The Registry
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
exports.FAILURE_REGISTRY = [
|
|
26
|
+
// ── Grounding ──────────────────────────────────────────────
|
|
27
|
+
{
|
|
28
|
+
type: 'grounding.empty_fabrication',
|
|
29
|
+
label: 'Empty Fabrication',
|
|
30
|
+
category: 'grounding',
|
|
31
|
+
defaultSeverity: 'critical',
|
|
32
|
+
defaultAction: 'block',
|
|
33
|
+
status: 'stable',
|
|
34
|
+
description: 'Agent produced claims with no tool data whatsoever — pure hallucination.',
|
|
35
|
+
suppresses: [
|
|
36
|
+
'grounding.data_ignored',
|
|
37
|
+
'grounding.irrelevant_context',
|
|
38
|
+
'reasoning.overconfident_language',
|
|
39
|
+
],
|
|
40
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
41
|
+
tier: 'A',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'grounding.no_tool_call',
|
|
45
|
+
label: 'No Tool Call',
|
|
46
|
+
category: 'grounding',
|
|
47
|
+
defaultSeverity: 'high',
|
|
48
|
+
defaultAction: 'block',
|
|
49
|
+
status: 'stable',
|
|
50
|
+
description: 'Agent answered a factual question without calling any tools.',
|
|
51
|
+
suppresses: [
|
|
52
|
+
'grounding.irrelevant_context',
|
|
53
|
+
'reasoning.overconfident_language',
|
|
54
|
+
'grounding.unverified_value',
|
|
55
|
+
],
|
|
56
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
57
|
+
tier: 'A',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'grounding.unverified_value',
|
|
61
|
+
label: 'Unverified Value',
|
|
62
|
+
category: 'grounding',
|
|
63
|
+
defaultSeverity: 'medium',
|
|
64
|
+
defaultAction: 'warn',
|
|
65
|
+
status: 'stable',
|
|
66
|
+
description: 'Tools were called but specific values in the response could not be verified in tool outputs.',
|
|
67
|
+
suppresses: [],
|
|
68
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
69
|
+
tier: 'A',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'grounding.math_error',
|
|
73
|
+
label: 'Math Error',
|
|
74
|
+
category: 'grounding',
|
|
75
|
+
defaultSeverity: 'high',
|
|
76
|
+
defaultAction: 'block',
|
|
77
|
+
status: 'stable',
|
|
78
|
+
description: 'Numeric claim deviates significantly from tool output values, or inline arithmetic/unit conversion is wrong.',
|
|
79
|
+
suppresses: [],
|
|
80
|
+
industries: ['hr', 'finance', 'healthcare', 'logistics'],
|
|
81
|
+
tier: 'A',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'grounding.data_ignored',
|
|
85
|
+
label: 'Data Ignored',
|
|
86
|
+
category: 'grounding',
|
|
87
|
+
defaultSeverity: 'high',
|
|
88
|
+
defaultAction: 'block',
|
|
89
|
+
status: 'stable',
|
|
90
|
+
description: 'Tool returned data but agent used a different, ungrounded value, or omitted significant data without instruction to summarize.',
|
|
91
|
+
suppresses: ['reasoning.overconfident_language'],
|
|
92
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
93
|
+
tier: 'A',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: 'grounding.wrong_query',
|
|
97
|
+
label: 'Wrong Query',
|
|
98
|
+
category: 'grounding',
|
|
99
|
+
defaultSeverity: 'high',
|
|
100
|
+
defaultAction: 'warn',
|
|
101
|
+
status: 'stable',
|
|
102
|
+
description: 'Tool was called with parameters that don\'t match user\'s question.',
|
|
103
|
+
suppresses: ['grounding.irrelevant_context', 'reasoning.scope_mismatch'],
|
|
104
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
105
|
+
tier: 'A',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'grounding.incomplete_response',
|
|
109
|
+
label: 'Incomplete Response',
|
|
110
|
+
category: 'grounding',
|
|
111
|
+
defaultSeverity: 'medium',
|
|
112
|
+
defaultAction: 'warn',
|
|
113
|
+
status: 'stable',
|
|
114
|
+
description: 'Tools returned data but agent produced empty or trivial response.',
|
|
115
|
+
suppresses: ['grounding.question_not_answered'],
|
|
116
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
117
|
+
tier: 'A',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'grounding.contradictory_claims',
|
|
121
|
+
label: 'Contradictory Claims',
|
|
122
|
+
category: 'grounding',
|
|
123
|
+
defaultSeverity: 'high',
|
|
124
|
+
defaultAction: 'block',
|
|
125
|
+
status: 'stable',
|
|
126
|
+
description: 'Agent made multiple claims that contradict each other.',
|
|
127
|
+
suppresses: [],
|
|
128
|
+
industries: ['hr', 'finance', 'healthcare', 'legal'],
|
|
129
|
+
tier: 'C',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'grounding.tool_error_ignored',
|
|
133
|
+
label: 'Tool Error Ignored',
|
|
134
|
+
category: 'grounding',
|
|
135
|
+
defaultSeverity: 'high',
|
|
136
|
+
defaultAction: 'block',
|
|
137
|
+
status: 'stable',
|
|
138
|
+
description: 'Tool returned an error but agent proceeded as if it succeeded.',
|
|
139
|
+
suppresses: [
|
|
140
|
+
'grounding.data_ignored',
|
|
141
|
+
'grounding.irrelevant_context',
|
|
142
|
+
'reasoning.overconfident_language',
|
|
143
|
+
],
|
|
144
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
145
|
+
tier: 'A',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'grounding.excessive_tool_calls',
|
|
149
|
+
label: 'Excessive Tool Calls',
|
|
150
|
+
category: 'grounding',
|
|
151
|
+
defaultSeverity: 'medium',
|
|
152
|
+
defaultAction: 'warn',
|
|
153
|
+
status: 'stable',
|
|
154
|
+
description: 'Agent called the same tool repeatedly, suggesting a loop or retry storm.',
|
|
155
|
+
suppresses: [],
|
|
156
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
157
|
+
tier: 'A',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: 'grounding.stale_knowledge',
|
|
161
|
+
label: 'Stale Knowledge',
|
|
162
|
+
category: 'grounding',
|
|
163
|
+
defaultSeverity: 'medium',
|
|
164
|
+
defaultAction: 'warn',
|
|
165
|
+
status: 'stable',
|
|
166
|
+
description: 'Agent used outdated information when fresher tool data was available.',
|
|
167
|
+
suppresses: [],
|
|
168
|
+
industries: ['hr', 'finance', 'healthcare', 'legal'],
|
|
169
|
+
tier: 'B',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
type: 'grounding.irrelevant_context',
|
|
173
|
+
label: 'Irrelevant Context',
|
|
174
|
+
category: 'grounding',
|
|
175
|
+
defaultSeverity: 'medium',
|
|
176
|
+
defaultAction: 'warn',
|
|
177
|
+
status: 'stable',
|
|
178
|
+
description: 'Tool returned data unrelated to the user\'s question.',
|
|
179
|
+
suppresses: [],
|
|
180
|
+
industries: ['healthcare', 'logistics'],
|
|
181
|
+
tier: 'C',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: 'grounding.hallucinated_entity',
|
|
185
|
+
label: 'Hallucinated Entity',
|
|
186
|
+
category: 'grounding',
|
|
187
|
+
defaultSeverity: 'high',
|
|
188
|
+
defaultAction: 'block',
|
|
189
|
+
status: 'stable',
|
|
190
|
+
description: 'Agent mentioned a person or entity name not found in any tool output or system prompt.',
|
|
191
|
+
suppresses: [],
|
|
192
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
193
|
+
tier: 'A',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: 'grounding.question_not_answered',
|
|
197
|
+
label: 'Question Not Answered',
|
|
198
|
+
category: 'grounding',
|
|
199
|
+
defaultSeverity: 'high',
|
|
200
|
+
defaultAction: 'warn',
|
|
201
|
+
status: 'stable',
|
|
202
|
+
description: 'Agent returned correct data but did not perform the operation the user asked for, or only addressed some parts of a multi-part question.',
|
|
203
|
+
suppresses: [],
|
|
204
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
205
|
+
tier: 'A',
|
|
206
|
+
},
|
|
207
|
+
// ── Reasoning ──────────────────────────────────────────────
|
|
208
|
+
{
|
|
209
|
+
type: 'reasoning.scope_mismatch',
|
|
210
|
+
label: 'Scope Mismatch',
|
|
211
|
+
category: 'reasoning',
|
|
212
|
+
defaultSeverity: 'high',
|
|
213
|
+
defaultAction: 'warn',
|
|
214
|
+
status: 'stable',
|
|
215
|
+
description: 'Agent applied narrow/filtered data to answer a broad or different scope question.',
|
|
216
|
+
suppresses: [],
|
|
217
|
+
industries: ['hr', 'finance', 'logistics'],
|
|
218
|
+
tier: 'B',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
type: 'reasoning.overconfident_language',
|
|
222
|
+
label: 'Overconfident Language',
|
|
223
|
+
category: 'reasoning',
|
|
224
|
+
defaultSeverity: 'low',
|
|
225
|
+
defaultAction: 'observe',
|
|
226
|
+
status: 'stable',
|
|
227
|
+
description: 'Agent used certainty language ("exactly", "confirmed") on approximate/unverifiable data.',
|
|
228
|
+
suppresses: [],
|
|
229
|
+
industries: ['finance', 'healthcare', 'legal'],
|
|
230
|
+
tier: 'B',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
type: 'reasoning.language_mismatch',
|
|
234
|
+
label: 'Language Mismatch',
|
|
235
|
+
category: 'reasoning',
|
|
236
|
+
defaultSeverity: 'medium',
|
|
237
|
+
defaultAction: 'warn',
|
|
238
|
+
status: 'stable',
|
|
239
|
+
description: 'Agent responded in a different language than the user\'s input (e.g., English reply to Serbian question).',
|
|
240
|
+
suppresses: [],
|
|
241
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
242
|
+
tier: 'B',
|
|
243
|
+
},
|
|
244
|
+
// ── Orchestration ──────────────────────────────────────────
|
|
245
|
+
{
|
|
246
|
+
type: 'orchestration.malformed_tool_input',
|
|
247
|
+
label: 'Malformed Tool Input',
|
|
248
|
+
category: 'orchestration',
|
|
249
|
+
defaultSeverity: 'high',
|
|
250
|
+
defaultAction: 'block',
|
|
251
|
+
status: 'stable',
|
|
252
|
+
description: 'Tool was called with empty or structurally invalid input, causing an API validation error.',
|
|
253
|
+
suppresses: [
|
|
254
|
+
'grounding.tool_error_ignored',
|
|
255
|
+
'grounding.excessive_tool_calls',
|
|
256
|
+
],
|
|
257
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
258
|
+
tier: 'A',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
type: 'orchestration.raw_output_leak',
|
|
262
|
+
label: 'Raw Output Leak',
|
|
263
|
+
category: 'orchestration',
|
|
264
|
+
defaultSeverity: 'critical',
|
|
265
|
+
defaultAction: 'block',
|
|
266
|
+
status: 'stable',
|
|
267
|
+
description: 'Final response contains raw tool call markup (XML/JSON) instead of a user-facing answer.',
|
|
268
|
+
suppresses: [
|
|
269
|
+
'grounding.incomplete_response',
|
|
270
|
+
'grounding.hallucinated_entity',
|
|
271
|
+
],
|
|
272
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
273
|
+
tier: 'A',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
type: 'orchestration.intermediate_response_leak',
|
|
277
|
+
label: 'Intermediate Response Leak',
|
|
278
|
+
category: 'orchestration',
|
|
279
|
+
defaultSeverity: 'high',
|
|
280
|
+
defaultAction: 'block',
|
|
281
|
+
status: 'stable',
|
|
282
|
+
description: 'Final response contains intermediate LLM reasoning or planning text instead of a synthesized answer.',
|
|
283
|
+
suppresses: [
|
|
284
|
+
'grounding.incomplete_response',
|
|
285
|
+
],
|
|
286
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
287
|
+
tier: 'A',
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
type: 'orchestration.token_limit_truncation',
|
|
291
|
+
label: 'Token Limit Truncation',
|
|
292
|
+
category: 'orchestration',
|
|
293
|
+
defaultSeverity: 'high',
|
|
294
|
+
defaultAction: 'warn',
|
|
295
|
+
status: 'stable',
|
|
296
|
+
description: 'Response was truncated because the model hit the token limit (finish_reason: length).',
|
|
297
|
+
suppresses: [
|
|
298
|
+
'grounding.incomplete_response',
|
|
299
|
+
'grounding.question_not_answered',
|
|
300
|
+
],
|
|
301
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
302
|
+
tier: 'A',
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
type: 'orchestration.rate_limit_degradation',
|
|
306
|
+
label: 'Rate Limit Degradation',
|
|
307
|
+
category: 'orchestration',
|
|
308
|
+
defaultSeverity: 'high',
|
|
309
|
+
defaultAction: 'warn',
|
|
310
|
+
status: 'stable',
|
|
311
|
+
description: 'API rate limit (429) was hit during execution, potentially degrading response quality.',
|
|
312
|
+
suppresses: [
|
|
313
|
+
'grounding.tool_error_ignored',
|
|
314
|
+
'grounding.incomplete_response',
|
|
315
|
+
],
|
|
316
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
317
|
+
tier: 'A',
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: 'orchestration.quota_exhaustion',
|
|
321
|
+
label: 'Quota Exhaustion',
|
|
322
|
+
category: 'orchestration',
|
|
323
|
+
defaultSeverity: 'critical',
|
|
324
|
+
defaultAction: 'block',
|
|
325
|
+
status: 'stable',
|
|
326
|
+
description: 'API quota or billing limit reached (402/quota errors), causing degraded or fabricated responses.',
|
|
327
|
+
suppresses: [
|
|
328
|
+
'grounding.empty_fabrication',
|
|
329
|
+
'grounding.tool_error_ignored',
|
|
330
|
+
'grounding.incomplete_response',
|
|
331
|
+
],
|
|
332
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
333
|
+
tier: 'A',
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
type: 'orchestration.model_fallback',
|
|
337
|
+
label: 'Model Fallback',
|
|
338
|
+
category: 'orchestration',
|
|
339
|
+
defaultSeverity: 'medium',
|
|
340
|
+
defaultAction: 'warn',
|
|
341
|
+
status: 'stable',
|
|
342
|
+
description: 'A different (potentially lower-tier) model was used than what was requested.',
|
|
343
|
+
suppresses: [],
|
|
344
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
345
|
+
tier: 'B',
|
|
346
|
+
},
|
|
347
|
+
// ── Safety ─────────────────────────────────────────────────
|
|
348
|
+
{
|
|
349
|
+
type: 'safety.prompt_leak',
|
|
350
|
+
label: 'Prompt Leak',
|
|
351
|
+
category: 'safety',
|
|
352
|
+
defaultSeverity: 'critical',
|
|
353
|
+
defaultAction: 'block',
|
|
354
|
+
status: 'stable',
|
|
355
|
+
description: 'System prompt content was exposed in the agent\'s response.',
|
|
356
|
+
suppresses: [],
|
|
357
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
358
|
+
tier: 'A',
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
type: 'safety.sensitive_data_exposure',
|
|
362
|
+
label: 'Sensitive Data Exposure',
|
|
363
|
+
category: 'safety',
|
|
364
|
+
defaultSeverity: 'critical',
|
|
365
|
+
defaultAction: 'block',
|
|
366
|
+
status: 'stable',
|
|
367
|
+
description: 'Response contains PII, credentials, or other sensitive data patterns.',
|
|
368
|
+
suppresses: [],
|
|
369
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
370
|
+
tier: 'A',
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
type: 'grounding.entity_value_swap',
|
|
374
|
+
label: 'Entity Value Swap',
|
|
375
|
+
category: 'grounding',
|
|
376
|
+
defaultSeverity: 'high',
|
|
377
|
+
defaultAction: 'block',
|
|
378
|
+
status: 'stable',
|
|
379
|
+
description: 'Values from different entities were swapped — entity A\'s data was attributed to entity B.',
|
|
380
|
+
suppresses: ['grounding.contradictory_claims'],
|
|
381
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
382
|
+
tier: 'A',
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
type: 'reasoning.instruction_violation',
|
|
386
|
+
label: 'Instruction Violation',
|
|
387
|
+
category: 'reasoning',
|
|
388
|
+
defaultSeverity: 'high',
|
|
389
|
+
defaultAction: 'warn',
|
|
390
|
+
status: 'stable',
|
|
391
|
+
description: 'Response violates an explicit system prompt directive (e.g., forbidden content, required format).',
|
|
392
|
+
suppresses: [],
|
|
393
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
394
|
+
tier: 'B',
|
|
395
|
+
},
|
|
396
|
+
// ── Instrumentation ────────────────────────────────────────────
|
|
397
|
+
{
|
|
398
|
+
type: 'instrumentation.duplicate_user_input',
|
|
399
|
+
label: 'Duplicate User Input',
|
|
400
|
+
category: 'instrumentation',
|
|
401
|
+
defaultSeverity: 'medium',
|
|
402
|
+
defaultAction: 'warn',
|
|
403
|
+
status: 'stable',
|
|
404
|
+
description: 'Identical user input appears consecutively — the agent framework is double-sending or replaying messages.',
|
|
405
|
+
suppresses: [],
|
|
406
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
407
|
+
tier: 'B',
|
|
408
|
+
},
|
|
409
|
+
// ── Orchestration: Tool Budget ─────────────────────────────────
|
|
410
|
+
{
|
|
411
|
+
type: 'orchestration.tool_budget_exhaustion',
|
|
412
|
+
label: 'Tool Budget Exhaustion',
|
|
413
|
+
category: 'orchestration',
|
|
414
|
+
defaultSeverity: 'medium',
|
|
415
|
+
defaultAction: 'warn',
|
|
416
|
+
status: 'stable',
|
|
417
|
+
description: 'The agent hit the platform tool-call limit before completing the task, forcing a premature answer based on incomplete data.',
|
|
418
|
+
suppresses: ['grounding.question_not_answered', 'grounding.incomplete_response', 'grounding.data_ignored', 'grounding.stale_cross_turn_reuse'],
|
|
419
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
420
|
+
tier: 'B',
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
type: 'orchestration.tool_period_mismatch',
|
|
424
|
+
label: 'Tool Period Mismatch',
|
|
425
|
+
category: 'orchestration',
|
|
426
|
+
defaultSeverity: 'high',
|
|
427
|
+
defaultAction: 'warn',
|
|
428
|
+
status: 'stable',
|
|
429
|
+
description: 'Tool was called with correct date parameters but returned data for a different time period.',
|
|
430
|
+
suppresses: ['grounding.data_ignored', 'grounding.wrong_query'],
|
|
431
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
432
|
+
tier: 'A',
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
type: 'grounding.stale_cross_turn_reuse',
|
|
436
|
+
label: 'Stale Cross-Turn Reuse',
|
|
437
|
+
category: 'grounding',
|
|
438
|
+
defaultSeverity: 'high',
|
|
439
|
+
defaultAction: 'warn',
|
|
440
|
+
status: 'stable',
|
|
441
|
+
description: 'Response uses numeric values from earlier conversation turns without making fresh tool calls in the current turn.',
|
|
442
|
+
suppresses: ['grounding.data_ignored', 'grounding.unverified_value'],
|
|
443
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
444
|
+
tier: 'A',
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
type: 'orchestration.tool_capability_gap',
|
|
448
|
+
label: 'Tool Capability Gap',
|
|
449
|
+
category: 'orchestration',
|
|
450
|
+
defaultSeverity: 'medium',
|
|
451
|
+
defaultAction: 'warn',
|
|
452
|
+
status: 'stable',
|
|
453
|
+
description: 'Tool returned empty results for a query the user expected to work, because the tool does not support that search type or filter.',
|
|
454
|
+
suppresses: ['grounding.incomplete_response', 'grounding.question_not_answered'],
|
|
455
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
456
|
+
tier: 'B',
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
type: 'grounding.period_label_mismatch',
|
|
460
|
+
label: 'Period Label Mismatch',
|
|
461
|
+
category: 'grounding',
|
|
462
|
+
defaultSeverity: 'high',
|
|
463
|
+
defaultAction: 'block',
|
|
464
|
+
status: 'stable',
|
|
465
|
+
description: 'Response labels data as belonging to a specific time period (e.g. Q1, March) but the actual dates in tool output correspond to a different period.',
|
|
466
|
+
suppresses: ['grounding.data_ignored', 'reasoning.overconfident_language'],
|
|
467
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
468
|
+
tier: 'A',
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
type: 'grounding.aggregation_scope_error',
|
|
472
|
+
label: 'Aggregation Scope Error',
|
|
473
|
+
category: 'grounding',
|
|
474
|
+
defaultSeverity: 'high',
|
|
475
|
+
defaultAction: 'warn',
|
|
476
|
+
status: 'stable',
|
|
477
|
+
description: 'An aggregation (sum, average, etc.) was computed over the wrong subset of data — typically a period mismatch between what the user requested and what was actually summed.',
|
|
478
|
+
suppresses: ['grounding.question_not_answered'],
|
|
479
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
480
|
+
tier: 'A',
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
type: 'grounding.missing_grouping',
|
|
484
|
+
label: 'Missing Grouping Dimension',
|
|
485
|
+
category: 'grounding',
|
|
486
|
+
defaultSeverity: 'high',
|
|
487
|
+
defaultAction: 'warn',
|
|
488
|
+
status: 'stable',
|
|
489
|
+
description: 'User asked for results grouped by a dimension (e.g. "by department", "per sector") but the response provides a single flat aggregate without the requested breakdown.',
|
|
490
|
+
suppresses: ['grounding.question_not_answered', 'grounding.data_ignored'],
|
|
491
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
492
|
+
tier: 'A',
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
type: 'reasoning.transformational_intent',
|
|
496
|
+
label: 'Unverified Transformation',
|
|
497
|
+
category: 'reasoning',
|
|
498
|
+
defaultSeverity: 'medium',
|
|
499
|
+
defaultAction: 'observe',
|
|
500
|
+
status: 'stable',
|
|
501
|
+
description: 'The user\'s question implies a business-logic transformation (net/gross, after-tax, discount, derived metric) that cannot be verified from raw tool output alone. The response may be using raw values instead of the transformed ones.',
|
|
502
|
+
suppresses: [],
|
|
503
|
+
industries: ['hr', 'finance', 'healthcare', 'legal'],
|
|
504
|
+
tier: 'B',
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
type: 'grounding.metric_mismatch',
|
|
508
|
+
label: 'Metric Type Mismatch',
|
|
509
|
+
category: 'grounding',
|
|
510
|
+
defaultSeverity: 'high',
|
|
511
|
+
defaultAction: 'block',
|
|
512
|
+
status: 'stable',
|
|
513
|
+
description: 'The user requested a specific aggregation type (e.g. average) but the response contains a different aggregation (e.g. sum) computed from the same data.',
|
|
514
|
+
suppresses: ['grounding.question_not_answered', 'grounding.math_error'],
|
|
515
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
516
|
+
tier: 'A',
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
type: 'reasoning.partial_period_risk',
|
|
520
|
+
label: 'Partial Period Data',
|
|
521
|
+
category: 'reasoning',
|
|
522
|
+
defaultSeverity: 'medium',
|
|
523
|
+
defaultAction: 'observe',
|
|
524
|
+
status: 'stable',
|
|
525
|
+
description: 'The user asked about a period that is currently ongoing (e.g. "this month", "this quarter", YTD). Data for this period is inherently incomplete and aggregations may change by period end.',
|
|
526
|
+
suppresses: [],
|
|
527
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
528
|
+
tier: 'B',
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
type: 'orchestration.ai_unavailable',
|
|
532
|
+
label: 'AI Service Unavailable',
|
|
533
|
+
category: 'orchestration',
|
|
534
|
+
defaultSeverity: 'high',
|
|
535
|
+
defaultAction: 'warn',
|
|
536
|
+
status: 'stable',
|
|
537
|
+
description: 'The AI service was unavailable (timeout, overloaded, rate-limited, insufficient credits) and returned an error instead of a response. The user received a fallback error message rather than an AI-generated answer.',
|
|
538
|
+
suppresses: ['grounding.empty_fabrication', 'grounding.no_tool_call', 'grounding.incomplete_response', 'grounding.question_not_answered'],
|
|
539
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
540
|
+
tier: 'A',
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
type: 'orchestration.answer_refusal',
|
|
544
|
+
label: 'Answer Refusal',
|
|
545
|
+
category: 'orchestration',
|
|
546
|
+
defaultSeverity: 'critical',
|
|
547
|
+
defaultAction: 'warn',
|
|
548
|
+
status: 'stable',
|
|
549
|
+
description: 'The AI explicitly refused or admitted it cannot answer the user question, despite having tools available. The response contains refusal phrases like "I cannot confirm", "I was unable to", or explicit failure markers.',
|
|
550
|
+
suppresses: ['grounding.question_not_answered'],
|
|
551
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
552
|
+
tier: 'A',
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
type: 'orchestration.tool_selection_error',
|
|
556
|
+
label: 'Wrong Tool Selection',
|
|
557
|
+
category: 'orchestration',
|
|
558
|
+
defaultSeverity: 'high',
|
|
559
|
+
defaultAction: 'warn',
|
|
560
|
+
status: 'stable',
|
|
561
|
+
description: 'The AI selected inappropriate tools for the task, repeatedly calling the same ineffective tool or ignoring better-suited alternatives. Often co-occurs with tool_budget_exhaustion.',
|
|
562
|
+
suppresses: [],
|
|
563
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
564
|
+
tier: 'A',
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
type: 'orchestration.tool_output_error',
|
|
568
|
+
label: 'Tool Output Error',
|
|
569
|
+
category: 'orchestration',
|
|
570
|
+
defaultSeverity: 'high',
|
|
571
|
+
defaultAction: 'warn',
|
|
572
|
+
status: 'stable',
|
|
573
|
+
description: 'A tool call returned an error (SQL error, runtime exception, API failure) instead of valid data. The AI may have proceeded without acknowledging the error or retrying with corrected parameters.',
|
|
574
|
+
suppresses: [],
|
|
575
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
576
|
+
tier: 'A',
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
type: 'orchestration.fabricated_tool_input',
|
|
580
|
+
label: 'Fabricated Tool Input',
|
|
581
|
+
category: 'orchestration',
|
|
582
|
+
defaultSeverity: 'critical',
|
|
583
|
+
defaultAction: 'block',
|
|
584
|
+
status: 'stable',
|
|
585
|
+
description: 'The AI fabricated tool call parameter values (e.g. entity IDs, person_keys) that do not appear in any prior tool output. The tool returned errors for all fabricated values.',
|
|
586
|
+
suppresses: ['grounding.hallucinated_entity', 'orchestration.tool_selection_error', 'orchestration.tool_capability_gap'],
|
|
587
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'support', 'logistics'],
|
|
588
|
+
tier: 'S',
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
type: 'reasoning.percentage_error',
|
|
592
|
+
label: 'Percentage Calculation Error',
|
|
593
|
+
category: 'reasoning',
|
|
594
|
+
defaultSeverity: 'high',
|
|
595
|
+
defaultAction: 'block',
|
|
596
|
+
status: 'stable',
|
|
597
|
+
description: 'Agent computed a percentage incorrectly — wrong percentage-of-total, percentage change, or share calculation from tool output values.',
|
|
598
|
+
suppresses: ['grounding.math_error'],
|
|
599
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'logistics'],
|
|
600
|
+
tier: 'A',
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
type: 'reasoning.ratio_error',
|
|
604
|
+
label: 'Ratio / Proportion Error',
|
|
605
|
+
category: 'reasoning',
|
|
606
|
+
defaultSeverity: 'high',
|
|
607
|
+
defaultAction: 'block',
|
|
608
|
+
status: 'stable',
|
|
609
|
+
description: 'Agent stated a ratio or proportion that does not match the underlying data — e.g. "3:1 ratio" when data shows 2:1, or "3 out of 5" when data shows 2 out of 5.',
|
|
610
|
+
suppresses: ['grounding.math_error'],
|
|
611
|
+
industries: ['hr', 'finance', 'healthcare', 'legal', 'logistics'],
|
|
612
|
+
tier: 'A',
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
type: 'reasoning.multiplication_error',
|
|
616
|
+
label: 'Multiplication / Division Error',
|
|
617
|
+
category: 'reasoning',
|
|
618
|
+
defaultSeverity: 'high',
|
|
619
|
+
defaultAction: 'block',
|
|
620
|
+
status: 'stable',
|
|
621
|
+
description: 'Agent computed a product or quotient incorrectly — e.g. unit price × quantity ≠ total, or total / count ≠ per-unit value.',
|
|
622
|
+
suppresses: ['grounding.math_error'],
|
|
623
|
+
industries: ['finance', 'accounting', 'hr', 'logistics', 'legal'],
|
|
624
|
+
tier: 'A',
|
|
625
|
+
},
|
|
626
|
+
];
|
|
627
|
+
// ---------------------------------------------------------------------------
|
|
628
|
+
// Lookup helpers
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
/** Map for O(1) lookup by failure type. */
|
|
631
|
+
const registryMap = new Map(exports.FAILURE_REGISTRY.map((entry) => [entry.type, entry]));
|
|
632
|
+
/** Get registry entry for a failure type. */
|
|
633
|
+
function getRegistryEntry(type) {
|
|
634
|
+
return registryMap.get(type);
|
|
635
|
+
}
|
|
636
|
+
/** Get default severity for a failure type. */
|
|
637
|
+
function getDefaultSeverity(type) {
|
|
638
|
+
return registryMap.get(type)?.defaultSeverity ?? 'medium';
|
|
639
|
+
}
|
|
640
|
+
/** Get default action for a failure type. */
|
|
641
|
+
function getDefaultAction(type) {
|
|
642
|
+
return registryMap.get(type)?.defaultAction ?? 'warn';
|
|
643
|
+
}
|
|
644
|
+
/** Get all failure types in a category. */
|
|
645
|
+
function getByCategory(category) {
|
|
646
|
+
return exports.FAILURE_REGISTRY.filter((e) => e.category === category);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Compute effective severity based on failure type and detection confidence.
|
|
650
|
+
* High confidence → default severity. Lower confidence → demoted one level.
|
|
651
|
+
*/
|
|
652
|
+
function computeSeverity(type, confidence) {
|
|
653
|
+
const base = getDefaultSeverity(type);
|
|
654
|
+
if (confidence === 'high')
|
|
655
|
+
return base;
|
|
656
|
+
// Demote by one level for medium confidence
|
|
657
|
+
const demotion = {
|
|
658
|
+
critical: 'high',
|
|
659
|
+
high: 'medium',
|
|
660
|
+
medium: 'low',
|
|
661
|
+
low: 'low',
|
|
662
|
+
};
|
|
663
|
+
if (confidence === 'medium')
|
|
664
|
+
return demotion[base];
|
|
665
|
+
// Low confidence → demote two levels
|
|
666
|
+
return demotion[demotion[base]];
|
|
667
|
+
}
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
// Suppression logic
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
/** Types that a given primary failure suppresses. */
|
|
672
|
+
function getSuppressedTypes(type) {
|
|
673
|
+
return registryMap.get(type)?.suppresses ?? [];
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Severity rank for ordering (higher = more severe).
|
|
677
|
+
*/
|
|
678
|
+
const SEVERITY_RANK = {
|
|
679
|
+
critical: 4,
|
|
680
|
+
high: 3,
|
|
681
|
+
medium: 2,
|
|
682
|
+
low: 1,
|
|
683
|
+
};
|
|
684
|
+
/** Compare two severities. Returns positive if a > b. */
|
|
685
|
+
function compareSeverity(a, b) {
|
|
686
|
+
return SEVERITY_RANK[a] - SEVERITY_RANK[b];
|
|
687
|
+
}
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
// Repair ordering
|
|
690
|
+
// ---------------------------------------------------------------------------
|
|
691
|
+
/**
|
|
692
|
+
* Compute a recommended repair order for a set of active failure types.
|
|
693
|
+
*
|
|
694
|
+
* Uses the suppression graph: if A suppresses B then A is a root cause of B,
|
|
695
|
+
* and fixing A should be attempted first (B may disappear automatically).
|
|
696
|
+
* Within the same dependency level, higher severity comes first.
|
|
697
|
+
*
|
|
698
|
+
* Returns an ordered array of `{ type, reason }` pairs.
|
|
699
|
+
*/
|
|
700
|
+
function getRepairOrder(activeTypes) {
|
|
701
|
+
const active = new Set(activeTypes);
|
|
702
|
+
if (active.size === 0)
|
|
703
|
+
return [];
|
|
704
|
+
// Build dependency graph: for each active type, find which active types
|
|
705
|
+
// suppress it (its "blockers"). Those blockers should be fixed first.
|
|
706
|
+
const blockedBy = new Map();
|
|
707
|
+
for (const t of active) {
|
|
708
|
+
blockedBy.set(t, new Set());
|
|
709
|
+
}
|
|
710
|
+
for (const entry of exports.FAILURE_REGISTRY) {
|
|
711
|
+
if (!active.has(entry.type))
|
|
712
|
+
continue;
|
|
713
|
+
for (const suppressed of entry.suppresses) {
|
|
714
|
+
if (active.has(suppressed)) {
|
|
715
|
+
blockedBy.get(suppressed).add(entry.type);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// Kahn's algorithm — topological sort (roots first)
|
|
720
|
+
const inDegree = new Map();
|
|
721
|
+
for (const t of active) {
|
|
722
|
+
inDegree.set(t, blockedBy.get(t).size);
|
|
723
|
+
}
|
|
724
|
+
const queue = [];
|
|
725
|
+
for (const [t, deg] of inDegree) {
|
|
726
|
+
if (deg === 0)
|
|
727
|
+
queue.push(t);
|
|
728
|
+
}
|
|
729
|
+
// Stable sort within queue by severity descending
|
|
730
|
+
queue.sort((a, b) => {
|
|
731
|
+
const sa = SEVERITY_RANK[getDefaultSeverity(a)] ?? 0;
|
|
732
|
+
const sb = SEVERITY_RANK[getDefaultSeverity(b)] ?? 0;
|
|
733
|
+
return sb - sa;
|
|
734
|
+
});
|
|
735
|
+
const result = [];
|
|
736
|
+
const visited = new Set();
|
|
737
|
+
while (queue.length > 0) {
|
|
738
|
+
const current = queue.shift();
|
|
739
|
+
if (visited.has(current))
|
|
740
|
+
continue;
|
|
741
|
+
visited.add(current);
|
|
742
|
+
const deps = blockedBy.get(current);
|
|
743
|
+
const reason = deps.size === 0
|
|
744
|
+
? 'Root cause — fix this first.'
|
|
745
|
+
: `Depends on ${[...deps].join(', ')} — fix after those.`;
|
|
746
|
+
result.push({ type: current, reason });
|
|
747
|
+
// Decrease in-degree for neighbours
|
|
748
|
+
for (const entry of exports.FAILURE_REGISTRY) {
|
|
749
|
+
if (entry.type !== current)
|
|
750
|
+
continue;
|
|
751
|
+
for (const suppressed of entry.suppresses) {
|
|
752
|
+
if (!active.has(suppressed) || visited.has(suppressed))
|
|
753
|
+
continue;
|
|
754
|
+
const newDeg = (inDegree.get(suppressed) ?? 1) - 1;
|
|
755
|
+
inDegree.set(suppressed, newDeg);
|
|
756
|
+
if (newDeg === 0) {
|
|
757
|
+
// Insert in severity order
|
|
758
|
+
const si = SEVERITY_RANK[getDefaultSeverity(suppressed)] ?? 0;
|
|
759
|
+
let inserted = false;
|
|
760
|
+
for (let i = 0; i < queue.length; i++) {
|
|
761
|
+
const sq = SEVERITY_RANK[getDefaultSeverity(queue[i])] ?? 0;
|
|
762
|
+
if (si > sq) {
|
|
763
|
+
queue.splice(i, 0, suppressed);
|
|
764
|
+
inserted = true;
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (!inserted)
|
|
769
|
+
queue.push(suppressed);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// Any remaining (cycle protection) get appended at the end
|
|
775
|
+
for (const t of active) {
|
|
776
|
+
if (!visited.has(t)) {
|
|
777
|
+
result.push({ type: t, reason: 'Circular dependency — review manually.' });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return result;
|
|
781
|
+
}
|
|
782
|
+
// ---------------------------------------------------------------------------
|
|
783
|
+
// Tier enforcement
|
|
784
|
+
// ---------------------------------------------------------------------------
|
|
785
|
+
/** Maximum action allowed per tier: S→block, A→block, B→warn, C→observe. */
|
|
786
|
+
const TIER_MAX_ACTION = {
|
|
787
|
+
S: 'block',
|
|
788
|
+
A: 'block',
|
|
789
|
+
B: 'warn',
|
|
790
|
+
C: 'observe',
|
|
791
|
+
};
|
|
792
|
+
const ACTION_RANK = {
|
|
793
|
+
observe: 1,
|
|
794
|
+
warn: 2,
|
|
795
|
+
block: 3,
|
|
796
|
+
};
|
|
797
|
+
/**
|
|
798
|
+
* Get the maximum action allowed for a failure type based on its tier.
|
|
799
|
+
* Policy cannot escalate beyond this ceiling.
|
|
800
|
+
*/
|
|
801
|
+
function getTierMaxAction(type) {
|
|
802
|
+
const entry = registryMap.get(type);
|
|
803
|
+
if (!entry)
|
|
804
|
+
return 'observe';
|
|
805
|
+
return TIER_MAX_ACTION[entry.tier];
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Cap an action to the tier ceiling.
|
|
809
|
+
* If the requested action exceeds what the tier allows, returns the tier max.
|
|
810
|
+
*/
|
|
811
|
+
function capActionToTier(type, requestedAction) {
|
|
812
|
+
const maxAction = getTierMaxAction(type);
|
|
813
|
+
if (ACTION_RANK[requestedAction] > ACTION_RANK[maxAction]) {
|
|
814
|
+
return maxAction;
|
|
815
|
+
}
|
|
816
|
+
return requestedAction;
|
|
817
|
+
}
|
|
818
|
+
//# sourceMappingURL=index.js.map
|