@adversarylabs/sdk 0.1.4 → 0.1.5
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.
- package/README.md +46 -7
- package/dist/index.d.ts +24 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +124 -19
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/schemas/adversary.review.v1.schema.json +136 -0
- package/templates/basic/adversary.yaml +1 -1
- package/templates/basic/npm-shrinkwrap.json +59 -4
- package/templates/basic/package.json +1 -1
- package/templates/basic/src/index.ts +1 -7
- package/schemas/adversary.run.v1.schema.json +0 -275
package/README.md
CHANGED
|
@@ -9,11 +9,28 @@ and write runtime output.
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install @adversarylabs/sdk
|
|
12
|
+
npm install @adversarylabs/sdk@^0.1.5
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Requires Node 22 or newer and ESM.
|
|
16
16
|
|
|
17
|
+
## Migrating from 0.1.4
|
|
18
|
+
|
|
19
|
+
SDK 0.1.4 output is incompatible with the current strict Adversary CLI schema. Version 0.1.5 is
|
|
20
|
+
the canonical protocol release. Existing adversaries should:
|
|
21
|
+
|
|
22
|
+
1. Upgrade the package to `@adversarylabs/sdk@^0.1.5`.
|
|
23
|
+
2. Set `findings.format` to `adversary.review.v1` in `adversary.yaml`.
|
|
24
|
+
3. Stop reading `result.schemaVersion`; protocol selection is expressed by `protocolVersion: 1`
|
|
25
|
+
and the manifest format.
|
|
26
|
+
4. If consuming serialized evidence, read `file`, `line`, `endLine`, `message`, `snippet`, and
|
|
27
|
+
`metadata` instead of `location`, `label`, and `data`.
|
|
28
|
+
5. If consuming review scores, read the canonical review observation with key `score.<score-key>`;
|
|
29
|
+
the complete authored score is preserved in that note's metadata.
|
|
30
|
+
|
|
31
|
+
There is no compatibility adapter or dual wire format. Output is validated against the strict
|
|
32
|
+
`adversary.review.v1` schema before it is written.
|
|
33
|
+
|
|
17
34
|
## Author an adversary
|
|
18
35
|
|
|
19
36
|
```ts
|
|
@@ -287,7 +304,10 @@ ctx.review.score({
|
|
|
287
304
|
});
|
|
288
305
|
```
|
|
289
306
|
|
|
290
|
-
Scores are optional
|
|
307
|
+
Scores are optional and remain available as an authoring API and terminal section. Because the CLI
|
|
308
|
+
schema has no top-level score collection, the SDK serializes each score as a review
|
|
309
|
+
observation. The note key is `score.<score-key>`, its summary is the rendered score, and its metadata
|
|
310
|
+
preserves the authored `key`, `label`, `score`, `max`, and `summary`. No score data is discarded.
|
|
291
311
|
|
|
292
312
|
### Observation-First Authoring
|
|
293
313
|
|
|
@@ -325,10 +345,9 @@ type ReviewResult = {
|
|
|
325
345
|
assessment?: { risk: "none" | "low" | "medium" | "high" | "critical"; summary?: string };
|
|
326
346
|
positives: ReviewNote[];
|
|
327
347
|
observations: ReviewNote[];
|
|
328
|
-
scores?: ReviewScore[];
|
|
329
348
|
findings: ReviewFinding[];
|
|
330
349
|
opinion?: { ship?: boolean; summary: string };
|
|
331
|
-
suppressed: { findings: number };
|
|
350
|
+
suppressed: { observations: number; findings: number };
|
|
332
351
|
timing?: { totalMs?: number };
|
|
333
352
|
};
|
|
334
353
|
```
|
|
@@ -336,13 +355,29 @@ type ReviewResult = {
|
|
|
336
355
|
Renderers consume this result. The SDK includes `TerminalRenderer` and `JsonRenderer`:
|
|
337
356
|
|
|
338
357
|
```ts
|
|
339
|
-
const result = await app.run({ });
|
|
358
|
+
const result = await app.run({ input: { source: { path: "/repo" } } });
|
|
340
359
|
new TerminalRenderer().render(result);
|
|
341
360
|
new JsonRenderer().render(result);
|
|
342
361
|
```
|
|
343
362
|
|
|
344
363
|
Adversary implementations should not manually format review output.
|
|
345
364
|
|
|
365
|
+
### Canonical wire evidence
|
|
366
|
+
|
|
367
|
+
The authoring API continues to accept nested `location`, structured `data`, and `label`. Envelope
|
|
368
|
+
serialization translates those fields without losing their meaning:
|
|
369
|
+
|
|
370
|
+
| Authoring field | Canonical wire field |
|
|
371
|
+
| --- | --- |
|
|
372
|
+
| `location.file` | `file` |
|
|
373
|
+
| `location.line` | `line` |
|
|
374
|
+
| `location.endLine` | `endLine` |
|
|
375
|
+
| `data` | `metadata` |
|
|
376
|
+
| `label` when `message` is absent | `message` |
|
|
377
|
+
|
|
378
|
+
Wire evidence never contains `location`, `data`, or `label`. Findings likewise never contain SDK
|
|
379
|
+
diagnostic fields such as `synthesisSource`.
|
|
380
|
+
|
|
346
381
|
## Comment Sentence Example
|
|
347
382
|
|
|
348
383
|
`adversary.yaml`:
|
|
@@ -374,7 +409,7 @@ permissions:
|
|
|
374
409
|
env: []
|
|
375
410
|
|
|
376
411
|
findings:
|
|
377
|
-
format: adversary.
|
|
412
|
+
format: adversary.review.v1
|
|
378
413
|
```
|
|
379
414
|
|
|
380
415
|
`src/index.ts`:
|
|
@@ -512,7 +547,6 @@ Output shape:
|
|
|
512
547
|
{
|
|
513
548
|
"protocolVersion": 1,
|
|
514
549
|
"result": {
|
|
515
|
-
"schemaVersion": "adversary.review.v1",
|
|
516
550
|
"adversary": {
|
|
517
551
|
"name": "adversarylabs/example"
|
|
518
552
|
},
|
|
@@ -524,12 +558,17 @@ Output shape:
|
|
|
524
558
|
"observations": [],
|
|
525
559
|
"findings": [],
|
|
526
560
|
"suppressed": {
|
|
561
|
+
"observations": 0,
|
|
527
562
|
"findings": 0
|
|
528
563
|
}
|
|
529
564
|
}
|
|
530
565
|
}
|
|
531
566
|
```
|
|
532
567
|
|
|
568
|
+
The package exports the exact CLI schema at
|
|
569
|
+
`@adversarylabs/sdk/schemas/adversary.review.v1`. `writeOutput(...)` and
|
|
570
|
+
`runFromEnvironment(...)` validate the complete envelope against that schema before writing.
|
|
571
|
+
|
|
533
572
|
## Development
|
|
534
573
|
|
|
535
574
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export declare const DEFAULT_INPUT_PATH = "/adversary/input.json";
|
|
2
2
|
export declare const DEFAULT_OUTPUT_PATH = "/adversary/output.json";
|
|
3
3
|
export declare const ADVERSARY_RUN_PROTOCOL_VERSION = 1;
|
|
4
|
-
export declare const REVIEW_RESULT_SCHEMA_VERSION = "adversary.review.v1";
|
|
5
4
|
export declare const Severity: {
|
|
6
5
|
readonly Info: "info";
|
|
7
6
|
readonly Low: "low";
|
|
@@ -120,7 +119,6 @@ export interface ReviewFinding {
|
|
|
120
119
|
evidence: Evidence[];
|
|
121
120
|
recommendation?: string;
|
|
122
121
|
remediation?: Remediation;
|
|
123
|
-
synthesisSource?: "rule" | "generic";
|
|
124
122
|
tags?: string[];
|
|
125
123
|
metadata?: Record<string, unknown>;
|
|
126
124
|
}
|
|
@@ -156,7 +154,6 @@ export interface ReviewPolicy {
|
|
|
156
154
|
severityOverrides?: Record<string, Severity>;
|
|
157
155
|
}
|
|
158
156
|
export interface ReviewResult {
|
|
159
|
-
schemaVersion: typeof REVIEW_RESULT_SCHEMA_VERSION;
|
|
160
157
|
adversary: {
|
|
161
158
|
name: string;
|
|
162
159
|
version?: string;
|
|
@@ -168,10 +165,10 @@ export interface ReviewResult {
|
|
|
168
165
|
assessment?: ReviewAssessment;
|
|
169
166
|
positives: ReviewNote[];
|
|
170
167
|
observations: ReviewNote[];
|
|
171
|
-
scores?: ReviewScore[];
|
|
172
168
|
findings: ReviewFinding[];
|
|
173
169
|
opinion?: ReviewOpinion;
|
|
174
170
|
suppressed: {
|
|
171
|
+
observations: number;
|
|
175
172
|
findings: number;
|
|
176
173
|
};
|
|
177
174
|
timing?: {
|
|
@@ -185,7 +182,27 @@ export interface ReviewResult {
|
|
|
185
182
|
}
|
|
186
183
|
export interface AdversaryRunEnvelope {
|
|
187
184
|
protocolVersion: typeof ADVERSARY_RUN_PROTOCOL_VERSION;
|
|
188
|
-
result:
|
|
185
|
+
result: WireReviewResult;
|
|
186
|
+
}
|
|
187
|
+
export interface WireEvidence {
|
|
188
|
+
file?: string;
|
|
189
|
+
line?: number;
|
|
190
|
+
endLine?: number;
|
|
191
|
+
message?: string;
|
|
192
|
+
snippet?: string;
|
|
193
|
+
metadata?: Record<string, unknown>;
|
|
194
|
+
}
|
|
195
|
+
export interface WireReviewFinding extends Omit<ReviewFinding, "evidence"> {
|
|
196
|
+
evidence: WireEvidence[];
|
|
197
|
+
}
|
|
198
|
+
export interface WireReviewNote extends Omit<ReviewNote, "evidence"> {
|
|
199
|
+
evidence?: WireEvidence[];
|
|
200
|
+
}
|
|
201
|
+
export interface WireReviewResult extends Omit<ReviewResult, "positives" | "observations" | "findings" | "suppressedFindings"> {
|
|
202
|
+
positives: WireReviewNote[];
|
|
203
|
+
observations: WireReviewNote[];
|
|
204
|
+
findings: WireReviewFinding[];
|
|
205
|
+
suppressedFindings?: WireReviewFinding[];
|
|
189
206
|
}
|
|
190
207
|
export interface RuntimeInput {
|
|
191
208
|
source: {
|
|
@@ -286,7 +303,8 @@ export declare class Adversary {
|
|
|
286
303
|
}
|
|
287
304
|
export declare function createAdversaryRunEnvelope(result: ReviewResult): AdversaryRunEnvelope;
|
|
288
305
|
export declare function parseInput(path?: string): Promise<RuntimeInput>;
|
|
289
|
-
export declare function writeOutput(output:
|
|
306
|
+
export declare function writeOutput(output: AdversaryRunEnvelope, path?: string): Promise<void>;
|
|
307
|
+
export declare function validateRunEnvelope(output: unknown): Promise<void>;
|
|
290
308
|
export declare function normalizeConfidence(confidence: ConfidenceInput, thresholds?: ConfidenceThresholds): Confidence;
|
|
291
309
|
export declare function rankFindings(findings: ReviewFinding[]): ReviewFinding[];
|
|
292
310
|
export declare class JsonRenderer implements ReviewRenderer {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
3
4
|
export const DEFAULT_INPUT_PATH = "/adversary/input.json";
|
|
4
5
|
export const DEFAULT_OUTPUT_PATH = "/adversary/output.json";
|
|
5
6
|
export const ADVERSARY_RUN_PROTOCOL_VERSION = 1;
|
|
6
|
-
export const REVIEW_RESULT_SCHEMA_VERSION = "adversary.review.v1";
|
|
7
7
|
const verboseValues = new Set(["1", "true", "TRUE", "yes", "YES"]);
|
|
8
|
+
let envelopeValidator;
|
|
8
9
|
export const Severity = {
|
|
9
10
|
Info: "info",
|
|
10
11
|
Low: "low",
|
|
@@ -208,9 +209,61 @@ export class Adversary {
|
|
|
208
209
|
export function createAdversaryRunEnvelope(result) {
|
|
209
210
|
return {
|
|
210
211
|
protocolVersion: ADVERSARY_RUN_PROTOCOL_VERSION,
|
|
211
|
-
result,
|
|
212
|
+
result: toWireReviewResult(result),
|
|
212
213
|
};
|
|
213
214
|
}
|
|
215
|
+
function toWireReviewResult(result) {
|
|
216
|
+
return omitUndefined({
|
|
217
|
+
adversary: omitUndefined(result.adversary),
|
|
218
|
+
target: omitUndefined(result.target),
|
|
219
|
+
assessment: result.assessment === undefined ? undefined : omitUndefined({ ...result.assessment }),
|
|
220
|
+
positives: result.positives.map(toWireReviewNote),
|
|
221
|
+
observations: result.observations.map(toWireReviewNote),
|
|
222
|
+
findings: result.findings.map(toWireFinding),
|
|
223
|
+
opinion: result.opinion === undefined ? undefined : omitUndefined({ ...result.opinion }),
|
|
224
|
+
suppressed: result.suppressed,
|
|
225
|
+
timing: result.timing === undefined ? undefined : omitUndefined(result.timing),
|
|
226
|
+
suppressedFindings: result.suppressedFindings?.map(toWireFinding),
|
|
227
|
+
rawObservations: result.rawObservations,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function toWireReviewNote(note) {
|
|
231
|
+
return omitUndefined({
|
|
232
|
+
key: note.key,
|
|
233
|
+
summary: note.summary,
|
|
234
|
+
evidence: note.evidence?.map(toWireEvidence),
|
|
235
|
+
metadata: note.metadata,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function toWireFinding(finding) {
|
|
239
|
+
return omitUndefined({
|
|
240
|
+
id: finding.id,
|
|
241
|
+
ruleId: finding.ruleId,
|
|
242
|
+
groupKey: finding.groupKey,
|
|
243
|
+
title: finding.title,
|
|
244
|
+
category: finding.category,
|
|
245
|
+
severity: finding.severity,
|
|
246
|
+
confidence: finding.confidence,
|
|
247
|
+
summary: finding.summary,
|
|
248
|
+
whyItMatters: finding.whyItMatters,
|
|
249
|
+
impact: finding.impact,
|
|
250
|
+
evidence: finding.evidence.map(toWireEvidence),
|
|
251
|
+
recommendation: finding.recommendation,
|
|
252
|
+
remediation: finding.remediation === undefined ? undefined : omitUndefined({ ...finding.remediation }),
|
|
253
|
+
tags: finding.tags,
|
|
254
|
+
metadata: finding.metadata,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function toWireEvidence(evidence) {
|
|
258
|
+
return omitUndefined({
|
|
259
|
+
file: evidence.location?.file,
|
|
260
|
+
line: evidence.location?.line,
|
|
261
|
+
endLine: evidence.location?.endLine,
|
|
262
|
+
message: evidence.message ?? evidence.label,
|
|
263
|
+
snippet: evidence.snippet,
|
|
264
|
+
metadata: evidence.data,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
214
267
|
export async function parseInput(path = DEFAULT_INPUT_PATH) {
|
|
215
268
|
const raw = await readFile(path, "utf8");
|
|
216
269
|
const parsed = JSON.parse(raw);
|
|
@@ -226,9 +279,21 @@ export async function parseInput(path = DEFAULT_INPUT_PATH) {
|
|
|
226
279
|
return parsed;
|
|
227
280
|
}
|
|
228
281
|
export async function writeOutput(output, path = DEFAULT_OUTPUT_PATH) {
|
|
282
|
+
await validateRunEnvelope(output);
|
|
229
283
|
await mkdir(dirname(path), { recursive: true });
|
|
230
284
|
await writeFile(path, `${JSON.stringify(output, null, 2)}\n`, "utf8");
|
|
231
285
|
}
|
|
286
|
+
export async function validateRunEnvelope(output) {
|
|
287
|
+
let validator = envelopeValidator;
|
|
288
|
+
if (validator === undefined) {
|
|
289
|
+
const schema = JSON.parse(await readFile(new URL("../schemas/adversary.review.v1.schema.json", import.meta.url), "utf8"));
|
|
290
|
+
validator = new Ajv2020({ allErrors: true, strict: true }).compile(schema);
|
|
291
|
+
envelopeValidator = validator;
|
|
292
|
+
}
|
|
293
|
+
if (!validator(output)) {
|
|
294
|
+
throw new Error(`Invalid adversary.review.v1 envelope: ${JSON.stringify(validator.errors)}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
232
297
|
export function normalizeConfidence(confidence, thresholds = DEFAULT_CONFIDENCE_THRESHOLDS) {
|
|
233
298
|
if (isConfidence(confidence)) {
|
|
234
299
|
return confidence;
|
|
@@ -270,7 +335,7 @@ export class JsonRenderer {
|
|
|
270
335
|
this.write = write;
|
|
271
336
|
}
|
|
272
337
|
render(result) {
|
|
273
|
-
this.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
338
|
+
this.write(`${JSON.stringify(toWireReviewResult(result), null, 2)}\n`);
|
|
274
339
|
}
|
|
275
340
|
}
|
|
276
341
|
export class TerminalRenderer {
|
|
@@ -292,10 +357,12 @@ export class TerminalRenderer {
|
|
|
292
357
|
lines.push(normalizeParagraph(result.assessment.summary), "");
|
|
293
358
|
}
|
|
294
359
|
}
|
|
295
|
-
|
|
360
|
+
const scoreNotes = result.observations.filter(isScoreReviewNote);
|
|
361
|
+
const additionalObservations = result.observations.filter((note) => !isScoreReviewNote(note));
|
|
362
|
+
if (scoreNotes.length > 0) {
|
|
296
363
|
lines.push("Scores", "");
|
|
297
|
-
for (const
|
|
298
|
-
lines.push(
|
|
364
|
+
for (const note of scoreNotes) {
|
|
365
|
+
lines.push(note.summary);
|
|
299
366
|
}
|
|
300
367
|
lines.push("");
|
|
301
368
|
}
|
|
@@ -306,9 +373,9 @@ export class TerminalRenderer {
|
|
|
306
373
|
}
|
|
307
374
|
lines.push("");
|
|
308
375
|
}
|
|
309
|
-
if (
|
|
376
|
+
if (additionalObservations.length > 0) {
|
|
310
377
|
lines.push("Additional observations", "");
|
|
311
|
-
for (const observation of
|
|
378
|
+
for (const observation of additionalObservations) {
|
|
312
379
|
lines.push(`- ${normalizeParagraph(observation.summary)}`);
|
|
313
380
|
}
|
|
314
381
|
lines.push("");
|
|
@@ -456,9 +523,9 @@ function createReviewCollector() {
|
|
|
456
523
|
}
|
|
457
524
|
function buildReviewResult(input) {
|
|
458
525
|
const thresholds = input.policy.confidenceThresholds ?? DEFAULT_CONFIDENCE_THRESHOLDS;
|
|
459
|
-
const
|
|
526
|
+
const synthesis = synthesizeObservationFindings(input.collector.observations, thresholds, input.registry);
|
|
460
527
|
const allFindings = deduplicateFindings([
|
|
461
|
-
...
|
|
528
|
+
...synthesis.findings.map((finding) => ({ finding, deduplicate: true })),
|
|
462
529
|
...input.collector.findings,
|
|
463
530
|
]).map((finding) => calibrateFindingSeverity(finding, input.policy));
|
|
464
531
|
const ranked = rankFindings(allFindings);
|
|
@@ -479,9 +546,11 @@ function buildReviewResult(input) {
|
|
|
479
546
|
}
|
|
480
547
|
}
|
|
481
548
|
const positives = selectPositiveSignals(input.collector.positives);
|
|
482
|
-
const reviewObservations = deduplicateReviewObservations(
|
|
549
|
+
const reviewObservations = deduplicateReviewObservations([
|
|
550
|
+
...input.collector.reviewObservations,
|
|
551
|
+
...deduplicateScores(input.collector.scores).map(scoreToReviewNote),
|
|
552
|
+
], positives);
|
|
483
553
|
return omitUndefined({
|
|
484
|
-
schemaVersion: REVIEW_RESULT_SCHEMA_VERSION,
|
|
485
554
|
adversary: input.adversary,
|
|
486
555
|
target: omitUndefined({
|
|
487
556
|
repository: input.repository,
|
|
@@ -490,10 +559,10 @@ function buildReviewResult(input) {
|
|
|
490
559
|
assessment: input.collector.assessment ?? synthesizeAssessment(eligible, positives),
|
|
491
560
|
positives,
|
|
492
561
|
observations: reviewObservations,
|
|
493
|
-
scores: input.collector.scores.length > 0 ? deduplicateScores(input.collector.scores) : undefined,
|
|
494
562
|
findings: eligible,
|
|
495
563
|
opinion: input.collector.opinion ?? synthesizeOpinion(eligible),
|
|
496
564
|
suppressed: {
|
|
565
|
+
observations: synthesis.suppressedObservations,
|
|
497
566
|
findings: suppressedFindings.length,
|
|
498
567
|
},
|
|
499
568
|
timing: input.timing,
|
|
@@ -504,17 +573,19 @@ function buildReviewResult(input) {
|
|
|
504
573
|
function synthesizeObservationFindings(observations, thresholds, registry) {
|
|
505
574
|
const grouped = new Map();
|
|
506
575
|
const seen = new Set();
|
|
576
|
+
let suppressedObservations = 0;
|
|
507
577
|
for (const observation of observations) {
|
|
508
578
|
const rule = registry.lookup(observation.ruleId);
|
|
509
579
|
const groupKey = observation.groupKey ?? defaultObservationGroupKey(observation, rule);
|
|
510
580
|
const dedupeKey = stableStringify({ groupKey, observation });
|
|
511
581
|
if (observation.deduplicate !== false && seen.has(dedupeKey)) {
|
|
582
|
+
suppressedObservations += 1;
|
|
512
583
|
continue;
|
|
513
584
|
}
|
|
514
585
|
seen.add(dedupeKey);
|
|
515
586
|
grouped.set(groupKey, [...(grouped.get(groupKey) ?? []), observation]);
|
|
516
587
|
}
|
|
517
|
-
|
|
588
|
+
const findings = [...grouped.entries()].map(([groupKey, group]) => {
|
|
518
589
|
const first = group[0];
|
|
519
590
|
if (first === undefined) {
|
|
520
591
|
throw new Error("Cannot synthesize finding from an empty observation group.");
|
|
@@ -547,13 +618,13 @@ function synthesizeObservationFindings(observations, thresholds, registry) {
|
|
|
547
618
|
evidence,
|
|
548
619
|
recommendation: recommendation.length > 0 ? recommendation : undefined,
|
|
549
620
|
remediation: synthesis.remediation ?? first.remediation,
|
|
550
|
-
synthesisSource,
|
|
551
621
|
tags: synthesis.tags ?? uniqueStrings(group.flatMap((observation) => observation.tags ?? [])),
|
|
552
622
|
metadata: synthesis.metadata ?? first.metadata,
|
|
553
623
|
};
|
|
554
624
|
assertFindingInput(finding, `adversary aggregate rule "${first.ruleId}"`);
|
|
555
625
|
return finding;
|
|
556
626
|
});
|
|
627
|
+
return { findings, suppressedObservations };
|
|
557
628
|
}
|
|
558
629
|
function normalizeFindingInput(input, occurrence = 0) {
|
|
559
630
|
return omitUndefined({
|
|
@@ -623,10 +694,17 @@ function deduplicateEvidence(evidence) {
|
|
|
623
694
|
}
|
|
624
695
|
function normalizeEvidence(input) {
|
|
625
696
|
const legacy = input;
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
697
|
+
const hasLocation = legacy.location !== undefined ||
|
|
698
|
+
legacy.file !== undefined ||
|
|
699
|
+
legacy.line !== undefined ||
|
|
700
|
+
legacy.endLine !== undefined;
|
|
701
|
+
const location = hasLocation
|
|
702
|
+
? omitUndefined({
|
|
703
|
+
file: legacy.location?.file ?? legacy.file,
|
|
704
|
+
line: legacy.location?.line ?? legacy.line,
|
|
705
|
+
endLine: legacy.location?.endLine ?? legacy.endLine,
|
|
706
|
+
})
|
|
707
|
+
: undefined;
|
|
630
708
|
return omitUndefined({
|
|
631
709
|
location,
|
|
632
710
|
label: input.label,
|
|
@@ -769,6 +847,25 @@ function deduplicateScores(scores) {
|
|
|
769
847
|
}
|
|
770
848
|
return result.sort((left, right) => compareStrings(left.key, right.key));
|
|
771
849
|
}
|
|
850
|
+
function scoreToReviewNote(score) {
|
|
851
|
+
return {
|
|
852
|
+
key: `score.${score.key}`,
|
|
853
|
+
summary: formatScore(score),
|
|
854
|
+
metadata: {
|
|
855
|
+
kind: "score",
|
|
856
|
+
score: omitUndefined({
|
|
857
|
+
key: score.key,
|
|
858
|
+
label: score.label,
|
|
859
|
+
score: score.score,
|
|
860
|
+
max: score.max,
|
|
861
|
+
summary: score.summary,
|
|
862
|
+
}),
|
|
863
|
+
},
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function isScoreReviewNote(note) {
|
|
867
|
+
return note.metadata?.kind === "score" && isRecord(note.metadata.score);
|
|
868
|
+
}
|
|
772
869
|
function observationToEvidence(observation) {
|
|
773
870
|
const data = isRecord(observation.evidence)
|
|
774
871
|
? observation.evidence
|
|
@@ -1254,6 +1351,14 @@ function optionalEvidence(value, field) {
|
|
|
1254
1351
|
optionalPositiveInteger(value.location.line, `${field}.location.line`);
|
|
1255
1352
|
optionalPositiveInteger(value.location.endLine, `${field}.location.endLine`);
|
|
1256
1353
|
}
|
|
1354
|
+
const line = value.location?.line ?? input.line;
|
|
1355
|
+
const endLine = value.location?.endLine ?? input.endLine;
|
|
1356
|
+
if (endLine !== undefined && line === undefined) {
|
|
1357
|
+
throw new Error(`${field}.endLine requires line.`);
|
|
1358
|
+
}
|
|
1359
|
+
if (endLine !== undefined && line !== undefined && endLine < line) {
|
|
1360
|
+
throw new Error(`${field}.endLine must not precede line.`);
|
|
1361
|
+
}
|
|
1257
1362
|
if (value.data !== undefined && !isRecord(value.data)) {
|
|
1258
1363
|
throw new Error(`${field}.data must be an object.`);
|
|
1259
1364
|
}
|