@certscore/mcp 0.2.15 → 0.2.17
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 +82 -52
- package/dist/certscore-mcp.mjs +4193 -4166
- package/dist/index.js +1 -1
- package/dist/server.d.ts +33 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +374 -97
- package/dist/tools.d.ts +25 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +1074 -85
- package/package.json +1 -1
- package/server-light.json +19 -4
- package/server.json +2 -2
package/dist/tools.js
CHANGED
|
@@ -4,7 +4,27 @@ 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
|
-
const
|
|
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-action 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. For gpcResponse, use only GPC response, No observable GPC response, or indeterminate; do not call the result a GPC violation or say GPC was not honored. Keep its jurisdiction-neutral comparison separate from any explicitly returned California scoring policy. 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 postAcceptObservation or postRefusalObservation is confirmed and termination.kind is evidence_satisfied, state the returned 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. Treat post-Accept activity as a score-neutral behavior baseline unless a separately projected finding says otherwise. 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
|
+
const MCP_SCAN_CREATION_POLL_DELAY_SECONDS = 15;
|
|
17
|
+
const MCP_QUEUED_POLL_DELAY_SECONDS = 10;
|
|
18
|
+
const MCP_RUNNING_POLL_DELAY_SECONDS = 5;
|
|
19
|
+
function interpretationGuidance(statement = INTERPRETATION_STATEMENT) {
|
|
20
|
+
return {
|
|
21
|
+
scoreLabel: "CertScore score",
|
|
22
|
+
observableSignalsOnly: true,
|
|
23
|
+
doNotInferUnobservedTechnologies: true,
|
|
24
|
+
doNotInferLegalComplianceStatus: true,
|
|
25
|
+
statement
|
|
26
|
+
};
|
|
27
|
+
}
|
|
8
28
|
function toolResultSummary(payload) {
|
|
9
29
|
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
10
30
|
return "CertScore tool call completed. Read structuredContent for the result.";
|
|
@@ -13,10 +33,38 @@ function toolResultSummary(payload) {
|
|
|
13
33
|
const type = typeof record.type === "string" ? record.type : "result";
|
|
14
34
|
const status = typeof record.status === "string" ? `; status=${record.status}` : "";
|
|
15
35
|
const scanId = typeof record.scanId === "string" ? `; scanId=${record.scanId}` : "";
|
|
16
|
-
const score = typeof record.score === "number" ? `; score=${record.score}` : "";
|
|
17
|
-
|
|
36
|
+
const score = typeof record.score === "number" ? `; CertScore score=${record.score}` : "";
|
|
37
|
+
const preview = record.preConsentPreview && typeof record.preConsentPreview === "object" && !Array.isArray(record.preConsentPreview)
|
|
38
|
+
? record.preConsentPreview
|
|
39
|
+
: null;
|
|
40
|
+
const previewSummary = preview?.summary && typeof preview.summary === "object" && !Array.isArray(preview.summary)
|
|
41
|
+
? preview.summary
|
|
42
|
+
: null;
|
|
43
|
+
const preliminary = preview
|
|
44
|
+
? `; preliminary pre-consent preview=cookies ${previewSummary?.cookieCount ?? "unknown"}, trackers ${previewSummary?.trackerCount ?? "unknown"}, third-party requests ${previewSummary?.thirdPartyRequestCount ?? "unknown"}; preview is not final—continue status polling`
|
|
45
|
+
: "";
|
|
46
|
+
const recordLinks = record.links && typeof record.links === "object" && !Array.isArray(record.links)
|
|
47
|
+
? record.links
|
|
48
|
+
: null;
|
|
49
|
+
const reportUrl = typeof record.reportUrl === "string" && record.reportUrl.trim()
|
|
50
|
+
? record.reportUrl.trim()
|
|
51
|
+
: typeof recordLinks?.report === "string" && recordLinks.report.trim()
|
|
52
|
+
? recordLinks.report.trim()
|
|
53
|
+
: null;
|
|
54
|
+
const report = reportUrl ? `; full report=${reportUrl}` : "";
|
|
55
|
+
const provenanceRecord = record.provenance && typeof record.provenance === "object" && !Array.isArray(record.provenance)
|
|
56
|
+
? record.provenance
|
|
57
|
+
: null;
|
|
58
|
+
const retrieval = typeof provenanceRecord?.retrievalMode === "string" ? `; retrieval=${provenanceRecord.retrievalMode}` : "";
|
|
59
|
+
const creation = typeof provenanceRecord?.creationDecision === "string" ? `; creation=${provenanceRecord.creationDecision}` : "";
|
|
60
|
+
const provenance = retrieval || creation
|
|
61
|
+
? `${retrieval}${creation}`
|
|
62
|
+
: typeof provenanceRecord?.mode === "string"
|
|
63
|
+
? `; provenance=${provenanceRecord.mode}`
|
|
64
|
+
: "";
|
|
65
|
+
return `CertScore ${type}${status}${scanId}${score}${preliminary}${provenance}${report}. Full result is in structuredContent.`;
|
|
18
66
|
}
|
|
19
|
-
export function toToolResult(payload) {
|
|
67
|
+
export function toToolResult(payload, text) {
|
|
20
68
|
const structuredContent = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
21
69
|
? payload
|
|
22
70
|
: { value: payload };
|
|
@@ -25,7 +73,7 @@ export function toToolResult(payload) {
|
|
|
25
73
|
content: [
|
|
26
74
|
{
|
|
27
75
|
type: "text",
|
|
28
|
-
text: toolResultSummary(payload)
|
|
76
|
+
text: text ?? toolResultSummary(payload)
|
|
29
77
|
}
|
|
30
78
|
]
|
|
31
79
|
};
|
|
@@ -49,19 +97,29 @@ export function toToolError(error) {
|
|
|
49
97
|
? 30
|
|
50
98
|
: null;
|
|
51
99
|
const code = error instanceof CertScoreError ? error.code : "internal_error";
|
|
100
|
+
const reasonCode = terminalError?.reasonCode === "non_public_target" ? "non_public_target" : null;
|
|
52
101
|
const message = error instanceof Error ? error.message : "Unknown CertScore MCP error.";
|
|
102
|
+
const creationRateLimit = terminalError?.creationRateLimit && typeof terminalError.creationRateLimit === "object" && !Array.isArray(terminalError.creationRateLimit)
|
|
103
|
+
? terminalError.creationRateLimit
|
|
104
|
+
: null;
|
|
53
105
|
const recommendedNextAction = typeof terminalError?.recommendedNextAction === "string"
|
|
54
106
|
? terminalError.recommendedNextAction
|
|
55
|
-
:
|
|
56
|
-
? `Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request.
|
|
57
|
-
:
|
|
107
|
+
: creationRateLimit
|
|
108
|
+
? `No scan was created. Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request. If the limit continues after that delay, contact support@certscore.ai.`
|
|
109
|
+
: retryable
|
|
110
|
+
? `Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request. Stop and contact support@certscore.ai if the error repeats.`
|
|
111
|
+
: "Correct the request using the error details, then retry only if the requested operation is still appropriate.";
|
|
58
112
|
const payload = {
|
|
59
113
|
error: {
|
|
60
114
|
code,
|
|
115
|
+
reasonCode,
|
|
61
116
|
message,
|
|
62
117
|
retryable,
|
|
63
118
|
retryAfterSeconds,
|
|
64
119
|
recommendedNextAction,
|
|
120
|
+
...(creationRateLimit
|
|
121
|
+
? { creationRateLimit }
|
|
122
|
+
: {}),
|
|
65
123
|
...(error instanceof CertScoreError ? {
|
|
66
124
|
name: error.name,
|
|
67
125
|
status: error.status,
|
|
@@ -103,6 +161,9 @@ export function toInvalidArgumentsToolError(errorMessage) {
|
|
|
103
161
|
type: "certscore_tool_error",
|
|
104
162
|
status: "invalid_arguments",
|
|
105
163
|
error,
|
|
164
|
+
scoreLabel: "CertScore score",
|
|
165
|
+
provenance: scanProvenance({}, "unknown"),
|
|
166
|
+
interpretationGuidance: interpretationGuidance(),
|
|
106
167
|
recommendedNextTool: null,
|
|
107
168
|
recommendedNextAction,
|
|
108
169
|
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
@@ -114,6 +175,21 @@ export function toInvalidArgumentsToolError(errorMessage) {
|
|
|
114
175
|
isError: true
|
|
115
176
|
};
|
|
116
177
|
}
|
|
178
|
+
export function toInvalidScanIdToolError() {
|
|
179
|
+
const error = {
|
|
180
|
+
code: "invalid_scan_id",
|
|
181
|
+
field: "scanId",
|
|
182
|
+
message: "The scanId must be the canonical UUID returned by certscore_scan_site.",
|
|
183
|
+
retryable: false,
|
|
184
|
+
retryAfterSeconds: null,
|
|
185
|
+
recommendedNextAction: "Use the unchanged scanId returned by certscore_scan_site. Do not use placeholders, report URLs, domains, or job IDs.",
|
|
186
|
+
mcpCode: -32602
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
190
|
+
isError: true
|
|
191
|
+
};
|
|
192
|
+
}
|
|
117
193
|
function terminalErrorForResult(value) {
|
|
118
194
|
const existing = value.error && typeof value.error === "object" && !Array.isArray(value.error)
|
|
119
195
|
? value.error
|
|
@@ -169,23 +245,107 @@ function terminalErrorForResult(value) {
|
|
|
169
245
|
};
|
|
170
246
|
return fallback[String(value.status)] ?? null;
|
|
171
247
|
}
|
|
172
|
-
|
|
248
|
+
function scanProvenance(value, fallbackMode) {
|
|
249
|
+
const executionMode = value.executionMode === "new_scan" || value.executionMode === "reused_scan"
|
|
250
|
+
? value.executionMode
|
|
251
|
+
: null;
|
|
252
|
+
const reused = typeof value.reused === "boolean" ? value.reused : executionMode === "reused_scan" ? true : executionMode === "new_scan" ? false : null;
|
|
253
|
+
const mode = executionMode === "new_scan"
|
|
254
|
+
? "new_scan_started"
|
|
255
|
+
: executionMode === "reused_scan" || reused === true
|
|
256
|
+
? "existing_completed_scan_reused"
|
|
257
|
+
: fallbackMode;
|
|
258
|
+
const retrievalMode = fallbackMode === "existing_scan_retrieved"
|
|
259
|
+
? "scan_id_lookup"
|
|
260
|
+
: executionMode !== null || reused !== null
|
|
261
|
+
? "creation_response"
|
|
262
|
+
: "unknown";
|
|
263
|
+
const creationDecision = executionMode === "new_scan" || reused === false
|
|
264
|
+
? "new_scan"
|
|
265
|
+
: executionMode === "reused_scan" || reused === true
|
|
266
|
+
? "reused_scan"
|
|
267
|
+
: "unknown";
|
|
268
|
+
const retainedAgeSeconds = typeof value.reusedScanAgeSeconds === "number" && Number.isFinite(value.reusedScanAgeSeconds) && value.reusedScanAgeSeconds >= 0
|
|
269
|
+
? Math.floor(value.reusedScanAgeSeconds)
|
|
270
|
+
: null;
|
|
271
|
+
const completedAtMs = typeof value.completedAt === "string" ? Date.parse(value.completedAt) : Number.NaN;
|
|
272
|
+
const completedAgeSeconds = Number.isFinite(completedAtMs)
|
|
273
|
+
? Math.max(0, Math.floor((Date.now() - completedAtMs) / 1_000))
|
|
274
|
+
: null;
|
|
275
|
+
const scanAgeSeconds = retrievalMode === "creation_response"
|
|
276
|
+
? retainedAgeSeconds ?? completedAgeSeconds
|
|
277
|
+
: completedAgeSeconds;
|
|
278
|
+
return {
|
|
279
|
+
mode,
|
|
280
|
+
retrievalMode,
|
|
281
|
+
creationDecision,
|
|
282
|
+
scanAgeSeconds,
|
|
283
|
+
executionMode,
|
|
284
|
+
reused,
|
|
285
|
+
freshnessDecision: typeof value.freshnessDecision === "string" ? value.freshnessDecision : null
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown", pollGuidanceContext = "upstream") {
|
|
173
289
|
const status = String(value.status ?? "");
|
|
174
290
|
const active = status === "queued" || status === "running" || status === "finalizing";
|
|
175
291
|
const usable = status === "completed" || status === "completed_limited";
|
|
292
|
+
const upstreamRetryAfterSeconds = typeof value.retryAfterSeconds === "number" && Number.isFinite(value.retryAfterSeconds)
|
|
293
|
+
? Math.max(1, Math.ceil(value.retryAfterSeconds))
|
|
294
|
+
: null;
|
|
295
|
+
const retryAfterSeconds = active && pollGuidanceContext === "scan_creation"
|
|
296
|
+
? MCP_SCAN_CREATION_POLL_DELAY_SECONDS
|
|
297
|
+
: active && pollGuidanceContext === "scan_status"
|
|
298
|
+
? status === "queued"
|
|
299
|
+
? MCP_QUEUED_POLL_DELAY_SECONDS
|
|
300
|
+
: MCP_RUNNING_POLL_DELAY_SECONDS
|
|
301
|
+
: upstreamRetryAfterSeconds;
|
|
176
302
|
const error = terminalErrorForResult(value);
|
|
303
|
+
const stableScanId = typeof value.scanId === "string" && value.scanId.trim()
|
|
304
|
+
? value.scanId.trim()
|
|
305
|
+
: typeof value.scan_id === "string" && value.scan_id.trim()
|
|
306
|
+
? value.scan_id.trim()
|
|
307
|
+
: null;
|
|
308
|
+
const hasPreConsentPreview = Boolean(value.preConsentPreview &&
|
|
309
|
+
typeof value.preConsentPreview === "object" &&
|
|
310
|
+
!Array.isArray(value.preConsentPreview));
|
|
311
|
+
const reportUrl = usable
|
|
312
|
+
? typeof value.reportUrl === "string" && value.reportUrl.trim()
|
|
313
|
+
? value.reportUrl.trim()
|
|
314
|
+
: typeof value.links?.report === "string" && value.links.report.trim()
|
|
315
|
+
? value.links.report.trim()
|
|
316
|
+
: stableScanId
|
|
317
|
+
? `https://certscore.ai/scan/${encodeURIComponent(stableScanId)}`
|
|
318
|
+
: null
|
|
319
|
+
: null;
|
|
320
|
+
const returnedNextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
321
|
+
? value.recommendedNextAction.trim()
|
|
322
|
+
: null;
|
|
323
|
+
const activePollAction = `${retryAfterSeconds === null ? "Wait for the recommended delay" : `Wait at least ${retryAfterSeconds} seconds`}, then call certscore_get_scan_status once with scanId ${stableScanId ?? value.jobId}.`;
|
|
324
|
+
const activeNextAction = `${hasPreConsentPreview ? "The returned preConsentPreview is a partial preview of passive evidence. Its counts are checkpoint-only partial counts, not the full scan tally; do not present them as final totals or stop the workflow. " : ""}${activePollAction} Continue with certscore_get_scan_status using the unchanged scanId ${stableScanId ?? value.jobId}. Do not poll in parallel or resubmit certscore_scan_site while this scan is active. After completed or completed_limited, call certscore_get_scan_bundle for the completed scan's final returned tally, canonical findings, and limitations.`;
|
|
177
325
|
return {
|
|
178
326
|
...value,
|
|
327
|
+
retryAfterSeconds,
|
|
179
328
|
error,
|
|
329
|
+
reportUrl,
|
|
330
|
+
scoreLabel: "CertScore score",
|
|
331
|
+
provenance: scanProvenance(value, fallbackProvenanceMode),
|
|
332
|
+
interpretationGuidance: interpretationGuidance(),
|
|
180
333
|
recommendedNextTool: active ? "certscore_get_scan_status" : usable ? "certscore_get_scan_bundle" : null,
|
|
181
|
-
recommendedNextAction: error?.recommendedNextAction ??
|
|
182
|
-
?
|
|
183
|
-
: usable
|
|
184
|
-
? `Call certscore_get_scan_bundle with scanId ${
|
|
185
|
-
: "Review the result and retained limitations."),
|
|
334
|
+
recommendedNextAction: error?.recommendedNextAction ?? (active
|
|
335
|
+
? activeNextAction
|
|
336
|
+
: returnedNextAction ?? (usable
|
|
337
|
+
? `Call certscore_get_scan_bundle with scanId ${stableScanId ?? value.jobId} for the completed scan's final returned tally, canonical findings, and limitations.`
|
|
338
|
+
: "Review the result and retained limitations.")),
|
|
186
339
|
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
187
340
|
};
|
|
188
341
|
}
|
|
342
|
+
export function withMcpScanProvenanceGuidance(value, fallbackProvenanceMode) {
|
|
343
|
+
const guided = withMcpAgentGuidance(value, fallbackProvenanceMode, "scan_status");
|
|
344
|
+
return {
|
|
345
|
+
...guided,
|
|
346
|
+
interpretationGuidance: interpretationGuidance(`${INTERPRETATION_STATEMENT} ${SCAN_PROVENANCE_GROUNDING}`)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
189
349
|
export function boundEvidencePacket(payload, maxSerializedChars = MAX_EVIDENCE_PACKET_CHARS) {
|
|
190
350
|
const originalSerializedChars = measureSerializedChars(payload);
|
|
191
351
|
if (originalSerializedChars <= maxSerializedChars) {
|
|
@@ -247,12 +407,51 @@ function boundedText(value, maxChars) {
|
|
|
247
407
|
if (typeof value !== "string") {
|
|
248
408
|
return value;
|
|
249
409
|
}
|
|
250
|
-
|
|
410
|
+
if (value.length <= maxChars) {
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
413
|
+
const marker = "…[truncated]";
|
|
414
|
+
const contentLimit = Math.max(1, maxChars - marker.length);
|
|
415
|
+
const candidate = value.slice(0, contentLimit);
|
|
416
|
+
const boundary = candidate.search(/\s+\S*$/);
|
|
417
|
+
const bounded = boundary >= Math.floor(contentLimit * 0.6)
|
|
418
|
+
? candidate.slice(0, boundary)
|
|
419
|
+
: candidate;
|
|
420
|
+
return `${bounded.trimEnd()}${marker}`;
|
|
421
|
+
}
|
|
422
|
+
function compactFindingLink(finding) {
|
|
423
|
+
const self = typeof finding.links?.self === "string" && finding.links.self.trim()
|
|
424
|
+
? finding.links.self.trim()
|
|
425
|
+
: typeof finding.evidence?.excerpt?.evidenceUrl === "string" && finding.evidence.excerpt.evidenceUrl.trim()
|
|
426
|
+
? finding.evidence.excerpt.evidenceUrl.trim()
|
|
427
|
+
: null;
|
|
428
|
+
return self ? { self: self.slice(0, 2_048) } : undefined;
|
|
429
|
+
}
|
|
430
|
+
function withoutFindingDisclaimer(finding) {
|
|
431
|
+
const { disclaimer: _disclaimer, ...rest } = finding;
|
|
432
|
+
return rest;
|
|
251
433
|
}
|
|
252
|
-
function compactBundleFinding(finding) {
|
|
434
|
+
function compactBundleFinding(finding, tier = "standard") {
|
|
253
435
|
const evidence = finding.evidence && typeof finding.evidence === "object" && !Array.isArray(finding.evidence)
|
|
254
436
|
? finding.evidence
|
|
255
437
|
: {};
|
|
438
|
+
const core = tier === "core";
|
|
439
|
+
const links = compactFindingLink(finding);
|
|
440
|
+
if (core) {
|
|
441
|
+
const compactNextStep = typeof finding.nextStep === "string" && finding.nextStep.trim().length <= 64
|
|
442
|
+
? finding.nextStep.trim()
|
|
443
|
+
: null;
|
|
444
|
+
return {
|
|
445
|
+
type: finding.type,
|
|
446
|
+
id: finding.id,
|
|
447
|
+
label: boundedText(finding.label, 140),
|
|
448
|
+
criticality: finding.criticality,
|
|
449
|
+
confidence: finding.confidence,
|
|
450
|
+
plainEnglish: boundedText(finding.plainEnglish, 180),
|
|
451
|
+
...(compactNextStep ? { nextStep: compactNextStep } : {}),
|
|
452
|
+
evidenceUrl: links?.self ?? null
|
|
453
|
+
};
|
|
454
|
+
}
|
|
256
455
|
return {
|
|
257
456
|
type: finding.type,
|
|
258
457
|
id: finding.id,
|
|
@@ -275,9 +474,74 @@ function compactBundleFinding(finding) {
|
|
|
275
474
|
...(evidence.hasConsentContext !== undefined ? { hasConsentContext: evidence.hasConsentContext } : {}),
|
|
276
475
|
...(evidence.hasPolicyAnchor !== undefined ? { hasPolicyAnchor: evidence.hasPolicyAnchor } : {})
|
|
277
476
|
},
|
|
278
|
-
...(finding.nextStep !== undefined ? { nextStep: boundedText(finding.nextStep, 180) } : {})
|
|
477
|
+
...(finding.nextStep !== undefined ? { nextStep: boundedText(finding.nextStep, 180) } : {}),
|
|
478
|
+
...(links ? { links } : {})
|
|
279
479
|
};
|
|
280
480
|
}
|
|
481
|
+
function deduplicatedFullReport(input) {
|
|
482
|
+
const report = input.report;
|
|
483
|
+
const residual = { ...report };
|
|
484
|
+
const deduplicatedSections = [];
|
|
485
|
+
const returnedFindings = new Map(input.findings.flatMap((finding) => typeof finding.id === "string" ? [[finding.id, finding]] : []));
|
|
486
|
+
for (const key of ["findings", "topFindings"]) {
|
|
487
|
+
const section = residual[key];
|
|
488
|
+
const sectionFindingIds = Array.isArray(section)
|
|
489
|
+
? section.flatMap((finding) => finding && typeof finding === "object" && typeof finding.id === "string"
|
|
490
|
+
? [finding.id]
|
|
491
|
+
: [])
|
|
492
|
+
: [];
|
|
493
|
+
const coveredByCanonicalProjection = sectionFindingIds.every((id) => {
|
|
494
|
+
const finding = returnedFindings.get(id);
|
|
495
|
+
return finding
|
|
496
|
+
&& typeof finding.plainEnglish === "string"
|
|
497
|
+
&& typeof finding.evidence?.summary === "string"
|
|
498
|
+
&& compactFindingLink(finding) !== undefined;
|
|
499
|
+
});
|
|
500
|
+
if (Array.isArray(section) && sectionFindingIds.length === section.length && coveredByCanonicalProjection) {
|
|
501
|
+
delete residual[key];
|
|
502
|
+
deduplicatedSections.push(`fullReport.${key}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if ("transportSecurity" in residual && JSON.stringify(residual.transportSecurity) === JSON.stringify(input.transportSecurity)) {
|
|
506
|
+
delete residual.transportSecurity;
|
|
507
|
+
deduplicatedSections.push("fullReport.transportSecurity");
|
|
508
|
+
}
|
|
509
|
+
return { deduplicatedSections, residual };
|
|
510
|
+
}
|
|
511
|
+
function distinctSummaryFindings(findings) {
|
|
512
|
+
const labels = new Set();
|
|
513
|
+
return findings.filter((finding) => {
|
|
514
|
+
const key = typeof finding.label === "string" && finding.label.trim().length > 0
|
|
515
|
+
? finding.label.trim().toLocaleLowerCase()
|
|
516
|
+
: typeof finding.id === "string"
|
|
517
|
+
? finding.id
|
|
518
|
+
: JSON.stringify(finding);
|
|
519
|
+
if (labels.has(key))
|
|
520
|
+
return false;
|
|
521
|
+
labels.add(key);
|
|
522
|
+
return true;
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
const POST_REFUSAL_FINDING_IDS = new Set([
|
|
526
|
+
"post_refusal_non_essential_activity",
|
|
527
|
+
"pre_consent_storage_not_cleared",
|
|
528
|
+
"refusal_signal_contradicts_action",
|
|
529
|
+
]);
|
|
530
|
+
function postRefusalTruncationPriority(finding) {
|
|
531
|
+
const id = typeof finding.id === "string" ? finding.id : "";
|
|
532
|
+
if (POST_REFUSAL_FINDING_IDS.has(id))
|
|
533
|
+
return 0;
|
|
534
|
+
if (/^pre_consent_|preconsent/i.test(id))
|
|
535
|
+
return 2;
|
|
536
|
+
return 1;
|
|
537
|
+
}
|
|
538
|
+
function prioritizeFindingsForBoundedBundle(findings) {
|
|
539
|
+
return findings
|
|
540
|
+
.map((finding, index) => ({ finding, index }))
|
|
541
|
+
.sort((left, right) => postRefusalTruncationPriority(left.finding) - postRefusalTruncationPriority(right.finding) ||
|
|
542
|
+
left.index - right.index)
|
|
543
|
+
.map(({ finding }) => finding);
|
|
544
|
+
}
|
|
281
545
|
function compactPriorityEvidenceSummary(summary) {
|
|
282
546
|
const firstDigest = Array.isArray(summary.digests) ? summary.digests[0] : null;
|
|
283
547
|
const firstReference = summary.references && typeof summary.references === "object" && !Array.isArray(summary.references)
|
|
@@ -542,11 +806,21 @@ export function limitPreConsentRows(payload, options = {}) {
|
|
|
542
806
|
return payload;
|
|
543
807
|
}
|
|
544
808
|
const maxRows = Math.min(200, Math.max(1, options.maxRows ?? 200));
|
|
545
|
-
const
|
|
546
|
-
|
|
809
|
+
const reportedTotal = payload.summary && typeof payload.summary === "object" && !Array.isArray(payload.summary)
|
|
810
|
+
&& Number.isInteger(payload.summary.rowCount)
|
|
811
|
+
? Number(payload.summary.rowCount)
|
|
812
|
+
: 0;
|
|
813
|
+
const total = Math.max(rows.length, reportedTotal);
|
|
814
|
+
const returnedRows = rows.slice(0, maxRows);
|
|
815
|
+
const truncated = total > returnedRows.length;
|
|
547
816
|
return {
|
|
548
817
|
...payload,
|
|
549
|
-
rows:
|
|
818
|
+
rows: returnedRows,
|
|
819
|
+
evidenceMetadata: {
|
|
820
|
+
total,
|
|
821
|
+
returned: returnedRows.length,
|
|
822
|
+
truncated
|
|
823
|
+
},
|
|
550
824
|
summary: {
|
|
551
825
|
...(payload.summary && typeof payload.summary === "object" && !Array.isArray(payload.summary) ? payload.summary : {}),
|
|
552
826
|
totalRowCount: total,
|
|
@@ -554,24 +828,629 @@ export function limitPreConsentRows(payload, options = {}) {
|
|
|
554
828
|
}
|
|
555
829
|
};
|
|
556
830
|
}
|
|
831
|
+
function uniqueBoundedStrings(values, maxItems, maxChars) {
|
|
832
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0).map((value) => value.slice(0, maxChars)))].slice(0, maxItems);
|
|
833
|
+
}
|
|
834
|
+
function compactPreConsentRow(row) {
|
|
835
|
+
const cookieDetails = Array.isArray(row.cookieDetails) ? row.cookieDetails : [];
|
|
836
|
+
const domains = uniqueBoundedStrings([
|
|
837
|
+
...(Array.isArray(row.domains) ? row.domains : []),
|
|
838
|
+
row.host,
|
|
839
|
+
row.registrableDomain
|
|
840
|
+
], 12, 253);
|
|
841
|
+
const purposes = Array.isArray(row.purposes) ? row.purposes : [];
|
|
842
|
+
return {
|
|
843
|
+
id: String(row.id ?? `${row.kind ?? "unknown"}:${row.name ?? row.vendor ?? row.host ?? "observed-item"}`).slice(0, 512),
|
|
844
|
+
kind: ["cookie", "tracker", "request", "storage"].includes(row.kind) ? row.kind : "unknown",
|
|
845
|
+
name: String(row.name ?? row.vendor ?? row.host ?? "Observed item").slice(0, 256),
|
|
846
|
+
cookieNames: uniqueBoundedStrings(cookieDetails.map((cookie) => cookie && typeof cookie === "object" ? cookie.name : null), 24, 256),
|
|
847
|
+
vendor: typeof row.vendor === "string" ? row.vendor.slice(0, 160) : null,
|
|
848
|
+
purpose: typeof row.purpose === "string" ? row.purpose.slice(0, 160) : typeof purposes[0] === "string" ? purposes[0].slice(0, 160) : null,
|
|
849
|
+
category: typeof row.category === "string" ? row.category.slice(0, 160) : null,
|
|
850
|
+
confidence: ["high", "medium", "low"].includes(row.confidence) ? row.confidence : "unknown",
|
|
851
|
+
firstObservedAtMs: Number.isInteger(row.firstObservedAtMs) && row.firstObservedAtMs >= 0 ? row.firstObservedAtMs : null,
|
|
852
|
+
domains,
|
|
853
|
+
requestCount: Number.isInteger(row.requestCount) && row.requestCount >= 0 ? row.requestCount : null,
|
|
854
|
+
evidenceClassification: {
|
|
855
|
+
basis: "public_report_projection",
|
|
856
|
+
phase: "pre_consent",
|
|
857
|
+
observedBeforeConsent: row.observedBeforeConsent !== false,
|
|
858
|
+
party: ["first_party", "third_party", "mixed"].includes(row.party) ? row.party : "unknown",
|
|
859
|
+
priority: ["high", "medium", "review_needed", "contextual"].includes(row.priority) ? row.priority : "unknown"
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function preConsentBundleSection(payload, maxRows) {
|
|
864
|
+
const sourceRows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
865
|
+
const total = Math.max(sourceRows.length, Number.isInteger(payload.summary?.rowCount) ? payload.summary.rowCount : 0);
|
|
866
|
+
const rows = sourceRows.slice(0, maxRows).map((row) => compactPreConsentRow(row));
|
|
867
|
+
return {
|
|
868
|
+
summary: {
|
|
869
|
+
rowCount: total,
|
|
870
|
+
trackerCount: Number.isInteger(payload.summary?.trackerCount) ? payload.summary.trackerCount : 0,
|
|
871
|
+
cookieCount: Number.isInteger(payload.summary?.cookieCount) ? payload.summary.cookieCount : 0,
|
|
872
|
+
requestCount: Number.isInteger(payload.summary?.requestCount) ? payload.summary.requestCount : 0,
|
|
873
|
+
vendorCount: Number.isInteger(payload.summary?.vendorCount) ? payload.summary.vendorCount : 0,
|
|
874
|
+
domainCount: Number.isInteger(payload.summary?.domainCount) ? payload.summary.domainCount : 0
|
|
875
|
+
},
|
|
876
|
+
rows,
|
|
877
|
+
total,
|
|
878
|
+
returned: rows.length,
|
|
879
|
+
truncated: total > rows.length
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function neutralExecutiveSummary(value) {
|
|
883
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
const summary = value;
|
|
887
|
+
const score = typeof summary.score === "number" ? summary.score : null;
|
|
888
|
+
const scoreMetadata = summary.scoreMetadata && typeof summary.scoreMetadata === "object" && !Array.isArray(summary.scoreMetadata)
|
|
889
|
+
? { ...summary.scoreMetadata, metricLabel: "CertScore score" }
|
|
890
|
+
: summary.scoreMetadata;
|
|
891
|
+
const trackerFootprint = summary.trackerFootprint && typeof summary.trackerFootprint === "object" && !Array.isArray(summary.trackerFootprint)
|
|
892
|
+
? Object.fromEntries(Object.entries(summary.trackerFootprint).filter(([, entry]) => typeof entry === "number" || entry === null))
|
|
893
|
+
: undefined;
|
|
894
|
+
const policySurfaces = Array.isArray(summary.policySurfaces)
|
|
895
|
+
? summary.policySurfaces.slice(0, 5).flatMap((surface) => {
|
|
896
|
+
if (!surface || typeof surface !== "object" || Array.isArray(surface))
|
|
897
|
+
return [];
|
|
898
|
+
const row = surface;
|
|
899
|
+
return [{
|
|
900
|
+
type: boundedText(row.type, 80),
|
|
901
|
+
title: boundedText(row.title, 160),
|
|
902
|
+
url: boundedText(row.url, 2_048)
|
|
903
|
+
}];
|
|
904
|
+
})
|
|
905
|
+
: undefined;
|
|
906
|
+
return {
|
|
907
|
+
completionSummary: boundedText(summary.completionSummary, 280),
|
|
908
|
+
domain: boundedText(summary.domain, 253),
|
|
909
|
+
score,
|
|
910
|
+
scoreLabel: score === null ? "CertScore score" : `CertScore score: ${score}/100`,
|
|
911
|
+
...(scoreMetadata ? { scoreMetadata } : {}),
|
|
912
|
+
riskLevel: summary.riskLevel,
|
|
913
|
+
actionLabel: summary.actionLabel,
|
|
914
|
+
issuesToReview: summary.issuesToReview,
|
|
915
|
+
thirdPartyRequests: summary.thirdPartyRequests,
|
|
916
|
+
trackingClassifiedThirdPartyRequests: summary.trackingClassifiedThirdPartyRequests,
|
|
917
|
+
cookiesPreConsent: summary.cookiesPreConsent,
|
|
918
|
+
nonEssentialPreConsentStorage: summary.nonEssentialPreConsentStorage,
|
|
919
|
+
unclassifiedPreConsentStorageCount: summary.unclassifiedPreConsentStorageCount,
|
|
920
|
+
storageMetricLabel: boundedText(summary.storageMetricLabel, 120),
|
|
921
|
+
storageMetricScope: boundedText(summary.storageMetricScope, 120),
|
|
922
|
+
storageMetricStatus: boundedText(summary.storageMetricStatus, 120),
|
|
923
|
+
storageMetricExplanation: boundedText(summary.storageMetricExplanation, 280),
|
|
924
|
+
consentPlatform: boundedText(summary.consentPlatform, 160),
|
|
925
|
+
...(trackerFootprint ? { trackerFootprint } : {}),
|
|
926
|
+
...(policySurfaces ? { policySurfaces } : {}),
|
|
927
|
+
scanTimeSeconds: summary.scanTimeSeconds
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function findingText(finding, priorityLabel = "criticality") {
|
|
931
|
+
const evidence = finding.evidence && typeof finding.evidence === "object" && !Array.isArray(finding.evidence)
|
|
932
|
+
? finding.evidence
|
|
933
|
+
: {};
|
|
934
|
+
const lenses = Array.isArray(finding.reviewLenses) && finding.reviewLenses.length > 0
|
|
935
|
+
? finding.reviewLenses.slice(0, 2).join(", ")
|
|
936
|
+
: "not classified";
|
|
937
|
+
const nextStep = typeof finding.nextStep === "string" && finding.nextStep.trim()
|
|
938
|
+
? `; canonical next step=${boundedText(finding.nextStep.trim(), 180)}`
|
|
939
|
+
: "";
|
|
940
|
+
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}.`;
|
|
941
|
+
}
|
|
942
|
+
function executiveOverviewText(summary) {
|
|
943
|
+
if (!summary)
|
|
944
|
+
return null;
|
|
945
|
+
const tracker = summary.trackerFootprint && typeof summary.trackerFootprint === "object" && !Array.isArray(summary.trackerFootprint)
|
|
946
|
+
? summary.trackerFootprint
|
|
947
|
+
: {};
|
|
948
|
+
const policies = Array.isArray(summary.policySurfaces)
|
|
949
|
+
? summary.policySurfaces.flatMap((surface) => {
|
|
950
|
+
if (!surface || typeof surface !== "object" || Array.isArray(surface))
|
|
951
|
+
return [];
|
|
952
|
+
const row = surface;
|
|
953
|
+
return typeof row.title === "string" ? [row.title] : typeof row.type === "string" ? [row.type] : [];
|
|
954
|
+
}).slice(0, 5)
|
|
955
|
+
: [];
|
|
956
|
+
const values = [
|
|
957
|
+
`CMP/consent platform=${summary.consentPlatform ?? "not returned"}`,
|
|
958
|
+
`third-party requests=${summary.thirdPartyRequests ?? "unknown"}`,
|
|
959
|
+
`non-essential pre-consent storage=${summary.nonEssentialPreConsentStorage ?? summary.cookiesPreConsent ?? "unknown"}`,
|
|
960
|
+
`tracker vendors=${tracker.vendors ?? "unknown"}`,
|
|
961
|
+
`tracker domains=${tracker.domains ?? "unknown"}`,
|
|
962
|
+
`policy surfaces=${policies.length > 0 ? policies.join(", ") : "none returned"}`
|
|
963
|
+
];
|
|
964
|
+
return `Canonical report overview: ${values.join("; ")}.`;
|
|
965
|
+
}
|
|
966
|
+
function preConsentRowText(row, priorityLabel = "priority") {
|
|
967
|
+
const cookieNames = Array.isArray(row.cookieNames) && row.cookieNames.length > 0
|
|
968
|
+
? `; cookies=${row.cookieNames.slice(0, 8).join(", ")}${row.cookieNames.length > 8 ? ", …" : ""}`
|
|
969
|
+
: "";
|
|
970
|
+
const domains = Array.isArray(row.domains) && row.domains.length > 0
|
|
971
|
+
? `; domains=${row.domains.slice(0, 4).join(", ")}${row.domains.length > 4 ? ", …" : ""}`
|
|
972
|
+
: "";
|
|
973
|
+
const timing = typeof row.firstObservedAtMs === "number" ? `${(row.firstObservedAtMs / 1_000).toFixed(3)}s` : "unknown";
|
|
974
|
+
const classification = row.evidenceClassification && typeof row.evidenceClassification === "object"
|
|
975
|
+
? row.evidenceClassification
|
|
976
|
+
: {};
|
|
977
|
+
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"}.`;
|
|
978
|
+
}
|
|
979
|
+
function reportUrlFor(value) {
|
|
980
|
+
const scanId = extractScanId(value);
|
|
981
|
+
return typeof value.reportUrl === "string" && value.reportUrl.trim()
|
|
982
|
+
? value.reportUrl.trim()
|
|
983
|
+
: typeof value.links?.report === "string" && value.links.report.trim()
|
|
984
|
+
? value.links.report.trim()
|
|
985
|
+
: scanId
|
|
986
|
+
? `https://certscore.ai/scan/${encodeURIComponent(scanId)}`
|
|
987
|
+
: null;
|
|
988
|
+
}
|
|
989
|
+
function canonicalScanProvenanceText(value) {
|
|
990
|
+
const present = (field) => typeof field === "string" && field.trim() ? field.trim() : "unavailable";
|
|
991
|
+
const numeric = (field) => typeof field === "number" && Number.isFinite(field) ? String(field) : "unavailable";
|
|
992
|
+
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)}.`;
|
|
993
|
+
}
|
|
994
|
+
export function scanStatusText(value) {
|
|
995
|
+
const reportUrl = reportUrlFor(value);
|
|
996
|
+
const nextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
997
|
+
? value.recommendedNextAction.trim()
|
|
998
|
+
: "Review the returned status and retained limitations.";
|
|
999
|
+
return boundedPreviewResultText([
|
|
1000
|
+
`CertScore scan status: status=${value.status ?? "unknown"}.`,
|
|
1001
|
+
], [...preConsentPreviewTextLines(value), ...terminalLaneResultTextLines(value)], [
|
|
1002
|
+
`Next: ${nextAction}`,
|
|
1003
|
+
canonicalScanProvenanceText(value),
|
|
1004
|
+
`Full report: ${reportUrl ?? "not available"}.`,
|
|
1005
|
+
OBSERVATION_ONLY_DISCLAIMER,
|
|
1006
|
+
INTERPRETATION_STATEMENT,
|
|
1007
|
+
SCAN_PROVENANCE_GROUNDING
|
|
1008
|
+
]);
|
|
1009
|
+
}
|
|
1010
|
+
export function scanSiteText(value, leadingLines = []) {
|
|
1011
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1012
|
+
const active = value.status === "queued" || value.status === "running" || value.status === "finalizing";
|
|
1013
|
+
const nextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
1014
|
+
? value.recommendedNextAction.trim()
|
|
1015
|
+
: active
|
|
1016
|
+
? `Call certscore_get_scan_status sequentially with scanId ${scanId}; do not resubmit certscore_scan_site.`
|
|
1017
|
+
: "Review the returned result and retained limitations.";
|
|
1018
|
+
return boundedPreviewResultText([...leadingLines,
|
|
1019
|
+
`CertScore scan accepted: scanId=${scanId}; status=${value.status ?? "unknown"}.`,
|
|
1020
|
+
], [...preConsentPreviewTextLines(value), ...terminalLaneResultTextLines(value)], [
|
|
1021
|
+
`Next: ${nextAction}`,
|
|
1022
|
+
`Provenance: retrieval=${value.provenance?.retrievalMode ?? "unknown"}; creation=${value.provenance?.creationDecision ?? "unknown"}.`,
|
|
1023
|
+
canonicalScanProvenanceText(value),
|
|
1024
|
+
OBSERVATION_ONLY_DISCLAIMER,
|
|
1025
|
+
INTERPRETATION_STATEMENT,
|
|
1026
|
+
]);
|
|
1027
|
+
}
|
|
1028
|
+
function terminalLaneResultTextLines(value) {
|
|
1029
|
+
const lines = [];
|
|
1030
|
+
const gpcResponse = value.gpcResponse && typeof value.gpcResponse === "object" && !Array.isArray(value.gpcResponse)
|
|
1031
|
+
? value.gpcResponse
|
|
1032
|
+
: null;
|
|
1033
|
+
if (gpcResponse) {
|
|
1034
|
+
lines.push(`GPC response: ${gpcResponse.findingTitle ?? "GPC response"}; status=${gpcResponse.status ?? "indeterminate"}; Sec-GPC: 1 proof retained on ${gpcResponse.comparison?.enabledProof?.requestsWithSecGpc ?? 0} request(s).`);
|
|
1035
|
+
}
|
|
1036
|
+
for (const [field, label] of [["postAcceptObservation", "Accept Path"], ["postRefusalObservation", "Reject Path"]]) {
|
|
1037
|
+
const observation = value[field] && typeof value[field] === "object" && !Array.isArray(value[field])
|
|
1038
|
+
? value[field]
|
|
1039
|
+
: null;
|
|
1040
|
+
if (observation && typeof observation.interpretation === "string") {
|
|
1041
|
+
lines.push(`${label}: ${observation.interpretation}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return lines;
|
|
1045
|
+
}
|
|
1046
|
+
function compactPreviewValue(value, fallback = "unknown", maxChars = 280) {
|
|
1047
|
+
if (value === null || value === undefined)
|
|
1048
|
+
return fallback;
|
|
1049
|
+
const normalized = String(value).replace(/\s+/g, " ").trim();
|
|
1050
|
+
if (!normalized)
|
|
1051
|
+
return fallback;
|
|
1052
|
+
return normalized.length > maxChars ? `${normalized.slice(0, Math.max(1, maxChars - 1))}…` : normalized;
|
|
1053
|
+
}
|
|
1054
|
+
function previewObservedTiming(value) {
|
|
1055
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
1056
|
+
? `${Math.round(value)}ms (t+${(value / 1_000).toFixed(3)}s)`
|
|
1057
|
+
: "unavailable";
|
|
1058
|
+
}
|
|
1059
|
+
function preConsentPreviewTextLines(value) {
|
|
1060
|
+
const preview = value.preConsentPreview && typeof value.preConsentPreview === "object" && !Array.isArray(value.preConsentPreview)
|
|
1061
|
+
? value.preConsentPreview
|
|
1062
|
+
: null;
|
|
1063
|
+
if (!preview)
|
|
1064
|
+
return [];
|
|
1065
|
+
const cookies = Array.isArray(preview.cookies) ? preview.cookies : [];
|
|
1066
|
+
const trackers = Array.isArray(preview.trackers) ? preview.trackers : [];
|
|
1067
|
+
const operationalVendors = Array.isArray(preview.operationalVendors) ? preview.operationalVendors : [];
|
|
1068
|
+
const summary = preview.summary && typeof preview.summary === "object" && !Array.isArray(preview.summary)
|
|
1069
|
+
? preview.summary
|
|
1070
|
+
: {};
|
|
1071
|
+
const coverage = preview.runtimeCoverage && typeof preview.runtimeCoverage === "object" && !Array.isArray(preview.runtimeCoverage)
|
|
1072
|
+
? preview.runtimeCoverage
|
|
1073
|
+
: {};
|
|
1074
|
+
const capturedCookieCount = summary.cookieCount ?? cookies.length;
|
|
1075
|
+
const returnedCookieCount = summary.returnedCookieCount ?? cookies.length;
|
|
1076
|
+
const capturedTrackingVendorCount = summary.trackingVendorCount ?? summary.trackerCount ?? trackers.length;
|
|
1077
|
+
const returnedTrackingVendorCount = summary.returnedTrackingVendorCount ?? trackers.length;
|
|
1078
|
+
const capturedOperationalVendorCount = summary.operationalVendorCount ?? operationalVendors.length;
|
|
1079
|
+
const returnedOperationalVendorCount = summary.returnedOperationalVendorCount ?? operationalVendors.length;
|
|
1080
|
+
const lines = [
|
|
1081
|
+
`Partial pre-consent runtime preview: generated=${compactPreviewValue(preview.generatedAt, "unavailable", 80)}; lane=${compactPreviewValue(preview.sourceLane, "runtime_evidence", 80)}; coverage=${compactPreviewValue(coverage.status, "unknown", 80)}; cookies captured=${capturedCookieCount}; cookie identities returned=${returnedCookieCount}; tracking vendors captured=${capturedTrackingVendorCount}; tracking vendor identities returned=${returnedTrackingVendorCount}; operational/security/consent vendors captured=${capturedOperationalVendorCount}; operational identities returned=${returnedOperationalVendorCount}; all classified vendor observations=${summary.vendorCount ?? "unknown"}; third-party requests=${summary.thirdPartyRequestCount ?? "unknown"}.`,
|
|
1082
|
+
"PARTIAL PREVIEW: These are checkpoint-only partial counts, not the full scan tally. Do not present them as final totals or stop the workflow. Wait for terminal scan status, then call certscore_get_scan_bundle for the completed scan's final returned tally, canonical findings, and coverage limitations.",
|
|
1083
|
+
"Metric scope: tracking vendors exclude operational, security, and consent-management vendors. The completed inventory's broader trackerCount may include those categories, so do not compare that field directly with trackingVendorCount.",
|
|
1084
|
+
];
|
|
1085
|
+
if (cookies.length > 0) {
|
|
1086
|
+
lines.push("Cookies observed before a consent choice (metadata only; no cookie values):");
|
|
1087
|
+
for (const candidate of cookies) {
|
|
1088
|
+
const cookie = candidate && typeof candidate === "object" && !Array.isArray(candidate)
|
|
1089
|
+
? candidate
|
|
1090
|
+
: {};
|
|
1091
|
+
lines.push(`- Cookie ${compactPreviewValue(cookie.name, "unnamed", 256)}; domain=${compactPreviewValue(cookie.domain, "unavailable", 253)}; party=${compactPreviewValue(cookie.party, "unknown", 40)}; category/purpose=${compactPreviewValue(cookie.purpose, "unknown", 80)}; essentiality=${compactPreviewValue(cookie.essentiality, "unknown", 40)}; observedAtMs=${previewObservedTiming(cookie.observedAtMs)}.`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
lines.push("Cookies observed before a consent choice: none returned in this preliminary preview.");
|
|
1096
|
+
}
|
|
1097
|
+
if (trackers.length > 0) {
|
|
1098
|
+
lines.push("Tracking vendors/products observed before a consent choice:");
|
|
1099
|
+
for (const candidate of trackers) {
|
|
1100
|
+
const tracker = candidate && typeof candidate === "object" && !Array.isArray(candidate)
|
|
1101
|
+
? candidate
|
|
1102
|
+
: {};
|
|
1103
|
+
const domains = Array.isArray(tracker.domains)
|
|
1104
|
+
? tracker.domains.map((domain) => compactPreviewValue(domain, "unknown", 253)).join(", ")
|
|
1105
|
+
: "none returned";
|
|
1106
|
+
lines.push(`- Tracking vendor ${compactPreviewValue(tracker.vendor, "unknown", 160)}; product=${compactPreviewValue(tracker.product, "unavailable", 160)}; category/purpose=${compactPreviewValue(tracker.purpose, "unknown", 80)}; confidence=${compactPreviewValue(tracker.confidence, "unknown", 20)}; domains=${compactPreviewValue(domains, "none returned", 800)}.`);
|
|
1107
|
+
}
|
|
1108
|
+
lines.push("Tracker timing: per-tracker first-seen milliseconds are not part of the preliminary preview contract; do not infer them from cookie timing.");
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
lines.push("Tracking vendors/products observed before a consent choice: none returned in this partial preview.");
|
|
1112
|
+
}
|
|
1113
|
+
if (operationalVendors.length > 0) {
|
|
1114
|
+
lines.push("Operational, security, or consent-management vendors observed (separate from trackingVendorCount):");
|
|
1115
|
+
for (const candidate of operationalVendors) {
|
|
1116
|
+
const vendor = candidate && typeof candidate === "object" && !Array.isArray(candidate)
|
|
1117
|
+
? candidate
|
|
1118
|
+
: {};
|
|
1119
|
+
const domains = Array.isArray(vendor.domains)
|
|
1120
|
+
? vendor.domains.map((domain) => compactPreviewValue(domain, "unknown", 253)).join(", ")
|
|
1121
|
+
: "none returned";
|
|
1122
|
+
lines.push(`- Operational vendor ${compactPreviewValue(vendor.vendor, "unknown", 160)}; product=${compactPreviewValue(vendor.product, "unavailable", 160)}; category/purpose=${compactPreviewValue(vendor.purpose, "unknown", 80)}; confidence=${compactPreviewValue(vendor.confidence, "unknown", 20)}; domains=${compactPreviewValue(domains, "none returned", 800)}.`);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (String(capturedCookieCount) !== String(returnedCookieCount) ||
|
|
1126
|
+
String(capturedTrackingVendorCount) !== String(returnedTrackingVendorCount) ||
|
|
1127
|
+
String(capturedOperationalVendorCount) !== String(returnedOperationalVendorCount)) {
|
|
1128
|
+
lines.push(`Preview identity lists are bounded: ${capturedCookieCount} cookies captured/${returnedCookieCount} identities returned; ${capturedTrackingVendorCount} tracking vendors captured/${returnedTrackingVendorCount} identities returned; ${capturedOperationalVendorCount} operational vendors captured/${returnedOperationalVendorCount} identities returned. Use captured counts for checkpoint coverage and returned arrays for names.`);
|
|
1129
|
+
}
|
|
1130
|
+
const disclaimer = typeof preview.observationOnlyDisclaimer === "string" && preview.observationOnlyDisclaimer.trim()
|
|
1131
|
+
? preview.observationOnlyDisclaimer.trim()
|
|
1132
|
+
: "Preliminary passive observations only; not findings, a score, or a final result.";
|
|
1133
|
+
lines.push(`${compactPreviewValue(disclaimer, "Preliminary passive observations only.", 500)} Continue sequential status polling until terminal status; at completed or completed_limited, retrieve certscore_get_scan_bundle before reporting the full scan results or final returned tally.`);
|
|
1134
|
+
return lines;
|
|
1135
|
+
}
|
|
1136
|
+
function boundedPreviewResultText(prefix, bodyLines, suffix) {
|
|
1137
|
+
const lines = [...prefix];
|
|
1138
|
+
let rendered = 0;
|
|
1139
|
+
for (const line of bodyLines) {
|
|
1140
|
+
if ([...lines, line, ...suffix].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
1141
|
+
break;
|
|
1142
|
+
lines.push(line);
|
|
1143
|
+
rendered += 1;
|
|
1144
|
+
}
|
|
1145
|
+
if (rendered < bodyLines.length) {
|
|
1146
|
+
const omitted = `${bodyLines.length - rendered} additional preliminary preview line${bodyLines.length - rendered === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; all bounded rows remain in structuredContent.`;
|
|
1147
|
+
if ([...lines, omitted, ...suffix].join("\n").length <= MAX_TOOL_TEXT_CHARS)
|
|
1148
|
+
lines.push(omitted);
|
|
1149
|
+
}
|
|
1150
|
+
lines.push(...suffix);
|
|
1151
|
+
return lines.join("\n");
|
|
1152
|
+
}
|
|
1153
|
+
function boundedResultText(header, bodyLines, value) {
|
|
1154
|
+
const footer = [OBSERVATION_ONLY_DISCLAIMER, INTERPRETATION_STATEMENT];
|
|
1155
|
+
const reportUrl = reportUrlFor(value);
|
|
1156
|
+
const lines = [
|
|
1157
|
+
header,
|
|
1158
|
+
`Provenance: retrieval=${value.provenance?.retrievalMode ?? "unknown"}; original creation=${value.provenance?.creationDecision ?? "unknown"}; scan age seconds=${value.provenance?.scanAgeSeconds ?? "unavailable"}.`,
|
|
1159
|
+
`Full report: ${reportUrl ?? "not available"}.`
|
|
1160
|
+
];
|
|
1161
|
+
let rendered = 0;
|
|
1162
|
+
for (const line of bodyLines) {
|
|
1163
|
+
if ([...lines, line, ...footer].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
1164
|
+
break;
|
|
1165
|
+
lines.push(line);
|
|
1166
|
+
rendered += 1;
|
|
1167
|
+
}
|
|
1168
|
+
if (rendered < bodyLines.length) {
|
|
1169
|
+
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.`;
|
|
1170
|
+
if ([...lines, omitted, ...footer].join("\n").length <= MAX_TOOL_TEXT_CHARS)
|
|
1171
|
+
lines.push(omitted);
|
|
1172
|
+
}
|
|
1173
|
+
lines.push(...footer);
|
|
1174
|
+
return lines.join("\n");
|
|
1175
|
+
}
|
|
1176
|
+
export function findingListText(value, label = "Canonical projected findings") {
|
|
1177
|
+
const findings = Array.isArray(value.findings) ? value.findings : [];
|
|
1178
|
+
const pagination = value.pagination && typeof value.pagination === "object" && !Array.isArray(value.pagination)
|
|
1179
|
+
? value.pagination
|
|
1180
|
+
: {};
|
|
1181
|
+
const total = typeof pagination.total === "number" ? pagination.total : findings.length;
|
|
1182
|
+
const returned = typeof pagination.returned === "number" ? pagination.returned : findings.length;
|
|
1183
|
+
const truncated = pagination.truncated === true;
|
|
1184
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1185
|
+
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);
|
|
1186
|
+
}
|
|
1187
|
+
export function preConsentInventoryText(value) {
|
|
1188
|
+
const rows = Array.isArray(value.rows) ? value.rows : [];
|
|
1189
|
+
const metadata = value.evidenceMetadata && typeof value.evidenceMetadata === "object" && !Array.isArray(value.evidenceMetadata)
|
|
1190
|
+
? value.evidenceMetadata
|
|
1191
|
+
: {};
|
|
1192
|
+
const total = typeof metadata.total === "number"
|
|
1193
|
+
? metadata.total
|
|
1194
|
+
: typeof value.summary?.totalRowCount === "number"
|
|
1195
|
+
? value.summary.totalRowCount
|
|
1196
|
+
: typeof value.summary?.rowCount === "number"
|
|
1197
|
+
? value.summary.rowCount
|
|
1198
|
+
: rows.length;
|
|
1199
|
+
const returned = typeof metadata.returned === "number" ? metadata.returned : rows.length;
|
|
1200
|
+
const truncated = metadata.truncated === true || value.summary?.truncated === true;
|
|
1201
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1202
|
+
const domain = typeof value.domain === "string" ? value.domain : "unknown domain";
|
|
1203
|
+
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);
|
|
1204
|
+
}
|
|
1205
|
+
export function pulseReportText(value, label = "CertScore report") {
|
|
1206
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1207
|
+
const domain = typeof value.domain === "string" ? value.domain : "unknown domain";
|
|
1208
|
+
const score = typeof value.summary?.score === "number"
|
|
1209
|
+
? value.summary.score
|
|
1210
|
+
: typeof value.score === "number"
|
|
1211
|
+
? value.score
|
|
1212
|
+
: null;
|
|
1213
|
+
const findings = findingsFromReport(value);
|
|
1214
|
+
const overview = executiveOverviewText(value.executiveSummary ?? value.summary?.executiveSummary);
|
|
1215
|
+
const body = [
|
|
1216
|
+
...(overview ? [overview] : []),
|
|
1217
|
+
`Canonical projected findings returned in this ${label.toLocaleLowerCase()}: ${findings.length}.`,
|
|
1218
|
+
...findings.map((finding) => findingText(finding))
|
|
1219
|
+
];
|
|
1220
|
+
return boundedResultText(`${label} for ${domain}; scanId=${scanId}${score === null ? "" : `; CertScore score=${score}`}.`, body, value);
|
|
1221
|
+
}
|
|
1222
|
+
export function markdownReportText(value) {
|
|
1223
|
+
const markdown = typeof value.value === "string" ? value.value : "";
|
|
1224
|
+
const excerptLimit = Math.max(0, MAX_TOOL_TEXT_CHARS - 1_500);
|
|
1225
|
+
const excerpt = markdown.length > excerptLimit ? `${markdown.slice(0, excerptLimit)}\n…[markdown truncated for MCP TextContent]` : markdown;
|
|
1226
|
+
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);
|
|
1227
|
+
}
|
|
1228
|
+
export function scanBundleText(bundle) {
|
|
1229
|
+
const score = typeof bundle.score === "number" ? `; CertScore score=${bundle.score}` : "";
|
|
1230
|
+
const footer = [OBSERVATION_ONLY_DISCLAIMER, SCAN_BUNDLE_INTERPRETATION_STATEMENT];
|
|
1231
|
+
const lines = [
|
|
1232
|
+
SCAN_BUNDLE_RESPONSE_CONTRACT,
|
|
1233
|
+
`CertScore scan bundle for ${bundle.domain ?? "unknown domain"}; status=${bundle.status ?? "unknown"}${score}; scanId=${bundle.scanId ?? "unknown"}.`,
|
|
1234
|
+
canonicalScanProvenanceText(bundle),
|
|
1235
|
+
`Full report: ${bundle.reportUrl ?? (bundle.scanId ? `https://certscore.ai/scan/${encodeURIComponent(String(bundle.scanId))}` : "not available")}.`
|
|
1236
|
+
];
|
|
1237
|
+
const canAppend = (line) => [...lines, line, ...footer].join("\n").length <= MAX_TOOL_TEXT_CHARS;
|
|
1238
|
+
const append = (line) => {
|
|
1239
|
+
if (!canAppend(line))
|
|
1240
|
+
return false;
|
|
1241
|
+
lines.push(line);
|
|
1242
|
+
return true;
|
|
1243
|
+
};
|
|
1244
|
+
const coverage = bundle.coverage && typeof bundle.coverage === "object" && !Array.isArray(bundle.coverage)
|
|
1245
|
+
? bundle.coverage
|
|
1246
|
+
: null;
|
|
1247
|
+
if (coverage) {
|
|
1248
|
+
append(`Coverage: status=${coverage.status ?? "unknown"}; ${coverage.summary ?? "Review limitations before interpreting absence."}`);
|
|
1249
|
+
}
|
|
1250
|
+
const gpcResponse = bundle.gpcResponse && typeof bundle.gpcResponse === "object" && !Array.isArray(bundle.gpcResponse)
|
|
1251
|
+
? bundle.gpcResponse
|
|
1252
|
+
: null;
|
|
1253
|
+
if (gpcResponse) {
|
|
1254
|
+
const proof = gpcResponse.comparison?.enabledProof;
|
|
1255
|
+
const californiaPolicy = gpcResponse.californiaPolicy;
|
|
1256
|
+
append(`GPC response: ${gpcResponse.findingTitle ?? "GPC response"}; status=${gpcResponse.status ?? "indeterminate"}; Sec-GPC: 1 proof retained on ${proof?.requestsWithSecGpc ?? 0} request(s).`);
|
|
1257
|
+
if (californiaPolicy?.applied === true && californiaPolicy.deductionPoints === 15) {
|
|
1258
|
+
append("California scoring policy: −15 points. This score effect is separate from the jurisdiction-neutral GPC comparison.");
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const postAccept = bundle.postAcceptObservation && typeof bundle.postAcceptObservation === "object" && !Array.isArray(bundle.postAcceptObservation)
|
|
1262
|
+
? bundle.postAcceptObservation
|
|
1263
|
+
: null;
|
|
1264
|
+
if (postAccept && typeof postAccept.interpretation === "string") {
|
|
1265
|
+
const termination = postAccept.termination && typeof postAccept.termination === "object" && !Array.isArray(postAccept.termination)
|
|
1266
|
+
? postAccept.termination
|
|
1267
|
+
: null;
|
|
1268
|
+
const intentionalEvidenceStop = termination?.kind === "evidence_satisfied" && termination.intentional === true
|
|
1269
|
+
? " The observation then stopped intentionally because qualifying evidence had been captured."
|
|
1270
|
+
: "";
|
|
1271
|
+
append(`Accept Path: ${postAccept.interpretation}${intentionalEvidenceStop}`);
|
|
1272
|
+
for (const limitation of Array.isArray(postAccept.coverageLimitations)
|
|
1273
|
+
? postAccept.coverageLimitations.slice(0, 3)
|
|
1274
|
+
: []) {
|
|
1275
|
+
append(`Accept Path coverage limitation: ${limitation}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
const postRefusal = bundle.postRefusalObservation && typeof bundle.postRefusalObservation === "object" && !Array.isArray(bundle.postRefusalObservation)
|
|
1279
|
+
? bundle.postRefusalObservation
|
|
1280
|
+
: null;
|
|
1281
|
+
if (postRefusal && typeof postRefusal.interpretation === "string") {
|
|
1282
|
+
const termination = postRefusal.termination && typeof postRefusal.termination === "object" && !Array.isArray(postRefusal.termination)
|
|
1283
|
+
? postRefusal.termination
|
|
1284
|
+
: null;
|
|
1285
|
+
const intentionalEvidenceStop = termination?.kind === "evidence_satisfied" && termination.intentional === true
|
|
1286
|
+
? " The observation then stopped intentionally because qualifying evidence had been captured."
|
|
1287
|
+
: "";
|
|
1288
|
+
append(`Reject Path: ${postRefusal.interpretation}${intentionalEvidenceStop}`);
|
|
1289
|
+
for (const limitation of Array.isArray(postRefusal.coverageLimitations)
|
|
1290
|
+
? postRefusal.coverageLimitations.slice(0, 3)
|
|
1291
|
+
: []) {
|
|
1292
|
+
append(`Reject Path coverage limitation: ${limitation}`);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
const overview = executiveOverviewText(bundle.summary?.executiveSummary);
|
|
1296
|
+
if (overview)
|
|
1297
|
+
append(overview);
|
|
1298
|
+
const transportSecurity = bundle.transportSecurity && typeof bundle.transportSecurity === "object" && !Array.isArray(bundle.transportSecurity)
|
|
1299
|
+
? bundle.transportSecurity
|
|
1300
|
+
: null;
|
|
1301
|
+
if (transportSecurity) {
|
|
1302
|
+
const counts = transportSecurity.observationCounts && typeof transportSecurity.observationCounts === "object"
|
|
1303
|
+
? transportSecurity.observationCounts
|
|
1304
|
+
: {};
|
|
1305
|
+
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}.`);
|
|
1306
|
+
for (const observation of Array.isArray(transportSecurity.observations) ? transportSecurity.observations : []) {
|
|
1307
|
+
append(`Transport observation: ${observation.label ?? observation.id ?? "unknown"}; status=${observation.status ?? "unknown"}; ${observation.summary ?? "Review the structured transport evidence."}`);
|
|
1308
|
+
}
|
|
1309
|
+
for (const limitation of Array.isArray(transportSecurity.limitations) ? transportSecurity.limitations.slice(0, 3) : []) {
|
|
1310
|
+
append(`Transport limitation: ${limitation}`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const findings = Array.isArray(bundle.findings) ? bundle.findings : [];
|
|
1314
|
+
const findingTotal = bundle.findingsMetadata?.total ?? findings.length;
|
|
1315
|
+
const findingReturned = bundle.findingsMetadata?.returned ?? bundle.findingsMetadata?.shown ?? findings.length;
|
|
1316
|
+
append(`Canonical projected findings: ${findingReturned} of ${findingTotal} returned${bundle.findingsMetadata?.truncated ? " (truncated)" : ""}. These are already-projected review signals, not inferred technologies or legal conclusions.`);
|
|
1317
|
+
let findingsRendered = 0;
|
|
1318
|
+
for (const [index, finding] of findings.entries()) {
|
|
1319
|
+
const next = findingText(finding, "CertScore priority/classification");
|
|
1320
|
+
const remaining = findings.length - index - 1;
|
|
1321
|
+
const reserve = remaining > 0
|
|
1322
|
+
? `${remaining} additional returned finding${remaining === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; see structuredContent or the report URL.`
|
|
1323
|
+
: null;
|
|
1324
|
+
if ([...lines, next, ...(reserve ? [reserve] : []), ...footer].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
1325
|
+
break;
|
|
1326
|
+
lines.push(next);
|
|
1327
|
+
findingsRendered += 1;
|
|
1328
|
+
}
|
|
1329
|
+
if (findingsRendered < findings.length) {
|
|
1330
|
+
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.`);
|
|
1331
|
+
}
|
|
1332
|
+
const inventory = bundle.preConsentCookiesTrackers;
|
|
1333
|
+
if (inventory && typeof inventory === "object") {
|
|
1334
|
+
append(`Pre-consent cookie/tracker evidence: ${inventory.returned ?? 0} of ${inventory.total ?? 0} rows returned${inventory.truncated ? " (truncated)" : ""}.`);
|
|
1335
|
+
const rows = Array.isArray(inventory.rows) ? inventory.rows : [];
|
|
1336
|
+
let rowsRendered = 0;
|
|
1337
|
+
for (const [index, row] of rows.entries()) {
|
|
1338
|
+
const next = preConsentRowText(row, "CertScore priority");
|
|
1339
|
+
const remaining = rows.length - index - 1;
|
|
1340
|
+
const reserve = remaining > 0
|
|
1341
|
+
? `${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.`
|
|
1342
|
+
: null;
|
|
1343
|
+
if ([...lines, next, ...(reserve ? [reserve] : []), ...footer].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
1344
|
+
break;
|
|
1345
|
+
lines.push(next);
|
|
1346
|
+
rowsRendered += 1;
|
|
1347
|
+
}
|
|
1348
|
+
if (rowsRendered < rows.length) {
|
|
1349
|
+
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.`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
append("No row-level pre-consent inventory was available for this result; review coverage and limitations before interpreting absence.");
|
|
1354
|
+
}
|
|
1355
|
+
lines.push(...footer);
|
|
1356
|
+
return lines.join("\n");
|
|
1357
|
+
}
|
|
1358
|
+
function scanBundleTransportSecurity(report, detail) {
|
|
1359
|
+
const source = report.transportSecurity && typeof report.transportSecurity === "object" && !Array.isArray(report.transportSecurity)
|
|
1360
|
+
? report.transportSecurity
|
|
1361
|
+
: null;
|
|
1362
|
+
if (!source) {
|
|
1363
|
+
return {
|
|
1364
|
+
status: "unavailable",
|
|
1365
|
+
evidenceRetained: false,
|
|
1366
|
+
observationCounts: {
|
|
1367
|
+
total: 0,
|
|
1368
|
+
observedPositive: 0,
|
|
1369
|
+
concernOrReview: 0,
|
|
1370
|
+
notObserved: 0,
|
|
1371
|
+
unavailable: 0
|
|
1372
|
+
},
|
|
1373
|
+
observations: [],
|
|
1374
|
+
limitations: [
|
|
1375
|
+
"No canonical transport-security projection was available in this scan response. Do not infer a positive transport result."
|
|
1376
|
+
]
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
const observations = Array.isArray(source.observations) ? source.observations : [];
|
|
1380
|
+
const compactObservations = observations.slice(0, 8).map((observation) => {
|
|
1381
|
+
const row = observation && typeof observation === "object" && !Array.isArray(observation)
|
|
1382
|
+
? observation
|
|
1383
|
+
: {};
|
|
1384
|
+
return {
|
|
1385
|
+
id: row.id,
|
|
1386
|
+
label: boundedText(row.label, 140),
|
|
1387
|
+
status: row.status,
|
|
1388
|
+
assessmentStatus: row.assessmentStatus,
|
|
1389
|
+
evidenceState: row.evidenceState,
|
|
1390
|
+
summary: boundedText(row.summary, 180),
|
|
1391
|
+
evidenceRefs: []
|
|
1392
|
+
};
|
|
1393
|
+
});
|
|
1394
|
+
return {
|
|
1395
|
+
status: source.status === "available" || source.status === "limited" ? source.status : "unavailable",
|
|
1396
|
+
evidenceRetained: source.evidenceRetained === true,
|
|
1397
|
+
observationCounts: source.observationCounts && typeof source.observationCounts === "object" && !Array.isArray(source.observationCounts)
|
|
1398
|
+
? source.observationCounts
|
|
1399
|
+
: {
|
|
1400
|
+
total: observations.length,
|
|
1401
|
+
observedPositive: 0,
|
|
1402
|
+
concernOrReview: 0,
|
|
1403
|
+
notObserved: 0,
|
|
1404
|
+
unavailable: observations.length
|
|
1405
|
+
},
|
|
1406
|
+
observations: detail === "summary" || detail === "findings"
|
|
1407
|
+
? compactObservations
|
|
1408
|
+
: observations.map((observation) => compactEvidenceValue(observation, {
|
|
1409
|
+
arrayItems: 6,
|
|
1410
|
+
depth: 4,
|
|
1411
|
+
objectKeys: 12,
|
|
1412
|
+
stringChars: 1_200
|
|
1413
|
+
})),
|
|
1414
|
+
limitations: Array.isArray(source.limitations)
|
|
1415
|
+
? source.limitations.filter((value) => typeof value === "string").slice(0, 8)
|
|
1416
|
+
: [],
|
|
1417
|
+
...(detail === "full" && source.retainedSummary && typeof source.retainedSummary === "object" && !Array.isArray(source.retainedSummary)
|
|
1418
|
+
? {
|
|
1419
|
+
retainedSummary: compactEvidenceValue(source.retainedSummary, {
|
|
1420
|
+
arrayItems: 20,
|
|
1421
|
+
depth: 4,
|
|
1422
|
+
objectKeys: 24,
|
|
1423
|
+
stringChars: 1_200
|
|
1424
|
+
})
|
|
1425
|
+
}
|
|
1426
|
+
: {})
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
557
1429
|
export function buildScanBundle(input) {
|
|
558
1430
|
const detail = input.detail ?? "summary";
|
|
559
1431
|
const maxFindings = Math.min(50, Math.max(1, input.maxFindings ?? (detail === "summary" ? 5 : 20)));
|
|
560
1432
|
const maxPreConsentRows = Math.min(50, Math.max(1, input.maxPreConsentRows ?? 20));
|
|
561
1433
|
const evidence = (input.evidence ?? {});
|
|
562
1434
|
const report = (input.report ?? {});
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
1435
|
+
const transportSecurity = scanBundleTransportSecurity(report, detail);
|
|
1436
|
+
const allFindings = prioritizeFindingsForBoundedBundle(Array.isArray(input.findings.findings) ? input.findings.findings : []);
|
|
1437
|
+
const selectedFindings = detail === "summary" ? distinctSummaryFindings(allFindings) : allFindings;
|
|
1438
|
+
const selectedFindingRows = selectedFindings.slice(0, maxFindings);
|
|
1439
|
+
const findingDisclaimersDeduplicated = detail === "full"
|
|
1440
|
+
&& selectedFindingRows.some((finding) => typeof finding.disclaimer === "string");
|
|
1441
|
+
const findings = selectedFindingRows
|
|
1442
|
+
.map((finding) => detail === "full" ? withoutFindingDisclaimer(finding) : compactBundleFinding(finding));
|
|
1443
|
+
const deduplicatedReport = deduplicatedFullReport({ findings, report, transportSecurity });
|
|
1444
|
+
const preConsentCookiesTrackers = input.preConsentCookiesTrackers
|
|
1445
|
+
? preConsentBundleSection(input.preConsentCookiesTrackers, maxPreConsentRows)
|
|
1446
|
+
: null;
|
|
570
1447
|
const links = {
|
|
571
1448
|
...(input.scan.links ?? {}),
|
|
572
1449
|
...(report.links && typeof report.links === "object" && !Array.isArray(report.links) ? report.links : {})
|
|
573
1450
|
};
|
|
574
|
-
const
|
|
1451
|
+
const requestedMaxBytes = Math.min(200_000, Math.max(5_000, input.requestedMaxBytes ?? input.maxBytes ?? 50_000));
|
|
1452
|
+
const responseCeilingBytes = Math.min(200_000, Math.max(5_000, input.responseCeilingBytes ?? 200_000));
|
|
1453
|
+
const maxBytes = Math.min(requestedMaxBytes, responseCeilingBytes);
|
|
575
1454
|
const contentUrls = Object.fromEntries(Object.entries({
|
|
576
1455
|
report: input.scan.links?.report ?? links.report,
|
|
577
1456
|
findings: links.findings,
|
|
@@ -579,8 +1458,8 @@ export function buildScanBundle(input) {
|
|
|
579
1458
|
preConsentCookiesTrackers: links.preConsentCookiesTrackers
|
|
580
1459
|
}).filter(([, value]) => typeof value === "string"));
|
|
581
1460
|
const intentionallyOmitted = new Set();
|
|
582
|
-
if (
|
|
583
|
-
intentionallyOmitted.add("
|
|
1461
|
+
if (allFindings.length > findings.length)
|
|
1462
|
+
intentionallyOmitted.add("additionalFindings");
|
|
584
1463
|
if ((detail === "summary" || detail === "findings") && (Object.keys(evidence).length > 0 || allFindings.length > 0))
|
|
585
1464
|
intentionallyOmitted.add("evidence");
|
|
586
1465
|
if (detail !== "full" && Object.keys(report).length > 0)
|
|
@@ -592,12 +1471,19 @@ export function buildScanBundle(input) {
|
|
|
592
1471
|
scanId: input.scan.scanId,
|
|
593
1472
|
domain: input.scan.domain,
|
|
594
1473
|
url: input.scan.url ?? null,
|
|
1474
|
+
scanFrom: input.scan.scanFrom ?? null,
|
|
595
1475
|
status: input.scan.status,
|
|
596
1476
|
score: input.scan.score ?? null,
|
|
1477
|
+
scoreLabel: "CertScore score",
|
|
597
1478
|
scoreStatus: input.scan.scoreStatus ?? "final",
|
|
598
1479
|
scoreVersion: input.scan.scoreVersion ?? null,
|
|
599
1480
|
scoreUpdatedAt: input.scan.scoreUpdatedAt ?? null,
|
|
600
1481
|
riskLevel: input.scan.riskLevel ?? null,
|
|
1482
|
+
...(input.scan.gpcResponse ? { gpcResponse: input.scan.gpcResponse } : {}),
|
|
1483
|
+
postAcceptObservation: input.scan.postAcceptObservation ?? null,
|
|
1484
|
+
postRefusalObservation: input.scan.postRefusalObservation ?? null,
|
|
1485
|
+
provenance: scanProvenance(input.scan, "existing_scan_retrieved"),
|
|
1486
|
+
interpretationGuidance: interpretationGuidance(SCAN_BUNDLE_INTERPRETATION_STATEMENT),
|
|
601
1487
|
resultDisposition: input.scan.resultDisposition ?? null,
|
|
602
1488
|
noGo: input.scan.noGo ?? null,
|
|
603
1489
|
coverage: input.scan.coverage ?? null,
|
|
@@ -615,39 +1501,35 @@ export function buildScanBundle(input) {
|
|
|
615
1501
|
headline: report.summary && typeof report.summary === "object" && !Array.isArray(report.summary)
|
|
616
1502
|
? report.summary.headline ?? null
|
|
617
1503
|
: null,
|
|
618
|
-
executiveSummary: report.executiveSummary
|
|
1504
|
+
executiveSummary: neutralExecutiveSummary(report.executiveSummary),
|
|
619
1505
|
counts: report.counts ?? null,
|
|
620
1506
|
agentInterpretation: report.agentInterpretation ?? null
|
|
621
1507
|
},
|
|
622
1508
|
findings,
|
|
623
1509
|
findingsMetadata: {
|
|
624
1510
|
shown: findings.length,
|
|
1511
|
+
returned: findings.length,
|
|
625
1512
|
total: allFindings.length,
|
|
626
1513
|
truncated: allFindings.length > findings.length
|
|
627
1514
|
},
|
|
1515
|
+
transportSecurity,
|
|
628
1516
|
...(detail === "evidence" || detail === "full"
|
|
629
1517
|
? { evidenceSummary: bundleEvidenceSummary(evidence, allFindings, links, detail === "full") }
|
|
630
1518
|
: {}),
|
|
631
1519
|
...(detail === "full" ? {
|
|
632
|
-
fullReport: compactEvidenceValue(
|
|
1520
|
+
fullReport: compactEvidenceValue(deduplicatedReport.residual, {
|
|
633
1521
|
arrayItems: 50,
|
|
634
1522
|
depth: 8,
|
|
635
1523
|
objectKeys: 100,
|
|
636
1524
|
stringChars: 4_000
|
|
637
1525
|
})
|
|
638
1526
|
} : {}),
|
|
639
|
-
...(
|
|
640
|
-
summary: input.preConsentCookiesTrackers.summary,
|
|
641
|
-
rows: preConsentRows,
|
|
642
|
-
shown: preConsentRows.length,
|
|
643
|
-
total: Array.isArray(input.preConsentCookiesTrackers.rows) ? input.preConsentCookiesTrackers.rows.length : 0,
|
|
644
|
-
truncated: Array.isArray(input.preConsentCookiesTrackers.rows) && input.preConsentCookiesTrackers.rows.length > preConsentRows.length
|
|
645
|
-
} } : {}),
|
|
1527
|
+
...(preConsentCookiesTrackers ? { preConsentCookiesTrackers } : {}),
|
|
646
1528
|
links,
|
|
647
|
-
reportUrl: input.scan.links?.report ?? (typeof links.report === "string" ? links.report :
|
|
1529
|
+
reportUrl: input.scan.links?.report ?? (typeof links.report === "string" ? links.report : `https://certscore.ai/scan/${encodeURIComponent(input.scan.scanId)}`),
|
|
648
1530
|
recommendedNextTool: null,
|
|
649
|
-
recommendedNextAction: guidedScan.error?.recommendedNextAction ?? (detail === "summary" && allFindings.length >
|
|
650
|
-
? "Review
|
|
1531
|
+
recommendedNextAction: guidedScan.error?.recommendedNextAction ?? (detail === "summary" && allFindings.length > findings.length
|
|
1532
|
+
? "Review the returned overview and findings, then request detail=findings or a larger maxFindings value for more projected findings."
|
|
651
1533
|
: findings.length > 0
|
|
652
1534
|
? "Review the returned findings and follow their evidence references. Use detail=evidence only when deeper retained context is needed."
|
|
653
1535
|
: "Review coverage and limitations before interpreting the absence of findings."),
|
|
@@ -656,30 +1538,64 @@ export function buildScanBundle(input) {
|
|
|
656
1538
|
detail,
|
|
657
1539
|
heavyEvidenceIncluded: detail === "evidence" || detail === "full",
|
|
658
1540
|
findingsTruncated: allFindings.length > findings.length,
|
|
659
|
-
requestedMaxBytes
|
|
1541
|
+
requestedMaxBytes,
|
|
1542
|
+
effectiveMaxBytes: maxBytes,
|
|
1543
|
+
responseCeilingBytes,
|
|
1544
|
+
responseBudgetClamped: requestedMaxBytes > maxBytes,
|
|
660
1545
|
actualBytes: 0,
|
|
1546
|
+
fullPayloadBytes: 0,
|
|
661
1547
|
truncated: false,
|
|
1548
|
+
canonicalFindingsComplete: allFindings.length === findings.length,
|
|
662
1549
|
truncationReason: null,
|
|
663
1550
|
omittedSections: [...intentionallyOmitted],
|
|
1551
|
+
deduplicatedSections: detail === "full"
|
|
1552
|
+
? [
|
|
1553
|
+
...deduplicatedReport.deduplicatedSections,
|
|
1554
|
+
...(findingDisclaimersDeduplicated ? ["findings[].disclaimer"] : [])
|
|
1555
|
+
]
|
|
1556
|
+
: [],
|
|
664
1557
|
nextRecommendedMaxBytes: null,
|
|
665
1558
|
omittedContentAvailableViaUrl: intentionallyOmitted.size > 0 && Object.keys(contentUrls).length > 0,
|
|
666
1559
|
contentUrls
|
|
667
1560
|
},
|
|
668
1561
|
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER,
|
|
669
|
-
disclaimer:
|
|
1562
|
+
disclaimer: LEGAL_REVIEW_DISCLAIMER
|
|
670
1563
|
};
|
|
671
|
-
const
|
|
672
|
-
const markBudgetOmitted = (section, reason, requiredBytes = bundle.mcpMetadata.actualBytes) => {
|
|
1564
|
+
const markBudgetOmitted = (section, reason) => {
|
|
673
1565
|
bundle.mcpMetadata.truncated = true;
|
|
674
1566
|
bundle.mcpMetadata.truncationReason ??= reason;
|
|
675
1567
|
if (!bundle.mcpMetadata.omittedSections.includes(section)) {
|
|
676
1568
|
bundle.mcpMetadata.omittedSections.push(section);
|
|
677
1569
|
}
|
|
678
|
-
recommendationCandidates.push(requiredBytes);
|
|
679
1570
|
bundle.mcpMetadata.omittedContentAvailableViaUrl = Object.keys(contentUrls).length > 0;
|
|
680
1571
|
};
|
|
681
1572
|
const refresh = () => updateBundleActualBytes(bundle);
|
|
682
|
-
|
|
1573
|
+
const captureFullPayloadBytes = () => {
|
|
1574
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1575
|
+
refresh();
|
|
1576
|
+
const measured = bundle.mcpMetadata.actualBytes;
|
|
1577
|
+
if (bundle.mcpMetadata.fullPayloadBytes === measured)
|
|
1578
|
+
break;
|
|
1579
|
+
bundle.mcpMetadata.fullPayloadBytes = measured;
|
|
1580
|
+
}
|
|
1581
|
+
refresh();
|
|
1582
|
+
};
|
|
1583
|
+
const refreshTruncationGuidance = () => {
|
|
1584
|
+
const completeBytes = bundle.mcpMetadata.fullPayloadBytes;
|
|
1585
|
+
const canonicalFindingsComplete = bundle.findingsMetadata.returned === bundle.findingsMetadata.total &&
|
|
1586
|
+
bundle.findingsMetadata.truncated === false;
|
|
1587
|
+
bundle.mcpMetadata.canonicalFindingsComplete = canonicalFindingsComplete;
|
|
1588
|
+
bundle.mcpMetadata.nextRecommendedMaxBytes = completeBytes <= responseCeilingBytes
|
|
1589
|
+
? Math.max(5_000, Math.ceil(completeBytes / 1_000) * 1_000)
|
|
1590
|
+
: null;
|
|
1591
|
+
bundle.recommendedNextAction = canonicalFindingsComplete
|
|
1592
|
+
? "Canonical findings complete; retry only for omitted envelope detail."
|
|
1593
|
+
: bundle.mcpMetadata.nextRecommendedMaxBytes
|
|
1594
|
+
? `Retry with maxBytes=${bundle.mcpMetadata.nextRecommendedMaxBytes} to retrieve the complete requested tier, or open ${bundle.reportUrl ? "the report URL" : "an available content URL"}.`
|
|
1595
|
+
: `The complete requested tier exceeds the MCP byte ceiling; open ${bundle.reportUrl ? "the report URL" : "an available content URL"}.`;
|
|
1596
|
+
refresh();
|
|
1597
|
+
};
|
|
1598
|
+
captureFullPayloadBytes();
|
|
683
1599
|
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.fullReport) {
|
|
684
1600
|
markBudgetOmitted("fullReport", "full_report_omitted_to_byte_limit");
|
|
685
1601
|
delete bundle.fullReport;
|
|
@@ -691,25 +1607,21 @@ export function buildScanBundle(input) {
|
|
|
691
1607
|
bundle.evidenceSummary = compactSummary;
|
|
692
1608
|
refresh();
|
|
693
1609
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
markBudgetOmitted("
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
delete bundle.preConsentCookiesTrackers;
|
|
1610
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes &&
|
|
1611
|
+
(bundle.transportSecurity?.observations?.length > 0 || bundle.transportSecurity?.retainedSummary)) {
|
|
1612
|
+
markBudgetOmitted("transportSecurityDetail", "transport_security_detail_omitted_to_byte_limit");
|
|
1613
|
+
bundle.transportSecurity = {
|
|
1614
|
+
status: bundle.transportSecurity.status,
|
|
1615
|
+
evidenceRetained: bundle.transportSecurity.evidenceRetained,
|
|
1616
|
+
observationCounts: bundle.transportSecurity.observationCounts,
|
|
1617
|
+
observations: [],
|
|
1618
|
+
limitations: bundle.transportSecurity.limitations
|
|
1619
|
+
};
|
|
705
1620
|
refresh();
|
|
706
1621
|
}
|
|
707
|
-
|
|
708
|
-
markBudgetOmitted("
|
|
709
|
-
bundle.findings.
|
|
710
|
-
bundle.findingsMetadata.shown = bundle.findings.length;
|
|
711
|
-
bundle.findingsMetadata.truncated = true;
|
|
712
|
-
bundle.mcpMetadata.findingsTruncated = true;
|
|
1622
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.findings.length > 0) {
|
|
1623
|
+
markBudgetOmitted("findingDetail", "findings_compacted_to_preserve_core_rows");
|
|
1624
|
+
bundle.findings = bundle.findings.map((finding) => compactBundleFinding(finding, "core"));
|
|
713
1625
|
refresh();
|
|
714
1626
|
}
|
|
715
1627
|
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
@@ -737,36 +1649,78 @@ export function buildScanBundle(input) {
|
|
|
737
1649
|
bundle.links = Object.fromEntries(Object.entries(bundle.links).filter(([key]) => ["self", "report"].includes(key)));
|
|
738
1650
|
refresh();
|
|
739
1651
|
}
|
|
1652
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
1653
|
+
markBudgetOmitted("duplicateGuidance", "guidance_compacted_to_preserve_priority_content");
|
|
1654
|
+
bundle.interpretationGuidance = interpretationGuidance(COMPACT_SCAN_BUNDLE_INTERPRETATION_STATEMENT);
|
|
1655
|
+
bundle.observationOnlyDisclaimer = COMPACT_OBSERVATION_ONLY_DISCLAIMER;
|
|
1656
|
+
bundle.disclaimer = null;
|
|
1657
|
+
refresh();
|
|
1658
|
+
}
|
|
740
1659
|
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.evidenceSummary) {
|
|
741
1660
|
markBudgetOmitted("evidenceDetail", "evidence_compacted_to_preserve_priority_content");
|
|
742
1661
|
bundle.evidenceSummary = compactPriorityEvidenceSummary(bundle.evidenceSummary);
|
|
743
1662
|
refresh();
|
|
744
1663
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1664
|
+
const inventoryRows = bundle.preConsentCookiesTrackers?.rows;
|
|
1665
|
+
while (bundle.mcpMetadata.actualBytes > maxBytes && Array.isArray(inventoryRows) && inventoryRows.length > 0) {
|
|
1666
|
+
markBudgetOmitted("additionalPreConsentRows", "evidence_inventory_reduced_to_preserve_findings");
|
|
1667
|
+
inventoryRows.pop();
|
|
1668
|
+
bundle.preConsentCookiesTrackers.returned = inventoryRows.length;
|
|
1669
|
+
bundle.preConsentCookiesTrackers.truncated = true;
|
|
748
1670
|
refresh();
|
|
749
1671
|
}
|
|
750
|
-
if (bundle.mcpMetadata.actualBytes > maxBytes &&
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
bundle.
|
|
755
|
-
bundle.
|
|
1672
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes &&
|
|
1673
|
+
bundle.findings.length > 1 &&
|
|
1674
|
+
typeof contentUrls.findings === "string") {
|
|
1675
|
+
markBudgetOmitted("findingEvidenceUrls", "finding_evidence_urls_replaced_by_template");
|
|
1676
|
+
bundle.evidenceUrlTemplate = "{contentUrls.findings}/{findingId}";
|
|
1677
|
+
bundle.findings = bundle.findings.map((finding) => {
|
|
1678
|
+
const { evidenceUrl: _evidenceUrl, ...rest } = finding;
|
|
1679
|
+
return rest;
|
|
1680
|
+
});
|
|
1681
|
+
refresh();
|
|
1682
|
+
}
|
|
1683
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.preConsentCookiesTrackers) {
|
|
1684
|
+
markBudgetOmitted("preConsentCookiesTrackers", "pre_consent_summary_omitted_to_preserve_findings");
|
|
1685
|
+
delete bundle.preConsentCookiesTrackers;
|
|
1686
|
+
refresh();
|
|
1687
|
+
}
|
|
1688
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.links) {
|
|
1689
|
+
markBudgetOmitted("links", "duplicate_links_omitted_to_preserve_findings");
|
|
1690
|
+
delete bundle.links;
|
|
1691
|
+
refresh();
|
|
1692
|
+
}
|
|
1693
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.timing) {
|
|
1694
|
+
markBudgetOmitted("timing", "duplicate_timing_envelope_omitted_to_preserve_findings");
|
|
1695
|
+
delete bundle.timing;
|
|
756
1696
|
refresh();
|
|
757
1697
|
}
|
|
1698
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.summary) {
|
|
1699
|
+
markBudgetOmitted("summary", "duplicate_summary_envelope_omitted_to_preserve_findings");
|
|
1700
|
+
delete bundle.summary;
|
|
1701
|
+
refresh();
|
|
1702
|
+
}
|
|
1703
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.mcpMetadata.truncated) {
|
|
1704
|
+
refreshTruncationGuidance();
|
|
1705
|
+
}
|
|
758
1706
|
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.evidenceSummary) {
|
|
759
1707
|
markBudgetOmitted("evidence", "evidence_digest_omitted_to_byte_limit");
|
|
760
1708
|
delete bundle.evidenceSummary;
|
|
761
1709
|
bundle.mcpMetadata.heavyEvidenceIncluded = false;
|
|
762
1710
|
refresh();
|
|
763
1711
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
bundle.
|
|
767
|
-
bundle.
|
|
1712
|
+
while (bundle.mcpMetadata.actualBytes > maxBytes && bundle.findings.length > 1) {
|
|
1713
|
+
markBudgetOmitted("additionalFindings", "findings_reduced_to_byte_limit");
|
|
1714
|
+
bundle.findings.pop();
|
|
1715
|
+
bundle.findingsMetadata.shown = bundle.findings.length;
|
|
1716
|
+
bundle.findingsMetadata.returned = bundle.findings.length;
|
|
1717
|
+
bundle.findingsMetadata.truncated = true;
|
|
1718
|
+
bundle.mcpMetadata.findingsTruncated = true;
|
|
768
1719
|
refresh();
|
|
769
1720
|
}
|
|
1721
|
+
if (bundle.mcpMetadata.truncated) {
|
|
1722
|
+
refreshTruncationGuidance();
|
|
1723
|
+
}
|
|
770
1724
|
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
771
1725
|
const minimal = {
|
|
772
1726
|
type: bundle.type,
|
|
@@ -776,10 +1730,16 @@ export function buildScanBundle(input) {
|
|
|
776
1730
|
url: bundle.url,
|
|
777
1731
|
status: bundle.status,
|
|
778
1732
|
score: bundle.score,
|
|
1733
|
+
scoreLabel: bundle.scoreLabel,
|
|
779
1734
|
scoreStatus: bundle.scoreStatus,
|
|
780
1735
|
scoreVersion: bundle.scoreVersion,
|
|
781
1736
|
scoreUpdatedAt: bundle.scoreUpdatedAt,
|
|
782
1737
|
riskLevel: bundle.riskLevel,
|
|
1738
|
+
...(bundle.gpcResponse ? { gpcResponse: bundle.gpcResponse } : {}),
|
|
1739
|
+
postAcceptObservation: bundle.postAcceptObservation,
|
|
1740
|
+
postRefusalObservation: bundle.postRefusalObservation,
|
|
1741
|
+
provenance: bundle.provenance,
|
|
1742
|
+
interpretationGuidance: bundle.interpretationGuidance,
|
|
783
1743
|
resultDisposition: bundle.resultDisposition,
|
|
784
1744
|
noGo: bundle.noGo,
|
|
785
1745
|
coverage: null,
|
|
@@ -794,12 +1754,29 @@ export function buildScanBundle(input) {
|
|
|
794
1754
|
counts: bundle.summary?.counts ?? null,
|
|
795
1755
|
agentInterpretation: null
|
|
796
1756
|
},
|
|
797
|
-
findings:
|
|
1757
|
+
findings: bundle.findings.slice(0, 1),
|
|
798
1758
|
findingsMetadata: {
|
|
799
|
-
shown:
|
|
1759
|
+
shown: Math.min(1, bundle.findings.length),
|
|
1760
|
+
returned: Math.min(1, bundle.findings.length),
|
|
800
1761
|
total: bundle.findingsMetadata.total,
|
|
801
|
-
truncated: bundle.findingsMetadata.total >
|
|
1762
|
+
truncated: bundle.findingsMetadata.total > Math.min(1, bundle.findings.length)
|
|
1763
|
+
},
|
|
1764
|
+
transportSecurity: {
|
|
1765
|
+
status: bundle.transportSecurity.status,
|
|
1766
|
+
evidenceRetained: bundle.transportSecurity.evidenceRetained,
|
|
1767
|
+
observationCounts: bundle.transportSecurity.observationCounts,
|
|
1768
|
+
observations: [],
|
|
1769
|
+
limitations: bundle.transportSecurity.limitations.slice(0, 2)
|
|
802
1770
|
},
|
|
1771
|
+
...(bundle.preConsentCookiesTrackers ? {
|
|
1772
|
+
preConsentCookiesTrackers: {
|
|
1773
|
+
summary: bundle.preConsentCookiesTrackers.summary,
|
|
1774
|
+
rows: [],
|
|
1775
|
+
total: bundle.preConsentCookiesTrackers.total,
|
|
1776
|
+
returned: 0,
|
|
1777
|
+
truncated: bundle.preConsentCookiesTrackers.total > 0
|
|
1778
|
+
}
|
|
1779
|
+
} : {}),
|
|
803
1780
|
links: Object.fromEntries(Object.entries(bundle.links ?? {}).filter(([key]) => ["docs", "report", "self"].includes(key))),
|
|
804
1781
|
reportUrl: bundle.reportUrl,
|
|
805
1782
|
recommendedNextTool: null,
|
|
@@ -808,13 +1785,25 @@ export function buildScanBundle(input) {
|
|
|
808
1785
|
mcpMetadata: {
|
|
809
1786
|
detail,
|
|
810
1787
|
heavyEvidenceIncluded: false,
|
|
811
|
-
findingsTruncated: bundle.findingsMetadata.total >
|
|
812
|
-
requestedMaxBytes
|
|
1788
|
+
findingsTruncated: bundle.findingsMetadata.total > Math.min(1, bundle.findings.length),
|
|
1789
|
+
requestedMaxBytes,
|
|
1790
|
+
effectiveMaxBytes: maxBytes,
|
|
1791
|
+
responseCeilingBytes,
|
|
1792
|
+
responseBudgetClamped: requestedMaxBytes > maxBytes,
|
|
813
1793
|
actualBytes: 0,
|
|
1794
|
+
fullPayloadBytes: bundle.mcpMetadata.fullPayloadBytes,
|
|
814
1795
|
truncated: true,
|
|
1796
|
+
canonicalFindingsComplete: bundle.findingsMetadata.total === Math.min(1, bundle.findings.length),
|
|
815
1797
|
truncationReason: "minimal_canonical_result_returned_to_byte_limit",
|
|
816
|
-
omittedSections: [...new Set([
|
|
817
|
-
|
|
1798
|
+
omittedSections: [...new Set([
|
|
1799
|
+
...bundle.mcpMetadata.omittedSections,
|
|
1800
|
+
"summaryDetail",
|
|
1801
|
+
"evidence",
|
|
1802
|
+
...(bundle.findingsMetadata.total > Math.min(1, bundle.findings.length) ? ["additionalFindings"] : []),
|
|
1803
|
+
...(bundle.preConsentCookiesTrackers?.total > 0 ? ["additionalPreConsentRows"] : [])
|
|
1804
|
+
])],
|
|
1805
|
+
deduplicatedSections: bundle.mcpMetadata.deduplicatedSections,
|
|
1806
|
+
nextRecommendedMaxBytes: bundle.mcpMetadata.nextRecommendedMaxBytes,
|
|
818
1807
|
omittedContentAvailableViaUrl: Object.keys(contentUrls).length > 0,
|
|
819
1808
|
contentUrls
|
|
820
1809
|
},
|