@dreamdata_io/verify-tracking 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +756 -0
  3. package/dist/ai/checker.js +222 -0
  4. package/dist/ai/checker.js.map +1 -0
  5. package/dist/ai/checklist.js +190 -0
  6. package/dist/ai/checklist.js.map +1 -0
  7. package/dist/ai/cli-presets.js +27 -0
  8. package/dist/ai/cli-presets.js.map +1 -0
  9. package/dist/ai/evidence-view.js +143 -0
  10. package/dist/ai/evidence-view.js.map +1 -0
  11. package/dist/ai/local-cli.js +58 -0
  12. package/dist/ai/local-cli.js.map +1 -0
  13. package/dist/ai/openai-compatible.js +78 -0
  14. package/dist/ai/openai-compatible.js.map +1 -0
  15. package/dist/ai/response-parser.js +105 -0
  16. package/dist/ai/response-parser.js.map +1 -0
  17. package/dist/ai/types.js +2 -0
  18. package/dist/ai/types.js.map +1 -0
  19. package/dist/analysis/consent-mode.js +225 -0
  20. package/dist/analysis/consent-mode.js.map +1 -0
  21. package/dist/analysis/deployment.js +2658 -0
  22. package/dist/analysis/deployment.js.map +1 -0
  23. package/dist/analysis/diagnosis/copy.en.js +303 -0
  24. package/dist/analysis/diagnosis/copy.en.js.map +1 -0
  25. package/dist/analysis/diagnosis/diagnose.js +172 -0
  26. package/dist/analysis/diagnosis/diagnose.js.map +1 -0
  27. package/dist/analysis/diagnosis/docs.js +118 -0
  28. package/dist/analysis/diagnosis/docs.js.map +1 -0
  29. package/dist/analysis/diagnosis/index.js +5 -0
  30. package/dist/analysis/diagnosis/index.js.map +1 -0
  31. package/dist/analysis/diagnosis/rules.js +656 -0
  32. package/dist/analysis/diagnosis/rules.js.map +1 -0
  33. package/dist/analysis/diagnosis/types.js +2 -0
  34. package/dist/analysis/diagnosis/types.js.map +1 -0
  35. package/dist/cli.js +344 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/config/env.js +100 -0
  38. package/dist/config/env.js.map +1 -0
  39. package/dist/config/preflight.js +167 -0
  40. package/dist/config/preflight.js.map +1 -0
  41. package/dist/config/user-config.js +79 -0
  42. package/dist/config/user-config.js.map +1 -0
  43. package/dist/evidence/collect.js +527 -0
  44. package/dist/evidence/collect.js.map +1 -0
  45. package/dist/evidence/consent-accept.js +377 -0
  46. package/dist/evidence/consent-accept.js.map +1 -0
  47. package/dist/evidence/consent.js +340 -0
  48. package/dist/evidence/consent.js.map +1 -0
  49. package/dist/evidence/constants.js +615 -0
  50. package/dist/evidence/constants.js.map +1 -0
  51. package/dist/evidence/globals.js +132 -0
  52. package/dist/evidence/globals.js.map +1 -0
  53. package/dist/evidence/gtm-container.js +432 -0
  54. package/dist/evidence/gtm-container.js.map +1 -0
  55. package/dist/evidence/heuristic-banner.js +107 -0
  56. package/dist/evidence/heuristic-banner.js.map +1 -0
  57. package/dist/evidence/http-probe.js +139 -0
  58. package/dist/evidence/http-probe.js.map +1 -0
  59. package/dist/evidence/init-script.js +112 -0
  60. package/dist/evidence/init-script.js.map +1 -0
  61. package/dist/evidence/network.js +365 -0
  62. package/dist/evidence/network.js.map +1 -0
  63. package/dist/evidence/preference-center.js +368 -0
  64. package/dist/evidence/preference-center.js.map +1 -0
  65. package/dist/evidence/script.js +425 -0
  66. package/dist/evidence/script.js.map +1 -0
  67. package/dist/evidence/second-page.js +332 -0
  68. package/dist/evidence/second-page.js.map +1 -0
  69. package/dist/evidence/selector-probe.js +53 -0
  70. package/dist/evidence/selector-probe.js.map +1 -0
  71. package/dist/evidence/stealth.js +21 -0
  72. package/dist/evidence/stealth.js.map +1 -0
  73. package/dist/evidence/types.js +14 -0
  74. package/dist/evidence/types.js.map +1 -0
  75. package/dist/interactive/prompt.js +31 -0
  76. package/dist/interactive/prompt.js.map +1 -0
  77. package/dist/mcp/server.js +308 -0
  78. package/dist/mcp/server.js.map +1 -0
  79. package/dist/mcp/summary.js +41 -0
  80. package/dist/mcp/summary.js.map +1 -0
  81. package/dist/report/context.js +184 -0
  82. package/dist/report/context.js.map +1 -0
  83. package/dist/report/projection.js +125 -0
  84. package/dist/report/projection.js.map +1 -0
  85. package/dist/report/quote-check.js +90 -0
  86. package/dist/report/quote-check.js.map +1 -0
  87. package/dist/report/render.js +178 -0
  88. package/dist/report/render.js.map +1 -0
  89. package/dist/report/serializers.js +124 -0
  90. package/dist/report/serializers.js.map +1 -0
  91. package/dist/report/store.js +139 -0
  92. package/dist/report/store.js.map +1 -0
  93. package/dist/run/verify.js +77 -0
  94. package/dist/run/verify.js.map +1 -0
  95. package/dist/utils/duration.js +10 -0
  96. package/dist/utils/duration.js.map +1 -0
  97. package/dist/utils/errors.js +17 -0
  98. package/dist/utils/errors.js.map +1 -0
  99. package/dist/utils/logger.js +59 -0
  100. package/dist/utils/logger.js.map +1 -0
  101. package/dist/utils/runTimingsLog.js +27 -0
  102. package/dist/utils/runTimingsLog.js.map +1 -0
  103. package/dist/utils/slug.js +27 -0
  104. package/dist/utils/slug.js.map +1 -0
  105. package/dist/version.js +10 -0
  106. package/dist/version.js.map +1 -0
  107. package/package.json +57 -0
  108. package/scripts/install.sh +314 -0
@@ -0,0 +1,125 @@
1
+ import { extractLibraries, extractMessageId, formatLibraryBuild } from "../evidence/network.js";
2
+ function compactRequest(request) {
3
+ const { payload, ...rest } = request;
4
+ return {
5
+ ...rest,
6
+ messageId: extractMessageId(payload),
7
+ libraries: extractLibraries(payload).map(formatLibraryBuild),
8
+ };
9
+ }
10
+ function setNames(values) {
11
+ return Object.entries(values)
12
+ .filter(([, value]) => value !== null && value !== "")
13
+ .map(([name]) => name);
14
+ }
15
+ function compactStorage(storage) {
16
+ return {
17
+ cookieNames: setNames(storage.values),
18
+ localStorageNames: setNames(storage.localStorageValues),
19
+ ajsCookieNames: setNames(storage.ajsValues),
20
+ ajsLocalStorageNames: setNames(storage.ajsLocalStorageValues),
21
+ usesAjsCompatPrefix: storage.usesAjsCompatPrefix,
22
+ };
23
+ }
24
+ /**
25
+ * `raw` is the whole gtag argument object. Only `analytics_storage` is ever
26
+ * read from it, and `analyticsStorage` alongside is already that value
27
+ * resolved; `kind` keeps the one other thing it said — whether this was the
28
+ * declared default or a visitor's decision.
29
+ *
30
+ * The dedupe is belt-and-braces. init-script.ts used to stack a wrapper on
31
+ * window.dataLayer per re-assignment and report one push up to 13 times; that
32
+ * is fixed at the source, and this makes a return of it cost nothing here.
33
+ */
34
+ function compactConsentPushes(pushes) {
35
+ const seen = new Set();
36
+ const compact = [];
37
+ for (const push of pushes) {
38
+ const key = JSON.stringify(push);
39
+ if (seen.has(key))
40
+ continue;
41
+ seen.add(key);
42
+ compact.push({
43
+ // The typed field first; the reach into `raw` stays as the fallback
44
+ // because evidence collected before `verb` existed still has to project
45
+ // to the same `kind` it always did. The name stays `kind` so stored
46
+ // reports keep their shape.
47
+ kind: push.verb ?? (Array.isArray(push.raw) && typeof push.raw[1] === "string" ? push.raw[1] : null),
48
+ analyticsStorage: push.analyticsStorage,
49
+ region: push.region ?? null,
50
+ waitForUpdate: push.waitForUpdate ?? null,
51
+ timestamp: push.timestamp,
52
+ });
53
+ }
54
+ return compact;
55
+ }
56
+ function compactSecondPage(second) {
57
+ if (!second)
58
+ return null;
59
+ return {
60
+ ...second,
61
+ network: { ...second.network, requests: second.network.requests.map(compactRequest) },
62
+ hardLoad: !second.hardLoad
63
+ ? null
64
+ : {
65
+ ...second.hardLoad,
66
+ network: {
67
+ ...second.hardLoad.network,
68
+ requests: second.hardLoad.network.requests.map(compactRequest),
69
+ },
70
+ },
71
+ cookies: compactStorage(second.cookies),
72
+ };
73
+ }
74
+ /**
75
+ * Spread-and-override rather than a field list: a phase carries a lot that is
76
+ * already compact, and a new field added to EvidenceReport should reach the
77
+ * report on its own rather than being silently dropped here until someone
78
+ * notices.
79
+ */
80
+ function compactPhase(phase) {
81
+ return {
82
+ ...phase,
83
+ network: {
84
+ ...phase.network,
85
+ requests: phase.network.requests.map(compactRequest),
86
+ // Compacted for the same reason as `requests`, and more urgently: an
87
+ // unattributed request's payload belongs to whatever tool sent it, so
88
+ // this file is the last place its raw body should be reproduced.
89
+ unattributedRequests: phase.network.unattributedRequests.map(compactRequest),
90
+ },
91
+ cookies: compactStorage(phase.cookies),
92
+ consent: {
93
+ ...phase.consent,
94
+ dataLayerConsentPushes: compactConsentPushes(phase.consent.dataLayerConsentPushes),
95
+ },
96
+ secondPage: compactSecondPage(phase.secondPage),
97
+ };
98
+ }
99
+ /**
100
+ * `rawResponse` is the model's unparsed reply, and `verdicts` /
101
+ * `remediationAdvice` / `overallPass` are what parsing it produced — except
102
+ * when parsing FAILED. response-parser.ts falls back to inconclusive verdicts
103
+ * that carry no findings of their own, and there the raw text is the only
104
+ * record of what the model actually said, so it stays.
105
+ */
106
+ function compactResult(result) {
107
+ const { rawResponse, ...rest } = result;
108
+ return result.parseFailed ? { ...rest, rawResponse } : rest;
109
+ }
110
+ export function projectReport(evidence, analysis, result, meta) {
111
+ return {
112
+ slug: meta.slug ?? null,
113
+ url: evidence.url,
114
+ timestamp: evidence.timestamp,
115
+ // Run-level, not per-phase: nothing about what the site serves a plain
116
+ // HTTP GET varies with consent state. It is the raw form of
117
+ // deployment.pageReadability, kept so a reader can check the call.
118
+ httpProbe: evidence.httpProbe,
119
+ phases: evidence.phases.map(compactPhase),
120
+ deployment: analysis,
121
+ result: compactResult(result),
122
+ timings: meta.timings ?? null,
123
+ };
124
+ }
125
+ //# sourceMappingURL=projection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/report/projection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Does a verdict's `evidenceQuote` point at something real?
3
+ *
4
+ * The model is asked to cite a field path so a reader can check the claim
5
+ * against the JSON. Whether it did is worth measuring — a cited field that
6
+ * does not exist means the verdict rests on nothing — but the measurement has
7
+ * to distinguish two very different failures, and the first version of this
8
+ * did not:
9
+ *
10
+ * A quote naming `grant.network.invented` is a fabrication. A quote reading
11
+ * "clean-before-consent: fail, reject-blocks-normal: fail" cites real check
12
+ * ids in prose and fabricates nothing; it simply is not a path. Scoring both
13
+ * as "hallucinated" made the check unreadable — measured across the golden
14
+ * set, the trusted cloud baseline scored 2/7 on one account, worse than the
15
+ * local model it was there to judge.
16
+ */
17
+ import { valueAtPath } from "./store.js";
18
+ const PHASE_PREFIXES = ["clean", "grant", "reject"];
19
+ /**
20
+ * Every dotted/bracketed token embedded in free text, e.g. the
21
+ * "script.writeKey" inside `script.present: true, script.writeKey: "abc"`. A
22
+ * capable model returns a bare path; a smaller one tends to return prose that
23
+ * still names real fields, which is not the same failure as inventing one.
24
+ */
25
+ export function extractPathCandidates(text) {
26
+ // Brackets hold an index or a name, quoted or bare: `phases[0]`,
27
+ // `deployment.checks[page-view-counted-once]` and
28
+ // `deployment.checks['global-handoff']` are all citations, and the last two
29
+ // are the forms actually used for a cross-phase check — the quoted one by
30
+ // the model, the bare one by applyDeterministicChecks (see valueAtPath).
31
+ return [
32
+ ...new Set(text.match(/\b[a-zA-Z_][\w]*(?:\.[a-zA-Z_][\w]*|\[['"]?[\w.-]+['"]?\])+/g) ?? []),
33
+ ];
34
+ }
35
+ /**
36
+ * A path resolves against the analysis when prefixed `deployment.` (the
37
+ * deterministic overrides quote it that way), against a named phase when
38
+ * prefixed `clean.`/`grant.`/`reject.`, and otherwise against any phase — the
39
+ * prompt asks for a phase prefix, but a bare field name that is real evidence
40
+ * should not be scored the same as an invented one.
41
+ */
42
+ function lookup(report, candidate) {
43
+ const phases = Array.isArray(report.phases) ? report.phases : [];
44
+ if (candidate.startsWith("deployment.")) {
45
+ return valueAtPath(report.deployment, candidate.slice("deployment.".length));
46
+ }
47
+ const [first, ...rest] = candidate.split(".");
48
+ if (first !== undefined && PHASE_PREFIXES.includes(first) && rest.length > 0) {
49
+ const phase = phases.find((p) => p?.phase === first);
50
+ const value = phase ? valueAtPath(phase, rest.join(".")) : undefined;
51
+ if (value !== undefined)
52
+ return value;
53
+ }
54
+ for (const phase of phases) {
55
+ const value = valueAtPath(phase, candidate);
56
+ if (value !== undefined)
57
+ return value;
58
+ }
59
+ return valueAtPath(report.deployment, candidate);
60
+ }
61
+ function resolves(report, candidate) {
62
+ return lookup(report, candidate) !== undefined;
63
+ }
64
+ /**
65
+ * Every field path a quote names, with what the report actually holds there.
66
+ *
67
+ * A quote is usually prose with paths embedded in it — "script.present: true,
68
+ * script.writeKey: 'abc'" — so resolving it as one path answers the wrong
69
+ * question and answers it wrongly. This resolves each path it names, which is
70
+ * what a reader checking the claim needs anyway.
71
+ */
72
+ export function resolveCitations(report, quote) {
73
+ return extractPathCandidates(quote ?? "").map((path) => {
74
+ const value = lookup(report, path);
75
+ return { path, resolved: value !== undefined, value: value ?? null };
76
+ });
77
+ }
78
+ export function checkQuote(report, quote) {
79
+ const candidates = extractPathCandidates(quote ?? "");
80
+ if (candidates.length === 0)
81
+ return "unverifiable";
82
+ return candidates.some((candidate) => resolves(report, candidate)) ? "resolved" : "invented";
83
+ }
84
+ export function tallyQuotes(report, verdicts) {
85
+ const tally = { resolved: 0, unverifiable: 0, invented: 0 };
86
+ for (const verdict of verdicts)
87
+ tally[checkQuote(report, verdict.evidenceQuote)] += 1;
88
+ return tally;
89
+ }
90
+ //# sourceMappingURL=quote-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-check.js","sourceRoot":"","sources":["../../src/report/quote-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ import { causeLabel } from "../analysis/diagnosis/copy.en.js";
2
+ import { formatDuration } from "../utils/duration.js";
3
+ import { buildContextLines, buildRunContextLines } from "./context.js";
4
+ const RESET = "\x1b[0m";
5
+ const GREEN = "\x1b[32m";
6
+ const RED = "\x1b[31m";
7
+ const GRAY = "\x1b[90m";
8
+ const YELLOW = "\x1b[33m";
9
+ const DIAGNOSIS_LABEL_WIDTH = 11;
10
+ const DIAGNOSIS_WRAP_AT = 76;
11
+ /**
12
+ * Pads to the label column by VISIBLE width.
13
+ *
14
+ * A kind label carries colour escapes, and padEnd counts those, so measuring
15
+ * the raw string left the coloured rows short by however many bytes the
16
+ * escapes took and knocked the whole block out of alignment.
17
+ */
18
+ function padLabel(label) {
19
+ const visible = label.replace(/\x1b\[[0-9;]*m/g, "").length;
20
+ return label + " ".repeat(Math.max(1, DIAGNOSIS_LABEL_WIDTH - visible));
21
+ }
22
+ /**
23
+ * Wraps at word boundaries onto a hanging indent, so a long line stays
24
+ * readable and every value lines up in the same column.
25
+ *
26
+ * `gutter` is the first line's indent and `indent` every later line's. They
27
+ * differ only on a cause's opening row, where the rank number lives in the
28
+ * gutter — putting the rank inside the label instead pushed the label column
29
+ * out by a character or two per cause and broke the alignment it exists for.
30
+ */
31
+ function wrapInto(label, text, indent, gutter = indent) {
32
+ const lines = [];
33
+ let current = "";
34
+ for (const word of text.split(" ")) {
35
+ if (current.length > 0 && `${current} ${word}`.length > DIAGNOSIS_WRAP_AT) {
36
+ lines.push(current);
37
+ current = word;
38
+ }
39
+ else {
40
+ current = current.length === 0 ? word : `${current} ${word}`;
41
+ }
42
+ }
43
+ if (current.length > 0)
44
+ lines.push(current);
45
+ return lines.map((line, index) => index === 0 ? `${gutter}${padLabel(label)}${line}` : `${indent}${" ".repeat(DIAGNOSIS_LABEL_WIDTH)}${line}`);
46
+ }
47
+ function kindLabel(kind) {
48
+ if (kind === "blocking")
49
+ return `${RED}BLOCKING${RESET}`;
50
+ if (kind === "leaking")
51
+ return `${RED}LEAKING${RESET}`;
52
+ if (kind === "duplicate-counting")
53
+ return `${YELLOW}DUPLICATE${RESET}`;
54
+ if (kind === "hardening")
55
+ return `${YELLOW}HARDENING${RESET}`;
56
+ return `${GRAY}UNVERIFIED${RESET}`;
57
+ }
58
+ /**
59
+ * The finding, first.
60
+ *
61
+ * On a terminal this is now the whole report: the phase matrix and both
62
+ * per-check lists used to sit under it and are gone (see renderTextReport),
63
+ * because the reader this section exists for — a CS or AM person who wants to
64
+ * know what to tell the customer — was scrolling past thirty rows of detail
65
+ * that this block has already collapsed into one cause and one fix.
66
+ *
67
+ * Every heading, label and note below is the same wording the review
68
+ * dashboard (verification/review/index.html) prints for the same diagnosis,
69
+ * so the terminal and the dashboard cannot describe one run two ways. The
70
+ * terminal adds one row the dashboard has no room for — Explains, the bridge
71
+ * down to the check list underneath it — and spells the docs link out as
72
+ * title plus URL, because a terminal has no anchor text.
73
+ */
74
+ export function renderDiagnosis(diagnosis, unexplainedObservation) {
75
+ const lines = [];
76
+ if (diagnosis.causes.length === 0 && diagnosis.unexplainedChecks.length === 0 && !unexplainedObservation) {
77
+ // The dashboard renders an empty frame as nothing at all; a terminal has
78
+ // to say something, or the reader cannot tell a healthy run from a
79
+ // section that failed to print — and with the check lists gone there is
80
+ // nothing else on screen to read it off.
81
+ lines.push(diagnosis.hadFailures
82
+ ? `${YELLOW}What is wrong: checks failed but no catalogued cause matched them.${RESET}`
83
+ : `${GREEN}Nothing is wrong: every check that could be judged passed.${RESET}`);
84
+ return lines;
85
+ }
86
+ const everyCauseIsHardening = diagnosis.causes.length > 0 && diagnosis.causes.every((cause) => cause.kind === "hardening");
87
+ const onlyNotAVerdict = diagnosis.causes.length > 0 && diagnosis.causes.every((cause) => cause.kind === "not-a-verdict");
88
+ lines.push(onlyNotAVerdict
89
+ ? "Not a verdict about this site"
90
+ : everyCauseIsHardening
91
+ ? "Nothing is broken — one thing to harden"
92
+ : diagnosis.causes.length === 0
93
+ ? "Unexplained failures"
94
+ : "What is wrong — ranked, decided in code");
95
+ const ranked = diagnosis.causes.length > 1;
96
+ diagnosis.causes.forEach((cause, index) => {
97
+ // Numbered causes get a wider gutter so the rank sits outside the label
98
+ // column and every Fix/Docs/Explains value still lines up.
99
+ const indent = ranked ? " " : " ";
100
+ const gutter = ranked ? ` ${index + 1}. ` : " ";
101
+ // The kind sits in the label column, so the problem text lines up with
102
+ // the Fix and Docs values underneath it.
103
+ lines.push(...wrapInto(kindLabel(cause.kind), cause.problem, indent, gutter));
104
+ lines.push(...wrapInto("Fix", cause.fix, indent));
105
+ lines.push(`${indent}${padLabel("Docs")}${cause.docTitle} — ${cause.docUrl}`);
106
+ // The check ids the headline rests on. The terminal no longer prints the
107
+ // check list itself, so these are the handle a reader who does not trust
108
+ // the one-liner takes to the stored report or the review dashboard.
109
+ if (cause.explains.length > 0) {
110
+ lines.push(...wrapInto("Explains", cause.explains.join(", "), indent).map((line) => `${GRAY}${line}${RESET}`));
111
+ }
112
+ });
113
+ if (diagnosis.alsoFired.length > 0) {
114
+ lines.push(` ${GRAY}Also found, outside the top three: ${diagnosis.alsoFired.map(causeLabel).join("; ")}${RESET}`);
115
+ }
116
+ if (diagnosis.unexplainedChecks.length > 0) {
117
+ lines.push(` ${YELLOW}Not explained by any catalogued cause: ${diagnosis.unexplainedChecks.join(", ")} — a gap in the catalog, not a clean bill.${RESET}`);
118
+ }
119
+ // Labelled unverified, and visibly separated from the catalogued causes
120
+ // above: a model sentence must never be mistaken for a decided one. Printed
121
+ // whenever the run produced one — the dashboard does the same, and gating it
122
+ // on unexplainedChecks dropped the note on a run whose gap was a cause the
123
+ // catalog claimed but could not fix.
124
+ if (unexplainedObservation) {
125
+ lines.push(...wrapInto("Model's note", unexplainedObservation, " ").map((line) => `${GRAY}${line}${RESET}`), ` ${GRAY}${" ".repeat(DIAGNOSIS_LABEL_WIDTH)}(unverified, carries no fix or link)${RESET}`);
126
+ }
127
+ return lines;
128
+ }
129
+ export function renderTextReport(evidence, analysis, result, timings) {
130
+ const lines = [];
131
+ lines.push(`Dreamdata installation check for ${evidence.url}`);
132
+ lines.push(`Collected ${evidence.timestamp}`);
133
+ if (timings) {
134
+ lines.push(`${GRAY}Timing: collect evidence ${formatDuration(timings.collectEvidenceMs)}, analyze deployment ${formatDuration(timings.analyzeDeploymentMs)}, AI check ${formatDuration(timings.checkWithAIMs)}, total ${formatDuration(timings.totalMs)}${RESET}`);
135
+ }
136
+ lines.push("");
137
+ for (const line of buildRunContextLines(evidence, analysis))
138
+ lines.push(`${GRAY}${line}${RESET}`);
139
+ lines.push("");
140
+ for (const line of renderDiagnosis(analysis.diagnosis, result.unexplainedObservation))
141
+ lines.push(line);
142
+ lines.push("");
143
+ // No phase matrix, no per-check list, no checklist: on a run with eighteen
144
+ // checks and thirteen verdicts they filled a screen and a half with rows
145
+ // the diagnosis above has already collapsed into one cause and one fix, and
146
+ // the reader had to scroll past all of it to reach the verdict. Every one
147
+ // of them is still in the stored report — the markdown export renders the
148
+ // matrix and both lists, and the review dashboard renders them per check
149
+ // with the evidence behind a click. This is the terminal's summary view.
150
+ // A blocked run is not a failing site. Its verdicts are all inconclusive by
151
+ // then, so printing FAIL under them would be the report contradicting itself
152
+ // — and the contradiction a reader resolves in the site's disfavour.
153
+ lines.push(`Overall: ${analysis.pageReadability.unreadable
154
+ ? `${GRAY}INCONCLUSIVE — the browser was never served this site's page${RESET}`
155
+ : result.overallPass
156
+ ? `${GREEN}PASS${RESET}`
157
+ : `${RED}FAIL${RESET}`}`);
158
+ // No "Remediation advice" block: the fix for every catalogued cause is
159
+ // already printed above, decided in code and carrying a real docs link.
160
+ // The model's free-text version said the same thing less reliably — it is
161
+ // still stored on the report and still rendered by the dashboard and the
162
+ // markdown export, just not read out twice here.
163
+ const collectionErrors = evidence.phases.flatMap((phase) => phase.collectionErrors.map((err) => `${phase.phase}: ${err}`));
164
+ if (collectionErrors.length > 0) {
165
+ lines.push("");
166
+ lines.push(`${GRAY}Collection warnings:${RESET}`);
167
+ for (const err of collectionErrors)
168
+ lines.push(` - ${err}`);
169
+ }
170
+ return lines.join("\n");
171
+ }
172
+ /** Per-phase detail, printed only when the operator asks for it (--verbose). */
173
+ export function renderPhaseDetail(evidence) {
174
+ return evidence.phases
175
+ .map((phase) => [`${GRAY}${"─".repeat(60)}${RESET}`, ...buildContextLines(phase).map((l) => `${GRAY}${l}${RESET}`)].join("\n"))
176
+ .join("\n");
177
+ }
178
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/report/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { formatDuration } from "../utils/duration.js";
2
+ import { buildPhaseMatrixRows, buildRunContextLines } from "./context.js";
3
+ import { projectReport } from "./projection.js";
4
+ /**
5
+ * The report people and models read: every judgement, and the evidence behind
6
+ * it with the raw values that only fed those judgements taken back out. See
7
+ * projection.ts for what is dropped and why it is safe to drop.
8
+ */
9
+ export function serializeToJson(evidence, analysis, result, meta = {}) {
10
+ return JSON.stringify(projectReport(evidence, analysis, result, meta), null, 2);
11
+ }
12
+ /**
13
+ * The archive companion to serializeToJson: the same run, nothing trimmed.
14
+ * cli.ts writes both from one in-memory collection in one step, so the pair on
15
+ * disk is always ONE run rather than two that happen to share a slug — which is
16
+ * the whole point of keeping it, since re-running a live site never reproduces
17
+ * its evidence exactly.
18
+ */
19
+ export function serializeEvidenceToJson(evidence, analysis, result, meta = {}) {
20
+ return JSON.stringify({
21
+ slug: meta.slug ?? null,
22
+ url: evidence.url,
23
+ timestamp: evidence.timestamp,
24
+ browser: evidence.browser,
25
+ httpProbe: evidence.httpProbe,
26
+ phases: evidence.phases,
27
+ deployment: analysis,
28
+ result,
29
+ timings: meta.timings ?? null,
30
+ }, null, 2);
31
+ }
32
+ export function serializeToMarkdown(evidence, analysis, result, meta = {}) {
33
+ const lines = [];
34
+ lines.push(`# Dreamdata installation check — ${evidence.url}`);
35
+ lines.push("");
36
+ if (meta.slug)
37
+ lines.push(`Account: \`${meta.slug}\``);
38
+ lines.push(`Collected: ${evidence.timestamp}`);
39
+ lines.push(`Overall: **${analysis.pageReadability.unreadable
40
+ ? "INCONCLUSIVE — the browser was never served this site's page"
41
+ : result.overallPass
42
+ ? "PASS"
43
+ : "FAIL"}**`);
44
+ if (meta.timings) {
45
+ const t = meta.timings;
46
+ lines.push(`Timing: collect evidence ${formatDuration(t.collectEvidenceMs)}, analyze deployment ${formatDuration(t.analyzeDeploymentMs)}, AI check ${formatDuration(t.checkWithAIMs)}, total ${formatDuration(t.totalMs)}`);
47
+ }
48
+ lines.push("");
49
+ // Ahead of the observation context and the matrix, for the same reason the
50
+ // terminal puts it first: the reader who needs this section should not have
51
+ // to work through eighteen checks to reach it.
52
+ if (analysis.diagnosis.causes.length > 0) {
53
+ lines.push("## What is wrong");
54
+ lines.push("");
55
+ lines.push("Ranked root causes, decided in code from the resolved checks — not model-written.");
56
+ lines.push("");
57
+ for (const cause of analysis.diagnosis.causes) {
58
+ lines.push(`### ${cause.kind.toUpperCase()} — ${cause.problem}`);
59
+ lines.push("");
60
+ lines.push(`- **Fix:** ${cause.fix}`);
61
+ lines.push(`- **Docs:** [${cause.docTitle}](${cause.docUrl})`);
62
+ if (cause.explains.length > 0)
63
+ lines.push(`- **Explains:** ${cause.explains.map((id) => `\`${id}\``).join(", ")}`);
64
+ lines.push("");
65
+ }
66
+ if (analysis.diagnosis.alsoFired.length > 0) {
67
+ lines.push(`Also found, outside the top three: ${analysis.diagnosis.alsoFired.join(", ")}`);
68
+ lines.push("");
69
+ }
70
+ }
71
+ if (analysis.diagnosis.unexplainedChecks.length > 0) {
72
+ lines.push(`> Not explained by any catalogued cause: ${analysis.diagnosis.unexplainedChecks.map((id) => `\`${id}\``).join(", ")}`);
73
+ // Marked as the model's, and marked unverified: a sentence nobody decided
74
+ // must not sit in the same voice as the causes above it.
75
+ if (result.unexplainedObservation) {
76
+ lines.push(">");
77
+ lines.push(`> _Model's note (unverified, no fix or link): ${result.unexplainedObservation}_`);
78
+ }
79
+ lines.push("");
80
+ }
81
+ lines.push("## Observation context");
82
+ lines.push("");
83
+ for (const line of buildRunContextLines(evidence, analysis))
84
+ lines.push(`- ${line}`);
85
+ lines.push("");
86
+ lines.push("## Phase matrix");
87
+ lines.push("");
88
+ const [header, ...rows] = buildPhaseMatrixRows(analysis);
89
+ lines.push(`| ${header.join(" | ")} |`);
90
+ lines.push(`| ${header.map(() => "---").join(" | ")} |`);
91
+ for (const row of rows)
92
+ lines.push(`| ${row.map((cell) => cell.replace(/\|/g, "\\|")).join(" | ")} |`);
93
+ lines.push("");
94
+ lines.push("## Consent behaviour (compared across phases)");
95
+ lines.push("");
96
+ lines.push("| Check | Status | Detail |");
97
+ lines.push("| --- | --- | --- |");
98
+ for (const check of analysis.checks) {
99
+ lines.push(`| ${check.title} | ${check.status.toUpperCase()} | ${check.detail.replace(/\|/g, "\\|")} |`);
100
+ }
101
+ lines.push("");
102
+ lines.push("## Installation checklist");
103
+ lines.push("");
104
+ lines.push("| Item | Status | Feedback |");
105
+ lines.push("| --- | --- | --- |");
106
+ for (const verdict of result.verdicts) {
107
+ const status = verdict.passed === true ? "PASS" : verdict.passed === false ? "FAIL" : String(verdict.passed).toUpperCase();
108
+ lines.push(`| ${verdict.title} | ${status} | ${verdict.feedback.replace(/\|/g, "\\|")} |`);
109
+ }
110
+ if (result.remediationAdvice) {
111
+ lines.push("");
112
+ lines.push("## Remediation advice");
113
+ lines.push(result.remediationAdvice);
114
+ }
115
+ const collectionErrors = evidence.phases.flatMap((phase) => phase.collectionErrors.map((err) => `${phase.phase}: ${err}`));
116
+ if (collectionErrors.length > 0) {
117
+ lines.push("");
118
+ lines.push("## Collection warnings");
119
+ for (const err of collectionErrors)
120
+ lines.push(`- ${err}`);
121
+ }
122
+ return lines.join("\n");
123
+ }
124
+ //# sourceMappingURL=serializers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializers.js","sourceRoot":"","sources":["../../src/report/serializers.ts"],"names":[],"mappings":"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"}