@iris-eval/mcp-server 0.8.2 → 0.10.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.
Files changed (131) hide show
  1. package/README.md +9 -2
  2. package/dist/capabilities.d.ts +64 -0
  3. package/dist/capabilities.js +65 -0
  4. package/dist/config/defaults.js +17 -0
  5. package/dist/custom-rule-store.d.ts +4 -0
  6. package/dist/custom-rule-store.js +8 -3
  7. package/dist/dashboard/assets/{index-CyzO6OC7.js → index-CeJbaq6m.js} +1 -1
  8. package/dist/dashboard/index.html +1 -1
  9. package/dist/dashboard/routes/capabilities.d.ts +3 -0
  10. package/dist/dashboard/routes/capabilities.js +11 -0
  11. package/dist/dashboard/routes/health.d.ts +5 -1
  12. package/dist/dashboard/routes/health.js +15 -3
  13. package/dist/dashboard/routes/rules.js +4 -1
  14. package/dist/dashboard/routes/traces.d.ts +3 -0
  15. package/dist/dashboard/routes/traces.js +11 -30
  16. package/dist/dashboard/seed-demo-data.js +1 -1
  17. package/dist/dashboard/server.d.ts +2 -0
  18. package/dist/dashboard/server.js +6 -2
  19. package/dist/eval/accuracy.d.ts +41 -0
  20. package/dist/eval/accuracy.js +97 -0
  21. package/dist/eval/citation-verify/verifier.d.ts +16 -1
  22. package/dist/eval/citation-verify/verifier.js +14 -4
  23. package/dist/eval/compose.d.ts +57 -0
  24. package/dist/eval/compose.js +179 -0
  25. package/dist/eval/criticality.d.ts +15 -1
  26. package/dist/eval/criticality.js +6 -0
  27. package/dist/eval/decision-moment.js +33 -4
  28. package/dist/eval/dormant.d.ts +4 -0
  29. package/dist/eval/dormant.js +22 -0
  30. package/dist/eval/engine.d.ts +6 -2
  31. package/dist/eval/engine.js +126 -12
  32. package/dist/eval/failure-classes.d.ts +8 -0
  33. package/dist/eval/failure-classes.js +18 -0
  34. package/dist/eval/llm-judge/evaluator.d.ts +30 -0
  35. package/dist/eval/llm-judge/evaluator.js +26 -2
  36. package/dist/eval/published-accuracy.d.ts +230 -0
  37. package/dist/eval/published-accuracy.js +86 -0
  38. package/dist/eval/questions.d.ts +12 -0
  39. package/dist/eval/questions.js +14 -0
  40. package/dist/eval/response-schema.d.ts +652 -0
  41. package/dist/eval/response-schema.js +130 -0
  42. package/dist/eval/response.d.ts +12 -0
  43. package/dist/eval/response.js +30 -0
  44. package/dist/eval/risk.d.ts +60 -0
  45. package/dist/eval/risk.js +187 -0
  46. package/dist/eval/rules/completeness.js +36 -1
  47. package/dist/eval/rules/cost.d.ts +2 -2
  48. package/dist/eval/rules/cost.js +50 -6
  49. package/dist/eval/rules/custom.d.ts +0 -12
  50. package/dist/eval/rules/custom.js +22 -0
  51. package/dist/eval/rules/relevance.js +23 -2
  52. package/dist/eval/rules/safety.d.ts +6 -2
  53. package/dist/eval/rules/safety.js +224 -51
  54. package/dist/eval/seeded-random.d.ts +4 -0
  55. package/dist/eval/seeded-random.js +36 -0
  56. package/dist/eval/stamp.d.ts +14 -0
  57. package/dist/eval/stamp.js +89 -0
  58. package/dist/eval/stats.d.ts +33 -0
  59. package/dist/eval/stats.js +109 -0
  60. package/dist/eval/text/checksums.d.ts +23 -0
  61. package/dist/eval/text/checksums.js +97 -0
  62. package/dist/eval/text/normalise.d.ts +30 -0
  63. package/dist/eval/text/normalise.js +265 -0
  64. package/dist/eval/text/sentences.d.ts +15 -0
  65. package/dist/eval/text/sentences.js +149 -0
  66. package/dist/eval/verdict.d.ts +34 -0
  67. package/dist/eval/verdict.js +131 -0
  68. package/dist/index.js +5 -28
  69. package/dist/instructions.d.ts +17 -0
  70. package/dist/instructions.js +53 -0
  71. package/dist/judge-enablement.d.ts +34 -0
  72. package/dist/judge-enablement.js +78 -0
  73. package/dist/judge-enablement.json +10 -0
  74. package/dist/preferences.d.ts +1 -1
  75. package/dist/prompts.d.ts +3 -0
  76. package/dist/prompts.js +29 -0
  77. package/dist/resources/index.d.ts +5 -2
  78. package/dist/resources/index.js +65 -5
  79. package/dist/resources/uris.d.ts +12 -0
  80. package/dist/resources/uris.js +24 -0
  81. package/dist/retention.d.ts +20 -0
  82. package/dist/retention.js +44 -0
  83. package/dist/self-test.d.ts +1 -0
  84. package/dist/self-test.js +17 -3
  85. package/dist/server.d.ts +10 -1
  86. package/dist/server.js +34 -7
  87. package/dist/storage/index.js +1 -1
  88. package/dist/storage/migrations/007-eval-provenance.d.ts +3 -0
  89. package/dist/storage/migrations/007-eval-provenance.js +30 -0
  90. package/dist/storage/migrations/index.js +24 -4
  91. package/dist/storage/sqlite-adapter.d.ts +26 -1
  92. package/dist/storage/sqlite-adapter.js +149 -15
  93. package/dist/tools/delete-rule.d.ts +8 -0
  94. package/dist/tools/delete-rule.js +30 -38
  95. package/dist/tools/delete-trace.d.ts +5 -0
  96. package/dist/tools/delete-trace.js +24 -27
  97. package/dist/tools/deploy-rule.d.ts +13 -1
  98. package/dist/tools/deploy-rule.js +37 -34
  99. package/dist/tools/describe.d.ts +20 -0
  100. package/dist/tools/describe.js +36 -0
  101. package/dist/tools/errors.d.ts +36 -0
  102. package/dist/tools/errors.js +134 -0
  103. package/dist/tools/evaluate-output.d.ts +8 -1
  104. package/dist/tools/evaluate-output.js +39 -60
  105. package/dist/tools/evaluate-with-llm-judge.d.ts +34 -0
  106. package/dist/tools/evaluate-with-llm-judge.js +124 -69
  107. package/dist/tools/get-traces.d.ts +9 -0
  108. package/dist/tools/get-traces.js +29 -28
  109. package/dist/tools/index.d.ts +8 -0
  110. package/dist/tools/index.js +22 -1
  111. package/dist/tools/list-rules.d.ts +13 -0
  112. package/dist/tools/list-rules.js +43 -46
  113. package/dist/tools/log-trace.d.ts +4 -0
  114. package/dist/tools/log-trace.js +31 -29
  115. package/dist/tools/respond.d.ts +42 -0
  116. package/dist/tools/respond.js +90 -0
  117. package/dist/tools/strict-input.js +1 -1
  118. package/dist/tools/trace-link.d.ts +2 -0
  119. package/dist/tools/trace-link.js +13 -2
  120. package/dist/tools/verify-citations.d.ts +18 -2
  121. package/dist/tools/verify-citations.js +122 -96
  122. package/dist/types/config.d.ts +44 -0
  123. package/dist/types/eval.d.ts +309 -0
  124. package/dist/types/eval.js +2 -1
  125. package/dist/types/query.d.ts +2 -0
  126. package/package.json +1 -1
  127. package/server.json +2 -2
  128. package/dist/resources/dashboard-summary.d.ts +0 -3
  129. package/dist/resources/dashboard-summary.js +0 -16
  130. package/dist/resources/trace-detail.d.ts +0 -3
  131. package/dist/resources/trace-detail.js +0 -30
@@ -2,6 +2,13 @@ export interface IrisConfig {
2
2
  storage: {
3
3
  type: 'sqlite';
4
4
  path: string;
5
+ /**
6
+ * `critical_spans` stores each evaluation's output text with the spans a
7
+ * critical detector flagged replaced by `[REDACTED:<pattern>]`, so a tool
8
+ * that detects leaks need not keep the leak it found. The evidence
9
+ * offsets still index the original text the caller saw. Default `none`.
10
+ */
11
+ redact?: 'none' | 'critical_spans';
5
12
  };
6
13
  server: {
7
14
  name: string;
@@ -47,12 +54,49 @@ export interface IrisConfig {
47
54
  * name in both lists is a config error: it does not say what you want.
48
55
  */
49
56
  nonCriticalRules?: string[];
57
+ /** `risk` composes by kind (gates, vetoes, unknown, then the risk); `legacy` runs the pre-0.10.0 weighted mean. */
58
+ composer?: 'risk' | 'legacy';
59
+ /**
60
+ * How many wrongly blocked builds one shipped failure is worth. The risk
61
+ * threshold is 1 / (1 + this), so 1 means a false pass and a false block
62
+ * cost the same; a continuous-integration gate that hates flakiness sets
63
+ * it low, a compliance gate sets it high.
64
+ */
65
+ falsePassCost?: number;
66
+ /**
67
+ * What a critical rule that was ASKED and could not answer does to the
68
+ * verdict — defeated by the output, or configured invalidly. Not the
69
+ * same as never asked, which is coverage. Today's behaviour is `pass`,
70
+ * which is the fail-open seam; the default is `unknown`.
71
+ */
72
+ onCriticalSkipped?: 'unknown' | 'fail' | 'pass';
73
+ /** Inputs every evaluation must carry; an absent one makes the verdict unknown rather than clean. */
74
+ requiredEvidence?: string[];
75
+ /**
76
+ * Whether a threshold IRIS ships decides the verdict, or only advises.
77
+ * A default is our guess about a deployment we have never seen; a
78
+ * threshold you set is your decision. A policy with no number in it —
79
+ * "the output is empty" — gates either way.
80
+ */
81
+ defaultsGate?: boolean;
82
+ /** The prior that an output is bad before any rule speaks. 0.5 matches the proof corpus, not your traffic. */
83
+ prior?: number;
84
+ /**
85
+ * How that prior is spread over the failure classes the detectors
86
+ * examine. `per-output` keeps it at the stated value for the output as a
87
+ * whole; `per-class` applies it to each class independently, which makes
88
+ * installing another detector raise the prior before that detector has
89
+ * looked at anything.
90
+ */
91
+ priorMode?: 'per-output' | 'per-class';
50
92
  };
51
93
  logging: {
52
94
  level: 'debug' | 'info' | 'warn' | 'error';
53
95
  };
54
96
  retention: {
55
97
  days: number;
98
+ /** How often the sweep re-runs after boot, in hours; 0 disables the timer (the boot sweep still runs). Default 24. */
99
+ sweepIntervalHours: number;
56
100
  };
57
101
  security: {
58
102
  apiKey?: string;
@@ -8,6 +8,23 @@ export type EvalType = 'completeness' | 'relevance' | 'safety' | 'cost' | 'custo
8
8
  * under a real bundle, never under 'all'.
9
9
  */
10
10
  export type EvalResultType = EvalType | 'all';
11
+ /**
12
+ * What KIND of claim a rule makes — the mandate's distinction between a
13
+ * measurement (a statistic against a threshold), a detection (a pattern is
14
+ * present, with a measured error rate), an inference (a signal standing in
15
+ * for an unobservable property), a judgment (a model's reasoning), a policy
16
+ * (the deployment's own constraint) and an external verification. Kind is
17
+ * the claim; `mechanism` is how the claim is measured. The composer decides
18
+ * by kind and never averages kinds together.
19
+ */
20
+ export type ClaimKind = 'measurement' | 'detection' | 'inference' | 'judgment' | 'policy' | 'verification';
21
+ export type Mechanism = 'formula' | 'pattern' | 'heuristic' | 'model' | 'external';
22
+ /** An input a rule reads. A rule skips — never passes — when a declared need is absent. */
23
+ export type Need = 'output' | 'input' | 'expected' | 'tool_calls' | 'tool_outputs' | 'tools_catalogue' | 'cost' | 'tokens' | 'citations';
24
+ /** The evaluation question a rule answers; the registry is src/eval/questions.ts. */
25
+ export type QuestionId = 'safe_output' | 'grounded' | 'complete' | 'relevant' | 'task_completed' | 'tool_use_correct' | 'within_budget';
26
+ /** What went wrong, in the reader's words, independent of which rule caught it; the registry is src/eval/failure-classes.ts. */
27
+ export type FailureClass = 'pii_leak' | 'credential_leak' | 'injection' | 'injection_compliance' | 'silent_tool_failure' | 'tool_loop' | 'stub' | 'fabrication' | 'ungrounded' | 'incomplete_ask' | 'off_task' | 'over_budget' | 'format' | 'invalid_tool_call';
11
28
  export interface EvalRule {
12
29
  name: string;
13
30
  description: string;
@@ -25,6 +42,31 @@ export interface EvalRule {
25
42
  * is the verdict, and a critical violation must never be averaged away.
26
43
  */
27
44
  critical?: boolean;
45
+ /**
46
+ * The rule's metadata — what kind of claim it makes, how it measures it,
47
+ * what it reads, which question it answers, which failure classes a
48
+ * failing result belongs to, and the version of its definition. Every
49
+ * built-in declares all six (tests/unit/eval/rule-metadata.test.ts);
50
+ * custom types declare kind, mechanism, needs and version and leave the
51
+ * question to their author. Optional on the interface so a rule built
52
+ * elsewhere still compiles; a result from a rule without them carries no
53
+ * `kind`, which reads as unknown — never as a measurement.
54
+ */
55
+ kind?: ClaimKind;
56
+ mechanism?: Mechanism;
57
+ needs?: readonly Need[];
58
+ question?: QuestionId;
59
+ classes?: readonly FailureClass[];
60
+ /** Bumped when the rule's meaning changes, so a stored result names the definition that produced it. */
61
+ version?: number;
62
+ /**
63
+ * Who wrote this rule. `custom` marks anything `createCustomRule`
64
+ * produced — a deployed rule or one passed inline in the call. The
65
+ * composer needs it: for OUR rule a shipped threshold is a guess and only
66
+ * advises, while for THEIRS the severity they deployed it at is their own
67
+ * statement of how much it matters. Absent means built-in.
68
+ */
69
+ origin?: 'built-in' | 'custom';
28
70
  evaluate(context: EvalContext): EvalRuleResult;
29
71
  }
30
72
  export interface EvalContext {
@@ -63,9 +105,263 @@ export interface EvalContext {
63
105
  regexBudget?: {
64
106
  breaches: number;
65
107
  };
108
+ /**
109
+
110
+ * Whether this evaluation may call a paid provider. Set ONLY by the tools
111
+
112
+ * whose whole purpose is to do so — the LLM judge and the citation
113
+
114
+ * verifier. The engine refuses to run a judgment rule without it, which
115
+
116
+ * is what makes "evaluate_output never spends" a property of the engine
117
+
118
+ * rather than a promise in a tool description.
119
+
120
+ */
121
+ allowPaid?: boolean;
122
+ }
123
+ /**
124
+ * What the composer DID with a result under this deployment's configuration
125
+ * — distinct from `kind`, which is what the rule claims. Today's composer
126
+ * (a weighted mean plus the critical veto) knows two roles: `veto` for an
127
+ * effectively critical rule and `term` for one that feeds the score. The
128
+ * compose-by-kind release adds `gate` (a configured policy that decides),
129
+ * `risk` (a detection or inference feeding the risk estimate) and
130
+ * `advisory` (reported, deciding nothing).
131
+ */
132
+ export type Role = 'gate' | 'veto' | 'risk' | 'advisory' | 'term';
133
+ /**
134
+ * Why a rule skipped. `not_applicable`: the evidence it needs was not
135
+ * supplied (never asked — coverage). `defeated`: asked and could not answer,
136
+ * because this output stalled its pattern past the sandbox budget.
137
+ * `config_invalid`: asked and could not answer, because its definition is
138
+ * broken. A gate that fails closed treats the last two as unknown; the first
139
+ * is a coverage fact, not a verdict.
140
+ */
141
+ export type SkipClass = 'not_applicable' | 'defeated' | 'config_invalid';
142
+ export interface Interval {
143
+ point: number;
144
+ lo: number;
145
+ hi: number;
146
+ }
147
+ /**
148
+ * What a rule saw — typed, locatable, never an excerpt. A detection reports
149
+ * the OFFSETS of what it matched (into the raw text, so a leak detector can
150
+ * redact the span it found without ever repeating it); a trajectory rule the
151
+ * index of the call it judged; a measurement its statistic with a unit and
152
+ * the threshold it was held to; a signal that yields no offset yet reports
153
+ * its name and count. The reader can locate every claim; the stored row
154
+ * can be redacted; nothing here restates the offending text.
155
+ */
156
+ export type Evidence = {
157
+ type: 'span';
158
+ source: 'output' | 'input' | `tool_outputs[${number}]`;
159
+ start: number;
160
+ end: number;
161
+ label: string;
162
+ } | {
163
+ type: 'pattern';
164
+ name: string;
165
+ count: number;
166
+ } | {
167
+ type: 'toolCall';
168
+ index: number;
169
+ toolName: string;
170
+ label: string;
171
+ } | {
172
+ type: 'citation';
173
+ url: string;
174
+ status: 'resolved' | 'dead' | 'unverifiable' | 'supported' | 'unsupported';
175
+ } | {
176
+ type: 'count';
177
+ stat: string;
178
+ unit: string;
179
+ value: number;
180
+ threshold?: number;
181
+ thresholdSource?: 'default' | 'config' | 'call' | 'rule';
182
+ } | {
183
+ type: 'sample';
184
+ score: number;
185
+ selfReportedPass?: boolean;
186
+ rationaleHash: string;
187
+ };
188
+ /** A measurement's statistic — the number the rule computed, with its unit, before any score transform. */
189
+ export interface MeasuredValue {
190
+ stat: string;
191
+ unit: string;
192
+ value: number;
193
+ }
194
+ /** Evidence lists are capped so a pathological output cannot balloon a stored row. */
195
+ export declare const MAX_EVIDENCE_ITEMS = 25;
196
+ /**
197
+ * Which evaluation questions this evaluation judged, which it did not and
198
+ * why — coverage by question, not by rule count. `inputs` says what the
199
+ * call carried; a question is `judged` when at least one rule that answers
200
+ * it ran, `unjudged` when every such rule skipped (the reason names the
201
+ * missing input, or that the rule was defeated or broken), and
202
+ * `not_applicable` when no rule for it was in the selected bundles.
203
+ */
204
+ export interface Coverage {
205
+ inputs: Record<Need, boolean>;
206
+ questions: Array<{
207
+ id: QuestionId;
208
+ status: 'judged' | 'unjudged' | 'not_applicable';
209
+ why?: string;
210
+ }>;
211
+ /** Quarantined critical rules that did not run (surfaced by the rule-store release). */
212
+ dormant?: Array<{
213
+ ruleId: string;
214
+ name: string;
215
+ reason: string;
216
+ }>;
217
+ }
218
+ /**
219
+ * The verdict with its basis. `passed` is `state === 'pass'` and equals the
220
+ * top-level `passed`; `basis` says which layer decided — a configured policy,
221
+ * a detector's veto, nothing judged, or the score against the threshold.
222
+ * `risk` is null until the compose-by-kind release computes it.
223
+ */
224
+ /**
225
+ * A sentence a reader needs that the verdict alone does not carry, with who
226
+ * it is for and what to change. The one that must exist: when a rule
227
+ * visibly FIRED and the verdict still passed, say why and name the setting
228
+ * that would change it — "cost_under_threshold failed" beside
229
+ * "passed: true" reads as a bug to anyone who has not read the composer.
230
+ *
231
+ * `suggestions` remains for now and is rendered from these; it is deprecated
232
+ * from 0.13.0 and removed at 1.0, per VERSIONING.md's two-minor rule.
233
+ */
234
+ export interface Interpretation {
235
+ severity: 'block' | 'warn' | 'note';
236
+ addressee: 'agent' | 'operator' | 'author';
237
+ /** The rule this is about, when it is about one. */
238
+ rule?: string;
239
+ text: string;
240
+ /** The configuration key that changes this behaviour, when there is one. */
241
+ configKey?: string;
242
+ }
243
+ /** Placed on EvalResult by the engine; see Interpretation above. */
244
+ export interface Verdict {
245
+ state: 'pass' | 'fail' | 'unknown';
246
+ passed: boolean;
247
+ basis: 'policy_gate' | 'detector_veto' | 'critical_unknown' | 'required_evidence_missing' | 'risk_over_loss' | 'score_below_threshold' | 'clean' | 'no_rules';
248
+ by: string[];
249
+ risk: {
250
+ pBad: number;
251
+ lo: number;
252
+ hi: number;
253
+ perClass: Partial<Record<FailureClass, number | null>>;
254
+ assumptions: string[];
255
+ } | null;
256
+ confidence?: 'decisive' | 'marginal';
257
+ }
258
+ /** What produced this verdict, so it can be replayed or compared: the release, the ruleset, the configuration, the thresholds, the proof corpus, the time. */
259
+ export interface Provenance {
260
+ irisVersion: string;
261
+ rulesetHash: string;
262
+ configHash: string;
263
+ thresholds: {
264
+ default: number;
265
+ perRule?: Record<string, unknown>;
266
+ };
267
+ corpusVersion: string;
268
+ judgedAt: string;
66
269
  }
270
+ /**
271
+ * How wrong this result tends to be, and on what basis. `published_accuracy`
272
+ * carries the rule's measured numbers from the shipped proof (src/eval/
273
+ * published-accuracy.ts): for a fired detection or inference the positive
274
+ * predictive value at the stated prior, for one that did not fire the
275
+ * residual miss rate, each with a 95% credible interval. `definition` is a
276
+ * measurement's conformance to its formula (n cases, matched). `policy` is
277
+ * the deployment's own constraint — no error rate applies. `self_consistency`
278
+ * and `local_labels` arrive with the judge-through-the-engine and the
279
+ * own-traffic labels releases. `unmeasured` says why nothing can be stated.
280
+ */
281
+ export type Uncertainty = {
282
+ basis: 'published_accuracy';
283
+ fired: true;
284
+ ppv: Interval;
285
+ prior: {
286
+ pi: number;
287
+ source: 'default' | 'config' | 'estimated';
288
+ };
289
+ corpus: {
290
+ n: number;
291
+ tp: number;
292
+ fp: number;
293
+ fn: number;
294
+ tn: number;
295
+ version: string;
296
+ release: string;
297
+ labelling: 'same-model' | 'human-verified';
298
+ };
299
+ } | {
300
+ basis: 'published_accuracy';
301
+ fired: false;
302
+ missRate: Interval;
303
+ prior: {
304
+ pi: number;
305
+ source: 'default' | 'config' | 'estimated';
306
+ };
307
+ corpus: {
308
+ n: number;
309
+ tp: number;
310
+ fp: number;
311
+ fn: number;
312
+ tn: number;
313
+ version: string;
314
+ release: string;
315
+ labelling: 'same-model' | 'human-verified';
316
+ };
317
+ } | {
318
+ basis: 'definition';
319
+ conformance: {
320
+ n: number;
321
+ matched: number;
322
+ };
323
+ } | {
324
+ basis: 'self_consistency';
325
+ samples: number;
326
+ voteFraction: number;
327
+ scoreSd: number;
328
+ } | {
329
+ basis: 'local_labels';
330
+ precision: Interval;
331
+ n: number;
332
+ } | {
333
+ basis: 'policy';
334
+ } | {
335
+ basis: 'unmeasured';
336
+ why: string;
337
+ };
67
338
  export interface EvalRuleResult {
68
339
  ruleName: string;
340
+ /**
341
+ * What kind of claim this result makes, what the composer did with it,
342
+ * which question it answers and which failure classes a failure belongs
343
+ * to — stamped by the engine from the rule's declaration (0.9.0). Absent
344
+ * on results written before that release and on rules that declare no
345
+ * metadata; never fabricated on read.
346
+ */
347
+ kind?: ClaimKind;
348
+ role?: Role;
349
+ question?: QuestionId;
350
+ classes?: FailureClass[];
351
+ /** The version of the rule definition that produced this result. */
352
+ ruleVersion?: number;
353
+ /** Who wrote the rule: `custom` for anything createCustomRule produced. See EvalRule.origin. */
354
+ origin?: 'built-in' | 'custom';
355
+ /** Which of the rule's declared needs the call actually carried — what the rule SAW. */
356
+ saw?: Need[];
357
+ /** Present only when `skipped`; says whether the rule was never asked or was asked and could not answer. */
358
+ skipClass?: SkipClass;
359
+ /** How wrong this result tends to be, and on what basis. Present on every result that made a claim (not on skips). */
360
+ uncertainty?: Uncertainty;
361
+ /** What the rule saw: spans (offsets, never text), tool-call indices, pattern names, counts. Present on every fired detection or inference, and on measurements. */
362
+ evidence?: Evidence[];
363
+ /** A measurement's statistic and unit — the number before the score transform. */
364
+ value?: MeasuredValue;
69
365
  /**
70
366
  * Deployed rule id (rule-<hex>) when the rule came from the custom-rule
71
367
  * store. Absent for built-in rules and for inline custom_rules. Names are
@@ -169,6 +465,19 @@ export interface EvalResult {
169
465
  * rule_results carry a `category` per rule so a reader can regroup.
170
466
  */
171
467
  categories?: Partial<Record<EvalType, EvalCategoryResult>>;
468
+ /** The verdict with its basis (0.9.0) — computed by the engine, derived on read for stored rows that carry provenance. */
469
+ verdict?: Verdict;
470
+ /** Sentences a reader needs that the verdict alone does not carry (0.10.0). */
471
+ interpretations?: Interpretation[];
472
+ /** Coverage by evaluation question (0.9.0) — computed by the engine, derived on read from the stamped rule results. */
473
+ coverage?: Coverage;
474
+ /** What produced this verdict (0.9.0) — persisted; absent on rows written before it, never fabricated. */
475
+ provenance?: Provenance;
476
+ /** What the evaluation itself cost (the judge's spend); undefined for the free rules. */
477
+ eval_cost_usd?: number;
478
+ eval_tokens?: number;
479
+ /** Set when the linked trace was deleted (delete_trace or the retention sweep) and this row's text was erased. */
480
+ erased_at?: string;
172
481
  }
173
482
  export type CustomRuleType = 'regex_match' | 'regex_no_match' | 'min_length' | 'max_length' | 'contains_keywords' | 'excludes_keywords' | 'json_schema' | 'cost_threshold';
174
483
  export interface CustomRuleDefinition {
@@ -1 +1,2 @@
1
- export {};
1
+ /** Evidence lists are capped so a pathological output cannot balloon a stored row. */
2
+ export const MAX_EVIDENCE_ITEMS = 25;
@@ -82,6 +82,8 @@ export interface IStorageAdapter {
82
82
  getSpansByTraceId(tenantId: TenantId, traceId: string): Promise<Span[]>;
83
83
  insertEvalResult(tenantId: TenantId, result: EvalResult): Promise<void>;
84
84
  getEvalsByTraceId(tenantId: TenantId, traceId: string): Promise<EvalResult[]>;
85
+ /** One stored evaluation by id, in the same derived-on-read shape as every other reader; null when absent. */
86
+ getEvalById(tenantId: TenantId, id: string): Promise<EvalResult | null>;
85
87
  queryEvalResults(tenantId: TenantId, options: {
86
88
  eval_type?: string;
87
89
  passed?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iris-eval/mcp-server",
3
- "version": "0.8.2",
3
+ "version": "0.10.0",
4
4
  "description": "Stop shipping agents on vibes. Score every agent output for quality, safety, and cost.",
5
5
  "mcpName": "io.github.iris-eval/mcp-server",
6
6
  "type": "module",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/iris-eval/mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.8.2",
9
+ "version": "0.10.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@iris-eval/mcp-server",
14
- "version": "0.8.2",
14
+ "version": "0.10.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { IStorageAdapter } from '../types/query.js';
3
- export declare function registerDashboardSummaryResource(server: McpServer, storage: IStorageAdapter): void;
@@ -1,16 +0,0 @@
1
- import { LOCAL_TENANT } from '../types/tenant.js';
2
- export function registerDashboardSummaryResource(server, storage) {
3
- server.resource('dashboard-summary', 'iris://dashboard/summary', { description: 'Dashboard summary with key metrics and trends' }, async () => {
4
- // OSS single-tenant: summary scopes to the local user.
5
- const summary = await storage.getDashboardSummary(LOCAL_TENANT);
6
- return {
7
- contents: [
8
- {
9
- uri: 'iris://dashboard/summary',
10
- mimeType: 'application/json',
11
- text: JSON.stringify(summary, null, 2),
12
- },
13
- ],
14
- };
15
- });
16
- }
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { IStorageAdapter } from '../types/query.js';
3
- export declare function registerTraceDetailResource(server: McpServer, storage: IStorageAdapter): void;
@@ -1,30 +0,0 @@
1
- import { LOCAL_TENANT } from '../types/tenant.js';
2
- export function registerTraceDetailResource(server, storage) {
3
- server.resource('trace-detail', 'iris://traces/{trace_id}', { description: 'Full trace detail with spans and evaluation results' }, async (uri) => {
4
- const traceId = uri.pathname.split('/').pop();
5
- // OSS single-tenant: MCP caller is the local user.
6
- const trace = await storage.getTrace(LOCAL_TENANT, traceId);
7
- if (!trace) {
8
- return {
9
- contents: [
10
- {
11
- uri: uri.href,
12
- mimeType: 'application/json',
13
- text: JSON.stringify({ error: 'Trace not found' }),
14
- },
15
- ],
16
- };
17
- }
18
- const spans = await storage.getSpansByTraceId(LOCAL_TENANT, traceId);
19
- const evals = await storage.getEvalsByTraceId(LOCAL_TENANT, traceId);
20
- return {
21
- contents: [
22
- {
23
- uri: uri.href,
24
- mimeType: 'application/json',
25
- text: JSON.stringify({ trace, spans, evals }, null, 2),
26
- },
27
- ],
28
- };
29
- });
30
- }