@certscore/mcp 0.2.12 → 0.2.16
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 +151 -45
- package/dist/certscore-mcp.mjs +4377 -3997
- package/dist/index.js +1 -1
- package/dist/server.d.ts +42 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +388 -124
- package/dist/tools.d.ts +43 -59
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +1303 -65
- package/package.json +13 -12
- package/server-light.json +21 -6
- package/server.json +2 -2
package/dist/tools.js
CHANGED
|
@@ -4,7 +4,55 @@ export const MAX_EVIDENCE_PACKET_CHARS = 250_000;
|
|
|
4
4
|
const EVIDENCE_STRING_CHARS = 4_000;
|
|
5
5
|
const EVIDENCE_ARRAY_ITEMS = 40;
|
|
6
6
|
const EVIDENCE_OBJECT_KEYS = 80;
|
|
7
|
-
|
|
7
|
+
const MAX_TOOL_TEXT_CHARS = 8_000;
|
|
8
|
+
const LEGAL_REVIEW_DISCLAIMER = "CertScore results are automated public-web observations for human and agentic review, not legal advice, certification, or a compliance determination.";
|
|
9
|
+
const SCAN_PROVENANCE_GROUNDING = "retrievalMode describes how the current tool response obtained the scan; creationDecision describes whether the original scan request created or reused a scan only when that decision is retained. Never infer an unknown creationDecision from scan_id_lookup. For a reused or retrieved existing scan, use only persisted scanFrom and timestamps. Never infer its original scan region from the current request, the user's location, or a default execution region. If persisted region or timestamps are unavailable, report them as unavailable.";
|
|
10
|
+
const INTERPRETATION_STATEMENT = "The CertScore score covers observable public-web scan signals only. Do not infer technologies that are not listed in the returned evidence or any legal compliance status.";
|
|
11
|
+
const SCAN_BUNDLE_RESPONSE_CONTRACT = `Response contract: Report only observed CertScore evidence and CertScore classifications. criticality, priority, and confidence are CertScore metadata; regulatory review lenses are non-determinative CertScore review context—not legal severity, legal exposure, or a compliance determination. Absence of captured consent-action evidence does not establish what happens after Accept, Reject, or Decline. A confirmed post-refusal observation with termination.kind=evidence_satisfied means the observer intentionally stopped after retaining qualifying evidence; do not treat that termination as uncertainty about the returned observation. Keep any separately returned coverage limitation scoped to what was not measured. Do not extrapolate an observed embed, vendor, or request into unobserved cookies, fingerprinting, tracking, or processing, and do not infer violations or compliance beyond what CertScore observed. ${SCAN_PROVENANCE_GROUNDING}`;
|
|
12
|
+
const SCAN_BUNDLE_INTERPRETATION_STATEMENT = "Report only observed CertScore evidence and persisted CertScore classifications. Without corresponding captured post-action evidence, do not infer what Accept, Reject, Decline, or another consent action would do; say the scan does not establish what happens after that action. When postRefusalObservation is confirmed and termination.kind is evidence_satisfied, state the returned post-refusal observation directly and explain that observation stopped intentionally after qualifying evidence was retained. Do not characterize that termination as uncertainty about the observation; mention unmeasured longer-term persistence only when relevant. Do not speculate that an observed embed, vendor, or request may cause additional cookies, fingerprinting, tracking, or processing unless CertScore observed that behavior. Treat returned priority or severity as a CertScore classification, not regulatory criticality or legal exposure; prefer ‘observed privacy risk signal’ or ‘CertScore finding’. Do not infer unobserved technologies, legal compliance, or a legal violation from scores or findings.";
|
|
13
|
+
const COMPACT_SCAN_BUNDLE_INTERPRETATION_STATEMENT = "Use only returned CertScore observations and classifications. Do not infer unobserved technologies, post-consent behavior, legal compliance, or violations. Treat priority and severity as CertScore metadata.";
|
|
14
|
+
const OBSERVATION_ONLY_DISCLAIMER = `${LEGAL_REVIEW_DISCLAIMER} No-go, not-observed, and limited-coverage results are not proof of compliance.`;
|
|
15
|
+
const COMPACT_OBSERVATION_ONLY_DISCLAIMER = "Automated public-web observation, not legal advice or a compliance determination; missing or limited evidence is not proof of compliance.";
|
|
16
|
+
function interpretationGuidance(statement = INTERPRETATION_STATEMENT) {
|
|
17
|
+
return {
|
|
18
|
+
scoreLabel: "CertScore score",
|
|
19
|
+
observableSignalsOnly: true,
|
|
20
|
+
doNotInferUnobservedTechnologies: true,
|
|
21
|
+
doNotInferLegalComplianceStatus: true,
|
|
22
|
+
statement
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function toolResultSummary(payload) {
|
|
26
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
27
|
+
return "CertScore tool call completed. Read structuredContent for the result.";
|
|
28
|
+
}
|
|
29
|
+
const record = payload;
|
|
30
|
+
const type = typeof record.type === "string" ? record.type : "result";
|
|
31
|
+
const status = typeof record.status === "string" ? `; status=${record.status}` : "";
|
|
32
|
+
const scanId = typeof record.scanId === "string" ? `; scanId=${record.scanId}` : "";
|
|
33
|
+
const score = typeof record.score === "number" ? `; CertScore score=${record.score}` : "";
|
|
34
|
+
const recordLinks = record.links && typeof record.links === "object" && !Array.isArray(record.links)
|
|
35
|
+
? record.links
|
|
36
|
+
: null;
|
|
37
|
+
const reportUrl = typeof record.reportUrl === "string" && record.reportUrl.trim()
|
|
38
|
+
? record.reportUrl.trim()
|
|
39
|
+
: typeof recordLinks?.report === "string" && recordLinks.report.trim()
|
|
40
|
+
? recordLinks.report.trim()
|
|
41
|
+
: null;
|
|
42
|
+
const report = reportUrl ? `; full report=${reportUrl}` : "";
|
|
43
|
+
const provenanceRecord = record.provenance && typeof record.provenance === "object" && !Array.isArray(record.provenance)
|
|
44
|
+
? record.provenance
|
|
45
|
+
: null;
|
|
46
|
+
const retrieval = typeof provenanceRecord?.retrievalMode === "string" ? `; retrieval=${provenanceRecord.retrievalMode}` : "";
|
|
47
|
+
const creation = typeof provenanceRecord?.creationDecision === "string" ? `; creation=${provenanceRecord.creationDecision}` : "";
|
|
48
|
+
const provenance = retrieval || creation
|
|
49
|
+
? `${retrieval}${creation}`
|
|
50
|
+
: typeof provenanceRecord?.mode === "string"
|
|
51
|
+
? `; provenance=${provenanceRecord.mode}`
|
|
52
|
+
: "";
|
|
53
|
+
return `CertScore ${type}${status}${scanId}${score}${provenance}${report}. Full result is in structuredContent.`;
|
|
54
|
+
}
|
|
55
|
+
export function toToolResult(payload, text) {
|
|
8
56
|
const structuredContent = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
9
57
|
? payload
|
|
10
58
|
: { value: payload };
|
|
@@ -13,37 +61,258 @@ export function toToolResult(payload) {
|
|
|
13
61
|
content: [
|
|
14
62
|
{
|
|
15
63
|
type: "text",
|
|
16
|
-
text:
|
|
64
|
+
text: text ?? toolResultSummary(payload)
|
|
17
65
|
}
|
|
18
66
|
]
|
|
19
67
|
};
|
|
20
68
|
}
|
|
21
69
|
export function toToolError(error) {
|
|
22
|
-
const
|
|
23
|
-
?
|
|
24
|
-
|
|
70
|
+
const responseRecord = error instanceof CertScoreError && error.responseBody && typeof error.responseBody === "object" && !Array.isArray(error.responseBody)
|
|
71
|
+
? error.responseBody
|
|
72
|
+
: null;
|
|
73
|
+
const terminalError = responseRecord?.error && typeof responseRecord.error === "object" && !Array.isArray(responseRecord.error)
|
|
74
|
+
? responseRecord.error
|
|
75
|
+
: null;
|
|
76
|
+
const status = error instanceof CertScoreError ? error.status : undefined;
|
|
77
|
+
const retryable = typeof terminalError?.retryable === "boolean"
|
|
78
|
+
? terminalError.retryable
|
|
79
|
+
: status === 429 || (typeof status === "number" && status >= 500);
|
|
80
|
+
const retryAfterSeconds = typeof terminalError?.retryAfterSeconds === "number"
|
|
81
|
+
? terminalError.retryAfterSeconds
|
|
82
|
+
: error instanceof CertScoreError && "retryAfterSeconds" in error && typeof error.retryAfterSeconds === "number"
|
|
83
|
+
? error.retryAfterSeconds
|
|
84
|
+
: retryable
|
|
85
|
+
? 30
|
|
86
|
+
: null;
|
|
87
|
+
const code = error instanceof CertScoreError ? error.code : "internal_error";
|
|
88
|
+
const message = error instanceof Error ? error.message : "Unknown CertScore MCP error.";
|
|
89
|
+
const creationRateLimit = terminalError?.creationRateLimit && typeof terminalError.creationRateLimit === "object" && !Array.isArray(terminalError.creationRateLimit)
|
|
90
|
+
? terminalError.creationRateLimit
|
|
91
|
+
: null;
|
|
92
|
+
const recommendedNextAction = typeof terminalError?.recommendedNextAction === "string"
|
|
93
|
+
? terminalError.recommendedNextAction
|
|
94
|
+
: creationRateLimit
|
|
95
|
+
? `No scan was created. Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request. If the limit continues after that delay, contact support@certscore.ai.`
|
|
96
|
+
: retryable
|
|
97
|
+
? `Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request. Stop and contact support@certscore.ai if the error repeats.`
|
|
98
|
+
: "Correct the request using the error details, then retry only if the requested operation is still appropriate.";
|
|
99
|
+
const payload = {
|
|
100
|
+
error: {
|
|
101
|
+
code,
|
|
102
|
+
message,
|
|
103
|
+
retryable,
|
|
104
|
+
retryAfterSeconds,
|
|
105
|
+
recommendedNextAction,
|
|
106
|
+
...(creationRateLimit
|
|
107
|
+
? { creationRateLimit }
|
|
108
|
+
: {}),
|
|
109
|
+
...(error instanceof CertScoreError ? {
|
|
25
110
|
name: error.name,
|
|
26
|
-
message: error.message,
|
|
27
111
|
status: error.status,
|
|
28
|
-
code: error.code,
|
|
29
|
-
retryAfterSeconds: "retryAfterSeconds" in error ? error.retryAfterSeconds : undefined,
|
|
30
112
|
responseBody: truncateErrorResponseBody(error.responseBody)
|
|
31
|
-
}
|
|
113
|
+
} : {})
|
|
32
114
|
}
|
|
33
|
-
|
|
34
|
-
error: {
|
|
35
|
-
name: error instanceof Error ? error.name : "Error",
|
|
36
|
-
message: error instanceof Error ? error.message : "Unknown CertScore MCP error."
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
// MCP validates structuredContent against the tool's success output schema,
|
|
40
|
-
// including for isError results. Keep errors in text content so a bounded
|
|
41
|
-
// error envelope cannot be rejected as an invalid success resource.
|
|
115
|
+
};
|
|
42
116
|
return {
|
|
43
117
|
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
44
118
|
isError: true
|
|
45
119
|
};
|
|
46
120
|
}
|
|
121
|
+
export function toInvalidArgumentsToolError(errorMessage) {
|
|
122
|
+
const tool = errorMessage.match(/tool ([a-z_]+)/i)?.[1] ?? null;
|
|
123
|
+
const field = errorMessage.match(/\bat ([a-zA-Z0-9_.-]+)/)?.[1]
|
|
124
|
+
?? (errorMessage.includes("url") ? "url" : errorMessage.includes("scanId") ? "scanId" : null);
|
|
125
|
+
const missing = /required|expected string, received undefined/i.test(errorMessage);
|
|
126
|
+
const message = field
|
|
127
|
+
? missing
|
|
128
|
+
? `The ${field} field is required.`
|
|
129
|
+
: `The ${field} field is invalid.`
|
|
130
|
+
: `The arguments for ${tool ?? "this tool"} are invalid.`;
|
|
131
|
+
const recommendedNextAction = field === "url"
|
|
132
|
+
? "Provide a public URL or domain."
|
|
133
|
+
: field === "scanId"
|
|
134
|
+
? "Provide the stable scanId returned by certscore_scan_site."
|
|
135
|
+
: "Correct the named fields using the tool input schema, then retry.";
|
|
136
|
+
const error = {
|
|
137
|
+
code: "invalid_arguments",
|
|
138
|
+
message,
|
|
139
|
+
...(field ? { field } : {}),
|
|
140
|
+
retryable: false,
|
|
141
|
+
retryAfterSeconds: null,
|
|
142
|
+
recommendedNextAction,
|
|
143
|
+
mcpCode: -32602
|
|
144
|
+
};
|
|
145
|
+
const payload = tool === "certscore_scan_site"
|
|
146
|
+
? {
|
|
147
|
+
type: "certscore_tool_error",
|
|
148
|
+
status: "invalid_arguments",
|
|
149
|
+
error,
|
|
150
|
+
scoreLabel: "CertScore score",
|
|
151
|
+
provenance: scanProvenance({}, "unknown"),
|
|
152
|
+
interpretationGuidance: interpretationGuidance(),
|
|
153
|
+
recommendedNextTool: null,
|
|
154
|
+
recommendedNextAction,
|
|
155
|
+
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
156
|
+
}
|
|
157
|
+
: { error };
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
160
|
+
...(tool === "certscore_scan_site" ? { structuredContent: payload } : {}),
|
|
161
|
+
isError: true
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function toInvalidScanIdToolError() {
|
|
165
|
+
const error = {
|
|
166
|
+
code: "invalid_scan_id",
|
|
167
|
+
field: "scanId",
|
|
168
|
+
message: "The scanId must be the canonical UUID returned by certscore_scan_site.",
|
|
169
|
+
retryable: false,
|
|
170
|
+
retryAfterSeconds: null,
|
|
171
|
+
recommendedNextAction: "Use the unchanged scanId returned by certscore_scan_site. Do not use placeholders, report URLs, domains, or job IDs.",
|
|
172
|
+
mcpCode: -32602
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
176
|
+
isError: true
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function terminalErrorForResult(value) {
|
|
180
|
+
const existing = value.error && typeof value.error === "object" && !Array.isArray(value.error)
|
|
181
|
+
? value.error
|
|
182
|
+
: null;
|
|
183
|
+
if (existing) {
|
|
184
|
+
const retryable = existing.retryable === true;
|
|
185
|
+
return {
|
|
186
|
+
code: typeof existing.code === "string" ? existing.code : String(value.status ?? "scan_failed"),
|
|
187
|
+
message: typeof existing.message === "string" ? existing.message : "The scan did not produce a canonical result.",
|
|
188
|
+
retryable,
|
|
189
|
+
retryAfterSeconds: typeof existing.retryAfterSeconds === "number" ? existing.retryAfterSeconds : retryable ? 30 : null,
|
|
190
|
+
recommendedNextAction: typeof existing.recommendedNextAction === "string"
|
|
191
|
+
? existing.recommendedNextAction
|
|
192
|
+
: retryable
|
|
193
|
+
? "Wait for the recommended delay, then retry certscore_scan_site with freshness=refresh."
|
|
194
|
+
: "Stop and review the scan limitations before deciding whether to change the URL."
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (value.status === "completed_limited" && value.noGo) {
|
|
198
|
+
const retryable = value.noGo.retryLikelyToHelp === true;
|
|
199
|
+
return {
|
|
200
|
+
code: typeof value.noGo.reasonCode === "string" ? value.noGo.reasonCode : "completed_limited",
|
|
201
|
+
message: typeof value.noGo.explanation === "string" ? value.noGo.explanation : "The scan completed with a no-go limitation.",
|
|
202
|
+
retryable,
|
|
203
|
+
retryAfterSeconds: retryable ? 30 : null,
|
|
204
|
+
recommendedNextAction: typeof value.noGo.recommendedNextAction === "string"
|
|
205
|
+
? value.noGo.recommendedNextAction
|
|
206
|
+
: "Review the retained limitation and change the URL or site state before retrying."
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const fallback = {
|
|
210
|
+
failed: {
|
|
211
|
+
code: "scanner_runtime_failure",
|
|
212
|
+
message: "The scan ended before a canonical result could be produced.",
|
|
213
|
+
retryable: true,
|
|
214
|
+
retryAfterSeconds: 30,
|
|
215
|
+
recommendedNextAction: "Wait 30 seconds, then retry certscore_scan_site with freshness=refresh. Stop if the failure repeats."
|
|
216
|
+
},
|
|
217
|
+
expired: {
|
|
218
|
+
code: "scan_expired",
|
|
219
|
+
message: "The scan expired before a canonical result was available.",
|
|
220
|
+
retryable: true,
|
|
221
|
+
retryAfterSeconds: 30,
|
|
222
|
+
recommendedNextAction: "Wait 30 seconds, then retry certscore_scan_site with freshness=refresh."
|
|
223
|
+
},
|
|
224
|
+
rate_limited: {
|
|
225
|
+
code: "rate_limited",
|
|
226
|
+
message: "The scan is rate limited.",
|
|
227
|
+
retryable: true,
|
|
228
|
+
retryAfterSeconds: typeof value.retryAfterSeconds === "number" ? value.retryAfterSeconds : 30,
|
|
229
|
+
recommendedNextAction: "Wait for the recommended delay, then retry the same certscore_scan_site request."
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
return fallback[String(value.status)] ?? null;
|
|
233
|
+
}
|
|
234
|
+
function scanProvenance(value, fallbackMode) {
|
|
235
|
+
const executionMode = value.executionMode === "new_scan" || value.executionMode === "reused_scan"
|
|
236
|
+
? value.executionMode
|
|
237
|
+
: null;
|
|
238
|
+
const reused = typeof value.reused === "boolean" ? value.reused : executionMode === "reused_scan" ? true : executionMode === "new_scan" ? false : null;
|
|
239
|
+
const mode = executionMode === "new_scan"
|
|
240
|
+
? "new_scan_started"
|
|
241
|
+
: executionMode === "reused_scan" || reused === true
|
|
242
|
+
? "existing_completed_scan_reused"
|
|
243
|
+
: fallbackMode;
|
|
244
|
+
const retrievalMode = fallbackMode === "existing_scan_retrieved"
|
|
245
|
+
? "scan_id_lookup"
|
|
246
|
+
: executionMode !== null || reused !== null
|
|
247
|
+
? "creation_response"
|
|
248
|
+
: "unknown";
|
|
249
|
+
const creationDecision = executionMode === "new_scan" || reused === false
|
|
250
|
+
? "new_scan"
|
|
251
|
+
: executionMode === "reused_scan" || reused === true
|
|
252
|
+
? "reused_scan"
|
|
253
|
+
: "unknown";
|
|
254
|
+
const retainedAgeSeconds = typeof value.reusedScanAgeSeconds === "number" && Number.isFinite(value.reusedScanAgeSeconds) && value.reusedScanAgeSeconds >= 0
|
|
255
|
+
? Math.floor(value.reusedScanAgeSeconds)
|
|
256
|
+
: null;
|
|
257
|
+
const completedAtMs = typeof value.completedAt === "string" ? Date.parse(value.completedAt) : Number.NaN;
|
|
258
|
+
const completedAgeSeconds = Number.isFinite(completedAtMs)
|
|
259
|
+
? Math.max(0, Math.floor((Date.now() - completedAtMs) / 1_000))
|
|
260
|
+
: null;
|
|
261
|
+
const scanAgeSeconds = retrievalMode === "creation_response"
|
|
262
|
+
? retainedAgeSeconds ?? completedAgeSeconds
|
|
263
|
+
: completedAgeSeconds;
|
|
264
|
+
return {
|
|
265
|
+
mode,
|
|
266
|
+
retrievalMode,
|
|
267
|
+
creationDecision,
|
|
268
|
+
scanAgeSeconds,
|
|
269
|
+
executionMode,
|
|
270
|
+
reused,
|
|
271
|
+
freshnessDecision: typeof value.freshnessDecision === "string" ? value.freshnessDecision : null
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown") {
|
|
275
|
+
const status = String(value.status ?? "");
|
|
276
|
+
const active = status === "queued" || status === "running" || status === "finalizing";
|
|
277
|
+
const usable = status === "completed" || status === "completed_limited";
|
|
278
|
+
const error = terminalErrorForResult(value);
|
|
279
|
+
const stableScanId = typeof value.scanId === "string" && value.scanId.trim()
|
|
280
|
+
? value.scanId.trim()
|
|
281
|
+
: typeof value.scan_id === "string" && value.scan_id.trim()
|
|
282
|
+
? value.scan_id.trim()
|
|
283
|
+
: null;
|
|
284
|
+
const reportUrl = usable
|
|
285
|
+
? typeof value.reportUrl === "string" && value.reportUrl.trim()
|
|
286
|
+
? value.reportUrl.trim()
|
|
287
|
+
: typeof value.links?.report === "string" && value.links.report.trim()
|
|
288
|
+
? value.links.report.trim()
|
|
289
|
+
: stableScanId
|
|
290
|
+
? `https://certscore.ai/scan/${encodeURIComponent(stableScanId)}`
|
|
291
|
+
: null
|
|
292
|
+
: null;
|
|
293
|
+
return {
|
|
294
|
+
...value,
|
|
295
|
+
error,
|
|
296
|
+
reportUrl,
|
|
297
|
+
scoreLabel: "CertScore score",
|
|
298
|
+
provenance: scanProvenance(value, fallbackProvenanceMode),
|
|
299
|
+
interpretationGuidance: interpretationGuidance(),
|
|
300
|
+
recommendedNextTool: active ? "certscore_get_scan_status" : usable ? "certscore_get_scan_bundle" : null,
|
|
301
|
+
recommendedNextAction: error?.recommendedNextAction ?? value.recommendedNextAction ?? (active
|
|
302
|
+
? `Poll certscore_get_scan_status with scanId ${value.scanId ?? value.jobId} after the recommended delay.`
|
|
303
|
+
: usable
|
|
304
|
+
? `Call certscore_get_scan_bundle with scanId ${value.scanId ?? value.jobId} for the canonical findings and limitations.`
|
|
305
|
+
: "Review the result and retained limitations."),
|
|
306
|
+
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
export function withMcpScanProvenanceGuidance(value, fallbackProvenanceMode) {
|
|
310
|
+
const guided = withMcpAgentGuidance(value, fallbackProvenanceMode);
|
|
311
|
+
return {
|
|
312
|
+
...guided,
|
|
313
|
+
interpretationGuidance: interpretationGuidance(`${INTERPRETATION_STATEMENT} ${SCAN_PROVENANCE_GROUNDING}`)
|
|
314
|
+
};
|
|
315
|
+
}
|
|
47
316
|
export function boundEvidencePacket(payload, maxSerializedChars = MAX_EVIDENCE_PACKET_CHARS) {
|
|
48
317
|
const originalSerializedChars = measureSerializedChars(payload);
|
|
49
318
|
if (originalSerializedChars <= maxSerializedChars) {
|
|
@@ -87,6 +356,225 @@ export function boundEvidencePacket(payload, maxSerializedChars = MAX_EVIDENCE_P
|
|
|
87
356
|
function measureSerializedChars(value) {
|
|
88
357
|
return JSON.stringify(value)?.length ?? 0;
|
|
89
358
|
}
|
|
359
|
+
function measureSerializedBytes(value) {
|
|
360
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
361
|
+
}
|
|
362
|
+
function updateBundleActualBytes(bundle) {
|
|
363
|
+
bundle.mcpMetadata.actualBytes = 0;
|
|
364
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
365
|
+
const measured = measureSerializedBytes(bundle);
|
|
366
|
+
if (bundle.mcpMetadata.actualBytes === measured) {
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
bundle.mcpMetadata.actualBytes = measured;
|
|
370
|
+
}
|
|
371
|
+
return bundle.mcpMetadata.actualBytes;
|
|
372
|
+
}
|
|
373
|
+
function boundedText(value, maxChars) {
|
|
374
|
+
if (typeof value !== "string") {
|
|
375
|
+
return value;
|
|
376
|
+
}
|
|
377
|
+
if (value.length <= maxChars) {
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
const marker = "…[truncated]";
|
|
381
|
+
const contentLimit = Math.max(1, maxChars - marker.length);
|
|
382
|
+
const candidate = value.slice(0, contentLimit);
|
|
383
|
+
const boundary = candidate.search(/\s+\S*$/);
|
|
384
|
+
const bounded = boundary >= Math.floor(contentLimit * 0.6)
|
|
385
|
+
? candidate.slice(0, boundary)
|
|
386
|
+
: candidate;
|
|
387
|
+
return `${bounded.trimEnd()}${marker}`;
|
|
388
|
+
}
|
|
389
|
+
function compactFindingLink(finding) {
|
|
390
|
+
const self = typeof finding.links?.self === "string" && finding.links.self.trim()
|
|
391
|
+
? finding.links.self.trim()
|
|
392
|
+
: typeof finding.evidence?.excerpt?.evidenceUrl === "string" && finding.evidence.excerpt.evidenceUrl.trim()
|
|
393
|
+
? finding.evidence.excerpt.evidenceUrl.trim()
|
|
394
|
+
: null;
|
|
395
|
+
return self ? { self: self.slice(0, 2_048) } : undefined;
|
|
396
|
+
}
|
|
397
|
+
function withoutFindingDisclaimer(finding) {
|
|
398
|
+
const { disclaimer: _disclaimer, ...rest } = finding;
|
|
399
|
+
return rest;
|
|
400
|
+
}
|
|
401
|
+
function compactBundleFinding(finding, tier = "standard") {
|
|
402
|
+
const evidence = finding.evidence && typeof finding.evidence === "object" && !Array.isArray(finding.evidence)
|
|
403
|
+
? finding.evidence
|
|
404
|
+
: {};
|
|
405
|
+
const core = tier === "core";
|
|
406
|
+
const links = compactFindingLink(finding);
|
|
407
|
+
if (core) {
|
|
408
|
+
const compactNextStep = typeof finding.nextStep === "string" && finding.nextStep.trim().length <= 64
|
|
409
|
+
? finding.nextStep.trim()
|
|
410
|
+
: null;
|
|
411
|
+
return {
|
|
412
|
+
type: finding.type,
|
|
413
|
+
id: finding.id,
|
|
414
|
+
label: boundedText(finding.label, 140),
|
|
415
|
+
criticality: finding.criticality,
|
|
416
|
+
confidence: finding.confidence,
|
|
417
|
+
plainEnglish: boundedText(finding.plainEnglish, 180),
|
|
418
|
+
...(compactNextStep ? { nextStep: compactNextStep } : {}),
|
|
419
|
+
evidenceUrl: links?.self ?? null
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
type: finding.type,
|
|
424
|
+
id: finding.id,
|
|
425
|
+
scanId: finding.scanId,
|
|
426
|
+
label: boundedText(finding.label, 140),
|
|
427
|
+
criticality: finding.criticality,
|
|
428
|
+
confidence: finding.confidence,
|
|
429
|
+
plainEnglish: boundedText(finding.plainEnglish, 280),
|
|
430
|
+
...(finding.resultDisposition ? { resultDisposition: finding.resultDisposition } : {}),
|
|
431
|
+
...(finding.noGo ? { noGo: finding.noGo } : {}),
|
|
432
|
+
reviewLenses: Array.isArray(finding.reviewLenses) ? finding.reviewLenses.slice(0, 2) : [],
|
|
433
|
+
evidence: {
|
|
434
|
+
basis: evidence.basis,
|
|
435
|
+
summary: boundedText(evidence.summary, 220),
|
|
436
|
+
...(evidence.phase !== undefined ? { phase: evidence.phase } : {}),
|
|
437
|
+
exampleCount: evidence.exampleCount,
|
|
438
|
+
examplesShown: evidence.examplesShown,
|
|
439
|
+
...(evidence.hasTimingAnchor !== undefined ? { hasTimingAnchor: evidence.hasTimingAnchor } : {}),
|
|
440
|
+
...(evidence.hasVendorAnchor !== undefined ? { hasVendorAnchor: evidence.hasVendorAnchor } : {}),
|
|
441
|
+
...(evidence.hasConsentContext !== undefined ? { hasConsentContext: evidence.hasConsentContext } : {}),
|
|
442
|
+
...(evidence.hasPolicyAnchor !== undefined ? { hasPolicyAnchor: evidence.hasPolicyAnchor } : {})
|
|
443
|
+
},
|
|
444
|
+
...(finding.nextStep !== undefined ? { nextStep: boundedText(finding.nextStep, 180) } : {}),
|
|
445
|
+
...(links ? { links } : {})
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function deduplicatedFullReport(input) {
|
|
449
|
+
const report = input.report;
|
|
450
|
+
const residual = { ...report };
|
|
451
|
+
const deduplicatedSections = [];
|
|
452
|
+
const returnedFindings = new Map(input.findings.flatMap((finding) => typeof finding.id === "string" ? [[finding.id, finding]] : []));
|
|
453
|
+
for (const key of ["findings", "topFindings"]) {
|
|
454
|
+
const section = residual[key];
|
|
455
|
+
const sectionFindingIds = Array.isArray(section)
|
|
456
|
+
? section.flatMap((finding) => finding && typeof finding === "object" && typeof finding.id === "string"
|
|
457
|
+
? [finding.id]
|
|
458
|
+
: [])
|
|
459
|
+
: [];
|
|
460
|
+
const coveredByCanonicalProjection = sectionFindingIds.every((id) => {
|
|
461
|
+
const finding = returnedFindings.get(id);
|
|
462
|
+
return finding
|
|
463
|
+
&& typeof finding.plainEnglish === "string"
|
|
464
|
+
&& typeof finding.evidence?.summary === "string"
|
|
465
|
+
&& compactFindingLink(finding) !== undefined;
|
|
466
|
+
});
|
|
467
|
+
if (Array.isArray(section) && sectionFindingIds.length === section.length && coveredByCanonicalProjection) {
|
|
468
|
+
delete residual[key];
|
|
469
|
+
deduplicatedSections.push(`fullReport.${key}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if ("transportSecurity" in residual && JSON.stringify(residual.transportSecurity) === JSON.stringify(input.transportSecurity)) {
|
|
473
|
+
delete residual.transportSecurity;
|
|
474
|
+
deduplicatedSections.push("fullReport.transportSecurity");
|
|
475
|
+
}
|
|
476
|
+
return { deduplicatedSections, residual };
|
|
477
|
+
}
|
|
478
|
+
function distinctSummaryFindings(findings) {
|
|
479
|
+
const labels = new Set();
|
|
480
|
+
return findings.filter((finding) => {
|
|
481
|
+
const key = typeof finding.label === "string" && finding.label.trim().length > 0
|
|
482
|
+
? finding.label.trim().toLocaleLowerCase()
|
|
483
|
+
: typeof finding.id === "string"
|
|
484
|
+
? finding.id
|
|
485
|
+
: JSON.stringify(finding);
|
|
486
|
+
if (labels.has(key))
|
|
487
|
+
return false;
|
|
488
|
+
labels.add(key);
|
|
489
|
+
return true;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
const POST_REFUSAL_FINDING_IDS = new Set([
|
|
493
|
+
"post_refusal_non_essential_activity",
|
|
494
|
+
"pre_consent_storage_not_cleared",
|
|
495
|
+
"refusal_signal_contradicts_action",
|
|
496
|
+
]);
|
|
497
|
+
function postRefusalTruncationPriority(finding) {
|
|
498
|
+
const id = typeof finding.id === "string" ? finding.id : "";
|
|
499
|
+
if (POST_REFUSAL_FINDING_IDS.has(id))
|
|
500
|
+
return 0;
|
|
501
|
+
if (/^pre_consent_|preconsent/i.test(id))
|
|
502
|
+
return 2;
|
|
503
|
+
return 1;
|
|
504
|
+
}
|
|
505
|
+
function prioritizeFindingsForBoundedBundle(findings) {
|
|
506
|
+
return findings
|
|
507
|
+
.map((finding, index) => ({ finding, index }))
|
|
508
|
+
.sort((left, right) => postRefusalTruncationPriority(left.finding) - postRefusalTruncationPriority(right.finding) ||
|
|
509
|
+
left.index - right.index)
|
|
510
|
+
.map(({ finding }) => finding);
|
|
511
|
+
}
|
|
512
|
+
function compactPriorityEvidenceSummary(summary) {
|
|
513
|
+
const firstDigest = Array.isArray(summary.digests) ? summary.digests[0] : null;
|
|
514
|
+
const firstReference = summary.references && typeof summary.references === "object" && !Array.isArray(summary.references)
|
|
515
|
+
? Object.entries(summary.references).find(([, value]) => typeof value === "string")
|
|
516
|
+
: null;
|
|
517
|
+
return {
|
|
518
|
+
digests: firstDigest ? [{
|
|
519
|
+
findingId: firstDigest.findingId,
|
|
520
|
+
basis: firstDigest.basis ?? null,
|
|
521
|
+
summary: boundedText(firstDigest.summary, 180) ?? null,
|
|
522
|
+
phase: firstDigest.phase ?? null,
|
|
523
|
+
evidenceUrl: firstDigest.evidenceUrl ?? firstReference?.[1] ?? null
|
|
524
|
+
}] : [],
|
|
525
|
+
evidenceAvailable: summary.evidenceAvailable === true,
|
|
526
|
+
evidenceSafetyNotes: [],
|
|
527
|
+
references: firstDigest ? {} : firstReference ? { [firstReference[0]]: firstReference[1] } : {}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function bundleEvidenceSummary(evidence, findings, links, includeDiagnostics) {
|
|
531
|
+
const digests = findings.slice(0, 3).map((finding) => {
|
|
532
|
+
const findingEvidence = finding.evidence && typeof finding.evidence === "object" && !Array.isArray(finding.evidence)
|
|
533
|
+
? finding.evidence
|
|
534
|
+
: {};
|
|
535
|
+
return {
|
|
536
|
+
findingId: finding.id,
|
|
537
|
+
basis: findingEvidence.basis ?? null,
|
|
538
|
+
summary: boundedText(findingEvidence.summary, 500) ?? null,
|
|
539
|
+
phase: findingEvidence.phase ?? null,
|
|
540
|
+
evidenceUrl: findingEvidence.excerpt && typeof findingEvidence.excerpt === "object" && !Array.isArray(findingEvidence.excerpt)
|
|
541
|
+
? findingEvidence.excerpt.evidenceUrl ?? null
|
|
542
|
+
: typeof links.findings === "string"
|
|
543
|
+
? links.findings
|
|
544
|
+
: typeof links.report === "string"
|
|
545
|
+
? links.report
|
|
546
|
+
: null
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
return {
|
|
550
|
+
digests,
|
|
551
|
+
evidenceAvailable: digests.length > 0 || Object.keys(evidence).length > 0,
|
|
552
|
+
evidenceSafetyNotes: Array.isArray(evidence.evidenceSafetyNotes)
|
|
553
|
+
? evidence.evidenceSafetyNotes.slice(0, 3).map((note) => boundedText(note, 300))
|
|
554
|
+
: [],
|
|
555
|
+
references: Object.fromEntries(Object.entries(links).filter(([key, value]) => ["findings", "pulse", "report", "preConsentCookiesTrackers"].includes(key) && typeof value === "string")),
|
|
556
|
+
...(includeDiagnostics ? {
|
|
557
|
+
projectionDiagnostics: compactEvidenceValue(evidence.projectionDiagnostics ?? null, {
|
|
558
|
+
arrayItems: 12,
|
|
559
|
+
depth: 4,
|
|
560
|
+
objectKeys: 24,
|
|
561
|
+
stringChars: 600
|
|
562
|
+
}),
|
|
563
|
+
coverageDiagnostics: compactEvidenceValue(evidence.coverageDiagnostics ?? null, {
|
|
564
|
+
arrayItems: 12,
|
|
565
|
+
depth: 4,
|
|
566
|
+
objectKeys: 24,
|
|
567
|
+
stringChars: 600
|
|
568
|
+
}),
|
|
569
|
+
policySurfaceCoverage: compactEvidenceValue(evidence.policySurfaceCoverage ?? null, {
|
|
570
|
+
arrayItems: 12,
|
|
571
|
+
depth: 4,
|
|
572
|
+
objectKeys: 24,
|
|
573
|
+
stringChars: 600
|
|
574
|
+
})
|
|
575
|
+
} : {})
|
|
576
|
+
};
|
|
577
|
+
}
|
|
90
578
|
function compactEvidenceValue(value, options) {
|
|
91
579
|
if (value === null || value === undefined) {
|
|
92
580
|
return value;
|
|
@@ -285,11 +773,21 @@ export function limitPreConsentRows(payload, options = {}) {
|
|
|
285
773
|
return payload;
|
|
286
774
|
}
|
|
287
775
|
const maxRows = Math.min(200, Math.max(1, options.maxRows ?? 200));
|
|
288
|
-
const
|
|
289
|
-
|
|
776
|
+
const reportedTotal = payload.summary && typeof payload.summary === "object" && !Array.isArray(payload.summary)
|
|
777
|
+
&& Number.isInteger(payload.summary.rowCount)
|
|
778
|
+
? Number(payload.summary.rowCount)
|
|
779
|
+
: 0;
|
|
780
|
+
const total = Math.max(rows.length, reportedTotal);
|
|
781
|
+
const returnedRows = rows.slice(0, maxRows);
|
|
782
|
+
const truncated = total > returnedRows.length;
|
|
290
783
|
return {
|
|
291
784
|
...payload,
|
|
292
|
-
rows:
|
|
785
|
+
rows: returnedRows,
|
|
786
|
+
evidenceMetadata: {
|
|
787
|
+
total,
|
|
788
|
+
returned: returnedRows.length,
|
|
789
|
+
truncated
|
|
790
|
+
},
|
|
293
791
|
summary: {
|
|
294
792
|
...(payload.summary && typeof payload.summary === "object" && !Array.isArray(payload.summary) ? payload.summary : {}),
|
|
295
793
|
totalRowCount: total,
|
|
@@ -297,72 +795,812 @@ export function limitPreConsentRows(payload, options = {}) {
|
|
|
297
795
|
}
|
|
298
796
|
};
|
|
299
797
|
}
|
|
798
|
+
function uniqueBoundedStrings(values, maxItems, maxChars) {
|
|
799
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0).map((value) => value.slice(0, maxChars)))].slice(0, maxItems);
|
|
800
|
+
}
|
|
801
|
+
function compactPreConsentRow(row) {
|
|
802
|
+
const cookieDetails = Array.isArray(row.cookieDetails) ? row.cookieDetails : [];
|
|
803
|
+
const domains = uniqueBoundedStrings([
|
|
804
|
+
...(Array.isArray(row.domains) ? row.domains : []),
|
|
805
|
+
row.host,
|
|
806
|
+
row.registrableDomain
|
|
807
|
+
], 12, 253);
|
|
808
|
+
const purposes = Array.isArray(row.purposes) ? row.purposes : [];
|
|
809
|
+
return {
|
|
810
|
+
id: String(row.id ?? `${row.kind ?? "unknown"}:${row.name ?? row.vendor ?? row.host ?? "observed-item"}`).slice(0, 512),
|
|
811
|
+
kind: ["cookie", "tracker", "request", "storage"].includes(row.kind) ? row.kind : "unknown",
|
|
812
|
+
name: String(row.name ?? row.vendor ?? row.host ?? "Observed item").slice(0, 256),
|
|
813
|
+
cookieNames: uniqueBoundedStrings(cookieDetails.map((cookie) => cookie && typeof cookie === "object" ? cookie.name : null), 24, 256),
|
|
814
|
+
vendor: typeof row.vendor === "string" ? row.vendor.slice(0, 160) : null,
|
|
815
|
+
purpose: typeof row.purpose === "string" ? row.purpose.slice(0, 160) : typeof purposes[0] === "string" ? purposes[0].slice(0, 160) : null,
|
|
816
|
+
category: typeof row.category === "string" ? row.category.slice(0, 160) : null,
|
|
817
|
+
confidence: ["high", "medium", "low"].includes(row.confidence) ? row.confidence : "unknown",
|
|
818
|
+
firstObservedAtMs: Number.isInteger(row.firstObservedAtMs) && row.firstObservedAtMs >= 0 ? row.firstObservedAtMs : null,
|
|
819
|
+
domains,
|
|
820
|
+
requestCount: Number.isInteger(row.requestCount) && row.requestCount >= 0 ? row.requestCount : null,
|
|
821
|
+
evidenceClassification: {
|
|
822
|
+
basis: "public_report_projection",
|
|
823
|
+
phase: "pre_consent",
|
|
824
|
+
observedBeforeConsent: row.observedBeforeConsent !== false,
|
|
825
|
+
party: ["first_party", "third_party", "mixed"].includes(row.party) ? row.party : "unknown",
|
|
826
|
+
priority: ["high", "medium", "review_needed", "contextual"].includes(row.priority) ? row.priority : "unknown"
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function preConsentBundleSection(payload, maxRows) {
|
|
831
|
+
const sourceRows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
832
|
+
const total = Math.max(sourceRows.length, Number.isInteger(payload.summary?.rowCount) ? payload.summary.rowCount : 0);
|
|
833
|
+
const rows = sourceRows.slice(0, maxRows).map((row) => compactPreConsentRow(row));
|
|
834
|
+
return {
|
|
835
|
+
summary: {
|
|
836
|
+
rowCount: total,
|
|
837
|
+
trackerCount: Number.isInteger(payload.summary?.trackerCount) ? payload.summary.trackerCount : 0,
|
|
838
|
+
cookieCount: Number.isInteger(payload.summary?.cookieCount) ? payload.summary.cookieCount : 0,
|
|
839
|
+
requestCount: Number.isInteger(payload.summary?.requestCount) ? payload.summary.requestCount : 0,
|
|
840
|
+
vendorCount: Number.isInteger(payload.summary?.vendorCount) ? payload.summary.vendorCount : 0,
|
|
841
|
+
domainCount: Number.isInteger(payload.summary?.domainCount) ? payload.summary.domainCount : 0
|
|
842
|
+
},
|
|
843
|
+
rows,
|
|
844
|
+
total,
|
|
845
|
+
returned: rows.length,
|
|
846
|
+
truncated: total > rows.length
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function neutralExecutiveSummary(value) {
|
|
850
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
const summary = value;
|
|
854
|
+
const score = typeof summary.score === "number" ? summary.score : null;
|
|
855
|
+
const scoreMetadata = summary.scoreMetadata && typeof summary.scoreMetadata === "object" && !Array.isArray(summary.scoreMetadata)
|
|
856
|
+
? { ...summary.scoreMetadata, metricLabel: "CertScore score" }
|
|
857
|
+
: summary.scoreMetadata;
|
|
858
|
+
const trackerFootprint = summary.trackerFootprint && typeof summary.trackerFootprint === "object" && !Array.isArray(summary.trackerFootprint)
|
|
859
|
+
? Object.fromEntries(Object.entries(summary.trackerFootprint).filter(([, entry]) => typeof entry === "number" || entry === null))
|
|
860
|
+
: undefined;
|
|
861
|
+
const policySurfaces = Array.isArray(summary.policySurfaces)
|
|
862
|
+
? summary.policySurfaces.slice(0, 5).flatMap((surface) => {
|
|
863
|
+
if (!surface || typeof surface !== "object" || Array.isArray(surface))
|
|
864
|
+
return [];
|
|
865
|
+
const row = surface;
|
|
866
|
+
return [{
|
|
867
|
+
type: boundedText(row.type, 80),
|
|
868
|
+
title: boundedText(row.title, 160),
|
|
869
|
+
url: boundedText(row.url, 2_048)
|
|
870
|
+
}];
|
|
871
|
+
})
|
|
872
|
+
: undefined;
|
|
873
|
+
return {
|
|
874
|
+
completionSummary: boundedText(summary.completionSummary, 280),
|
|
875
|
+
domain: boundedText(summary.domain, 253),
|
|
876
|
+
score,
|
|
877
|
+
scoreLabel: score === null ? "CertScore score" : `CertScore score: ${score}/100`,
|
|
878
|
+
...(scoreMetadata ? { scoreMetadata } : {}),
|
|
879
|
+
riskLevel: summary.riskLevel,
|
|
880
|
+
actionLabel: summary.actionLabel,
|
|
881
|
+
issuesToReview: summary.issuesToReview,
|
|
882
|
+
thirdPartyRequests: summary.thirdPartyRequests,
|
|
883
|
+
trackingClassifiedThirdPartyRequests: summary.trackingClassifiedThirdPartyRequests,
|
|
884
|
+
cookiesPreConsent: summary.cookiesPreConsent,
|
|
885
|
+
nonEssentialPreConsentStorage: summary.nonEssentialPreConsentStorage,
|
|
886
|
+
unclassifiedPreConsentStorageCount: summary.unclassifiedPreConsentStorageCount,
|
|
887
|
+
storageMetricLabel: boundedText(summary.storageMetricLabel, 120),
|
|
888
|
+
storageMetricScope: boundedText(summary.storageMetricScope, 120),
|
|
889
|
+
storageMetricStatus: boundedText(summary.storageMetricStatus, 120),
|
|
890
|
+
storageMetricExplanation: boundedText(summary.storageMetricExplanation, 280),
|
|
891
|
+
consentPlatform: boundedText(summary.consentPlatform, 160),
|
|
892
|
+
...(trackerFootprint ? { trackerFootprint } : {}),
|
|
893
|
+
...(policySurfaces ? { policySurfaces } : {}),
|
|
894
|
+
scanTimeSeconds: summary.scanTimeSeconds
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function findingText(finding, priorityLabel = "criticality") {
|
|
898
|
+
const evidence = finding.evidence && typeof finding.evidence === "object" && !Array.isArray(finding.evidence)
|
|
899
|
+
? finding.evidence
|
|
900
|
+
: {};
|
|
901
|
+
const lenses = Array.isArray(finding.reviewLenses) && finding.reviewLenses.length > 0
|
|
902
|
+
? finding.reviewLenses.slice(0, 2).join(", ")
|
|
903
|
+
: "not classified";
|
|
904
|
+
const nextStep = typeof finding.nextStep === "string" && finding.nextStep.trim()
|
|
905
|
+
? `; canonical next step=${boundedText(finding.nextStep.trim(), 180)}`
|
|
906
|
+
: "";
|
|
907
|
+
return `- ${finding.label ?? finding.id ?? "Projected finding"}; ${priorityLabel}=${finding.criticality ?? "unknown"}; confidence=${finding.confidence ?? "unknown"}; observation=${finding.plainEnglish ?? evidence.summary ?? "No compact description available"}; evidence=${evidence.basis ?? "unknown"}/${evidence.phase ?? "phase unknown"}: ${evidence.summary ?? "No compact evidence summary available"}; review lenses=${lenses}${nextStep}.`;
|
|
908
|
+
}
|
|
909
|
+
function executiveOverviewText(summary) {
|
|
910
|
+
if (!summary)
|
|
911
|
+
return null;
|
|
912
|
+
const tracker = summary.trackerFootprint && typeof summary.trackerFootprint === "object" && !Array.isArray(summary.trackerFootprint)
|
|
913
|
+
? summary.trackerFootprint
|
|
914
|
+
: {};
|
|
915
|
+
const policies = Array.isArray(summary.policySurfaces)
|
|
916
|
+
? summary.policySurfaces.flatMap((surface) => {
|
|
917
|
+
if (!surface || typeof surface !== "object" || Array.isArray(surface))
|
|
918
|
+
return [];
|
|
919
|
+
const row = surface;
|
|
920
|
+
return typeof row.title === "string" ? [row.title] : typeof row.type === "string" ? [row.type] : [];
|
|
921
|
+
}).slice(0, 5)
|
|
922
|
+
: [];
|
|
923
|
+
const values = [
|
|
924
|
+
`CMP/consent platform=${summary.consentPlatform ?? "not returned"}`,
|
|
925
|
+
`third-party requests=${summary.thirdPartyRequests ?? "unknown"}`,
|
|
926
|
+
`non-essential pre-consent storage=${summary.nonEssentialPreConsentStorage ?? summary.cookiesPreConsent ?? "unknown"}`,
|
|
927
|
+
`tracker vendors=${tracker.vendors ?? "unknown"}`,
|
|
928
|
+
`tracker domains=${tracker.domains ?? "unknown"}`,
|
|
929
|
+
`policy surfaces=${policies.length > 0 ? policies.join(", ") : "none returned"}`
|
|
930
|
+
];
|
|
931
|
+
return `Canonical report overview: ${values.join("; ")}.`;
|
|
932
|
+
}
|
|
933
|
+
function preConsentRowText(row, priorityLabel = "priority") {
|
|
934
|
+
const cookieNames = Array.isArray(row.cookieNames) && row.cookieNames.length > 0
|
|
935
|
+
? `; cookies=${row.cookieNames.slice(0, 8).join(", ")}${row.cookieNames.length > 8 ? ", …" : ""}`
|
|
936
|
+
: "";
|
|
937
|
+
const domains = Array.isArray(row.domains) && row.domains.length > 0
|
|
938
|
+
? `; domains=${row.domains.slice(0, 4).join(", ")}${row.domains.length > 4 ? ", …" : ""}`
|
|
939
|
+
: "";
|
|
940
|
+
const timing = typeof row.firstObservedAtMs === "number" ? `${(row.firstObservedAtMs / 1_000).toFixed(3)}s` : "unknown";
|
|
941
|
+
const classification = row.evidenceClassification && typeof row.evidenceClassification === "object"
|
|
942
|
+
? row.evidenceClassification
|
|
943
|
+
: {};
|
|
944
|
+
return `- ${row.kind}: ${row.name}${cookieNames}; vendor=${row.vendor ?? "unknown"}; purpose=${row.purpose ?? "unknown"}; category=${row.category ?? "unknown"}; first observed=${timing}${domains}; evidence=${classification.basis ?? "unknown"}/${classification.phase ?? "unknown"}/${classification.party ?? "unknown"}; observedBeforeConsent=${classification.observedBeforeConsent ?? "unknown"}; ${priorityLabel}=${classification.priority ?? "unknown"}; confidence=${row.confidence ?? "unknown"}.`;
|
|
945
|
+
}
|
|
946
|
+
function reportUrlFor(value) {
|
|
947
|
+
const scanId = extractScanId(value);
|
|
948
|
+
return typeof value.reportUrl === "string" && value.reportUrl.trim()
|
|
949
|
+
? value.reportUrl.trim()
|
|
950
|
+
: typeof value.links?.report === "string" && value.links.report.trim()
|
|
951
|
+
? value.links.report.trim()
|
|
952
|
+
: scanId
|
|
953
|
+
? `https://certscore.ai/scan/${encodeURIComponent(scanId)}`
|
|
954
|
+
: null;
|
|
955
|
+
}
|
|
956
|
+
function canonicalScanProvenanceText(value) {
|
|
957
|
+
const present = (field) => typeof field === "string" && field.trim() ? field.trim() : "unavailable";
|
|
958
|
+
const numeric = (field) => typeof field === "number" && Number.isFinite(field) ? String(field) : "unavailable";
|
|
959
|
+
return `Canonical scan provenance: scanId=${present(extractScanId(value))}; scanFrom/execution region=${present(value.scanFrom)}; completedAt=${present(value.completedAt)}; startedAt=${present(value.startedAt)}; createdAt=${present(value.createdAt)}; retrieval mode=${present(value.provenance?.retrievalMode)}; original creation decision=${present(value.provenance?.creationDecision)}; scan age seconds=${numeric(value.provenance?.scanAgeSeconds)}; compatibility provenance mode=${present(value.provenance?.mode)}.`;
|
|
960
|
+
}
|
|
961
|
+
export function scanStatusText(value) {
|
|
962
|
+
const reportUrl = reportUrlFor(value);
|
|
963
|
+
return [
|
|
964
|
+
`CertScore scan status: status=${value.status ?? "unknown"}.`,
|
|
965
|
+
canonicalScanProvenanceText(value),
|
|
966
|
+
`Full report: ${reportUrl ?? "not available"}.`,
|
|
967
|
+
OBSERVATION_ONLY_DISCLAIMER,
|
|
968
|
+
INTERPRETATION_STATEMENT,
|
|
969
|
+
SCAN_PROVENANCE_GROUNDING
|
|
970
|
+
].join("\n");
|
|
971
|
+
}
|
|
972
|
+
function boundedResultText(header, bodyLines, value) {
|
|
973
|
+
const footer = [OBSERVATION_ONLY_DISCLAIMER, INTERPRETATION_STATEMENT];
|
|
974
|
+
const reportUrl = reportUrlFor(value);
|
|
975
|
+
const lines = [
|
|
976
|
+
header,
|
|
977
|
+
`Provenance: retrieval=${value.provenance?.retrievalMode ?? "unknown"}; original creation=${value.provenance?.creationDecision ?? "unknown"}; scan age seconds=${value.provenance?.scanAgeSeconds ?? "unavailable"}.`,
|
|
978
|
+
`Full report: ${reportUrl ?? "not available"}.`
|
|
979
|
+
];
|
|
980
|
+
let rendered = 0;
|
|
981
|
+
for (const line of bodyLines) {
|
|
982
|
+
if ([...lines, line, ...footer].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
983
|
+
break;
|
|
984
|
+
lines.push(line);
|
|
985
|
+
rendered += 1;
|
|
986
|
+
}
|
|
987
|
+
if (rendered < bodyLines.length) {
|
|
988
|
+
const omitted = `${bodyLines.length - rendered} additional returned row${bodyLines.length - rendered === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; use structuredContent or the report URL.`;
|
|
989
|
+
if ([...lines, omitted, ...footer].join("\n").length <= MAX_TOOL_TEXT_CHARS)
|
|
990
|
+
lines.push(omitted);
|
|
991
|
+
}
|
|
992
|
+
lines.push(...footer);
|
|
993
|
+
return lines.join("\n");
|
|
994
|
+
}
|
|
995
|
+
export function findingListText(value, label = "Canonical projected findings") {
|
|
996
|
+
const findings = Array.isArray(value.findings) ? value.findings : [];
|
|
997
|
+
const pagination = value.pagination && typeof value.pagination === "object" && !Array.isArray(value.pagination)
|
|
998
|
+
? value.pagination
|
|
999
|
+
: {};
|
|
1000
|
+
const total = typeof pagination.total === "number" ? pagination.total : findings.length;
|
|
1001
|
+
const returned = typeof pagination.returned === "number" ? pagination.returned : findings.length;
|
|
1002
|
+
const truncated = pagination.truncated === true;
|
|
1003
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1004
|
+
return boundedResultText(`${label} for scanId=${scanId}: ${returned} of ${total} returned${truncated ? " (truncated)" : ""}. These are canonical projected review signals, not MCP-derived findings.`, findings.map((finding) => findingText(finding)), value);
|
|
1005
|
+
}
|
|
1006
|
+
export function preConsentInventoryText(value) {
|
|
1007
|
+
const rows = Array.isArray(value.rows) ? value.rows : [];
|
|
1008
|
+
const metadata = value.evidenceMetadata && typeof value.evidenceMetadata === "object" && !Array.isArray(value.evidenceMetadata)
|
|
1009
|
+
? value.evidenceMetadata
|
|
1010
|
+
: {};
|
|
1011
|
+
const total = typeof metadata.total === "number"
|
|
1012
|
+
? metadata.total
|
|
1013
|
+
: typeof value.summary?.totalRowCount === "number"
|
|
1014
|
+
? value.summary.totalRowCount
|
|
1015
|
+
: typeof value.summary?.rowCount === "number"
|
|
1016
|
+
? value.summary.rowCount
|
|
1017
|
+
: rows.length;
|
|
1018
|
+
const returned = typeof metadata.returned === "number" ? metadata.returned : rows.length;
|
|
1019
|
+
const truncated = metadata.truncated === true || value.summary?.truncated === true;
|
|
1020
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1021
|
+
const domain = typeof value.domain === "string" ? value.domain : "unknown domain";
|
|
1022
|
+
return boundedResultText(`Pre-consent cookie/tracker evidence for ${domain}; scanId=${scanId}; ${returned} of ${total} rows returned${truncated ? " (truncated)" : ""}. Enumerate only these observed rows.`, rows.map((row) => preConsentRowText(compactPreConsentRow(row))), value);
|
|
1023
|
+
}
|
|
1024
|
+
export function pulseReportText(value, label = "CertScore report") {
|
|
1025
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1026
|
+
const domain = typeof value.domain === "string" ? value.domain : "unknown domain";
|
|
1027
|
+
const score = typeof value.summary?.score === "number"
|
|
1028
|
+
? value.summary.score
|
|
1029
|
+
: typeof value.score === "number"
|
|
1030
|
+
? value.score
|
|
1031
|
+
: null;
|
|
1032
|
+
const findings = findingsFromReport(value);
|
|
1033
|
+
const overview = executiveOverviewText(value.executiveSummary ?? value.summary?.executiveSummary);
|
|
1034
|
+
const body = [
|
|
1035
|
+
...(overview ? [overview] : []),
|
|
1036
|
+
`Canonical projected findings returned in this ${label.toLocaleLowerCase()}: ${findings.length}.`,
|
|
1037
|
+
...findings.map((finding) => findingText(finding))
|
|
1038
|
+
];
|
|
1039
|
+
return boundedResultText(`${label} for ${domain}; scanId=${scanId}${score === null ? "" : `; CertScore score=${score}`}.`, body, value);
|
|
1040
|
+
}
|
|
1041
|
+
export function markdownReportText(value) {
|
|
1042
|
+
const markdown = typeof value.value === "string" ? value.value : "";
|
|
1043
|
+
const excerptLimit = Math.max(0, MAX_TOOL_TEXT_CHARS - 1_500);
|
|
1044
|
+
const excerpt = markdown.length > excerptLimit ? `${markdown.slice(0, excerptLimit)}\n…[markdown truncated for MCP TextContent]` : markdown;
|
|
1045
|
+
return boundedResultText(`CertScore report for scanId=${extractScanId(value) ?? "unknown"}. The following is a bounded canonical report excerpt.`, excerpt ? [excerpt] : ["No Markdown report body was returned."], value);
|
|
1046
|
+
}
|
|
1047
|
+
export function scanBundleText(bundle) {
|
|
1048
|
+
const score = typeof bundle.score === "number" ? `; CertScore score=${bundle.score}` : "";
|
|
1049
|
+
const footer = [OBSERVATION_ONLY_DISCLAIMER, SCAN_BUNDLE_INTERPRETATION_STATEMENT];
|
|
1050
|
+
const lines = [
|
|
1051
|
+
SCAN_BUNDLE_RESPONSE_CONTRACT,
|
|
1052
|
+
`CertScore scan bundle for ${bundle.domain ?? "unknown domain"}; status=${bundle.status ?? "unknown"}${score}; scanId=${bundle.scanId ?? "unknown"}.`,
|
|
1053
|
+
canonicalScanProvenanceText(bundle),
|
|
1054
|
+
`Full report: ${bundle.reportUrl ?? (bundle.scanId ? `https://certscore.ai/scan/${encodeURIComponent(String(bundle.scanId))}` : "not available")}.`
|
|
1055
|
+
];
|
|
1056
|
+
const canAppend = (line) => [...lines, line, ...footer].join("\n").length <= MAX_TOOL_TEXT_CHARS;
|
|
1057
|
+
const append = (line) => {
|
|
1058
|
+
if (!canAppend(line))
|
|
1059
|
+
return false;
|
|
1060
|
+
lines.push(line);
|
|
1061
|
+
return true;
|
|
1062
|
+
};
|
|
1063
|
+
const coverage = bundle.coverage && typeof bundle.coverage === "object" && !Array.isArray(bundle.coverage)
|
|
1064
|
+
? bundle.coverage
|
|
1065
|
+
: null;
|
|
1066
|
+
if (coverage) {
|
|
1067
|
+
append(`Coverage: status=${coverage.status ?? "unknown"}; ${coverage.summary ?? "Review limitations before interpreting absence."}`);
|
|
1068
|
+
}
|
|
1069
|
+
const postRefusal = bundle.postRefusalObservation && typeof bundle.postRefusalObservation === "object" && !Array.isArray(bundle.postRefusalObservation)
|
|
1070
|
+
? bundle.postRefusalObservation
|
|
1071
|
+
: null;
|
|
1072
|
+
if (postRefusal && typeof postRefusal.interpretation === "string") {
|
|
1073
|
+
const termination = postRefusal.termination && typeof postRefusal.termination === "object" && !Array.isArray(postRefusal.termination)
|
|
1074
|
+
? postRefusal.termination
|
|
1075
|
+
: null;
|
|
1076
|
+
const intentionalEvidenceStop = termination?.kind === "evidence_satisfied" && termination.intentional === true
|
|
1077
|
+
? " The observation then stopped intentionally because qualifying evidence had been captured."
|
|
1078
|
+
: "";
|
|
1079
|
+
append(`Reject Path: ${postRefusal.interpretation}${intentionalEvidenceStop}`);
|
|
1080
|
+
for (const limitation of Array.isArray(postRefusal.coverageLimitations)
|
|
1081
|
+
? postRefusal.coverageLimitations.slice(0, 3)
|
|
1082
|
+
: []) {
|
|
1083
|
+
append(`Reject Path coverage limitation: ${limitation}`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const overview = executiveOverviewText(bundle.summary?.executiveSummary);
|
|
1087
|
+
if (overview)
|
|
1088
|
+
append(overview);
|
|
1089
|
+
const transportSecurity = bundle.transportSecurity && typeof bundle.transportSecurity === "object" && !Array.isArray(bundle.transportSecurity)
|
|
1090
|
+
? bundle.transportSecurity
|
|
1091
|
+
: null;
|
|
1092
|
+
if (transportSecurity) {
|
|
1093
|
+
const counts = transportSecurity.observationCounts && typeof transportSecurity.observationCounts === "object"
|
|
1094
|
+
? transportSecurity.observationCounts
|
|
1095
|
+
: {};
|
|
1096
|
+
append(`Transport security: status=${transportSecurity.status ?? "unavailable"}; retained evidence=${transportSecurity.evidenceRetained === true ? "yes" : "no"}; canonical observations=${counts.total ?? 0}; concerns/review=${counts.concernOrReview ?? 0}; unavailable=${counts.unavailable ?? 0}.`);
|
|
1097
|
+
for (const observation of Array.isArray(transportSecurity.observations) ? transportSecurity.observations : []) {
|
|
1098
|
+
append(`Transport observation: ${observation.label ?? observation.id ?? "unknown"}; status=${observation.status ?? "unknown"}; ${observation.summary ?? "Review the structured transport evidence."}`);
|
|
1099
|
+
}
|
|
1100
|
+
for (const limitation of Array.isArray(transportSecurity.limitations) ? transportSecurity.limitations.slice(0, 3) : []) {
|
|
1101
|
+
append(`Transport limitation: ${limitation}`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
const findings = Array.isArray(bundle.findings) ? bundle.findings : [];
|
|
1105
|
+
const findingTotal = bundle.findingsMetadata?.total ?? findings.length;
|
|
1106
|
+
const findingReturned = bundle.findingsMetadata?.returned ?? bundle.findingsMetadata?.shown ?? findings.length;
|
|
1107
|
+
append(`Canonical projected findings: ${findingReturned} of ${findingTotal} returned${bundle.findingsMetadata?.truncated ? " (truncated)" : ""}. These are already-projected review signals, not inferred technologies or legal conclusions.`);
|
|
1108
|
+
let findingsRendered = 0;
|
|
1109
|
+
for (const [index, finding] of findings.entries()) {
|
|
1110
|
+
const next = findingText(finding, "CertScore priority/classification");
|
|
1111
|
+
const remaining = findings.length - index - 1;
|
|
1112
|
+
const reserve = remaining > 0
|
|
1113
|
+
? `${remaining} additional returned finding${remaining === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; see structuredContent or the report URL.`
|
|
1114
|
+
: null;
|
|
1115
|
+
if ([...lines, next, ...(reserve ? [reserve] : []), ...footer].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
1116
|
+
break;
|
|
1117
|
+
lines.push(next);
|
|
1118
|
+
findingsRendered += 1;
|
|
1119
|
+
}
|
|
1120
|
+
if (findingsRendered < findings.length) {
|
|
1121
|
+
append(`${findings.length - findingsRendered} additional returned finding${findings.length - findingsRendered === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; see structuredContent or the report URL.`);
|
|
1122
|
+
}
|
|
1123
|
+
const inventory = bundle.preConsentCookiesTrackers;
|
|
1124
|
+
if (inventory && typeof inventory === "object") {
|
|
1125
|
+
append(`Pre-consent cookie/tracker evidence: ${inventory.returned ?? 0} of ${inventory.total ?? 0} rows returned${inventory.truncated ? " (truncated)" : ""}.`);
|
|
1126
|
+
const rows = Array.isArray(inventory.rows) ? inventory.rows : [];
|
|
1127
|
+
let rowsRendered = 0;
|
|
1128
|
+
for (const [index, row] of rows.entries()) {
|
|
1129
|
+
const next = preConsentRowText(row, "CertScore priority");
|
|
1130
|
+
const remaining = rows.length - index - 1;
|
|
1131
|
+
const reserve = remaining > 0
|
|
1132
|
+
? `${remaining} additional returned pre-consent row${remaining === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; see structuredContent or the report URL.`
|
|
1133
|
+
: null;
|
|
1134
|
+
if ([...lines, next, ...(reserve ? [reserve] : []), ...footer].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
1135
|
+
break;
|
|
1136
|
+
lines.push(next);
|
|
1137
|
+
rowsRendered += 1;
|
|
1138
|
+
}
|
|
1139
|
+
if (rowsRendered < rows.length) {
|
|
1140
|
+
append(`${rows.length - rowsRendered} additional returned pre-consent row${rows.length - rowsRendered === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; see structuredContent or the report URL.`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
append("No row-level pre-consent inventory was available for this result; review coverage and limitations before interpreting absence.");
|
|
1145
|
+
}
|
|
1146
|
+
lines.push(...footer);
|
|
1147
|
+
return lines.join("\n");
|
|
1148
|
+
}
|
|
1149
|
+
function scanBundleTransportSecurity(report, detail) {
|
|
1150
|
+
const source = report.transportSecurity && typeof report.transportSecurity === "object" && !Array.isArray(report.transportSecurity)
|
|
1151
|
+
? report.transportSecurity
|
|
1152
|
+
: null;
|
|
1153
|
+
if (!source) {
|
|
1154
|
+
return {
|
|
1155
|
+
status: "unavailable",
|
|
1156
|
+
evidenceRetained: false,
|
|
1157
|
+
observationCounts: {
|
|
1158
|
+
total: 0,
|
|
1159
|
+
observedPositive: 0,
|
|
1160
|
+
concernOrReview: 0,
|
|
1161
|
+
notObserved: 0,
|
|
1162
|
+
unavailable: 0
|
|
1163
|
+
},
|
|
1164
|
+
observations: [],
|
|
1165
|
+
limitations: [
|
|
1166
|
+
"No canonical transport-security projection was available in this scan response. Do not infer a positive transport result."
|
|
1167
|
+
]
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
const observations = Array.isArray(source.observations) ? source.observations : [];
|
|
1171
|
+
const compactObservations = observations.slice(0, 8).map((observation) => {
|
|
1172
|
+
const row = observation && typeof observation === "object" && !Array.isArray(observation)
|
|
1173
|
+
? observation
|
|
1174
|
+
: {};
|
|
1175
|
+
return {
|
|
1176
|
+
id: row.id,
|
|
1177
|
+
label: boundedText(row.label, 140),
|
|
1178
|
+
status: row.status,
|
|
1179
|
+
assessmentStatus: row.assessmentStatus,
|
|
1180
|
+
evidenceState: row.evidenceState,
|
|
1181
|
+
summary: boundedText(row.summary, 180),
|
|
1182
|
+
evidenceRefs: []
|
|
1183
|
+
};
|
|
1184
|
+
});
|
|
1185
|
+
return {
|
|
1186
|
+
status: source.status === "available" || source.status === "limited" ? source.status : "unavailable",
|
|
1187
|
+
evidenceRetained: source.evidenceRetained === true,
|
|
1188
|
+
observationCounts: source.observationCounts && typeof source.observationCounts === "object" && !Array.isArray(source.observationCounts)
|
|
1189
|
+
? source.observationCounts
|
|
1190
|
+
: {
|
|
1191
|
+
total: observations.length,
|
|
1192
|
+
observedPositive: 0,
|
|
1193
|
+
concernOrReview: 0,
|
|
1194
|
+
notObserved: 0,
|
|
1195
|
+
unavailable: observations.length
|
|
1196
|
+
},
|
|
1197
|
+
observations: detail === "summary" || detail === "findings"
|
|
1198
|
+
? compactObservations
|
|
1199
|
+
: observations.map((observation) => compactEvidenceValue(observation, {
|
|
1200
|
+
arrayItems: 6,
|
|
1201
|
+
depth: 4,
|
|
1202
|
+
objectKeys: 12,
|
|
1203
|
+
stringChars: 1_200
|
|
1204
|
+
})),
|
|
1205
|
+
limitations: Array.isArray(source.limitations)
|
|
1206
|
+
? source.limitations.filter((value) => typeof value === "string").slice(0, 8)
|
|
1207
|
+
: [],
|
|
1208
|
+
...(detail === "full" && source.retainedSummary && typeof source.retainedSummary === "object" && !Array.isArray(source.retainedSummary)
|
|
1209
|
+
? {
|
|
1210
|
+
retainedSummary: compactEvidenceValue(source.retainedSummary, {
|
|
1211
|
+
arrayItems: 20,
|
|
1212
|
+
depth: 4,
|
|
1213
|
+
objectKeys: 24,
|
|
1214
|
+
stringChars: 1_200
|
|
1215
|
+
})
|
|
1216
|
+
}
|
|
1217
|
+
: {})
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
300
1220
|
export function buildScanBundle(input) {
|
|
301
|
-
const
|
|
1221
|
+
const detail = input.detail ?? "summary";
|
|
1222
|
+
const maxFindings = Math.min(50, Math.max(1, input.maxFindings ?? (detail === "summary" ? 5 : 20)));
|
|
302
1223
|
const maxPreConsentRows = Math.min(50, Math.max(1, input.maxPreConsentRows ?? 20));
|
|
303
|
-
const evidence = input.evidence;
|
|
304
|
-
const report = input.report;
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
1224
|
+
const evidence = (input.evidence ?? {});
|
|
1225
|
+
const report = (input.report ?? {});
|
|
1226
|
+
const transportSecurity = scanBundleTransportSecurity(report, detail);
|
|
1227
|
+
const allFindings = prioritizeFindingsForBoundedBundle(Array.isArray(input.findings.findings) ? input.findings.findings : []);
|
|
1228
|
+
const selectedFindings = detail === "summary" ? distinctSummaryFindings(allFindings) : allFindings;
|
|
1229
|
+
const selectedFindingRows = selectedFindings.slice(0, maxFindings);
|
|
1230
|
+
const findingDisclaimersDeduplicated = detail === "full"
|
|
1231
|
+
&& selectedFindingRows.some((finding) => typeof finding.disclaimer === "string");
|
|
1232
|
+
const findings = selectedFindingRows
|
|
1233
|
+
.map((finding) => detail === "full" ? withoutFindingDisclaimer(finding) : compactBundleFinding(finding));
|
|
1234
|
+
const deduplicatedReport = deduplicatedFullReport({ findings, report, transportSecurity });
|
|
1235
|
+
const preConsentCookiesTrackers = input.preConsentCookiesTrackers
|
|
1236
|
+
? preConsentBundleSection(input.preConsentCookiesTrackers, maxPreConsentRows)
|
|
1237
|
+
: null;
|
|
309
1238
|
const links = {
|
|
310
1239
|
...(input.scan.links ?? {}),
|
|
311
1240
|
...(report.links && typeof report.links === "object" && !Array.isArray(report.links) ? report.links : {})
|
|
312
1241
|
};
|
|
313
|
-
|
|
1242
|
+
const requestedMaxBytes = Math.min(200_000, Math.max(5_000, input.requestedMaxBytes ?? input.maxBytes ?? 50_000));
|
|
1243
|
+
const responseCeilingBytes = Math.min(200_000, Math.max(5_000, input.responseCeilingBytes ?? 200_000));
|
|
1244
|
+
const maxBytes = Math.min(requestedMaxBytes, responseCeilingBytes);
|
|
1245
|
+
const contentUrls = Object.fromEntries(Object.entries({
|
|
1246
|
+
report: input.scan.links?.report ?? links.report,
|
|
1247
|
+
findings: links.findings,
|
|
1248
|
+
evidence: links.pulse,
|
|
1249
|
+
preConsentCookiesTrackers: links.preConsentCookiesTrackers
|
|
1250
|
+
}).filter(([, value]) => typeof value === "string"));
|
|
1251
|
+
const intentionallyOmitted = new Set();
|
|
1252
|
+
if (allFindings.length > findings.length)
|
|
1253
|
+
intentionallyOmitted.add("additionalFindings");
|
|
1254
|
+
if ((detail === "summary" || detail === "findings") && (Object.keys(evidence).length > 0 || allFindings.length > 0))
|
|
1255
|
+
intentionallyOmitted.add("evidence");
|
|
1256
|
+
if (detail !== "full" && Object.keys(report).length > 0)
|
|
1257
|
+
intentionallyOmitted.add("fullReport");
|
|
1258
|
+
const guidedScan = withMcpAgentGuidance(input.scan);
|
|
1259
|
+
const bundle = {
|
|
314
1260
|
type: "certscore_scan_bundle",
|
|
1261
|
+
detail,
|
|
315
1262
|
scanId: input.scan.scanId,
|
|
1263
|
+
domain: input.scan.domain,
|
|
1264
|
+
url: input.scan.url ?? null,
|
|
1265
|
+
scanFrom: input.scan.scanFrom ?? null,
|
|
316
1266
|
status: input.scan.status,
|
|
1267
|
+
score: input.scan.score ?? null,
|
|
1268
|
+
scoreLabel: "CertScore score",
|
|
1269
|
+
scoreStatus: input.scan.scoreStatus ?? "final",
|
|
1270
|
+
scoreVersion: input.scan.scoreVersion ?? null,
|
|
1271
|
+
scoreUpdatedAt: input.scan.scoreUpdatedAt ?? null,
|
|
1272
|
+
riskLevel: input.scan.riskLevel ?? null,
|
|
1273
|
+
postRefusalObservation: input.scan.postRefusalObservation ?? null,
|
|
1274
|
+
provenance: scanProvenance(input.scan, "existing_scan_retrieved"),
|
|
1275
|
+
interpretationGuidance: interpretationGuidance(SCAN_BUNDLE_INTERPRETATION_STATEMENT),
|
|
317
1276
|
resultDisposition: input.scan.resultDisposition ?? null,
|
|
318
1277
|
noGo: input.scan.noGo ?? null,
|
|
319
|
-
|
|
1278
|
+
coverage: input.scan.coverage ?? null,
|
|
1279
|
+
createdAt: input.scan.createdAt ?? null,
|
|
1280
|
+
startedAt: input.scan.startedAt ?? null,
|
|
1281
|
+
completedAt: input.scan.completedAt ?? null,
|
|
1282
|
+
scanTimeSeconds: input.scan.scanTimeSeconds ?? null,
|
|
1283
|
+
timing: {
|
|
1284
|
+
createdAt: input.scan.createdAt ?? null,
|
|
1285
|
+
startedAt: input.scan.startedAt ?? null,
|
|
1286
|
+
completedAt: input.scan.completedAt ?? null,
|
|
1287
|
+
scanTimeSeconds: input.scan.scanTimeSeconds ?? null
|
|
1288
|
+
},
|
|
320
1289
|
summary: {
|
|
321
|
-
|
|
322
|
-
|
|
1290
|
+
headline: report.summary && typeof report.summary === "object" && !Array.isArray(report.summary)
|
|
1291
|
+
? report.summary.headline ?? null
|
|
1292
|
+
: null,
|
|
1293
|
+
executiveSummary: neutralExecutiveSummary(report.executiveSummary),
|
|
323
1294
|
counts: report.counts ?? null,
|
|
324
|
-
coverage: report.coverage ?? input.scan.coverage ?? null,
|
|
325
1295
|
agentInterpretation: report.agentInterpretation ?? null
|
|
326
1296
|
},
|
|
327
1297
|
findings,
|
|
328
1298
|
findingsMetadata: {
|
|
329
1299
|
shown: findings.length,
|
|
330
|
-
|
|
331
|
-
|
|
1300
|
+
returned: findings.length,
|
|
1301
|
+
total: allFindings.length,
|
|
1302
|
+
truncated: allFindings.length > findings.length
|
|
332
1303
|
},
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
arrayItems: 20,
|
|
344
|
-
depth: 5,
|
|
345
|
-
objectKeys: 40,
|
|
346
|
-
stringChars: 1_000
|
|
347
|
-
}),
|
|
348
|
-
policySurfaceCoverage: compactEvidenceValue(evidence.policySurfaceCoverage ?? null, {
|
|
349
|
-
arrayItems: 20,
|
|
350
|
-
depth: 5,
|
|
351
|
-
objectKeys: 40,
|
|
352
|
-
stringChars: 1_000
|
|
1304
|
+
transportSecurity,
|
|
1305
|
+
...(detail === "evidence" || detail === "full"
|
|
1306
|
+
? { evidenceSummary: bundleEvidenceSummary(evidence, allFindings, links, detail === "full") }
|
|
1307
|
+
: {}),
|
|
1308
|
+
...(detail === "full" ? {
|
|
1309
|
+
fullReport: compactEvidenceValue(deduplicatedReport.residual, {
|
|
1310
|
+
arrayItems: 50,
|
|
1311
|
+
depth: 8,
|
|
1312
|
+
objectKeys: 100,
|
|
1313
|
+
stringChars: 4_000
|
|
353
1314
|
})
|
|
354
|
-
},
|
|
355
|
-
preConsentCookiesTrackers: {
|
|
356
|
-
summary: input.preConsentCookiesTrackers.summary,
|
|
357
|
-
rows: preConsentRows,
|
|
358
|
-
shown: preConsentRows.length,
|
|
359
|
-
total: Array.isArray(input.preConsentCookiesTrackers.rows) ? input.preConsentCookiesTrackers.rows.length : 0,
|
|
360
|
-
truncated: Array.isArray(input.preConsentCookiesTrackers.rows) && input.preConsentCookiesTrackers.rows.length > preConsentRows.length
|
|
361
|
-
},
|
|
1315
|
+
} : {}),
|
|
1316
|
+
...(preConsentCookiesTrackers ? { preConsentCookiesTrackers } : {}),
|
|
362
1317
|
links,
|
|
363
|
-
|
|
364
|
-
|
|
1318
|
+
reportUrl: input.scan.links?.report ?? (typeof links.report === "string" ? links.report : `https://certscore.ai/scan/${encodeURIComponent(input.scan.scanId)}`),
|
|
1319
|
+
recommendedNextTool: null,
|
|
1320
|
+
recommendedNextAction: guidedScan.error?.recommendedNextAction ?? (detail === "summary" && allFindings.length > findings.length
|
|
1321
|
+
? "Review the returned overview and findings, then request detail=findings or a larger maxFindings value for more projected findings."
|
|
1322
|
+
: findings.length > 0
|
|
1323
|
+
? "Review the returned findings and follow their evidence references. Use detail=evidence only when deeper retained context is needed."
|
|
1324
|
+
: "Review coverage and limitations before interpreting the absence of findings."),
|
|
1325
|
+
error: guidedScan.error,
|
|
1326
|
+
mcpMetadata: {
|
|
1327
|
+
detail,
|
|
1328
|
+
heavyEvidenceIncluded: detail === "evidence" || detail === "full",
|
|
1329
|
+
findingsTruncated: allFindings.length > findings.length,
|
|
1330
|
+
requestedMaxBytes,
|
|
1331
|
+
effectiveMaxBytes: maxBytes,
|
|
1332
|
+
responseCeilingBytes,
|
|
1333
|
+
responseBudgetClamped: requestedMaxBytes > maxBytes,
|
|
1334
|
+
actualBytes: 0,
|
|
1335
|
+
fullPayloadBytes: 0,
|
|
1336
|
+
truncated: false,
|
|
1337
|
+
canonicalFindingsComplete: allFindings.length === findings.length,
|
|
1338
|
+
truncationReason: null,
|
|
1339
|
+
omittedSections: [...intentionallyOmitted],
|
|
1340
|
+
deduplicatedSections: detail === "full"
|
|
1341
|
+
? [
|
|
1342
|
+
...deduplicatedReport.deduplicatedSections,
|
|
1343
|
+
...(findingDisclaimersDeduplicated ? ["findings[].disclaimer"] : [])
|
|
1344
|
+
]
|
|
1345
|
+
: [],
|
|
1346
|
+
nextRecommendedMaxBytes: null,
|
|
1347
|
+
omittedContentAvailableViaUrl: intentionallyOmitted.size > 0 && Object.keys(contentUrls).length > 0,
|
|
1348
|
+
contentUrls
|
|
1349
|
+
},
|
|
1350
|
+
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER,
|
|
1351
|
+
disclaimer: LEGAL_REVIEW_DISCLAIMER
|
|
365
1352
|
};
|
|
1353
|
+
const markBudgetOmitted = (section, reason) => {
|
|
1354
|
+
bundle.mcpMetadata.truncated = true;
|
|
1355
|
+
bundle.mcpMetadata.truncationReason ??= reason;
|
|
1356
|
+
if (!bundle.mcpMetadata.omittedSections.includes(section)) {
|
|
1357
|
+
bundle.mcpMetadata.omittedSections.push(section);
|
|
1358
|
+
}
|
|
1359
|
+
bundle.mcpMetadata.omittedContentAvailableViaUrl = Object.keys(contentUrls).length > 0;
|
|
1360
|
+
};
|
|
1361
|
+
const refresh = () => updateBundleActualBytes(bundle);
|
|
1362
|
+
const captureFullPayloadBytes = () => {
|
|
1363
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1364
|
+
refresh();
|
|
1365
|
+
const measured = bundle.mcpMetadata.actualBytes;
|
|
1366
|
+
if (bundle.mcpMetadata.fullPayloadBytes === measured)
|
|
1367
|
+
break;
|
|
1368
|
+
bundle.mcpMetadata.fullPayloadBytes = measured;
|
|
1369
|
+
}
|
|
1370
|
+
refresh();
|
|
1371
|
+
};
|
|
1372
|
+
const refreshTruncationGuidance = () => {
|
|
1373
|
+
const completeBytes = bundle.mcpMetadata.fullPayloadBytes;
|
|
1374
|
+
const canonicalFindingsComplete = bundle.findingsMetadata.returned === bundle.findingsMetadata.total &&
|
|
1375
|
+
bundle.findingsMetadata.truncated === false;
|
|
1376
|
+
bundle.mcpMetadata.canonicalFindingsComplete = canonicalFindingsComplete;
|
|
1377
|
+
bundle.mcpMetadata.nextRecommendedMaxBytes = completeBytes <= responseCeilingBytes
|
|
1378
|
+
? Math.max(5_000, Math.ceil(completeBytes / 1_000) * 1_000)
|
|
1379
|
+
: null;
|
|
1380
|
+
bundle.recommendedNextAction = canonicalFindingsComplete
|
|
1381
|
+
? "Canonical findings complete; retry only for omitted envelope detail."
|
|
1382
|
+
: bundle.mcpMetadata.nextRecommendedMaxBytes
|
|
1383
|
+
? `Retry with maxBytes=${bundle.mcpMetadata.nextRecommendedMaxBytes} to retrieve the complete requested tier, or open ${bundle.reportUrl ? "the report URL" : "an available content URL"}.`
|
|
1384
|
+
: `The complete requested tier exceeds the MCP byte ceiling; open ${bundle.reportUrl ? "the report URL" : "an available content URL"}.`;
|
|
1385
|
+
refresh();
|
|
1386
|
+
};
|
|
1387
|
+
captureFullPayloadBytes();
|
|
1388
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.fullReport) {
|
|
1389
|
+
markBudgetOmitted("fullReport", "full_report_omitted_to_byte_limit");
|
|
1390
|
+
delete bundle.fullReport;
|
|
1391
|
+
refresh();
|
|
1392
|
+
}
|
|
1393
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && detail === "full" && bundle.evidenceSummary) {
|
|
1394
|
+
const compactSummary = bundleEvidenceSummary(evidence, allFindings, links, false);
|
|
1395
|
+
markBudgetOmitted("evidenceDiagnostics", "evidence_diagnostics_omitted_to_byte_limit");
|
|
1396
|
+
bundle.evidenceSummary = compactSummary;
|
|
1397
|
+
refresh();
|
|
1398
|
+
}
|
|
1399
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes &&
|
|
1400
|
+
(bundle.transportSecurity?.observations?.length > 0 || bundle.transportSecurity?.retainedSummary)) {
|
|
1401
|
+
markBudgetOmitted("transportSecurityDetail", "transport_security_detail_omitted_to_byte_limit");
|
|
1402
|
+
bundle.transportSecurity = {
|
|
1403
|
+
status: bundle.transportSecurity.status,
|
|
1404
|
+
evidenceRetained: bundle.transportSecurity.evidenceRetained,
|
|
1405
|
+
observationCounts: bundle.transportSecurity.observationCounts,
|
|
1406
|
+
observations: [],
|
|
1407
|
+
limitations: bundle.transportSecurity.limitations
|
|
1408
|
+
};
|
|
1409
|
+
refresh();
|
|
1410
|
+
}
|
|
1411
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.findings.length > 0) {
|
|
1412
|
+
markBudgetOmitted("findingDetail", "findings_compacted_to_preserve_core_rows");
|
|
1413
|
+
bundle.findings = bundle.findings.map((finding) => compactBundleFinding(finding, "core"));
|
|
1414
|
+
refresh();
|
|
1415
|
+
}
|
|
1416
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
1417
|
+
markBudgetOmitted("summaryDetail", "summary_compacted_to_byte_limit");
|
|
1418
|
+
bundle.summary = {
|
|
1419
|
+
headline: boundedText(bundle.summary?.headline, 240) ?? null,
|
|
1420
|
+
executiveSummary: null,
|
|
1421
|
+
counts: bundle.summary?.counts ? { totalAutomatedFindingCount: bundle.findingsMetadata.total } : null,
|
|
1422
|
+
agentInterpretation: null
|
|
1423
|
+
};
|
|
1424
|
+
refresh();
|
|
1425
|
+
}
|
|
1426
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
1427
|
+
markBudgetOmitted("coverageDetail", "coverage_compacted_to_byte_limit");
|
|
1428
|
+
bundle.coverage = compactEvidenceValue(bundle.coverage, {
|
|
1429
|
+
arrayItems: 3,
|
|
1430
|
+
depth: 3,
|
|
1431
|
+
objectKeys: 8,
|
|
1432
|
+
stringChars: 240
|
|
1433
|
+
});
|
|
1434
|
+
refresh();
|
|
1435
|
+
}
|
|
1436
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.links) {
|
|
1437
|
+
markBudgetOmitted("additionalLinks", "links_compacted_to_byte_limit");
|
|
1438
|
+
bundle.links = Object.fromEntries(Object.entries(bundle.links).filter(([key]) => ["self", "report"].includes(key)));
|
|
1439
|
+
refresh();
|
|
1440
|
+
}
|
|
1441
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
1442
|
+
markBudgetOmitted("duplicateGuidance", "guidance_compacted_to_preserve_priority_content");
|
|
1443
|
+
bundle.interpretationGuidance = interpretationGuidance(COMPACT_SCAN_BUNDLE_INTERPRETATION_STATEMENT);
|
|
1444
|
+
bundle.observationOnlyDisclaimer = COMPACT_OBSERVATION_ONLY_DISCLAIMER;
|
|
1445
|
+
bundle.disclaimer = null;
|
|
1446
|
+
refresh();
|
|
1447
|
+
}
|
|
1448
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.evidenceSummary) {
|
|
1449
|
+
markBudgetOmitted("evidenceDetail", "evidence_compacted_to_preserve_priority_content");
|
|
1450
|
+
bundle.evidenceSummary = compactPriorityEvidenceSummary(bundle.evidenceSummary);
|
|
1451
|
+
refresh();
|
|
1452
|
+
}
|
|
1453
|
+
const inventoryRows = bundle.preConsentCookiesTrackers?.rows;
|
|
1454
|
+
while (bundle.mcpMetadata.actualBytes > maxBytes && Array.isArray(inventoryRows) && inventoryRows.length > 0) {
|
|
1455
|
+
markBudgetOmitted("additionalPreConsentRows", "evidence_inventory_reduced_to_preserve_findings");
|
|
1456
|
+
inventoryRows.pop();
|
|
1457
|
+
bundle.preConsentCookiesTrackers.returned = inventoryRows.length;
|
|
1458
|
+
bundle.preConsentCookiesTrackers.truncated = true;
|
|
1459
|
+
refresh();
|
|
1460
|
+
}
|
|
1461
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes &&
|
|
1462
|
+
bundle.findings.length > 1 &&
|
|
1463
|
+
typeof contentUrls.findings === "string") {
|
|
1464
|
+
markBudgetOmitted("findingEvidenceUrls", "finding_evidence_urls_replaced_by_template");
|
|
1465
|
+
bundle.evidenceUrlTemplate = "{contentUrls.findings}/{findingId}";
|
|
1466
|
+
bundle.findings = bundle.findings.map((finding) => {
|
|
1467
|
+
const { evidenceUrl: _evidenceUrl, ...rest } = finding;
|
|
1468
|
+
return rest;
|
|
1469
|
+
});
|
|
1470
|
+
refresh();
|
|
1471
|
+
}
|
|
1472
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.preConsentCookiesTrackers) {
|
|
1473
|
+
markBudgetOmitted("preConsentCookiesTrackers", "pre_consent_summary_omitted_to_preserve_findings");
|
|
1474
|
+
delete bundle.preConsentCookiesTrackers;
|
|
1475
|
+
refresh();
|
|
1476
|
+
}
|
|
1477
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.links) {
|
|
1478
|
+
markBudgetOmitted("links", "duplicate_links_omitted_to_preserve_findings");
|
|
1479
|
+
delete bundle.links;
|
|
1480
|
+
refresh();
|
|
1481
|
+
}
|
|
1482
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.timing) {
|
|
1483
|
+
markBudgetOmitted("timing", "duplicate_timing_envelope_omitted_to_preserve_findings");
|
|
1484
|
+
delete bundle.timing;
|
|
1485
|
+
refresh();
|
|
1486
|
+
}
|
|
1487
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.summary) {
|
|
1488
|
+
markBudgetOmitted("summary", "duplicate_summary_envelope_omitted_to_preserve_findings");
|
|
1489
|
+
delete bundle.summary;
|
|
1490
|
+
refresh();
|
|
1491
|
+
}
|
|
1492
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.mcpMetadata.truncated) {
|
|
1493
|
+
refreshTruncationGuidance();
|
|
1494
|
+
}
|
|
1495
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.evidenceSummary) {
|
|
1496
|
+
markBudgetOmitted("evidence", "evidence_digest_omitted_to_byte_limit");
|
|
1497
|
+
delete bundle.evidenceSummary;
|
|
1498
|
+
bundle.mcpMetadata.heavyEvidenceIncluded = false;
|
|
1499
|
+
refresh();
|
|
1500
|
+
}
|
|
1501
|
+
while (bundle.mcpMetadata.actualBytes > maxBytes && bundle.findings.length > 1) {
|
|
1502
|
+
markBudgetOmitted("additionalFindings", "findings_reduced_to_byte_limit");
|
|
1503
|
+
bundle.findings.pop();
|
|
1504
|
+
bundle.findingsMetadata.shown = bundle.findings.length;
|
|
1505
|
+
bundle.findingsMetadata.returned = bundle.findings.length;
|
|
1506
|
+
bundle.findingsMetadata.truncated = true;
|
|
1507
|
+
bundle.mcpMetadata.findingsTruncated = true;
|
|
1508
|
+
refresh();
|
|
1509
|
+
}
|
|
1510
|
+
if (bundle.mcpMetadata.truncated) {
|
|
1511
|
+
refreshTruncationGuidance();
|
|
1512
|
+
}
|
|
1513
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
1514
|
+
const minimal = {
|
|
1515
|
+
type: bundle.type,
|
|
1516
|
+
detail: bundle.detail,
|
|
1517
|
+
scanId: bundle.scanId,
|
|
1518
|
+
domain: bundle.domain,
|
|
1519
|
+
url: bundle.url,
|
|
1520
|
+
status: bundle.status,
|
|
1521
|
+
score: bundle.score,
|
|
1522
|
+
scoreLabel: bundle.scoreLabel,
|
|
1523
|
+
scoreStatus: bundle.scoreStatus,
|
|
1524
|
+
scoreVersion: bundle.scoreVersion,
|
|
1525
|
+
scoreUpdatedAt: bundle.scoreUpdatedAt,
|
|
1526
|
+
riskLevel: bundle.riskLevel,
|
|
1527
|
+
postRefusalObservation: bundle.postRefusalObservation,
|
|
1528
|
+
provenance: bundle.provenance,
|
|
1529
|
+
interpretationGuidance: bundle.interpretationGuidance,
|
|
1530
|
+
resultDisposition: bundle.resultDisposition,
|
|
1531
|
+
noGo: bundle.noGo,
|
|
1532
|
+
coverage: null,
|
|
1533
|
+
createdAt: bundle.createdAt,
|
|
1534
|
+
startedAt: bundle.startedAt,
|
|
1535
|
+
completedAt: bundle.completedAt,
|
|
1536
|
+
scanTimeSeconds: bundle.scanTimeSeconds,
|
|
1537
|
+
timing: bundle.timing,
|
|
1538
|
+
summary: {
|
|
1539
|
+
headline: bundle.summary?.headline ?? null,
|
|
1540
|
+
executiveSummary: null,
|
|
1541
|
+
counts: bundle.summary?.counts ?? null,
|
|
1542
|
+
agentInterpretation: null
|
|
1543
|
+
},
|
|
1544
|
+
findings: bundle.findings.slice(0, 1),
|
|
1545
|
+
findingsMetadata: {
|
|
1546
|
+
shown: Math.min(1, bundle.findings.length),
|
|
1547
|
+
returned: Math.min(1, bundle.findings.length),
|
|
1548
|
+
total: bundle.findingsMetadata.total,
|
|
1549
|
+
truncated: bundle.findingsMetadata.total > Math.min(1, bundle.findings.length)
|
|
1550
|
+
},
|
|
1551
|
+
transportSecurity: {
|
|
1552
|
+
status: bundle.transportSecurity.status,
|
|
1553
|
+
evidenceRetained: bundle.transportSecurity.evidenceRetained,
|
|
1554
|
+
observationCounts: bundle.transportSecurity.observationCounts,
|
|
1555
|
+
observations: [],
|
|
1556
|
+
limitations: bundle.transportSecurity.limitations.slice(0, 2)
|
|
1557
|
+
},
|
|
1558
|
+
...(bundle.preConsentCookiesTrackers ? {
|
|
1559
|
+
preConsentCookiesTrackers: {
|
|
1560
|
+
summary: bundle.preConsentCookiesTrackers.summary,
|
|
1561
|
+
rows: [],
|
|
1562
|
+
total: bundle.preConsentCookiesTrackers.total,
|
|
1563
|
+
returned: 0,
|
|
1564
|
+
truncated: bundle.preConsentCookiesTrackers.total > 0
|
|
1565
|
+
}
|
|
1566
|
+
} : {}),
|
|
1567
|
+
links: Object.fromEntries(Object.entries(bundle.links ?? {}).filter(([key]) => ["docs", "report", "self"].includes(key))),
|
|
1568
|
+
reportUrl: bundle.reportUrl,
|
|
1569
|
+
recommendedNextTool: null,
|
|
1570
|
+
recommendedNextAction: bundle.recommendedNextAction,
|
|
1571
|
+
error: bundle.error,
|
|
1572
|
+
mcpMetadata: {
|
|
1573
|
+
detail,
|
|
1574
|
+
heavyEvidenceIncluded: false,
|
|
1575
|
+
findingsTruncated: bundle.findingsMetadata.total > Math.min(1, bundle.findings.length),
|
|
1576
|
+
requestedMaxBytes,
|
|
1577
|
+
effectiveMaxBytes: maxBytes,
|
|
1578
|
+
responseCeilingBytes,
|
|
1579
|
+
responseBudgetClamped: requestedMaxBytes > maxBytes,
|
|
1580
|
+
actualBytes: 0,
|
|
1581
|
+
fullPayloadBytes: bundle.mcpMetadata.fullPayloadBytes,
|
|
1582
|
+
truncated: true,
|
|
1583
|
+
canonicalFindingsComplete: bundle.findingsMetadata.total === Math.min(1, bundle.findings.length),
|
|
1584
|
+
truncationReason: "minimal_canonical_result_returned_to_byte_limit",
|
|
1585
|
+
omittedSections: [...new Set([
|
|
1586
|
+
...bundle.mcpMetadata.omittedSections,
|
|
1587
|
+
"summaryDetail",
|
|
1588
|
+
"evidence",
|
|
1589
|
+
...(bundle.findingsMetadata.total > Math.min(1, bundle.findings.length) ? ["additionalFindings"] : []),
|
|
1590
|
+
...(bundle.preConsentCookiesTrackers?.total > 0 ? ["additionalPreConsentRows"] : [])
|
|
1591
|
+
])],
|
|
1592
|
+
deduplicatedSections: bundle.mcpMetadata.deduplicatedSections,
|
|
1593
|
+
nextRecommendedMaxBytes: bundle.mcpMetadata.nextRecommendedMaxBytes,
|
|
1594
|
+
omittedContentAvailableViaUrl: Object.keys(contentUrls).length > 0,
|
|
1595
|
+
contentUrls
|
|
1596
|
+
},
|
|
1597
|
+
observationOnlyDisclaimer: bundle.observationOnlyDisclaimer,
|
|
1598
|
+
disclaimer: bundle.disclaimer
|
|
1599
|
+
};
|
|
1600
|
+
updateBundleActualBytes(minimal);
|
|
1601
|
+
return minimal;
|
|
1602
|
+
}
|
|
1603
|
+
return bundle;
|
|
366
1604
|
}
|
|
367
1605
|
export function explainFinding(report, findingId) {
|
|
368
1606
|
const finding = findingsFromReport(report).find((candidate) => candidate.id === findingId);
|